[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:
DeltaDizzy
2025-02-19 23:08:17 -06:00
committed by GitHub
parent ac1705ae2b
commit da47f06d70
99 changed files with 778 additions and 330 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -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

View File

@@ -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"

View File

@@ -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"