mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-21 01:01:43 +00:00
[wpiutil] Add high speed data logging
This commit is contained in:
821
wpiutil/src/main/native/cpp/DataLog.cpp
Normal file
821
wpiutil/src/main/native/cpp/DataLog.cpp
Normal file
@@ -0,0 +1,821 @@
|
||||
// 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 "wpi/Synchronization.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 <atomic>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include "fmt/format.h"
|
||||
#include "wpi/Endian.h"
|
||||
#include "wpi/Logger.h"
|
||||
#include "wpi/MathExtras.h"
|
||||
#include "wpi/fs.h"
|
||||
#include "wpi/timestamp.h"
|
||||
|
||||
using namespace wpi::log;
|
||||
|
||||
static constexpr size_t kBlockSize = 16 * 1024;
|
||||
static constexpr size_t kRecordMaxHeaderSize = 17;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
class DataLog::Buffer {
|
||||
public:
|
||||
explicit Buffer(size_t alloc = kBlockSize)
|
||||
: m_buf{new uint8_t[alloc]}, m_maxLen{alloc} {}
|
||||
~Buffer() { delete[] m_buf; }
|
||||
|
||||
Buffer(const Buffer&) = delete;
|
||||
Buffer& operator=(const Buffer&) = delete;
|
||||
|
||||
Buffer(Buffer&& oth)
|
||||
: m_buf{oth.m_buf}, m_len{oth.m_len}, m_maxLen{oth.m_maxLen} {
|
||||
oth.m_buf = nullptr;
|
||||
oth.m_len = 0;
|
||||
oth.m_maxLen = 0;
|
||||
}
|
||||
|
||||
Buffer& operator=(Buffer&& oth) {
|
||||
if (m_buf) {
|
||||
delete[] m_buf;
|
||||
}
|
||||
m_buf = oth.m_buf;
|
||||
m_len = oth.m_len;
|
||||
m_maxLen = oth.m_maxLen;
|
||||
oth.m_buf = nullptr;
|
||||
oth.m_len = 0;
|
||||
oth.m_maxLen = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
uint8_t* Reserve(size_t size) {
|
||||
assert(size <= GetRemaining());
|
||||
uint8_t* rv = m_buf + m_len;
|
||||
m_len += size;
|
||||
return rv;
|
||||
}
|
||||
|
||||
void Unreserve(size_t size) { m_len -= size; }
|
||||
|
||||
void Clear() { m_len = 0; }
|
||||
|
||||
size_t GetRemaining() const { return m_maxLen - m_len; }
|
||||
|
||||
wpi::span<uint8_t> GetData() { return {m_buf, m_len}; }
|
||||
wpi::span<const uint8_t> GetData() const { return {m_buf, m_len}; }
|
||||
|
||||
private:
|
||||
uint8_t* m_buf;
|
||||
size_t m_len = 0;
|
||||
size_t m_maxLen;
|
||||
};
|
||||
|
||||
static void DefaultLog(unsigned int level, const char* file, unsigned int line,
|
||||
const char* msg) {
|
||||
if (level > wpi::WPI_LOG_INFO) {
|
||||
fmt::print(stderr, "DataLog: {}\n", msg);
|
||||
} else if (level == wpi::WPI_LOG_INFO) {
|
||||
fmt::print("DataLog: {}\n", msg);
|
||||
}
|
||||
}
|
||||
|
||||
static wpi::Logger defaultMessageLog{DefaultLog};
|
||||
|
||||
DataLog::DataLog(std::string_view dir, std::string_view filename, double period,
|
||||
std::string_view extraHeader)
|
||||
: DataLog{defaultMessageLog, dir, filename, period, extraHeader} {}
|
||||
|
||||
DataLog::DataLog(wpi::Logger& msglog, std::string_view dir,
|
||||
std::string_view filename, double period,
|
||||
std::string_view extraHeader)
|
||||
: m_msglog{msglog},
|
||||
m_period{period},
|
||||
m_extraHeader{extraHeader},
|
||||
m_newFilename{filename},
|
||||
m_thread{[this, dir = std::string{dir}] { WriterThreadMain(dir); }} {}
|
||||
|
||||
DataLog::DataLog(std::function<void(wpi::span<const uint8_t> data)> write,
|
||||
double period, std::string_view extraHeader)
|
||||
: DataLog{defaultMessageLog, std::move(write), period, extraHeader} {}
|
||||
|
||||
DataLog::DataLog(wpi::Logger& msglog,
|
||||
std::function<void(wpi::span<const uint8_t> data)> write,
|
||||
double period, std::string_view extraHeader)
|
||||
: m_msglog{msglog},
|
||||
m_period{period},
|
||||
m_extraHeader{extraHeader},
|
||||
m_thread{[this, write = std::move(write)] {
|
||||
WriterThreadMain(std::move(write));
|
||||
}} {}
|
||||
|
||||
DataLog::~DataLog() {
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_active = false;
|
||||
m_doFlush = true;
|
||||
}
|
||||
m_cond.notify_all();
|
||||
m_thread.join();
|
||||
}
|
||||
|
||||
void DataLog::SetFilename(std::string_view filename) {
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_newFilename = filename;
|
||||
}
|
||||
m_cond.notify_all();
|
||||
}
|
||||
|
||||
void DataLog::Flush() {
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_doFlush = true;
|
||||
}
|
||||
m_cond.notify_all();
|
||||
}
|
||||
|
||||
void DataLog::Pause() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_paused = true;
|
||||
}
|
||||
|
||||
void DataLog::Resume() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_paused = false;
|
||||
}
|
||||
|
||||
static void WriteToFile(fs::file_t f, wpi::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;
|
||||
}
|
||||
|
||||
void DataLog::WriterThreadMain(std::string_view dir) {
|
||||
std::chrono::duration<double> periodTime{m_period};
|
||||
|
||||
std::error_code ec;
|
||||
fs::path dirPath{dir};
|
||||
std::string filename;
|
||||
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
filename = std::move(m_newFilename);
|
||||
m_newFilename.clear();
|
||||
}
|
||||
|
||||
if (filename.empty()) {
|
||||
filename = MakeRandomFilename();
|
||||
}
|
||||
|
||||
// try preferred filename, or randomize it a few times, before giving up
|
||||
fs::file_t f;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
// open file for append
|
||||
#ifdef _WIN32
|
||||
// WIN32 doesn't allow combination of CreateNew and Append
|
||||
f = fs::OpenFileForWrite(dirPath / filename, ec, fs::CD_CreateNew,
|
||||
fs::OF_None);
|
||||
#else
|
||||
f = fs::OpenFileForWrite(dirPath / filename, ec, fs::CD_CreateNew,
|
||||
fs::OF_Append);
|
||||
#endif
|
||||
if (ec) {
|
||||
WPI_ERROR(m_msglog, "Could not open log file '{}': {}",
|
||||
(dirPath / filename).string(), ec.message());
|
||||
// try again with random filename
|
||||
filename = MakeRandomFilename();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (f == fs::kInvalidFile) {
|
||||
WPI_ERROR(m_msglog, "{}", "Could not open log file, no log being saved");
|
||||
} else {
|
||||
WPI_INFO(m_msglog, "Logging to '{}'", (dirPath / filename).string());
|
||||
}
|
||||
|
||||
// write header (version 1.0)
|
||||
if (f != fs::kInvalidFile) {
|
||||
const uint8_t header[] = {'W', 'P', 'I', 'L', 'O', 'G', 0, 1};
|
||||
WriteToFile(f, header, filename, m_msglog);
|
||||
uint8_t extraLen[4];
|
||||
support::endian::write32le(extraLen, m_extraHeader.size());
|
||||
WriteToFile(f, extraLen, filename, m_msglog);
|
||||
if (m_extraHeader.size() > 0) {
|
||||
WriteToFile(f,
|
||||
{reinterpret_cast<const uint8_t*>(m_extraHeader.data()),
|
||||
m_extraHeader.size()},
|
||||
filename, m_msglog);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Buffer> toWrite;
|
||||
|
||||
std::unique_lock lock{m_mutex};
|
||||
while (m_active) {
|
||||
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_newFilename.empty()) {
|
||||
auto newFilename = std::move(m_newFilename);
|
||||
m_newFilename.clear();
|
||||
lock.unlock();
|
||||
// rename
|
||||
if (filename != newFilename) {
|
||||
fs::rename(dirPath / filename, dirPath / newFilename, ec);
|
||||
}
|
||||
if (ec) {
|
||||
WPI_ERROR(m_msglog, "Could not rename log file from '{}' to '{}': {}",
|
||||
filename, newFilename, ec.message());
|
||||
} else {
|
||||
WPI_INFO(m_msglog, "Renamed log file from '{}' to '{}'", filename,
|
||||
newFilename);
|
||||
}
|
||||
filename = std::move(newFilename);
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
if (doFlush || m_doFlush) {
|
||||
// flush to file
|
||||
m_doFlush = false;
|
||||
if (m_outgoing.empty()) {
|
||||
continue;
|
||||
}
|
||||
// swap outgoing with empty vector
|
||||
toWrite.swap(m_outgoing);
|
||||
|
||||
if (f != fs::kInvalidFile) {
|
||||
lock.unlock();
|
||||
// write buffers to file
|
||||
for (auto&& buf : toWrite) {
|
||||
WriteToFile(f, buf.GetData(), filename, m_msglog);
|
||||
}
|
||||
|
||||
// sync to storage
|
||||
#if defined(__linux__)
|
||||
::fdatasync(f);
|
||||
#elif defined(__APPLE__)
|
||||
::fsync(f);
|
||||
#endif
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
// release buffers back to free list
|
||||
for (auto&& buf : toWrite) {
|
||||
buf.Clear();
|
||||
m_free.emplace_back(std::move(buf));
|
||||
}
|
||||
toWrite.resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (f != fs::kInvalidFile) {
|
||||
fs::CloseFile(f);
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::WriterThreadMain(
|
||||
std::function<void(wpi::span<const uint8_t> data)> write) {
|
||||
std::chrono::duration<double> periodTime{m_period};
|
||||
|
||||
// write header (version 1.0)
|
||||
{
|
||||
const uint8_t header[] = {'W', 'P', 'I', 'L', 'O', 'G', 0, 1};
|
||||
write(header);
|
||||
uint8_t extraLen[4];
|
||||
support::endian::write32le(extraLen, m_extraHeader.size());
|
||||
write(extraLen);
|
||||
if (m_extraHeader.size() > 0) {
|
||||
write({reinterpret_cast<const uint8_t*>(m_extraHeader.data()),
|
||||
m_extraHeader.size()});
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Buffer> toWrite;
|
||||
|
||||
std::unique_lock lock{m_mutex};
|
||||
while (m_active) {
|
||||
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;
|
||||
if (m_outgoing.empty()) {
|
||||
continue;
|
||||
}
|
||||
// swap outgoing with empty vector
|
||||
toWrite.swap(m_outgoing);
|
||||
|
||||
lock.unlock();
|
||||
// write buffers
|
||||
for (auto&& buf : toWrite) {
|
||||
if (!buf.GetData().empty()) {
|
||||
write(buf.GetData());
|
||||
}
|
||||
}
|
||||
lock.lock();
|
||||
|
||||
// release buffers back to free list
|
||||
for (auto&& buf : toWrite) {
|
||||
buf.Clear();
|
||||
m_free.emplace_back(std::move(buf));
|
||||
}
|
||||
toWrite.resize(0);
|
||||
}
|
||||
}
|
||||
|
||||
write({}); // indicate EOF
|
||||
}
|
||||
|
||||
// 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};
|
||||
auto& entryInfo = m_entries[name];
|
||||
if (entryInfo.id == 0) {
|
||||
entryInfo.id = ++m_lastId;
|
||||
}
|
||||
auto& savedCount = m_entryCounts[entryInfo.id];
|
||||
++savedCount;
|
||||
if (savedCount > 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;
|
||||
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, entryInfo.id);
|
||||
AppendStringImpl(name);
|
||||
AppendStringImpl(type);
|
||||
AppendStringImpl(metadata);
|
||||
|
||||
return entryInfo.id;
|
||||
}
|
||||
|
||||
void DataLog::Finish(int entry, int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
auto& savedCount = m_entryCounts[entry];
|
||||
if (savedCount == 0) {
|
||||
return;
|
||||
}
|
||||
--savedCount;
|
||||
if (savedCount != 0) {
|
||||
return;
|
||||
}
|
||||
m_entryCounts.erase(entry);
|
||||
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};
|
||||
uint8_t* buf = StartRecord(entry, 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_free.empty()) {
|
||||
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(wpi::span<const uint8_t> data) {
|
||||
while (data.size() > kBlockSize) {
|
||||
uint8_t* buf = Reserve(kBlockSize);
|
||||
std::memcpy(buf, data.data(), kBlockSize);
|
||||
data = data.subspan(kBlockSize);
|
||||
}
|
||||
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, wpi::span<const uint8_t> data,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
return;
|
||||
}
|
||||
StartRecord(entry, timestamp, data.size(), 0);
|
||||
AppendImpl(data);
|
||||
}
|
||||
|
||||
void DataLog::AppendRaw2(int entry,
|
||||
wpi::span<const wpi::span<const uint8_t>> data,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
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) {
|
||||
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) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, 4, 4);
|
||||
if constexpr (wpi::support::endian::system_endianness() ==
|
||||
wpi::support::little) {
|
||||
std::memcpy(buf, &value, 4);
|
||||
} else {
|
||||
wpi::support::endian::write32le(buf, wpi::FloatToBits(value));
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendDouble(int entry, double value, int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
|
||||
if constexpr (wpi::support::endian::system_endianness() ==
|
||||
wpi::support::little) {
|
||||
std::memcpy(buf, &value, 8);
|
||||
} else {
|
||||
wpi::support::endian::write64le(buf, wpi::DoubleToBits(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, wpi::span<const bool> arr,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
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, wpi::span<const int> arr,
|
||||
int64_t timestamp) {
|
||||
if (entry <= 0) {
|
||||
return;
|
||||
}
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if (m_paused) {
|
||||
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, wpi::span<const uint8_t> arr,
|
||||
int64_t timestamp) {
|
||||
AppendRaw(entry, arr, timestamp);
|
||||
}
|
||||
|
||||
void DataLog::AppendIntegerArray(int entry, wpi::span<const int64_t> arr,
|
||||
int64_t timestamp) {
|
||||
if constexpr (wpi::support::endian::system_endianness() ==
|
||||
wpi::support::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) {
|
||||
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, wpi::span<const float> arr,
|
||||
int64_t timestamp) {
|
||||
if constexpr (wpi::support::endian::system_endianness() ==
|
||||
wpi::support::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) {
|
||||
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, wpi::FloatToBits(val));
|
||||
buf += 4;
|
||||
}
|
||||
arr = arr.subspan(kBlockSize / 4);
|
||||
}
|
||||
buf = Reserve(arr.size() * 4);
|
||||
for (auto val : arr) {
|
||||
wpi::support::endian::write32le(buf, wpi::FloatToBits(val));
|
||||
buf += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendDoubleArray(int entry, wpi::span<const double> arr,
|
||||
int64_t timestamp) {
|
||||
if constexpr (wpi::support::endian::system_endianness() ==
|
||||
wpi::support::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) {
|
||||
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, wpi::DoubleToBits(val));
|
||||
buf += 8;
|
||||
}
|
||||
arr = arr.subspan(kBlockSize / 8);
|
||||
}
|
||||
buf = Reserve(arr.size() * 8);
|
||||
for (auto val : arr) {
|
||||
wpi::support::endian::write64le(buf, wpi::DoubleToBits(val));
|
||||
buf += 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DataLog::AppendStringArray(int entry, wpi::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) {
|
||||
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,
|
||||
wpi::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) {
|
||||
return;
|
||||
}
|
||||
uint8_t* buf = StartRecord(entry, timestamp, size, 4);
|
||||
wpi::support::endian::write32le(buf, arr.size());
|
||||
for (auto sv : arr) {
|
||||
AppendStringImpl(sv);
|
||||
}
|
||||
}
|
||||
307
wpiutil/src/main/native/cpp/DataLogReader.cpp
Normal file
307
wpiutil/src/main/native/cpp/DataLogReader.cpp
Normal file
@@ -0,0 +1,307 @@
|
||||
// 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 "wpi/DataLog.h"
|
||||
#include "wpi/Endian.h"
|
||||
#include "wpi/MathExtras.h"
|
||||
|
||||
using namespace wpi::log;
|
||||
|
||||
static bool ReadString(wpi::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 = wpi::BitsToFloat(wpi::support::endian::read32le(m_data.data()));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DataLogRecord::GetDouble(double* value) const {
|
||||
if (m_data.size() != 8) {
|
||||
return false;
|
||||
}
|
||||
*value = wpi::BitsToDouble(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(
|
||||
wpi::BitsToFloat(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(
|
||||
wpi::BitsToDouble(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(wpi::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;
|
||||
}
|
||||
@@ -82,17 +82,6 @@ namespace fs {
|
||||
const file_t kInvalidFile = INVALID_HANDLE_VALUE;
|
||||
|
||||
static DWORD nativeDisposition(CreationDisposition Disp, OpenFlags Flags) {
|
||||
// This is a compatibility hack. Really we should respect the creation
|
||||
// disposition, but a lot of old code relied on the implicit assumption that
|
||||
// OF_Append implied it would open an existing file. Since the disposition is
|
||||
// now explicit and defaults to CD_CreateAlways, this assumption would cause
|
||||
// any usage of OF_Append to append to a new file, even if the file already
|
||||
// existed. A better solution might have two new creation dispositions:
|
||||
// CD_AppendAlways and CD_AppendNew. This would also address the problem of
|
||||
// OF_Append being used on a read-only descriptor, which doesn't make sense.
|
||||
if (Flags & OF_Append)
|
||||
return OPEN_ALWAYS;
|
||||
|
||||
switch (Disp) {
|
||||
case CD_CreateAlways:
|
||||
return CREATE_ALWAYS;
|
||||
@@ -251,12 +240,6 @@ static int nativeOpenFlags(CreationDisposition Disp, OpenFlags Flags,
|
||||
Result |= O_RDWR;
|
||||
}
|
||||
|
||||
// This is for compatibility with old code that assumed F_Append implied
|
||||
// would open an existing file. See Windows/Path.inc for a longer comment.
|
||||
if (Flags & F_Append) {
|
||||
Disp = CD_OpenAlways;
|
||||
}
|
||||
|
||||
if (Disp == CD_CreateNew) {
|
||||
Result |= O_CREAT; // Create if it doesn't exist.
|
||||
Result |= O_EXCL; // Fail if it does.
|
||||
|
||||
357
wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp
Normal file
357
wpiutil/src/main/native/cpp/jni/DataLogJNI.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
// 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 "edu_wpi_first_util_datalog_DataLogJNI.h"
|
||||
#include "wpi/DataLog.h"
|
||||
#include "wpi/jni_util.h"
|
||||
|
||||
using namespace wpi::java;
|
||||
using namespace wpi::log;
|
||||
|
||||
extern "C" {
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: create
|
||||
* Signature: (Ljava/lang/String;Ljava/lang/String;DLjava/lang/String;)J
|
||||
*/
|
||||
JNIEXPORT jlong JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_create
|
||||
(JNIEnv* env, jclass, jstring dir, jstring filename, jdouble period,
|
||||
jstring extraHeader)
|
||||
{
|
||||
return reinterpret_cast<jlong>(new DataLog{JStringRef{env, dir},
|
||||
JStringRef{env, filename}, period,
|
||||
JStringRef{env, extraHeader}});
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: setFilename
|
||||
* Signature: (JLjava/lang/String;)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_setFilename
|
||||
(JNIEnv* env, jclass, jlong impl, jstring filename)
|
||||
{
|
||||
if (impl == 0) {
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->SetFilename(JStringRef{env, filename});
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: flush
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_flush
|
||||
(JNIEnv*, jclass, jlong impl)
|
||||
{
|
||||
if (impl == 0) {
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->Flush();
|
||||
}
|
||||
|
||||
/*
|
||||
* Class: edu_wpi_first_util_datalog_DataLogJNI
|
||||
* Method: pause
|
||||
* Signature: (J)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_pause
|
||||
(JNIEnv*, jclass, jlong impl)
|
||||
{
|
||||
if (impl == 0) {
|
||||
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*, jclass, jlong impl)
|
||||
{
|
||||
if (impl == 0) {
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->Resume();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
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*, jclass, jlong impl, jint entry, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
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) {
|
||||
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[BJ)V
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_edu_wpi_first_util_datalog_DataLogJNI_appendRaw
|
||||
(JNIEnv* env, jclass, jlong impl, jint entry, jbyteArray value,
|
||||
jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
return;
|
||||
}
|
||||
JByteArrayRef cvalue{env, value};
|
||||
reinterpret_cast<DataLog*>(impl)->AppendRaw(
|
||||
entry,
|
||||
{reinterpret_cast<const uint8_t*>(cvalue.array().data()), cvalue.size()},
|
||||
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*, jclass, jlong impl, jint entry, jboolean value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
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*, jclass, jlong impl, jint entry, jlong value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
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*, jclass, jlong impl, jint entry, jfloat value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
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*, jclass, jlong impl, jint entry, jdouble value, jlong timestamp)
|
||||
{
|
||||
if (impl == 0) {
|
||||
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) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendBooleanArray(
|
||||
entry, JBooleanArrayRef{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) {
|
||||
return;
|
||||
}
|
||||
JLongArrayRef jarr{env, value};
|
||||
if constexpr (sizeof(jlong) == sizeof(int64_t)) {
|
||||
reinterpret_cast<DataLog*>(impl)->AppendIntegerArray(
|
||||
entry,
|
||||
{reinterpret_cast<const int64_t*>(jarr.array().data()),
|
||||
jarr.array().size()},
|
||||
timestamp);
|
||||
} else {
|
||||
wpi::SmallVector<int64_t, 16> arr;
|
||||
arr.reserve(jarr.size());
|
||||
for (auto v : jarr.array()) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendFloatArray(
|
||||
entry, JFloatArrayRef{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) {
|
||||
return;
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendDoubleArray(
|
||||
entry, JDoubleArrayRef{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) {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
arr.emplace_back(JStringRef{env, elem}.str());
|
||||
}
|
||||
reinterpret_cast<DataLog*>(impl)->AppendStringArray(entry, arr, timestamp);
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
Reference in New Issue
Block a user