2020-03-21 20:43:34 -07:00
|
|
|
// 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"
|
|
|
|
|
|
2024-03-17 18:39:03 -07:00
|
|
|
#include <bit>
|
2020-03-21 20:43:34 -07:00
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <cstring>
|
|
|
|
|
#include <vector>
|
|
|
|
|
|
|
|
|
|
#include "wpi/Endian.h"
|
|
|
|
|
#include "wpi/Logger.h"
|
2023-10-19 21:41:47 -07:00
|
|
|
#include "wpi/SmallString.h"
|
2024-05-12 06:25:42 -07:00
|
|
|
#include "wpi/print.h"
|
2020-03-21 20:43:34 -07:00
|
|
|
#include "wpi/timestamp.h"
|
|
|
|
|
|
|
|
|
|
using namespace wpi::log;
|
|
|
|
|
|
|
|
|
|
static constexpr size_t kRecordMaxHeaderSize = 17;
|
2023-10-01 14:01:49 -07:00
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
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);
|
2023-10-01 14:01:49 -07:00
|
|
|
}
|
|
|
|
|
}
|
2020-03-21 20:43:34 -07:00
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
wpi::Logger DataLog::s_defaultMessageLog{DefaultLog};
|
|
|
|
|
|
2020-03-21 20:43:34 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
void DataLog::StartFile() {
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
|
|
|
|
if (m_active) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-03-21 20:43:34 -07:00
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
// Grab previously pending writes
|
|
|
|
|
std::vector<Buffer> bufs;
|
|
|
|
|
bufs.swap(m_outgoing);
|
|
|
|
|
m_outgoing.reserve(bufs.size() + 1);
|
2020-03-21 20:43:34 -07:00
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
// 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());
|
2020-03-21 20:43:34 -07:00
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
// 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);
|
|
|
|
|
if (!entryInfo.second.schemaData.empty()) {
|
|
|
|
|
StartRecord(entryInfo.second.id, 0, entryInfo.second.schemaData.size(),
|
|
|
|
|
0);
|
|
|
|
|
AppendImpl(entryInfo.second.schemaData);
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
// Append previously pending writes
|
|
|
|
|
for (auto&& buf : bufs) {
|
|
|
|
|
m_outgoing.emplace_back(std::move(buf));
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
m_active = true;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
void DataLog::FlushBufs(std::vector<Buffer>* writeBufs) {
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
|
|
|
|
writeBufs->swap(m_outgoing);
|
|
|
|
|
DoReleaseBufs(&m_outgoing);
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
void DataLog::ReleaseBufs(std::vector<Buffer>* bufs) {
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
|
|
|
|
DoReleaseBufs(bufs);
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLog::Pause() {
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
m_paused = true;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLog::Resume() {
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
m_paused = false;
|
2023-11-03 20:34:43 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLog::Stop() {
|
2024-05-12 14:09:43 -07:00
|
|
|
std::scoped_lock lock{m_mutex};
|
|
|
|
|
m_active = false;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
void DataLog::BufferHalfFull() {}
|
|
|
|
|
|
2023-10-19 21:41:47 -07:00
|
|
|
bool DataLog::HasSchema(std::string_view name) const {
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
|
|
|
|
wpi::SmallString<128> fullName{"/.schema/"};
|
|
|
|
|
fullName += name;
|
|
|
|
|
auto it = m_entries.find(fullName);
|
|
|
|
|
return it != m_entries.end();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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};
|
|
|
|
|
wpi::SmallString<128> fullName{"/.schema/"};
|
|
|
|
|
fullName += name;
|
|
|
|
|
auto& entryInfo = m_entries[fullName];
|
|
|
|
|
if (entryInfo.id != 0) {
|
|
|
|
|
return; // don't add duplicates
|
|
|
|
|
}
|
2023-11-03 20:34:43 -07:00
|
|
|
entryInfo.schemaData.assign(schema.begin(), schema.end());
|
2023-10-19 21:41:47 -07:00
|
|
|
int entry = StartImpl(fullName, type, {}, timestamp);
|
|
|
|
|
|
|
|
|
|
// inline AppendRaw() without releasing lock
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
[[unlikely]] return; // should never happen, but check anyway
|
|
|
|
|
}
|
2024-05-12 14:09:43 -07:00
|
|
|
if (!m_active) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
|
|
|
|
}
|
2023-10-19 21:41:47 -07:00
|
|
|
StartRecord(entry, timestamp, schema.size(), 0);
|
|
|
|
|
AppendImpl(schema);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-21 20:43:34 -07:00
|
|
|
// 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};
|
2023-10-19 21:41:47 -07:00
|
|
|
return StartImpl(name, type, metadata, timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int DataLog::StartImpl(std::string_view name, std::string_view type,
|
|
|
|
|
std::string_view metadata, int64_t timestamp) {
|
2020-03-21 20:43:34 -07:00
|
|
|
auto& entryInfo = m_entries[name];
|
|
|
|
|
if (entryInfo.id == 0) {
|
|
|
|
|
entryInfo.id = ++m_lastId;
|
|
|
|
|
}
|
2023-11-03 20:34:43 -07:00
|
|
|
auto& entryInfo2 = m_entryIds[entryInfo.id];
|
|
|
|
|
++entryInfo2.count;
|
|
|
|
|
if (entryInfo2.count > 1) {
|
2020-03-21 20:43:34 -07:00
|
|
|
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;
|
2023-11-03 20:34:43 -07:00
|
|
|
entryInfo2.metadata = metadata;
|
|
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
if (!m_active) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[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) {
|
2020-03-21 20:43:34 -07:00
|
|
|
size_t strsize = name.size() + type.size() + metadata.size();
|
|
|
|
|
uint8_t* buf = StartRecord(0, timestamp, 5 + 12 + strsize, 5);
|
|
|
|
|
*buf++ = impl::kControlStart;
|
2023-11-03 20:34:43 -07:00
|
|
|
wpi::support::endian::write32le(buf, id);
|
2020-03-21 20:43:34 -07:00
|
|
|
AppendStringImpl(name);
|
|
|
|
|
AppendStringImpl(type);
|
|
|
|
|
AppendStringImpl(metadata);
|
|
|
|
|
}
|
|
|
|
|
|
2024-05-12 14:09:43 -07:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-21 20:43:34 -07:00
|
|
|
void DataLog::Finish(int entry, int64_t timestamp) {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2023-11-03 20:34:43 -07:00
|
|
|
auto& entryInfo2 = m_entryIds[entry];
|
|
|
|
|
if (entryInfo2.count == 0) {
|
2020-03-21 20:43:34 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2023-11-03 20:34:43 -07:00
|
|
|
--entryInfo2.count;
|
|
|
|
|
if (entryInfo2.count != 0) {
|
2020-03-21 20:43:34 -07:00
|
|
|
return;
|
|
|
|
|
}
|
2023-11-03 20:34:43 -07:00
|
|
|
m_entryIds.erase(entry);
|
2024-05-12 14:09:43 -07:00
|
|
|
if (!m_active) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
|
|
|
|
}
|
2020-03-21 20:43:34 -07:00
|
|
|
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};
|
2023-11-03 20:34:43 -07:00
|
|
|
m_entryIds[entry].metadata = metadata;
|
2024-05-12 14:09:43 -07:00
|
|
|
if (!m_active) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
|
|
|
|
}
|
2022-09-22 21:54:55 -07:00
|
|
|
uint8_t* buf = StartRecord(0, timestamp, 5 + 4 + metadata.size(), 5);
|
2020-03-21 20:43:34 -07:00
|
|
|
*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()) {
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_outgoing.size() == kMaxBufferCount / 2) {
|
|
|
|
|
[[unlikely]] BufferHalfFull();
|
|
|
|
|
}
|
2020-03-21 20:43:34 -07:00
|
|
|
if (m_free.empty()) {
|
2023-09-30 20:19:28 -07:00
|
|
|
if (m_outgoing.size() >= kMaxBufferCount) {
|
2024-05-12 14:09:43 -07:00
|
|
|
[[unlikely]]
|
|
|
|
|
if (BufferFull()) {
|
|
|
|
|
m_paused = true;
|
|
|
|
|
}
|
2023-09-30 20:19:28 -07:00
|
|
|
}
|
2020-03-21 20:43:34 -07:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendImpl(std::span<const uint8_t> data) {
|
2020-03-21 20:43:34 -07:00
|
|
|
while (data.size() > kBlockSize) {
|
|
|
|
|
uint8_t* buf = Reserve(kBlockSize);
|
|
|
|
|
std::memcpy(buf, data.data(), kBlockSize);
|
|
|
|
|
data = data.subspan(kBlockSize);
|
|
|
|
|
}
|
2023-12-23 13:39:39 -08:00
|
|
|
if (!data.empty()) {
|
|
|
|
|
uint8_t* buf = Reserve(data.size());
|
|
|
|
|
std::memcpy(buf, data.data(), data.size());
|
|
|
|
|
}
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()});
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendRaw(int entry, std::span<const uint8_t> data,
|
2020-03-21 20:43:34 -07:00
|
|
|
int64_t timestamp) {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
StartRecord(entry, timestamp, data.size(), 0);
|
|
|
|
|
AppendImpl(data);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLog::AppendRaw2(int entry,
|
2022-10-15 16:33:14 -07:00
|
|
|
std::span<const std::span<const uint8_t>> data,
|
2020-03-21 20:43:34 -07:00
|
|
|
int64_t timestamp) {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
uint8_t* buf = StartRecord(entry, timestamp, 4, 4);
|
2024-03-17 18:39:03 -07:00
|
|
|
if constexpr (std::endian::native == std::endian::little) {
|
2020-03-21 20:43:34 -07:00
|
|
|
std::memcpy(buf, &value, 4);
|
|
|
|
|
} else {
|
2023-09-21 19:54:33 -07:00
|
|
|
wpi::support::endian::write32le(buf, wpi::bit_cast<uint32_t>(value));
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLog::AppendDouble(int entry, double value, int64_t timestamp) {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
|
2024-03-17 18:39:03 -07:00
|
|
|
if constexpr (std::endian::native == std::endian::little) {
|
2020-03-21 20:43:34 -07:00
|
|
|
std::memcpy(buf, &value, 8);
|
|
|
|
|
} else {
|
2023-09-21 19:54:33 -07:00
|
|
|
wpi::support::endian::write64le(buf, wpi::bit_cast<uint64_t>(value));
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void DataLog::AppendString(int entry, std::string_view value,
|
|
|
|
|
int64_t timestamp) {
|
|
|
|
|
AppendRaw(entry,
|
|
|
|
|
{reinterpret_cast<const uint8_t*>(value.data()), value.size()},
|
|
|
|
|
timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendBooleanArray(int entry, std::span<const bool> arr,
|
2020-03-21 20:43:34 -07:00
|
|
|
int64_t timestamp) {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendBooleanArray(int entry, std::span<const int> arr,
|
2020-03-21 20:43:34 -07:00
|
|
|
int64_t timestamp) {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendBooleanArray(int entry, std::span<const uint8_t> arr,
|
2020-03-21 20:43:34 -07:00
|
|
|
int64_t timestamp) {
|
|
|
|
|
AppendRaw(entry, arr, timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendIntegerArray(int entry, std::span<const int64_t> arr,
|
2020-03-21 20:43:34 -07:00
|
|
|
int64_t timestamp) {
|
2024-03-17 18:39:03 -07:00
|
|
|
if constexpr (std::endian::native == std::endian::little) {
|
2020-03-21 20:43:34 -07:00
|
|
|
AppendRaw(entry,
|
|
|
|
|
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
|
|
|
|
|
timestamp);
|
|
|
|
|
} else {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendFloatArray(int entry, std::span<const float> arr,
|
2020-03-21 20:43:34 -07:00
|
|
|
int64_t timestamp) {
|
2024-03-17 18:39:03 -07:00
|
|
|
if constexpr (std::endian::native == std::endian::little) {
|
2020-03-21 20:43:34 -07:00
|
|
|
AppendRaw(entry,
|
|
|
|
|
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 4},
|
|
|
|
|
timestamp);
|
|
|
|
|
} else {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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)) {
|
2023-09-21 19:54:33 -07:00
|
|
|
wpi::support::endian::write32le(buf, wpi::bit_cast<uint32_t>(val));
|
2020-03-21 20:43:34 -07:00
|
|
|
buf += 4;
|
|
|
|
|
}
|
|
|
|
|
arr = arr.subspan(kBlockSize / 4);
|
|
|
|
|
}
|
|
|
|
|
buf = Reserve(arr.size() * 4);
|
|
|
|
|
for (auto val : arr) {
|
2023-09-21 19:54:33 -07:00
|
|
|
wpi::support::endian::write32le(buf, wpi::bit_cast<uint32_t>(val));
|
2020-03-21 20:43:34 -07:00
|
|
|
buf += 4;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendDoubleArray(int entry, std::span<const double> arr,
|
2020-03-21 20:43:34 -07:00
|
|
|
int64_t timestamp) {
|
2024-03-17 18:39:03 -07:00
|
|
|
if constexpr (std::endian::native == std::endian::little) {
|
2020-03-21 20:43:34 -07:00
|
|
|
AppendRaw(entry,
|
|
|
|
|
{reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
|
|
|
|
|
timestamp);
|
|
|
|
|
} else {
|
|
|
|
|
if (entry <= 0) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
std::scoped_lock lock{m_mutex};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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)) {
|
2023-09-21 19:54:33 -07:00
|
|
|
wpi::support::endian::write64le(buf, wpi::bit_cast<uint64_t>(val));
|
2020-03-21 20:43:34 -07:00
|
|
|
buf += 8;
|
|
|
|
|
}
|
|
|
|
|
arr = arr.subspan(kBlockSize / 8);
|
|
|
|
|
}
|
|
|
|
|
buf = Reserve(arr.size() * 8);
|
|
|
|
|
for (auto val : arr) {
|
2023-09-21 19:54:33 -07:00
|
|
|
wpi::support::endian::write64le(buf, wpi::bit_cast<uint64_t>(val));
|
2020-03-21 20:43:34 -07:00
|
|
|
buf += 8;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-10-15 16:33:14 -07:00
|
|
|
void DataLog::AppendStringArray(int entry, std::span<const std::string> arr,
|
2020-03-21 20:43:34 -07:00
|
|
|
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};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
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,
|
2022-10-15 16:33:14 -07:00
|
|
|
std::span<const std::string_view> arr,
|
2020-03-21 20:43:34 -07:00
|
|
|
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};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2020-03-21 20:43:34 -07:00
|
|
|
}
|
|
|
|
|
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
|
|
|
|
|
wpi::support::endian::write32le(buf, arr.size());
|
2023-08-06 20:18:50 -07:00
|
|
|
for (auto&& sv : arr) {
|
2020-03-21 20:43:34 -07:00
|
|
|
AppendStringImpl(sv);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-06 20:18:50 -07:00
|
|
|
|
|
|
|
|
void DataLog::AppendStringArray(int entry,
|
|
|
|
|
std::span<const WPI_DataLog_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};
|
2024-05-12 14:09:43 -07:00
|
|
|
if (m_paused) {
|
2023-11-03 20:34:43 -07:00
|
|
|
[[unlikely]] return;
|
2023-08-06 20:18:50 -07:00
|
|
|
}
|
|
|
|
|
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
|
|
|
|
|
wpi::support::endian::write32le(buf, arr.size());
|
|
|
|
|
for (auto&& sv : arr) {
|
|
|
|
|
AppendStringImpl(sv.str);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-03 20:34:43 -07:00
|
|
|
void WPI_DataLog_Stop(struct WPI_DataLog* datalog) {
|
|
|
|
|
reinterpret_cast<DataLog*>(datalog)->Stop();
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-06 20:18:50 -07:00
|
|
|
int WPI_DataLog_Start(struct WPI_DataLog* datalog, const char* name,
|
|
|
|
|
const char* type, const char* metadata,
|
|
|
|
|
int64_t timestamp) {
|
|
|
|
|
return reinterpret_cast<DataLog*>(datalog)->Start(name, type, 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 char* metadata, int64_t timestamp) {
|
|
|
|
|
reinterpret_cast<DataLog*>(datalog)->SetMetadata(entry, 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 char* value, size_t len,
|
|
|
|
|
int64_t timestamp) {
|
|
|
|
|
reinterpret_cast<DataLog*>(datalog)->AppendString(entry, {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 WPI_DataLog_String* arr, size_t len,
|
|
|
|
|
int64_t timestamp) {
|
|
|
|
|
reinterpret_cast<DataLog*>(datalog)->AppendStringArray(entry, {arr, len},
|
|
|
|
|
timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 20:35:44 -08:00
|
|
|
void WPI_DataLog_AddSchemaString(struct WPI_DataLog* datalog, const char* name,
|
|
|
|
|
const char* type, const char* schema,
|
|
|
|
|
int64_t timestamp) {
|
2024-01-15 23:34:18 -08:00
|
|
|
reinterpret_cast<DataLog*>(datalog)->AddSchema(name, type, schema, timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
2024-01-19 20:35:44 -08:00
|
|
|
void WPI_DataLog_AddSchema(struct WPI_DataLog* datalog, const char* name,
|
|
|
|
|
const char* type, const uint8_t* schema,
|
|
|
|
|
size_t schema_len, int64_t timestamp) {
|
2024-01-15 23:34:18 -08:00
|
|
|
reinterpret_cast<DataLog*>(datalog)->AddSchema(
|
|
|
|
|
name, type, std::span<const uint8_t>{schema, schema_len}, timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-06 20:18:50 -07:00
|
|
|
} // extern "C"
|