mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-22 01:11:42 +00:00
[datalog] Move all DataLog functionality to new datalog library (#7641)
Currently the major DataLog backend API (reading and writing) is split between wpiutil and glass. In the interest of allowing code that wants to use these APIs to not need to link to glass and declutter wpiutil, all of those APIs are moved to a new library named "datalog". Signed-off-by: Jade Turner <spacey-sooty@proton.me> Co-authored-by: Jade Turner <spacey-sooty@proton.me> Co-authored-by: Gold856 <117957790+Gold856@users.noreply.github.com>
This commit is contained in:
@@ -1,862 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "wpi/DataLog.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <bit>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/Endian.h"
|
||||
#include "wpi/Logger.h"
|
||||
#include "wpi/SmallString.h"
|
||||
#include "wpi/print.h"
|
||||
#include "wpi/timestamp.h"
|
||||
|
||||
using namespace wpi::log;
|
||||
|
||||
static constexpr size_t kRecordMaxHeaderSize = 17;
|
||||
|
||||
static void DefaultLog(unsigned int level, const char* file, unsigned int line,
|
||||
const char* msg) {
|
||||
if (level > wpi::WPI_LOG_INFO) {
|
||||
wpi::print(stderr, "DataLog: {}\n", msg);
|
||||
} else if (level == wpi::WPI_LOG_INFO) {
|
||||
wpi::print("DataLog: {}\n", msg);
|
||||
}
|
||||
}
|
||||
|
||||
wpi::Logger DataLog::s_defaultMessageLog{DefaultLog};
|
||||
|
||||
template <typename T>
|
||||
static unsigned int WriteVarInt(uint8_t* buf, T val) {
|
||||
unsigned int len = 0;
|
||||
do {
|
||||
*buf++ = static_cast<unsigned int>(val) & 0xff;
|
||||
++len;
|
||||
val >>= 8;
|
||||
} while (val != 0);
|
||||
return len;
|
||||
}
|
||||
|
||||
// min size: 4, max size: 17
|
||||
static unsigned int WriteRecordHeader(uint8_t* buf, uint32_t entry,
|
||||
uint64_t timestamp,
|
||||
uint32_t payloadSize) {
|
||||
uint8_t* origbuf = buf++;
|
||||
|
||||
unsigned int entryLen = WriteVarInt(buf, entry);
|
||||
buf += entryLen;
|
||||
unsigned int payloadLen = WriteVarInt(buf, payloadSize);
|
||||
buf += payloadLen;
|
||||
unsigned int timestampLen =
|
||||
WriteVarInt(buf, timestamp == 0 ? wpi::Now() : timestamp);
|
||||
buf += timestampLen;
|
||||
*origbuf =
|
||||
((timestampLen - 1) << 4) | ((payloadLen - 1) << 2) | (entryLen - 1);
|
||||
return buf - origbuf;
|
||||
}
|
||||
|
||||
void DataLog::StartFile() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_active) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Grab previously pending writes
|
||||
std::vector<Buffer> bufs;
|
||||
bufs.swap(m_outgoing);
|
||||
m_outgoing.reserve(bufs.size() + 1);
|
||||
|
||||
// File header (version 1.0)
|
||||
uint8_t* buf = Reserve(m_extraHeader.size() + 12);
|
||||
static const uint8_t header[] = {'W', 'P', 'I', 'L', 'O', 'G', 0, 1};
|
||||
std::memcpy(buf, header, 8);
|
||||
support::endian::write32le(buf + 8, m_extraHeader.size());
|
||||
std::memcpy(buf + 12, m_extraHeader.data(), m_extraHeader.size());
|
||||
|
||||
// Existing start and schema data records
|
||||
for (auto&& entryInfo : m_entries) {
|
||||
AppendStartRecord(entryInfo.second.id, entryInfo.first,
|
||||
entryInfo.second.type,
|
||||
m_entryIds[entryInfo.second.id].metadata, 0);
|
||||
}
|
||||
|
||||
// Existing schema data records
|
||||
for (auto&& schemaInfo : m_schemas) {
|
||||
if (schemaInfo.second.id != 0) {
|
||||
StartRecord(schemaInfo.second.id, 0, schemaInfo.second.data.size(), 0);
|
||||
AppendImpl(schemaInfo.second.data);
|
||||
}
|
||||
}
|
||||
|
||||
// Append previously pending writes
|
||||
for (auto&& buf : bufs) {
|
||||
m_outgoing.emplace_back(std::move(buf));
|
||||
}
|
||||
|
||||
m_active = true;
|
||||
}
|
||||
|
||||
void DataLog::FlushBufs(std::vector<Buffer>* writeBufs) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
writeBufs->swap(m_outgoing);
|
||||
DoReleaseBufs(&m_outgoing);
|
||||
}
|
||||
|
||||
void DataLog::ReleaseBufs(std::vector<Buffer>* bufs) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
DoReleaseBufs(bufs);
|
||||
}
|
||||
|
||||
void DataLog::Pause() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_paused = true;
|
||||
}
|
||||
|
||||
void DataLog::Resume() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_paused = false;
|
||||
}
|
||||
|
||||
void DataLog::Stop() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_active = false;
|
||||
}
|
||||
|
||||
void DataLog::BufferHalfFull() {}
|
||||
|
||||
bool DataLog::HasSchema(std::string_view name) const {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_schemas.contains(name);
|
||||
}
|
||||
|
||||
void DataLog::AddSchema(std::string_view name, std::string_view type,
|
||||
std::span<const uint8_t> schema, int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto& schemaInfo = m_schemas[name];
|
||||
if (schemaInfo.id != 0) {
|
||||
return; // don't add duplicates
|
||||
}
|
||||
schemaInfo.data.assign(schema.begin(), schema.end());
|
||||
wpi::SmallString<128> fullName{"/.schema/"};
|
||||
fullName += name;
|
||||
int entry = StartImpl(fullName, type, {}, timestamp);
|
||||
|
||||
// inline AppendRaw() without releasing lock
|
||||
if (entry <= 0) {
|
||||
[[unlikely]] return; // should never happen, but check anyway
|
||||
}
|
||||
schemaInfo.id = entry;
|
||||
if (!m_active) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
StartRecord(entry, timestamp, schema.size(), 0);
|
||||
AppendImpl(schema);
|
||||
}
|
||||
|
||||
// Control records use the following format:
|
||||
// 1-byte type
|
||||
// 4-byte entry
|
||||
// rest of data (depending on type)
|
||||
|
||||
int DataLog::Start(std::string_view name, std::string_view type,
|
||||
std::string_view metadata, int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return StartImpl(name, type, metadata, timestamp);
|
||||
}
|
||||
|
||||
int DataLog::StartImpl(std::string_view name, std::string_view type,
|
||||
std::string_view metadata, int64_t timestamp) {
|
||||
auto& entryInfo = m_entries[name];
|
||||
if (entryInfo.id == 0) {
|
||||
entryInfo.id = ++m_lastId;
|
||||
}
|
||||
auto& entryInfo2 = m_entryIds[entryInfo.id];
|
||||
++entryInfo2.count;
|
||||
if (entryInfo2.count > 1) {
|
||||
if (entryInfo.type != type) {
|
||||
WPI_ERROR(m_msglog,
|
||||
"type mismatch for '{}': was '{}', requested '{}'; ignoring",
|
||||
name, entryInfo.type, type);
|
||||
return 0;
|
||||
}
|
||||
return entryInfo.id;
|
||||
}
|
||||
entryInfo.type = type;
|
||||
entryInfo2.metadata = metadata;
|
||||
|
||||
if (!m_active) {
|
||||
[[unlikely]] return entryInfo.id;
|
||||
}
|
||||
|
||||
AppendStartRecord(entryInfo.id, name, type, metadata, timestamp);
|
||||
return entryInfo.id;
|
||||
}
|
||||
|
||||
void DataLog::AppendStartRecord(int id, std::string_view name,
|
||||
std::string_view type,
|
||||
std::string_view metadata, int64_t timestamp) {
|
||||
size_t strsize = name.size() + type.size() + metadata.size();
|
||||
uint8_t* buf = StartRecord(0, timestamp, 5 + 12 + strsize, 5);
|
||||
*buf++ = impl::kControlStart;
|
||||
wpi::support::endian::write32le(buf, id);
|
||||
AppendStringImpl(name);
|
||||
AppendStringImpl(type);
|
||||
AppendStringImpl(metadata);
|
||||
}
|
||||
|
||||
void DataLog::DoReleaseBufs(std::vector<Buffer>* bufs) {
|
||||
for (auto&& buf : *bufs) {
|
||||
buf.Clear();
|
||||
if (m_free.size() < kMaxFreeCount) {
|
||||
[[likely]] m_free.emplace_back(std::move(buf));
|
||||
}
|
||||
}
|
||||
bufs->resize(0);
|
||||
}
|
||||
|
||||
void DataLog::Finish(int entry, int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto& entryInfo2 = m_entryIds[entry];
|
||||
if (entryInfo2.count == 0) {
|
||||
return;
|
||||
}
|
||||
--entryInfo2.count;
|
||||
if (entryInfo2.count != 0) {
|
||||
return;
|
||||
}
|
||||
m_entryIds.erase(entry);
|
||||
if (!m_active) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(0, timestamp, 5, 5);
|
||||
*buf++ = impl::kControlFinish;
|
||||
wpi::support::endian::write32le(buf, entry);
|
||||
}
|
||||
|
||||
void DataLog::SetMetadata(int entry, std::string_view metadata,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_entryIds[entry].metadata = metadata;
|
||||
if (!m_active) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(0, timestamp, 5 + 4 + metadata.size(), 5);
|
||||
*buf++ = impl::kControlSetMetadata;
|
||||
wpi::support::endian::write32le(buf, entry);
|
||||
AppendStringImpl(metadata);
|
||||
}
|
||||
|
||||
uint8_t* DataLog::Reserve(size_t size) {
|
||||
assert(size <= kBlockSize);
|
||||
if (m_outgoing.empty() || size > m_outgoing.back().GetRemaining()) {
|
||||
if (m_outgoing.size() == kMaxBufferCount / 2) {
|
||||
[[unlikely]] BufferHalfFull();
|
||||
}
|
||||
if (m_free.empty()) {
|
||||
if (m_outgoing.size() >= kMaxBufferCount) {
|
||||
[[unlikely]]
|
||||
if (BufferFull()) {
|
||||
m_paused = true;
|
||||
}
|
||||
}
|
||||
m_outgoing.emplace_back();
|
||||
} else {
|
||||
m_outgoing.emplace_back(std::move(m_free.back()));
|
||||
m_free.pop_back();
|
||||
}
|
||||
}
|
||||
return m_outgoing.back().Reserve(size);
|
||||
}
|
||||
|
||||
uint8_t* DataLog::StartRecord(uint32_t entry, uint64_t timestamp,
|
||||
uint32_t payloadSize, size_t reserveSize) {
|
||||
uint8_t* buf = Reserve(kRecordMaxHeaderSize + reserveSize);
|
||||
auto headerLen = WriteRecordHeader(buf, entry, timestamp, payloadSize);
|
||||
m_outgoing.back().Unreserve(kRecordMaxHeaderSize - headerLen);
|
||||
buf += headerLen;
|
||||
return buf;
|
||||
}
|
||||
|
||||
void DataLog::AppendImpl(std::span<const uint8_t> data) {
|
||||
while (data.size() > kBlockSize) {
|
||||
uint8_t* buf = Reserve(kBlockSize);
|
||||
std::memcpy(buf, data.data(), kBlockSize);
|
||||
data = data.subspan(kBlockSize);
|
||||
}
|
||||
if (!data.empty()) {
|
||||
uint8_t* buf = Reserve(data.size());
|
||||
std::memcpy(buf, data.data(), data.size());
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendStringImpl(std::string_view str) {
|
||||
uint8_t* buf = Reserve(4);
|
||||
wpi::support::endian::write32le(buf, str.size());
|
||||
AppendImpl({reinterpret_cast<const uint8_t*>(str.data()), str.size()});
|
||||
}
|
||||
|
||||
void DataLog::AppendRaw(int entry, std::span<const uint8_t> data,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
StartRecord(entry, timestamp, data.size(), 0);
|
||||
AppendImpl(data);
|
||||
}
|
||||
|
||||
void DataLog::AppendRaw2(int entry,
|
||||
std::span<const std::span<const uint8_t>> data,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
size_t size = 0;
|
||||
for (auto&& chunk : data) {
|
||||
size += chunk.size();
|
||||
}
|
||||
StartRecord(entry, timestamp, size, 0);
|
||||
for (auto chunk : data) {
|
||||
AppendImpl(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendBoolean(int entry, bool value, int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, 1, 1);
|
||||
buf[0] = value ? 1 : 0;
|
||||
}
|
||||
|
||||
void DataLog::AppendInteger(int entry, int64_t value, int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
|
||||
wpi::support::endian::write64le(buf, value);
|
||||
}
|
||||
|
||||
void DataLog::AppendFloat(int entry, float value, int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, 4, 4);
|
||||
if constexpr (std::endian::native == std::endian::little) {
|
||||
std::memcpy(buf, &value, 4);
|
||||
} else {
|
||||
wpi::support::endian::write32le(buf, std::bit_cast<uint32_t>(value));
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendDouble(int entry, double value, int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
|
||||
if constexpr (std::endian::native == std::endian::little) {
|
||||
std::memcpy(buf, &value, 8);
|
||||
} else {
|
||||
wpi::support::endian::write64le(buf, std::bit_cast<uint64_t>(value));
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendString(int entry, std::string_view value,
|
||||
int64_t timestamp) {
|
||||
AppendRaw(entry,
|
||||
{reinterpret_cast<const uint8_t*>(value.data()), value.size()},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
void DataLog::AppendBooleanArray(int entry, std::span<const bool> arr,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
StartRecord(entry, timestamp, arr.size(), 0);
|
||||
uint8_t* buf;
|
||||
while (arr.size() > kBlockSize) {
|
||||
buf = Reserve(kBlockSize);
|
||||
for (auto val : arr.subspan(0, kBlockSize)) {
|
||||
*buf++ = val ? 1 : 0;
|
||||
}
|
||||
arr = arr.subspan(kBlockSize);
|
||||
}
|
||||
buf = Reserve(arr.size());
|
||||
for (auto val : arr) {
|
||||
*buf++ = val ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendBooleanArray(int entry, std::span<const int> arr,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
StartRecord(entry, timestamp, arr.size(), 0);
|
||||
uint8_t* buf;
|
||||
while (arr.size() > kBlockSize) {
|
||||
buf = Reserve(kBlockSize);
|
||||
for (auto val : arr.subspan(0, kBlockSize)) {
|
||||
*buf++ = val & 1;
|
||||
}
|
||||
arr = arr.subspan(kBlockSize);
|
||||
}
|
||||
buf = Reserve(arr.size());
|
||||
for (auto val : arr) {
|
||||
*buf++ = val & 1;
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendBooleanArray(int entry, std::span<const uint8_t> arr,
|
||||
int64_t timestamp) {
|
||||
AppendRaw(entry, arr, timestamp);
|
||||
}
|
||||
|
||||
void DataLog::AppendIntegerArray(int entry, std::span<const int64_t> arr,
|
||||
int64_t timestamp) {
|
||||
if constexpr (std::endian::native == std::endian::little) {
|
||||
AppendRaw(entry,
|
||||
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
|
||||
timestamp);
|
||||
} else {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
StartRecord(entry, timestamp, arr.size() * 8, 0);
|
||||
uint8_t* buf;
|
||||
while ((arr.size() * 8) > kBlockSize) {
|
||||
buf = Reserve(kBlockSize);
|
||||
for (auto val : arr.subspan(0, kBlockSize / 8)) {
|
||||
wpi::support::endian::write64le(buf, val);
|
||||
buf += 8;
|
||||
}
|
||||
arr = arr.subspan(kBlockSize / 8);
|
||||
}
|
||||
buf = Reserve(arr.size() * 8);
|
||||
for (auto val : arr) {
|
||||
wpi::support::endian::write64le(buf, val);
|
||||
buf += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendFloatArray(int entry, std::span<const float> arr,
|
||||
int64_t timestamp) {
|
||||
if constexpr (std::endian::native == std::endian::little) {
|
||||
AppendRaw(entry,
|
||||
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 4},
|
||||
timestamp);
|
||||
} else {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
StartRecord(entry, timestamp, arr.size() * 4, 0);
|
||||
uint8_t* buf;
|
||||
while ((arr.size() * 4) > kBlockSize) {
|
||||
buf = Reserve(kBlockSize);
|
||||
for (auto val : arr.subspan(0, kBlockSize / 4)) {
|
||||
wpi::support::endian::write32le(buf, std::bit_cast<uint32_t>(val));
|
||||
buf += 4;
|
||||
}
|
||||
arr = arr.subspan(kBlockSize / 4);
|
||||
}
|
||||
buf = Reserve(arr.size() * 4);
|
||||
for (auto val : arr) {
|
||||
wpi::support::endian::write32le(buf, std::bit_cast<uint32_t>(val));
|
||||
buf += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendDoubleArray(int entry, std::span<const double> arr,
|
||||
int64_t timestamp) {
|
||||
if constexpr (std::endian::native == std::endian::little) {
|
||||
AppendRaw(entry,
|
||||
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
|
||||
timestamp);
|
||||
} else {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
StartRecord(entry, timestamp, arr.size() * 8, 0);
|
||||
uint8_t* buf;
|
||||
while ((arr.size() * 8) > kBlockSize) {
|
||||
buf = Reserve(kBlockSize);
|
||||
for (auto val : arr.subspan(0, kBlockSize / 8)) {
|
||||
wpi::support::endian::write64le(buf, std::bit_cast<uint64_t>(val));
|
||||
buf += 8;
|
||||
}
|
||||
arr = arr.subspan(kBlockSize / 8);
|
||||
}
|
||||
buf = Reserve(arr.size() * 8);
|
||||
for (auto val : arr) {
|
||||
wpi::support::endian::write64le(buf, std::bit_cast<uint64_t>(val));
|
||||
buf += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendStringArray(int entry, std::span<const std::string> arr,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
// storage: 4-byte array length, each string prefixed by 4-byte length
|
||||
// calculate total size
|
||||
size_t size = 4;
|
||||
for (auto&& str : arr) {
|
||||
size += 4 + str.size();
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
|
||||
wpi::support::endian::write32le(buf, arr.size());
|
||||
for (auto&& str : arr) {
|
||||
AppendStringImpl(str);
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendStringArray(int entry,
|
||||
std::span<const std::string_view> arr,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
// storage: 4-byte array length, each string prefixed by 4-byte length
|
||||
// calculate total size
|
||||
size_t size = 4;
|
||||
for (auto&& str : arr) {
|
||||
size += 4 + str.size();
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
|
||||
wpi::support::endian::write32le(buf, arr.size());
|
||||
for (auto&& sv : arr) {
|
||||
AppendStringImpl(sv);
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendStringArray(int entry,
|
||||
std::span<const struct WPI_String> arr,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
// storage: 4-byte array length, each string prefixed by 4-byte length
|
||||
// calculate total size
|
||||
size_t size = 4;
|
||||
for (auto&& str : arr) {
|
||||
size += 4 + str.len;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
[[unlikely]] return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
|
||||
wpi::support::endian::write32le(buf, arr.size());
|
||||
for (auto&& sv : arr) {
|
||||
AppendStringImpl(sv.str);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename V1, typename V2>
|
||||
inline bool UpdateImpl(std::optional<std::vector<V1>>& lastValue,
|
||||
std::span<const V2> data) {
|
||||
if (!lastValue || !std::equal(data.begin(), data.end(), lastValue->begin(),
|
||||
lastValue->end())) {
|
||||
if (lastValue) {
|
||||
lastValue->assign(data.begin(), data.end());
|
||||
} else {
|
||||
lastValue = std::vector<V1>{data.begin(), data.end()};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename V1>
|
||||
inline bool UpdateImpl(std::optional<std::vector<V1>>& lastValue,
|
||||
std::span<const bool> data) {
|
||||
if (!lastValue || !std::equal(data.begin(), data.end(), lastValue->begin(),
|
||||
lastValue->end(), [](auto a, auto b) {
|
||||
return a == static_cast<bool>(b);
|
||||
})) {
|
||||
if (lastValue) {
|
||||
lastValue->assign(data.begin(), data.end());
|
||||
} else {
|
||||
lastValue = std::vector<V1>{data.begin(), data.end()};
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RawLogEntry::Update(std::span<const uint8_t> data, int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, data)) {
|
||||
Append(data, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void BooleanArrayLogEntry::Update(std::span<const bool> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void BooleanArrayLogEntry::Update(std::span<const int> arr, int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void BooleanArrayLogEntry::Update(std::span<const uint8_t> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void IntegerArrayLogEntry::Update(std::span<const int64_t> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void FloatArrayLogEntry::Update(std::span<const float> arr, int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void DoubleArrayLogEntry::Update(std::span<const double> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void StringArrayLogEntry::Update(std::span<const std::string> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
void StringArrayLogEntry::Update(std::span<const std::string_view> arr,
|
||||
int64_t timestamp) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (UpdateImpl(m_lastValue, arr)) {
|
||||
Append(arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
void WPI_DataLog_Release(struct WPI_DataLog* datalog) {
|
||||
delete reinterpret_cast<DataLog*>(datalog);
|
||||
}
|
||||
|
||||
void WPI_DataLog_Flush(struct WPI_DataLog* datalog) {
|
||||
reinterpret_cast<DataLog*>(datalog)->Flush();
|
||||
}
|
||||
|
||||
void WPI_DataLog_Pause(struct WPI_DataLog* datalog) {
|
||||
reinterpret_cast<DataLog*>(datalog)->Pause();
|
||||
}
|
||||
|
||||
void WPI_DataLog_Resume(struct WPI_DataLog* datalog) {
|
||||
reinterpret_cast<DataLog*>(datalog)->Resume();
|
||||
}
|
||||
|
||||
void WPI_DataLog_Stop(struct WPI_DataLog* datalog) {
|
||||
reinterpret_cast<DataLog*>(datalog)->Stop();
|
||||
}
|
||||
|
||||
int WPI_DataLog_Start(struct WPI_DataLog* datalog,
|
||||
const struct WPI_String* name,
|
||||
const struct WPI_String* type,
|
||||
const struct WPI_String* metadata, int64_t timestamp) {
|
||||
return reinterpret_cast<DataLog*>(datalog)->Start(
|
||||
wpi::to_string_view(name), wpi::to_string_view(type),
|
||||
wpi::to_string_view(metadata), timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_Finish(struct WPI_DataLog* datalog, int entry,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->Finish(entry, timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_SetMetadata(struct WPI_DataLog* datalog, int entry,
|
||||
const struct WPI_String* metadata,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->SetMetadata(
|
||||
entry, wpi::to_string_view(metadata), timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendRaw(struct WPI_DataLog* datalog, int entry,
|
||||
const uint8_t* data, size_t len, int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendRaw(entry, {data, len}, timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendBoolean(struct WPI_DataLog* datalog, int entry,
|
||||
int value, int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendBoolean(entry, value, timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendInteger(struct WPI_DataLog* datalog, int entry,
|
||||
int64_t value, int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendInteger(entry, value, timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendFloat(struct WPI_DataLog* datalog, int entry,
|
||||
float value, int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendFloat(entry, value, timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendDouble(struct WPI_DataLog* datalog, int entry,
|
||||
double value, int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendDouble(entry, value, timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendString(struct WPI_DataLog* datalog, int entry,
|
||||
const struct WPI_String* value,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendString(
|
||||
entry, {value->str, value->len}, timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendBooleanArray(struct WPI_DataLog* datalog, int entry,
|
||||
const int* arr, size_t len,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendBooleanArray(entry, {arr, len},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendBooleanArrayByte(struct WPI_DataLog* datalog, int entry,
|
||||
const uint8_t* arr, size_t len,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendBooleanArray(entry, {arr, len},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendIntegerArray(struct WPI_DataLog* datalog, int entry,
|
||||
const int64_t* arr, size_t len,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendIntegerArray(entry, {arr, len},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendFloatArray(struct WPI_DataLog* datalog, int entry,
|
||||
const float* arr, size_t len,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendFloatArray(entry, {arr, len},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendDoubleArray(struct WPI_DataLog* datalog, int entry,
|
||||
const double* arr, size_t len,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendDoubleArray(entry, {arr, len},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AppendStringArray(struct WPI_DataLog* datalog, int entry,
|
||||
const struct WPI_String* arr, size_t len,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AppendStringArray(entry, {arr, len},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AddSchemaString(struct WPI_DataLog* datalog,
|
||||
const struct WPI_String* name,
|
||||
const struct WPI_String* type,
|
||||
const struct WPI_String* schema,
|
||||
int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AddSchema(
|
||||
wpi::to_string_view(name), wpi::to_string_view(type),
|
||||
wpi::to_string_view(schema), timestamp);
|
||||
}
|
||||
|
||||
void WPI_DataLog_AddSchema(struct WPI_DataLog* datalog,
|
||||
const struct WPI_String* name,
|
||||
const struct WPI_String* type, const uint8_t* schema,
|
||||
size_t schema_len, int64_t timestamp) {
|
||||
reinterpret_cast<DataLog*>(datalog)->AddSchema(
|
||||
wpi::to_string_view(name), wpi::to_string_view(type),
|
||||
std::span<const uint8_t>{schema, schema_len}, timestamp);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -1,490 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "wpi/DataLogBackgroundWriter.h"
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#include <windows.h> // NOLINT(build/include_order)
|
||||
|
||||
#endif
|
||||
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "wpi/Logger.h"
|
||||
#include "wpi/fs.h"
|
||||
|
||||
using namespace wpi::log;
|
||||
|
||||
static constexpr uintmax_t kMinFreeSpace = 5 * 1024 * 1024;
|
||||
|
||||
static std::string FormatBytesSize(uintmax_t value) {
|
||||
static constexpr uintmax_t kKiB = 1024;
|
||||
static constexpr uintmax_t kMiB = kKiB * 1024;
|
||||
static constexpr uintmax_t kGiB = kMiB * 1024;
|
||||
if (value >= kGiB) {
|
||||
return fmt::format("{:.1f} GiB", static_cast<double>(value) / kGiB);
|
||||
} else if (value >= kMiB) {
|
||||
return fmt::format("{:.1f} MiB", static_cast<double>(value) / kMiB);
|
||||
} else if (value >= kKiB) {
|
||||
return fmt::format("{:.1f} KiB", static_cast<double>(value) / kKiB);
|
||||
} else {
|
||||
return fmt::format("{} B", value);
|
||||
}
|
||||
}
|
||||
|
||||
DataLogBackgroundWriter::DataLogBackgroundWriter(std::string_view dir,
|
||||
std::string_view filename,
|
||||
double period,
|
||||
std::string_view extraHeader)
|
||||
: DataLogBackgroundWriter{s_defaultMessageLog, dir, filename, period,
|
||||
extraHeader} {}
|
||||
|
||||
DataLogBackgroundWriter::DataLogBackgroundWriter(wpi::Logger& msglog,
|
||||
std::string_view dir,
|
||||
std::string_view filename,
|
||||
double period,
|
||||
std::string_view extraHeader)
|
||||
: DataLog{msglog, extraHeader},
|
||||
m_period{period},
|
||||
m_newFilename{filename},
|
||||
m_thread{[this, dir = std::string{dir}] { WriterThreadMain(dir); }} {}
|
||||
|
||||
DataLogBackgroundWriter::DataLogBackgroundWriter(
|
||||
std::function<void(std::span<const uint8_t> data)> write, double period,
|
||||
std::string_view extraHeader)
|
||||
: DataLogBackgroundWriter{s_defaultMessageLog, std::move(write), period,
|
||||
extraHeader} {}
|
||||
|
||||
DataLogBackgroundWriter::DataLogBackgroundWriter(
|
||||
wpi::Logger& msglog,
|
||||
std::function<void(std::span<const uint8_t> data)> write, double period,
|
||||
std::string_view extraHeader)
|
||||
: DataLog{msglog, extraHeader},
|
||||
m_period{period},
|
||||
m_thread{[this, write = std::move(write)] {
|
||||
WriterThreadMain(std::move(write));
|
||||
}} {}
|
||||
|
||||
DataLogBackgroundWriter::~DataLogBackgroundWriter() {
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_shutdown = true;
|
||||
m_doFlush = true;
|
||||
}
|
||||
m_cond.notify_all();
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void DataLogBackgroundWriter::SetFilename(std::string_view filename) {
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_newFilename = filename;
|
||||
}
|
||||
m_cond.notify_all();
|
||||
}
|
||||
|
||||
void DataLogBackgroundWriter::Flush() {
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_doFlush = true;
|
||||
}
|
||||
m_cond.notify_all();
|
||||
}
|
||||
|
||||
void DataLogBackgroundWriter::Pause() {
|
||||
DataLog::Pause();
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_state = kPaused;
|
||||
}
|
||||
|
||||
void DataLogBackgroundWriter::Resume() {
|
||||
DataLog::Resume();
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_state == kPaused) {
|
||||
m_state = kActive;
|
||||
} else if (m_state == kStopped) {
|
||||
m_state = kStart;
|
||||
}
|
||||
}
|
||||
|
||||
void DataLogBackgroundWriter::Stop() {
|
||||
DataLog::Stop();
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_state = kStopped;
|
||||
m_newFilename.clear();
|
||||
}
|
||||
m_cond.notify_all();
|
||||
}
|
||||
|
||||
static void WriteToFile(fs::file_t f, std::span<const uint8_t> data,
|
||||
std::string_view filename, wpi::Logger& msglog) {
|
||||
do {
|
||||
#ifdef _WIN32
|
||||
DWORD ret;
|
||||
if (!WriteFile(f, data.data(), data.size(), &ret, nullptr)) {
|
||||
WPI_ERROR(msglog, "Error writing to log file '{}': {}", filename,
|
||||
GetLastError());
|
||||
break;
|
||||
}
|
||||
#else
|
||||
ssize_t ret = ::write(f, data.data(), data.size());
|
||||
if (ret < 0) {
|
||||
// If it's a recoverable error, swallow it and retry the write
|
||||
if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise it's a non-recoverable error; quit trying
|
||||
WPI_ERROR(msglog, "Error writing to log file '{}': {}", filename,
|
||||
std::strerror(errno));
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
// The write may have written some or all of the data
|
||||
data = data.subspan(ret);
|
||||
} while (data.size() > 0);
|
||||
}
|
||||
|
||||
static std::string MakeRandomFilename() {
|
||||
// build random filename
|
||||
static std::random_device dev;
|
||||
static std::mt19937 rng(dev());
|
||||
std::uniform_int_distribution<int> dist(0, 15);
|
||||
const char* v = "0123456789abcdef";
|
||||
std::string filename = "wpilog_";
|
||||
for (int i = 0; i < 16; i++) {
|
||||
filename += v[dist(rng)];
|
||||
}
|
||||
filename += ".wpilog";
|
||||
return filename;
|
||||
}
|
||||
|
||||
struct DataLogBackgroundWriter::WriterThreadState {
|
||||
explicit WriterThreadState(std::string_view dir)
|
||||
: dirPath{dir.empty() ? "." : dir} {}
|
||||
WriterThreadState(const WriterThreadState&) = delete;
|
||||
WriterThreadState& operator=(const WriterThreadState&) = delete;
|
||||
~WriterThreadState() { Close(); }
|
||||
|
||||
void Close() {
|
||||
if (f != WPI_kInvalidFile) {
|
||||
fs::CloseFile(f);
|
||||
f = WPI_kInvalidFile;
|
||||
}
|
||||
}
|
||||
|
||||
void SetFilename(std::string_view fn) {
|
||||
baseFilename = fn;
|
||||
filename = fn;
|
||||
path = dirPath / filename;
|
||||
segmentCount = 1;
|
||||
}
|
||||
|
||||
void IncrementFilename() {
|
||||
fs::path basePath{baseFilename};
|
||||
filename = fmt::format("{}.{}{}", basePath.stem().string(), ++segmentCount,
|
||||
basePath.extension().string());
|
||||
path = dirPath / filename;
|
||||
}
|
||||
|
||||
fs::path dirPath;
|
||||
std::string baseFilename;
|
||||
std::string filename;
|
||||
fs::path path;
|
||||
fs::file_t f = WPI_kInvalidFile;
|
||||
uintmax_t freeSpace = UINTMAX_MAX;
|
||||
int segmentCount = 1;
|
||||
};
|
||||
|
||||
void DataLogBackgroundWriter::BufferHalfFull() {
|
||||
Flush();
|
||||
}
|
||||
|
||||
bool DataLogBackgroundWriter::BufferFull() {
|
||||
WPI_ERROR(m_msglog,
|
||||
"outgoing buffers exceeded threshold, pausing logging--"
|
||||
"consider flushing to disk more frequently (smaller period)");
|
||||
return true;
|
||||
}
|
||||
|
||||
void DataLogBackgroundWriter::StartLogFile(WriterThreadState& state) {
|
||||
std::error_code ec;
|
||||
|
||||
if (state.filename.empty()) {
|
||||
state.SetFilename(MakeRandomFilename());
|
||||
}
|
||||
|
||||
// get free space
|
||||
auto freeSpaceInfo = fs::space(state.dirPath, ec);
|
||||
if (!ec) {
|
||||
state.freeSpace = freeSpaceInfo.available;
|
||||
} else {
|
||||
state.freeSpace = UINTMAX_MAX;
|
||||
}
|
||||
if (state.freeSpace < kMinFreeSpace) {
|
||||
WPI_ERROR(m_msglog,
|
||||
"Insufficient free space ({} available), no log being saved",
|
||||
FormatBytesSize(state.freeSpace));
|
||||
m_state = kStopped;
|
||||
} else {
|
||||
// try preferred filename, or randomize it a few times, before giving up
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
// open file for append
|
||||
#ifdef _WIN32
|
||||
// WIN32 doesn't allow combination of CreateNew and Append
|
||||
state.f =
|
||||
fs::OpenFileForWrite(state.path, ec, fs::CD_CreateNew, fs::OF_None);
|
||||
#else
|
||||
state.f =
|
||||
fs::OpenFileForWrite(state.path, ec, fs::CD_CreateNew, fs::OF_Append);
|
||||
#endif
|
||||
if (ec) {
|
||||
WPI_ERROR(m_msglog, "Could not open log file '{}': {}",
|
||||
state.path.string(), ec.message());
|
||||
// try again with random filename
|
||||
state.SetFilename(MakeRandomFilename());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (state.f == WPI_kInvalidFile) {
|
||||
WPI_ERROR(m_msglog, "Could not open log file, no log being saved");
|
||||
} else {
|
||||
WPI_INFO(m_msglog, "Logging to '{}' ({} free space)", state.path.string(),
|
||||
FormatBytesSize(state.freeSpace));
|
||||
}
|
||||
}
|
||||
|
||||
// start file
|
||||
if (state.f != WPI_kInvalidFile) {
|
||||
StartFile();
|
||||
}
|
||||
}
|
||||
|
||||
void DataLogBackgroundWriter::WriterThreadMain(std::string_view dir) {
|
||||
std::chrono::duration<double> periodTime{m_period};
|
||||
|
||||
WriterThreadState state{dir};
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
state.SetFilename(m_newFilename);
|
||||
m_newFilename.clear();
|
||||
}
|
||||
StartLogFile(state);
|
||||
|
||||
std::error_code ec;
|
||||
std::vector<DataLog::Buffer> toWrite;
|
||||
int freeSpaceCount = 0;
|
||||
int checkExistCount = 0;
|
||||
bool blocked = false;
|
||||
uintmax_t written = 0;
|
||||
|
||||
std::unique_lock lock{m_mutex};
|
||||
do {
|
||||
bool doFlush = false;
|
||||
auto timeoutTime = std::chrono::steady_clock::now() + periodTime;
|
||||
if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout) {
|
||||
doFlush = true;
|
||||
}
|
||||
|
||||
if (m_state == kStopped) {
|
||||
state.Close();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool doStart = false;
|
||||
|
||||
// if file was deleted, recreate it with the same name
|
||||
if (++checkExistCount >= 10) {
|
||||
checkExistCount = 0;
|
||||
lock.unlock();
|
||||
bool exists = fs::exists(state.path, ec);
|
||||
lock.lock();
|
||||
if (!ec && !exists) {
|
||||
state.Close();
|
||||
state.IncrementFilename();
|
||||
WPI_INFO(m_msglog, "Log file deleted, recreating as fresh log '{}'",
|
||||
state.filename);
|
||||
doStart = true;
|
||||
}
|
||||
}
|
||||
|
||||
// start new file if file exceeds 1.8 GB
|
||||
if (written > 1800000000ull) {
|
||||
state.Close();
|
||||
state.IncrementFilename();
|
||||
WPI_INFO(m_msglog, "Log file reached 1.8 GB, starting new file '{}'",
|
||||
state.filename);
|
||||
doStart = true;
|
||||
}
|
||||
|
||||
if (m_state == kStart || doStart) {
|
||||
lock.unlock();
|
||||
DataLog::Stop();
|
||||
StartLogFile(state);
|
||||
lock.lock();
|
||||
if (m_state == kStopped) {
|
||||
continue;
|
||||
}
|
||||
m_state = kActive;
|
||||
written = 0;
|
||||
}
|
||||
|
||||
if (!m_newFilename.empty() && state.f != WPI_kInvalidFile) {
|
||||
auto newFilename = std::move(m_newFilename);
|
||||
m_newFilename.clear();
|
||||
// rename
|
||||
if (state.filename != newFilename) {
|
||||
lock.unlock();
|
||||
fs::rename(state.path, state.dirPath / newFilename, ec);
|
||||
lock.lock();
|
||||
}
|
||||
if (ec) {
|
||||
WPI_ERROR(m_msglog, "Could not rename log file from '{}' to '{}': {}",
|
||||
state.filename, newFilename, ec.message());
|
||||
} else {
|
||||
WPI_INFO(m_msglog, "Renamed log file from '{}' to '{}'", state.filename,
|
||||
newFilename);
|
||||
}
|
||||
state.SetFilename(newFilename);
|
||||
}
|
||||
|
||||
if (doFlush || m_doFlush) {
|
||||
// flush to file
|
||||
m_doFlush = false;
|
||||
DataLog::FlushBufs(&toWrite);
|
||||
if (toWrite.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state.f != WPI_kInvalidFile && !blocked) {
|
||||
lock.unlock();
|
||||
|
||||
// update free space every 10 flushes (in case other things are writing)
|
||||
if (++freeSpaceCount >= 10) {
|
||||
freeSpaceCount = 0;
|
||||
auto freeSpaceInfo = fs::space(state.dirPath, ec);
|
||||
if (!ec) {
|
||||
state.freeSpace = freeSpaceInfo.available;
|
||||
} else {
|
||||
state.freeSpace = UINTMAX_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
// write buffers to file
|
||||
for (auto&& buf : toWrite) {
|
||||
// stop writing when we go below the minimum free space
|
||||
state.freeSpace -= buf.GetData().size();
|
||||
written += buf.GetData().size();
|
||||
if (state.freeSpace < kMinFreeSpace) {
|
||||
[[unlikely]] WPI_ERROR(
|
||||
m_msglog,
|
||||
"Stopped logging due to low free space ({} available)",
|
||||
FormatBytesSize(state.freeSpace));
|
||||
blocked = true;
|
||||
break;
|
||||
}
|
||||
WriteToFile(state.f, buf.GetData(), state.filename, m_msglog);
|
||||
}
|
||||
|
||||
// sync to storage
|
||||
#if defined(__linux__)
|
||||
::fdatasync(state.f);
|
||||
#elif defined(__APPLE__)
|
||||
::fsync(state.f);
|
||||
#endif
|
||||
lock.lock();
|
||||
if (blocked) {
|
||||
[[unlikely]] m_state = kPaused;
|
||||
}
|
||||
}
|
||||
|
||||
// release buffers back to free list
|
||||
ReleaseBufs(&toWrite);
|
||||
}
|
||||
} while (!m_shutdown);
|
||||
}
|
||||
|
||||
void DataLogBackgroundWriter::WriterThreadMain(
|
||||
std::function<void(std::span<const uint8_t> data)> write) {
|
||||
std::chrono::duration<double> periodTime{m_period};
|
||||
|
||||
StartFile();
|
||||
|
||||
std::vector<DataLog::Buffer> toWrite;
|
||||
|
||||
std::unique_lock lock{m_mutex};
|
||||
do {
|
||||
bool doFlush = false;
|
||||
auto timeoutTime = std::chrono::steady_clock::now() + periodTime;
|
||||
if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout) {
|
||||
doFlush = true;
|
||||
}
|
||||
|
||||
if (doFlush || m_doFlush) {
|
||||
// flush to file
|
||||
m_doFlush = false;
|
||||
DataLog::FlushBufs(&toWrite);
|
||||
if (toWrite.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
// write buffers
|
||||
for (auto&& buf : toWrite) {
|
||||
if (!buf.GetData().empty()) {
|
||||
write(buf.GetData());
|
||||
}
|
||||
}
|
||||
lock.lock();
|
||||
|
||||
// release buffers back to free list
|
||||
ReleaseBufs(&toWrite);
|
||||
}
|
||||
} while (!m_shutdown);
|
||||
|
||||
write({}); // indicate EOF
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct WPI_DataLog* WPI_DataLog_CreateBackgroundWriter(
|
||||
const struct WPI_String* dir, const struct WPI_String* filename,
|
||||
double period, const struct WPI_String* extraHeader) {
|
||||
return reinterpret_cast<WPI_DataLog*>(new DataLogBackgroundWriter{
|
||||
wpi::to_string_view(dir), wpi::to_string_view(filename), period,
|
||||
wpi::to_string_view(extraHeader)});
|
||||
}
|
||||
|
||||
struct WPI_DataLog* WPI_DataLog_CreateBackgroundWriter_Func(
|
||||
void (*write)(void* ptr, const uint8_t* data, size_t len), void* ptr,
|
||||
double period, const struct WPI_String* extraHeader) {
|
||||
return reinterpret_cast<WPI_DataLog*>(new DataLogBackgroundWriter{
|
||||
[=](auto data) { write(ptr, data.data(), data.size()); }, period,
|
||||
wpi::to_string_view(extraHeader)});
|
||||
}
|
||||
|
||||
void WPI_DataLog_SetBackgroundWriterFilename(
|
||||
struct WPI_DataLog* datalog, const struct WPI_String* filename) {
|
||||
reinterpret_cast<DataLogBackgroundWriter*>(datalog)->SetFilename(
|
||||
wpi::to_string_view(filename));
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -1,309 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "wpi/DataLogReader.h"
|
||||
|
||||
#include <bit>
|
||||
#include <utility>
|
||||
|
||||
#include "wpi/DataLog.h"
|
||||
#include "wpi/Endian.h"
|
||||
|
||||
using namespace wpi::log;
|
||||
|
||||
static bool ReadString(std::span<const uint8_t>* buf, std::string_view* str) {
|
||||
if (buf->size() < 4) {
|
||||
*str = {};
|
||||
return false;
|
||||
}
|
||||
uint32_t len = wpi::support::endian::read32le(buf->data());
|
||||
if (len > (buf->size() - 4)) {
|
||||
*str = {};
|
||||
return false;
|
||||
}
|
||||
*str = {reinterpret_cast<const char*>(buf->data() + 4), len};
|
||||
*buf = buf->subspan(len + 4);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::IsStart() const {
|
||||
return m_entry == 0 && m_data.size() >= 17 &&
|
||||
m_data[0] == impl::kControlStart;
|
||||
}
|
||||
|
||||
bool DataLogRecord::IsFinish() const {
|
||||
return m_entry == 0 && m_data.size() == 5 &&
|
||||
m_data[0] == impl::kControlFinish;
|
||||
}
|
||||
|
||||
bool DataLogRecord::IsSetMetadata() const {
|
||||
return m_entry == 0 && m_data.size() >= 9 &&
|
||||
m_data[0] == impl::kControlSetMetadata;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetStartData(StartRecordData* out) const {
|
||||
if (!IsStart()) {
|
||||
return false;
|
||||
}
|
||||
out->entry = wpi::support::endian::read32le(&m_data[1]);
|
||||
auto buf = m_data.subspan(5);
|
||||
if (!ReadString(&buf, &out->name)) {
|
||||
return false;
|
||||
}
|
||||
if (!ReadString(&buf, &out->type)) {
|
||||
return false;
|
||||
}
|
||||
if (!ReadString(&buf, &out->metadata)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetFinishEntry(int* out) const {
|
||||
if (!IsFinish()) {
|
||||
return false;
|
||||
}
|
||||
*out = wpi::support::endian::read32le(&m_data[1]);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetSetMetadataData(MetadataRecordData* out) const {
|
||||
if (!IsSetMetadata()) {
|
||||
return false;
|
||||
}
|
||||
out->entry = wpi::support::endian::read32le(&m_data[1]);
|
||||
auto buf = m_data.subspan(5);
|
||||
return ReadString(&buf, &out->metadata);
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetBoolean(bool* value) const {
|
||||
if (m_data.size() != 1) {
|
||||
return false;
|
||||
}
|
||||
*value = m_data[0] != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetInteger(int64_t* value) const {
|
||||
if (m_data.size() != 8) {
|
||||
return false;
|
||||
}
|
||||
*value = wpi::support::endian::read64le(m_data.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetFloat(float* value) const {
|
||||
if (m_data.size() != 4) {
|
||||
return false;
|
||||
}
|
||||
*value = std::bit_cast<float>(wpi::support::endian::read32le(m_data.data()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetDouble(double* value) const {
|
||||
if (m_data.size() != 8) {
|
||||
return false;
|
||||
}
|
||||
*value = std::bit_cast<double>(wpi::support::endian::read64le(m_data.data()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetString(std::string_view* value) const {
|
||||
*value = {reinterpret_cast<const char*>(m_data.data()), m_data.size()};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetBooleanArray(std::vector<int>* arr) const {
|
||||
arr->clear();
|
||||
arr->reserve(m_data.size());
|
||||
for (auto v : m_data) {
|
||||
arr->push_back(v);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetIntegerArray(std::vector<int64_t>* arr) const {
|
||||
arr->clear();
|
||||
if ((m_data.size() % 8) != 0) {
|
||||
return false;
|
||||
}
|
||||
arr->reserve(m_data.size() / 8);
|
||||
for (size_t pos = 0; pos < m_data.size(); pos += 8) {
|
||||
arr->push_back(wpi::support::endian::read64le(&m_data[pos]));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetFloatArray(std::vector<float>* arr) const {
|
||||
arr->clear();
|
||||
if ((m_data.size() % 4) != 0) {
|
||||
return false;
|
||||
}
|
||||
arr->reserve(m_data.size() / 4);
|
||||
for (size_t pos = 0; pos < m_data.size(); pos += 4) {
|
||||
arr->push_back(
|
||||
std::bit_cast<float>(wpi::support::endian::read32le(&m_data[pos])));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetDoubleArray(std::vector<double>* arr) const {
|
||||
arr->clear();
|
||||
if ((m_data.size() % 8) != 0) {
|
||||
return false;
|
||||
}
|
||||
arr->reserve(m_data.size() / 8);
|
||||
for (size_t pos = 0; pos < m_data.size(); pos += 8) {
|
||||
arr->push_back(
|
||||
std::bit_cast<double>(wpi::support::endian::read64le(&m_data[pos])));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetStringArray(std::vector<std::string_view>* arr) const {
|
||||
arr->clear();
|
||||
if (m_data.size() < 4) {
|
||||
return false;
|
||||
}
|
||||
uint32_t size = wpi::support::endian::read32le(m_data.data());
|
||||
// sanity check size
|
||||
if (size > ((m_data.size() - 4) / 4)) {
|
||||
return false;
|
||||
}
|
||||
auto buf = m_data.subspan(4);
|
||||
arr->reserve(size);
|
||||
for (uint32_t i = 0; i < size; ++i) {
|
||||
std::string_view str;
|
||||
if (!ReadString(&buf, &str)) {
|
||||
arr->clear();
|
||||
return false;
|
||||
}
|
||||
arr->push_back(str);
|
||||
}
|
||||
// any left over? treat as corrupt
|
||||
if (!buf.empty()) {
|
||||
arr->clear();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
DataLogReader::DataLogReader(std::unique_ptr<MemoryBuffer> buffer)
|
||||
: m_buf{std::move(buffer)} {}
|
||||
|
||||
bool DataLogReader::IsValid() const {
|
||||
if (!m_buf) {
|
||||
return false;
|
||||
}
|
||||
auto buf = m_buf->GetBuffer();
|
||||
return buf.size() >= 12 &&
|
||||
std::string_view{reinterpret_cast<const char*>(buf.data()), 6} ==
|
||||
"WPILOG" &&
|
||||
wpi::support::endian::read16le(&buf[6]) >= 0x0100;
|
||||
}
|
||||
|
||||
uint16_t DataLogReader::GetVersion() const {
|
||||
if (!m_buf) {
|
||||
return 0;
|
||||
}
|
||||
auto buf = m_buf->GetBuffer();
|
||||
if (buf.size() < 12) {
|
||||
return 0;
|
||||
}
|
||||
return wpi::support::endian::read16le(&buf[6]);
|
||||
}
|
||||
|
||||
std::string_view DataLogReader::GetExtraHeader() const {
|
||||
if (!m_buf) {
|
||||
return {};
|
||||
}
|
||||
auto buf = m_buf->GetBuffer();
|
||||
if (buf.size() < 8) {
|
||||
return {};
|
||||
}
|
||||
std::string_view rv;
|
||||
buf = buf.subspan(8);
|
||||
ReadString(&buf, &rv);
|
||||
return rv;
|
||||
}
|
||||
|
||||
DataLogReader::iterator DataLogReader::begin() const {
|
||||
if (!m_buf) {
|
||||
return end();
|
||||
}
|
||||
auto buf = m_buf->GetBuffer();
|
||||
if (buf.size() < 12) {
|
||||
return end();
|
||||
}
|
||||
uint32_t size = wpi::support::endian::read32le(&buf[8]);
|
||||
if (buf.size() < (12 + size)) {
|
||||
return end();
|
||||
}
|
||||
return DataLogIterator{this, 12 + size};
|
||||
}
|
||||
|
||||
static uint64_t ReadVarInt(std::span<const uint8_t> buf) {
|
||||
uint64_t val = 0;
|
||||
int shift = 0;
|
||||
for (auto v : buf) {
|
||||
val |= static_cast<uint64_t>(v) << shift;
|
||||
shift += 8;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
bool DataLogReader::GetRecord(size_t* pos, DataLogRecord* out) const {
|
||||
if (!m_buf) {
|
||||
return false;
|
||||
}
|
||||
auto buf = m_buf->GetBuffer();
|
||||
if (*pos >= buf.size()) {
|
||||
return false;
|
||||
}
|
||||
buf = buf.subspan(*pos);
|
||||
if (buf.size() < 4) { // minimum header length
|
||||
return false;
|
||||
}
|
||||
unsigned int entryLen = (buf[0] & 0x3) + 1;
|
||||
unsigned int sizeLen = ((buf[0] >> 2) & 0x3) + 1;
|
||||
unsigned int timestampLen = ((buf[0] >> 4) & 0x7) + 1;
|
||||
unsigned int headerLen = 1 + entryLen + sizeLen + timestampLen;
|
||||
if (buf.size() < headerLen) {
|
||||
return false;
|
||||
}
|
||||
int entry = ReadVarInt(buf.subspan(1, entryLen));
|
||||
uint32_t size = ReadVarInt(buf.subspan(1 + entryLen, sizeLen));
|
||||
if (size > (buf.size() - headerLen)) {
|
||||
return false;
|
||||
}
|
||||
int64_t timestamp =
|
||||
ReadVarInt(buf.subspan(1 + entryLen + sizeLen, timestampLen));
|
||||
*out = DataLogRecord{entry, timestamp, buf.subspan(headerLen, size)};
|
||||
*pos += headerLen + size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogReader::GetNextRecord(size_t* pos) const {
|
||||
if (!m_buf) {
|
||||
return false;
|
||||
}
|
||||
auto buf = m_buf->GetBuffer();
|
||||
if (buf.size() < (*pos + 4)) { // minimum header length
|
||||
return false;
|
||||
}
|
||||
unsigned int entryLen = (buf[*pos] & 0x3) + 1;
|
||||
unsigned int sizeLen = ((buf[*pos] >> 2) & 0x3) + 1;
|
||||
unsigned int timestampLen = ((buf[*pos] >> 4) & 0x7) + 1;
|
||||
unsigned int headerLen = 1 + entryLen + sizeLen + timestampLen;
|
||||
if (buf.size() < (*pos + headerLen)) {
|
||||
return false;
|
||||
}
|
||||
uint32_t size = ReadVarInt(buf.subspan(*pos + 1 + entryLen, sizeLen));
|
||||
// check this way to avoid overflow
|
||||
if (size >= (buf.size() - *pos - headerLen)) {
|
||||
return false;
|
||||
}
|
||||
*pos += headerLen + size;
|
||||
return true;
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "wpi/DataLogWriter.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "wpi/raw_ostream.h"
|
||||
|
||||
using namespace wpi::log;
|
||||
|
||||
static std::unique_ptr<wpi::raw_ostream> CheckOpen(std::string_view filename,
|
||||
std::error_code& ec) {
|
||||
auto rv = std::make_unique<wpi::raw_fd_ostream>(filename, ec);
|
||||
if (ec) {
|
||||
return nullptr;
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
DataLogWriter::DataLogWriter(std::string_view filename, std::error_code& ec,
|
||||
std::string_view extraHeader)
|
||||
: DataLogWriter{s_defaultMessageLog, filename, ec, extraHeader} {}
|
||||
|
||||
DataLogWriter::DataLogWriter(wpi::Logger& msglog, std::string_view filename,
|
||||
std::error_code& ec, std::string_view extraHeader)
|
||||
: DataLogWriter{msglog, CheckOpen(filename, ec), extraHeader} {
|
||||
if (ec) {
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
DataLogWriter::DataLogWriter(std::unique_ptr<wpi::raw_ostream> os,
|
||||
std::string_view extraHeader)
|
||||
: DataLogWriter{s_defaultMessageLog, std::move(os), extraHeader} {}
|
||||
|
||||
DataLogWriter::DataLogWriter(wpi::Logger& msglog,
|
||||
std::unique_ptr<wpi::raw_ostream> os,
|
||||
std::string_view extraHeader)
|
||||
: DataLog{msglog, extraHeader}, m_os{std::move(os)} {
|
||||
StartFile();
|
||||
}
|
||||
|
||||
DataLogWriter::~DataLogWriter() {
|
||||
if (m_os) {
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
|
||||
void DataLogWriter::Flush() {
|
||||
if (!m_os) {
|
||||
return;
|
||||
}
|
||||
std::vector<Buffer> writeBufs;
|
||||
FlushBufs(&writeBufs);
|
||||
for (auto&& buf : writeBufs) {
|
||||
(*m_os) << buf.GetData();
|
||||
}
|
||||
ReleaseBufs(&writeBufs);
|
||||
}
|
||||
|
||||
void DataLogWriter::Stop() {
|
||||
DataLog::Stop();
|
||||
Flush();
|
||||
m_os.reset();
|
||||
}
|
||||
|
||||
bool DataLogWriter::BufferFull() {
|
||||
Flush();
|
||||
return false;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
struct WPI_DataLog* WPI_DataLog_CreateWriter(
|
||||
const struct WPI_String* filename, int* errorCode,
|
||||
const struct WPI_String* extraHeader) {
|
||||
std::error_code ec;
|
||||
auto rv = reinterpret_cast<WPI_DataLog*>(new DataLogWriter{
|
||||
wpi::to_string_view(filename), ec, wpi::to_string_view(extraHeader)});
|
||||
*errorCode = ec.value();
|
||||
return rv;
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -1,104 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include "wpi/FileLogger.h"
|
||||
|
||||
#ifdef __linux__
|
||||
#include <fcntl.h>
|
||||
#include <sys/inotify.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "wpi/StringExtras.h"
|
||||
|
||||
namespace wpi {
|
||||
FileLogger::FileLogger(std::string_view file,
|
||||
std::function<void(std::string_view)> callback)
|
||||
#ifdef __linux__
|
||||
: m_fileHandle{open(file.data(), O_RDONLY)},
|
||||
m_inotifyHandle{inotify_init()},
|
||||
m_inotifyWatchHandle{
|
||||
inotify_add_watch(m_inotifyHandle, file.data(), IN_MODIFY)},
|
||||
m_thread{[=, this] {
|
||||
char buf[8000];
|
||||
char eventBuf[sizeof(struct inotify_event) + NAME_MAX + 1];
|
||||
lseek(m_fileHandle, 0, SEEK_END);
|
||||
while (read(m_inotifyHandle, eventBuf, sizeof(eventBuf)) > 0) {
|
||||
int bufLen = 0;
|
||||
if ((bufLen = read(m_fileHandle, buf, sizeof(buf))) > 0) {
|
||||
callback(std::string_view{buf, static_cast<size_t>(bufLen)});
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
}
|
||||
}}
|
||||
#endif
|
||||
{
|
||||
}
|
||||
FileLogger::FileLogger(std::string_view file, log::DataLog& log,
|
||||
std::string_view key)
|
||||
: FileLogger(file, Buffer([entry = log.Start(key, "string"),
|
||||
&log](std::string_view line) {
|
||||
log.AppendString(entry, line, 0);
|
||||
})) {}
|
||||
FileLogger::FileLogger(FileLogger&& other)
|
||||
#ifdef __linux__
|
||||
: m_fileHandle{std::exchange(other.m_fileHandle, -1)},
|
||||
m_inotifyHandle{std::exchange(other.m_inotifyHandle, -1)},
|
||||
m_inotifyWatchHandle{std::exchange(other.m_inotifyWatchHandle, -1)},
|
||||
m_thread{std::move(other.m_thread)}
|
||||
#endif
|
||||
{
|
||||
}
|
||||
FileLogger& FileLogger::operator=(FileLogger&& rhs) {
|
||||
#ifdef __linux__
|
||||
std::swap(m_fileHandle, rhs.m_fileHandle);
|
||||
std::swap(m_inotifyHandle, rhs.m_inotifyHandle);
|
||||
std::swap(m_inotifyWatchHandle, rhs.m_inotifyWatchHandle);
|
||||
m_thread = std::move(rhs.m_thread);
|
||||
#endif
|
||||
return *this;
|
||||
}
|
||||
FileLogger::~FileLogger() {
|
||||
#ifdef __linux__
|
||||
if (m_inotifyWatchHandle != -1) {
|
||||
inotify_rm_watch(m_inotifyHandle, m_inotifyWatchHandle);
|
||||
}
|
||||
if (m_inotifyHandle != -1) {
|
||||
close(m_inotifyHandle);
|
||||
}
|
||||
if (m_fileHandle != -1) {
|
||||
close(m_fileHandle);
|
||||
}
|
||||
if (m_thread.joinable()) {
|
||||
m_thread.join();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::function<void(std::string_view)> FileLogger::Buffer(
|
||||
std::function<void(std::string_view)> callback) {
|
||||
return [callback,
|
||||
buf = wpi::SmallVector<char, 64>{}](std::string_view data) mutable {
|
||||
buf.append(data.begin(), data.end());
|
||||
if (!wpi::contains({data.data(), data.size()}, "\n")) {
|
||||
return;
|
||||
}
|
||||
auto [wholeData, extra] = wpi::rsplit({buf.data(), buf.size()}, "\n");
|
||||
std::string leftover{extra};
|
||||
|
||||
callback(wholeData);
|
||||
buf.clear();
|
||||
buf.append(leftover.begin(), leftover.end());
|
||||
};
|
||||
}
|
||||
} // namespace wpi
|
||||
@@ -1,614 +0,0 @@
|
||||
// Copyright (c) FIRST and other WPILib contributors.
|
||||
// Open Source Software; you can modify and/or share it under the terms of
|
||||
// the WPILib BSD license file in the root directory of this project.
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "WPIUtilJNI.h"
|
||||
#include "edu_wpi_first_util_datalog_DataLogJNI.h"
|
||||
#include "wpi/DataLog.h"
|
||||
#include "wpi/DataLogBackgroundWriter.h"
|
||||
#include "wpi/DataLogWriter.h"
|
||||
#include "wpi/jni_util.h"
|
||||
|
||||
using namespace wpi::java;
|
||||
using namespace wpi::log;
|
||||
|
||||
namespace {
|
||||
class buf_ostream : public wpi::raw_uvector_ostream {
|
||||
private:
|
||||
std::vector<uint8_t> data;
|
||||
|
||||
public:
|
||||
buf_ostream() : raw_uvector_ostream{data} {}
|
||||
|
||||
void clear() { data.clear(); }
|
||||
};
|
||||
} // namespace
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: bgCreate
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_bgCreate
|
||||
(JNIEnv* env, jclass, jstring dir, jstring filename, jdouble period,
|
||||
jstring extraHeader)
|
||||
{
|
||||
if (!dir) {
|
||||
wpi::ThrowNullPointerException(env, "dir is null");
|
||||
return 0;
|
||||
}
|
||||
if (!filename) {
|
||||
wpi::ThrowNullPointerException(env, "filename is null");
|
||||
return 0;
|
||||
}
|
||||
if (!extraHeader) {
|
||||
wpi::ThrowNullPointerException(env, "extraHeader is null");
|
||||
return 0;
|
||||
}
|
||||
return reinterpret_cast<jlong>(new DataLogBackgroundWriter{
|
||||
JStringRef{env, dir}, JStringRef{env, filename}, period,
|
||||
JStringRef{env, extraHeader}});
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: bgSetFilename
|
||||
* Signature: (JLjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_bgSetFilename
|
||||
(JNIEnv* env, jclass, jlong impl, jstring filename)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
if (!filename) {
|
||||
wpi::ThrowNullPointerException(env, "filename is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLogBackgroundWriter*>(impl)->SetFilename(
|
||||
JStringRef{env, filename});
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: fgCreate
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_fgCreate
|
||||
(JNIEnv* env, jclass, jstring filename, jstring extraHeader)
|
||||
{
|
||||
if (!filename) {
|
||||
wpi::ThrowNullPointerException(env, "filename is null");
|
||||
return 0;
|
||||
}
|
||||
if (!extraHeader) {
|
||||
wpi::ThrowNullPointerException(env, "extraHeader is null");
|
||||
return 0;
|
||||
}
|
||||
std::error_code ec;
|
||||
auto writer = new DataLogWriter{JStringRef{env, filename}, ec,
|
||||
JStringRef{env, extraHeader}};
|
||||
if (ec) {
|
||||
wpi::ThrowIOException(env, ec.message());
|
||||
delete writer;
|
||||
return 0;
|
||||
}
|
||||
return reinterpret_cast<jlong>(writer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: fgCreateMemory
|
||||
* Signature: (Ljava/lang/String;)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_fgCreateMemory
|
||||
(JNIEnv* env, jclass, jstring extraHeader)
|
||||
{
|
||||
if (!extraHeader) {
|
||||
wpi::ThrowNullPointerException(env, "extraHeader is null");
|
||||
return 0;
|
||||
}
|
||||
auto writer = new DataLogWriter{std::make_unique<buf_ostream>(),
|
||||
JStringRef{env, extraHeader}};
|
||||
return reinterpret_cast<jlong>(writer);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: flush
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_flush
|
||||
(JNIEnv* env, jclass, jlong impl)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->Flush();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: copyWriteBuffer
|
||||
* Signature: (J[BI)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_copyWriteBuffer
|
||||
(JNIEnv* env, jclass, jlong impl, jbyteArray buf, jint start)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return 0;
|
||||
}
|
||||
auto writer = reinterpret_cast<DataLogWriter*>(impl);
|
||||
writer->Flush();
|
||||
auto& stream = static_cast<buf_ostream&>(writer->GetStream());
|
||||
JSpan<jbyte> jbuf{env, buf};
|
||||
auto arr = stream.array();
|
||||
if (start < 0 || static_cast<size_t>(start) >= arr.size()) {
|
||||
stream.clear();
|
||||
return 0;
|
||||
}
|
||||
size_t qty = (std::min)(jbuf.size(), arr.size() - start);
|
||||
std::copy(arr.begin(), arr.begin() + qty, jbuf.begin());
|
||||
return qty;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: pause
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_pause
|
||||
(JNIEnv* env, jclass, jlong impl)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->Pause();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: resume
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_resume
|
||||
(JNIEnv* env, jclass, jlong impl)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->Resume();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: stop
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_stop
|
||||
(JNIEnv* env, jclass, jlong impl)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->Stop();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: addSchema
|
||||
* Signature: (JLjava/lang/String;Ljava/lang/String;[BJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_addSchema
|
||||
(JNIEnv* env, jclass, jlong impl, jstring name, jstring type,
|
||||
jbyteArray schema, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AddSchema(
|
||||
JStringRef{env, name}, JStringRef{env, type},
|
||||
JSpan<const jbyte>{env, schema}.uarray(), timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: addSchemaString
|
||||
* Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_addSchemaString
|
||||
(JNIEnv* env, jclass, jlong impl, jstring name, jstring type, jstring schema,
|
||||
jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
JStringRef schemaStr{env, schema};
|
||||
std::string_view schemaView = schemaStr.str();
|
||||
reinterpret_cast<DataLog*>(impl)->AddSchema(
|
||||
JStringRef{env, name}, JStringRef{env, type},
|
||||
{reinterpret_cast<const uint8_t*>(schemaView.data()), schemaView.size()},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: start
|
||||
* Signature: (JLjava/lang/String;Ljava/lang/String;Ljava/lang/String;J)I
|
||||
*/
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_start
|
||||
(JNIEnv* env, jclass, jlong impl, jstring name, jstring type,
|
||||
jstring metadata, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return 0;
|
||||
}
|
||||
return reinterpret_cast<DataLog*>(impl)->Start(
|
||||
JStringRef{env, name}, JStringRef{env, type}, JStringRef{env, metadata},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: finish
|
||||
* Signature: (JIJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_finish
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->Finish(entry, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: setMetadata
|
||||
* Signature: (JILjava/lang/String;J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_setMetadata
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jstring metadata,
|
||||
jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->SetMetadata(
|
||||
entry, JStringRef{env, metadata}, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: close
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_close
|
||||
(JNIEnv*, jclass, jlong impl)
|
||||
{
|
||||
delete reinterpret_cast<DataLog*>(impl);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendRaw
|
||||
* Signature: (JI[BIIJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendRaw
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jbyteArray value, jint start,
|
||||
jint length, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
wpi::ThrowNullPointerException(env, "value is null");
|
||||
return;
|
||||
}
|
||||
if (start < 0) {
|
||||
wpi::ThrowIndexOobException(env, "start must be >= 0");
|
||||
return;
|
||||
}
|
||||
if (length < 0) {
|
||||
wpi::ThrowIndexOobException(env, "length must be >= 0");
|
||||
return;
|
||||
}
|
||||
CriticalJSpan<const jbyte> cvalue{env, value};
|
||||
if (static_cast<unsigned int>(start + length) > cvalue.size()) {
|
||||
wpi::ThrowIndexOobException(
|
||||
env, "start + len must be smaller than array length");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendRaw(
|
||||
entry, cvalue.uarray().subspan(start, length), timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendRawBuffer
|
||||
* Signature: (JILjava/lang/Object;IIJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendRawBuffer
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jobject value, jint start,
|
||||
jint length, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
wpi::ThrowNullPointerException(env, "value is null");
|
||||
return;
|
||||
}
|
||||
if (start < 0) {
|
||||
wpi::ThrowIndexOobException(env, "start must be >= 0");
|
||||
return;
|
||||
}
|
||||
if (length < 0) {
|
||||
wpi::ThrowIndexOobException(env, "length must be >= 0");
|
||||
return;
|
||||
}
|
||||
JSpan<const jbyte> cvalue{env, value, static_cast<size_t>(start + length)};
|
||||
if (!cvalue) {
|
||||
wpi::ThrowIllegalArgumentException(env,
|
||||
"value must be a native ByteBuffer");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendRaw(
|
||||
entry, cvalue.uarray().subspan(start, length), timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendBoolean
|
||||
* Signature: (JIZJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendBoolean
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jboolean value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendBoolean(entry, value, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendInteger
|
||||
* Signature: (JIJJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendInteger
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jlong value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendInteger(entry, value, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendFloat
|
||||
* Signature: (JIFJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendFloat
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jfloat value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendFloat(entry, value, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendDouble
|
||||
* Signature: (JIDJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendDouble
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jdouble value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendDouble(entry, value, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendString
|
||||
* Signature: (JILjava/lang/String;J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendString
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jstring value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendString(entry, JStringRef{env, value},
|
||||
timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendBooleanArray
|
||||
* Signature: (JI[ZJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendBooleanArray
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jbooleanArray value,
|
||||
jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
wpi::ThrowNullPointerException(env, "value is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendBooleanArray(
|
||||
entry, JSpan<const jboolean>{env, value}, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendIntegerArray
|
||||
* Signature: (JI[JJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendIntegerArray
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jlongArray value,
|
||||
jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
wpi::ThrowNullPointerException(env, "value is null");
|
||||
return;
|
||||
}
|
||||
JSpan<const jlong> jarr{env, value};
|
||||
if constexpr (sizeof(jlong) == sizeof(int64_t)) {
|
||||
reinterpret_cast<DataLog*>(impl)->AppendIntegerArray(
|
||||
entry, {reinterpret_cast<const int64_t*>(jarr.data()), jarr.size()},
|
||||
timestamp);
|
||||
} else {
|
||||
wpi::SmallVector<int64_t, 16> arr;
|
||||
arr.reserve(jarr.size());
|
||||
for (auto v : jarr) {
|
||||
arr.push_back(v);
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendIntegerArray(entry, arr, timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendFloatArray
|
||||
* Signature: (JI[FJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendFloatArray
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jfloatArray value,
|
||||
jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
wpi::ThrowNullPointerException(env, "value is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendFloatArray(
|
||||
entry, JSpan<const jfloat>{env, value}, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendDoubleArray
|
||||
* Signature: (JI[DJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendDoubleArray
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jdoubleArray value,
|
||||
jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
wpi::ThrowNullPointerException(env, "value is null");
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendDoubleArray(
|
||||
entry, JSpan<const jdouble>{env, value}, timestamp);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: appendStringArray
|
||||
* Signature: (JI[Ljava/lang/Object;J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendStringArray
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jobjectArray value,
|
||||
jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
wpi::ThrowNullPointerException(env, "impl is null");
|
||||
return;
|
||||
}
|
||||
if (!value) {
|
||||
wpi::ThrowNullPointerException(env, "value is null");
|
||||
return;
|
||||
}
|
||||
size_t len = env->GetArrayLength(value);
|
||||
std::vector<std::string> arr;
|
||||
arr.reserve(len);
|
||||
for (size_t i = 0; i < len; ++i) {
|
||||
JLocal<jstring> elem{
|
||||
env, static_cast<jstring>(env->GetObjectArrayElement(value, i))};
|
||||
if (!elem) {
|
||||
wpi::ThrowNullPointerException(
|
||||
env, fmt::format("string at element {} is null", i));
|
||||
return;
|
||||
}
|
||||
arr.emplace_back(JStringRef{env, elem}.str());
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendStringArray(entry, arr, timestamp);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
@@ -7,8 +7,6 @@
|
||||
#include <jni.h>
|
||||
|
||||
#include "edu_wpi_first_util_WPIUtilJNI.h"
|
||||
#include "wpi/DataLog.h"
|
||||
#include "wpi/FileLogger.h"
|
||||
#include "wpi/RawFrame.h"
|
||||
#include "wpi/RuntimeCheck.h"
|
||||
#include "wpi/Synchronization.h"
|
||||
@@ -464,42 +462,4 @@ Java_edu_wpi_first_util_WPIUtilJNI_setRawFrameInfo
|
||||
f->stride = stride;
|
||||
f->pixelFormat = pixelFormat;
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: createFileLogger
|
||||
* Signature: (Ljava/lang/String;JLjava/lang/String;)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_createFileLogger
|
||||
(JNIEnv* env, jclass, jstring file, jlong log, jstring key)
|
||||
{
|
||||
if (!file) {
|
||||
wpi::ThrowNullPointerException(env, "file is null");
|
||||
return 0;
|
||||
}
|
||||
auto* f = reinterpret_cast<wpi::log::DataLog*>(log);
|
||||
if (!f) {
|
||||
wpi::ThrowNullPointerException(env, "log is null");
|
||||
return 0;
|
||||
}
|
||||
if (!key) {
|
||||
wpi::ThrowNullPointerException(env, "key is null");
|
||||
return 0;
|
||||
}
|
||||
return reinterpret_cast<jlong>(
|
||||
new wpi::FileLogger{JStringRef{env, file}, *f, JStringRef{env, key}});
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_WPIUtilJNI
|
||||
* Method: freeFileLogger
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_WPIUtilJNI_freeFileLogger
|
||||
(JNIEnv* env, jclass, jlong fileTail)
|
||||
{
|
||||
delete reinterpret_cast<wpi::FileLogger*>(fileTail);
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
Reference in New Issue
Block a user