blob: d05a49e43b70bd52a78c79493d668e5bfdb61f0f [file] [log] [blame]
James Kuszmaulcf324122023-01-14 14:07:17 -08001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#include "wpi/DataLog.h"
6
7#include "wpi/Synchronization.h"
8
9#ifndef _WIN32
10#include <unistd.h>
11#endif
12
13#ifdef _WIN32
14#ifndef WIN32_LEAN_AND_MEAN
15#define WIN32_LEAN_AND_MEAN
16#endif
17
18#include <windows.h> // NOLINT(build/include_order)
19
20#endif
21
22#include <atomic>
23#include <cstdio>
24#include <cstdlib>
25#include <cstring>
26#include <random>
27#include <vector>
28
James Kuszmaulb13e13f2023-11-22 20:44:04 -080029#include <fmt/format.h>
30
James Kuszmaulcf324122023-01-14 14:07:17 -080031#include "wpi/Endian.h"
32#include "wpi/Logger.h"
33#include "wpi/MathExtras.h"
James Kuszmaulb13e13f2023-11-22 20:44:04 -080034#include "wpi/SmallString.h"
James Kuszmaulcf324122023-01-14 14:07:17 -080035#include "wpi/fs.h"
36#include "wpi/timestamp.h"
37
38using namespace wpi::log;
39
40static constexpr size_t kBlockSize = 16 * 1024;
James Kuszmaulb13e13f2023-11-22 20:44:04 -080041static constexpr size_t kMaxBufferCount = 1024 * 1024 / kBlockSize;
42static constexpr size_t kMaxFreeCount = 256 * 1024 / kBlockSize;
James Kuszmaulcf324122023-01-14 14:07:17 -080043static constexpr size_t kRecordMaxHeaderSize = 17;
James Kuszmaulb13e13f2023-11-22 20:44:04 -080044static constexpr uintmax_t kMinFreeSpace = 5 * 1024 * 1024;
45
46static std::string FormatBytesSize(uintmax_t value) {
47 static constexpr uintmax_t kKiB = 1024;
48 static constexpr uintmax_t kMiB = kKiB * 1024;
49 static constexpr uintmax_t kGiB = kMiB * 1024;
50 if (value >= kGiB) {
51 return fmt::format("{:.1f} GiB", static_cast<double>(value) / kGiB);
52 } else if (value >= kMiB) {
53 return fmt::format("{:.1f} MiB", static_cast<double>(value) / kMiB);
54 } else if (value >= kKiB) {
55 return fmt::format("{:.1f} KiB", static_cast<double>(value) / kKiB);
56 } else {
57 return fmt::format("{} B", value);
58 }
59}
James Kuszmaulcf324122023-01-14 14:07:17 -080060
61template <typename T>
62static unsigned int WriteVarInt(uint8_t* buf, T val) {
63 unsigned int len = 0;
64 do {
65 *buf++ = static_cast<unsigned int>(val) & 0xff;
66 ++len;
67 val >>= 8;
68 } while (val != 0);
69 return len;
70}
71
72// min size: 4, max size: 17
73static unsigned int WriteRecordHeader(uint8_t* buf, uint32_t entry,
74 uint64_t timestamp,
75 uint32_t payloadSize) {
76 uint8_t* origbuf = buf++;
77
78 unsigned int entryLen = WriteVarInt(buf, entry);
79 buf += entryLen;
80 unsigned int payloadLen = WriteVarInt(buf, payloadSize);
81 buf += payloadLen;
82 unsigned int timestampLen =
83 WriteVarInt(buf, timestamp == 0 ? wpi::Now() : timestamp);
84 buf += timestampLen;
85 *origbuf =
86 ((timestampLen - 1) << 4) | ((payloadLen - 1) << 2) | (entryLen - 1);
87 return buf - origbuf;
88}
89
90class DataLog::Buffer {
91 public:
92 explicit Buffer(size_t alloc = kBlockSize)
93 : m_buf{new uint8_t[alloc]}, m_maxLen{alloc} {}
94 ~Buffer() { delete[] m_buf; }
95
96 Buffer(const Buffer&) = delete;
97 Buffer& operator=(const Buffer&) = delete;
98
99 Buffer(Buffer&& oth)
100 : m_buf{oth.m_buf}, m_len{oth.m_len}, m_maxLen{oth.m_maxLen} {
101 oth.m_buf = nullptr;
102 oth.m_len = 0;
103 oth.m_maxLen = 0;
104 }
105
106 Buffer& operator=(Buffer&& oth) {
107 if (m_buf) {
108 delete[] m_buf;
109 }
110 m_buf = oth.m_buf;
111 m_len = oth.m_len;
112 m_maxLen = oth.m_maxLen;
113 oth.m_buf = nullptr;
114 oth.m_len = 0;
115 oth.m_maxLen = 0;
116 return *this;
117 }
118
119 uint8_t* Reserve(size_t size) {
120 assert(size <= GetRemaining());
121 uint8_t* rv = m_buf + m_len;
122 m_len += size;
123 return rv;
124 }
125
126 void Unreserve(size_t size) { m_len -= size; }
127
128 void Clear() { m_len = 0; }
129
130 size_t GetRemaining() const { return m_maxLen - m_len; }
131
132 std::span<uint8_t> GetData() { return {m_buf, m_len}; }
133 std::span<const uint8_t> GetData() const { return {m_buf, m_len}; }
134
135 private:
136 uint8_t* m_buf;
137 size_t m_len = 0;
138 size_t m_maxLen;
139};
140
141static void DefaultLog(unsigned int level, const char* file, unsigned int line,
142 const char* msg) {
143 if (level > wpi::WPI_LOG_INFO) {
144 fmt::print(stderr, "DataLog: {}\n", msg);
145 } else if (level == wpi::WPI_LOG_INFO) {
146 fmt::print("DataLog: {}\n", msg);
147 }
148}
149
150static wpi::Logger defaultMessageLog{DefaultLog};
151
152DataLog::DataLog(std::string_view dir, std::string_view filename, double period,
153 std::string_view extraHeader)
154 : DataLog{defaultMessageLog, dir, filename, period, extraHeader} {}
155
156DataLog::DataLog(wpi::Logger& msglog, std::string_view dir,
157 std::string_view filename, double period,
158 std::string_view extraHeader)
159 : m_msglog{msglog},
160 m_period{period},
161 m_extraHeader{extraHeader},
162 m_newFilename{filename},
163 m_thread{[this, dir = std::string{dir}] { WriterThreadMain(dir); }} {}
164
165DataLog::DataLog(std::function<void(std::span<const uint8_t> data)> write,
166 double period, std::string_view extraHeader)
167 : DataLog{defaultMessageLog, std::move(write), period, extraHeader} {}
168
169DataLog::DataLog(wpi::Logger& msglog,
170 std::function<void(std::span<const uint8_t> data)> write,
171 double period, std::string_view extraHeader)
172 : m_msglog{msglog},
173 m_period{period},
174 m_extraHeader{extraHeader},
175 m_thread{[this, write = std::move(write)] {
176 WriterThreadMain(std::move(write));
177 }} {}
178
179DataLog::~DataLog() {
180 {
181 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800182 m_state = kShutdown;
James Kuszmaulcf324122023-01-14 14:07:17 -0800183 m_doFlush = true;
184 }
185 m_cond.notify_all();
186 m_thread.join();
187}
188
189void DataLog::SetFilename(std::string_view filename) {
190 {
191 std::scoped_lock lock{m_mutex};
192 m_newFilename = filename;
193 }
194 m_cond.notify_all();
195}
196
197void DataLog::Flush() {
198 {
199 std::scoped_lock lock{m_mutex};
200 m_doFlush = true;
201 }
202 m_cond.notify_all();
203}
204
205void DataLog::Pause() {
206 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800207 m_state = kPaused;
James Kuszmaulcf324122023-01-14 14:07:17 -0800208}
209
210void DataLog::Resume() {
211 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800212 if (m_state == kPaused) {
213 m_state = kActive;
214 } else if (m_state == kStopped) {
215 m_state = kStart;
216 }
217}
218
219void DataLog::Stop() {
220 {
221 std::scoped_lock lock{m_mutex};
222 m_state = kStopped;
223 m_newFilename.clear();
224 }
225 m_cond.notify_all();
226}
227
228bool DataLog::HasSchema(std::string_view name) const {
229 std::scoped_lock lock{m_mutex};
230 wpi::SmallString<128> fullName{"/.schema/"};
231 fullName += name;
232 auto it = m_entries.find(fullName);
233 return it != m_entries.end();
234}
235
236void DataLog::AddSchema(std::string_view name, std::string_view type,
237 std::span<const uint8_t> schema, int64_t timestamp) {
238 std::scoped_lock lock{m_mutex};
239 wpi::SmallString<128> fullName{"/.schema/"};
240 fullName += name;
241 auto& entryInfo = m_entries[fullName];
242 if (entryInfo.id != 0) {
243 return; // don't add duplicates
244 }
245 entryInfo.schemaData.assign(schema.begin(), schema.end());
246 int entry = StartImpl(fullName, type, {}, timestamp);
247
248 // inline AppendRaw() without releasing lock
249 if (entry <= 0) {
250 [[unlikely]] return; // should never happen, but check anyway
251 }
252 if (m_state != kActive && m_state != kPaused) {
253 [[unlikely]] return;
254 }
255 StartRecord(entry, timestamp, schema.size(), 0);
256 AppendImpl(schema);
James Kuszmaulcf324122023-01-14 14:07:17 -0800257}
258
259static void WriteToFile(fs::file_t f, std::span<const uint8_t> data,
260 std::string_view filename, wpi::Logger& msglog) {
261 do {
262#ifdef _WIN32
263 DWORD ret;
264 if (!WriteFile(f, data.data(), data.size(), &ret, nullptr)) {
265 WPI_ERROR(msglog, "Error writing to log file '{}': {}", filename,
266 GetLastError());
267 break;
268 }
269#else
270 ssize_t ret = ::write(f, data.data(), data.size());
271 if (ret < 0) {
272 // If it's a recoverable error, swallow it and retry the write
273 if (errno == EINTR || errno == EAGAIN || errno == EWOULDBLOCK) {
274 continue;
275 }
276
277 // Otherwise it's a non-recoverable error; quit trying
278 WPI_ERROR(msglog, "Error writing to log file '{}': {}", filename,
279 std::strerror(errno));
280 break;
281 }
282#endif
283
284 // The write may have written some or all of the data
285 data = data.subspan(ret);
286 } while (data.size() > 0);
287}
288
289static std::string MakeRandomFilename() {
290 // build random filename
291 static std::random_device dev;
292 static std::mt19937 rng(dev());
293 std::uniform_int_distribution<int> dist(0, 15);
294 const char* v = "0123456789abcdef";
295 std::string filename = "wpilog_";
296 for (int i = 0; i < 16; i++) {
297 filename += v[dist(rng)];
298 }
299 filename += ".wpilog";
300 return filename;
301}
302
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800303struct DataLog::WriterThreadState {
304 explicit WriterThreadState(std::string_view dir) : dirPath{dir} {}
305 WriterThreadState(const WriterThreadState&) = delete;
306 WriterThreadState& operator=(const WriterThreadState&) = delete;
307 ~WriterThreadState() { Close(); }
James Kuszmaulcf324122023-01-14 14:07:17 -0800308
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800309 void Close() {
310 if (f != fs::kInvalidFile) {
311 fs::CloseFile(f);
312 f = fs::kInvalidFile;
James Kuszmaulcf324122023-01-14 14:07:17 -0800313 }
314 }
315
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800316 void SetFilename(std::string_view fn) {
317 baseFilename = fn;
318 filename = fn;
319 path = dirPath / filename;
320 segmentCount = 1;
321 }
322
323 void IncrementFilename() {
324 fs::path basePath{baseFilename};
325 filename = fmt::format("{}.{}{}", basePath.stem().string(), ++segmentCount,
326 basePath.extension().string());
327 path = dirPath / filename;
328 }
329
330 fs::path dirPath;
331 std::string baseFilename;
332 std::string filename;
333 fs::path path;
334 fs::file_t f = fs::kInvalidFile;
335 uintmax_t freeSpace = UINTMAX_MAX;
336 int segmentCount = 1;
337};
338
339void DataLog::StartLogFile(WriterThreadState& state) {
340 std::error_code ec;
341
342 if (state.filename.empty()) {
343 state.SetFilename(MakeRandomFilename());
344 }
345
346 // get free space
347 auto freeSpaceInfo = fs::space(state.dirPath, ec);
348 if (!ec) {
349 state.freeSpace = freeSpaceInfo.available;
James Kuszmaulcf324122023-01-14 14:07:17 -0800350 } else {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800351 state.freeSpace = UINTMAX_MAX;
352 }
353 if (state.freeSpace < kMinFreeSpace) {
354 WPI_ERROR(m_msglog,
355 "Insufficient free space ({} available), no log being saved",
356 FormatBytesSize(state.freeSpace));
357 } else {
358 // try preferred filename, or randomize it a few times, before giving up
359 for (int i = 0; i < 5; ++i) {
360 // open file for append
361#ifdef _WIN32
362 // WIN32 doesn't allow combination of CreateNew and Append
363 state.f =
364 fs::OpenFileForWrite(state.path, ec, fs::CD_CreateNew, fs::OF_None);
365#else
366 state.f =
367 fs::OpenFileForWrite(state.path, ec, fs::CD_CreateNew, fs::OF_Append);
368#endif
369 if (ec) {
370 WPI_ERROR(m_msglog, "Could not open log file '{}': {}",
371 state.path.string(), ec.message());
372 // try again with random filename
373 state.SetFilename(MakeRandomFilename());
374 } else {
375 break;
376 }
377 }
378
379 if (state.f == fs::kInvalidFile) {
380 WPI_ERROR(m_msglog, "Could not open log file, no log being saved");
381 } else {
382 WPI_INFO(m_msglog, "Logging to '{}' ({} free space)", state.path.string(),
383 FormatBytesSize(state.freeSpace));
384 }
James Kuszmaulcf324122023-01-14 14:07:17 -0800385 }
386
387 // write header (version 1.0)
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800388 if (state.f != fs::kInvalidFile) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800389 const uint8_t header[] = {'W', 'P', 'I', 'L', 'O', 'G', 0, 1};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800390 WriteToFile(state.f, header, state.filename, m_msglog);
James Kuszmaulcf324122023-01-14 14:07:17 -0800391 uint8_t extraLen[4];
392 support::endian::write32le(extraLen, m_extraHeader.size());
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800393 WriteToFile(state.f, extraLen, state.filename, m_msglog);
James Kuszmaulcf324122023-01-14 14:07:17 -0800394 if (m_extraHeader.size() > 0) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800395 WriteToFile(state.f,
James Kuszmaulcf324122023-01-14 14:07:17 -0800396 {reinterpret_cast<const uint8_t*>(m_extraHeader.data()),
397 m_extraHeader.size()},
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800398 state.filename, m_msglog);
James Kuszmaulcf324122023-01-14 14:07:17 -0800399 }
400 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800401}
James Kuszmaulcf324122023-01-14 14:07:17 -0800402
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800403void DataLog::WriterThreadMain(std::string_view dir) {
404 std::chrono::duration<double> periodTime{m_period};
405
406 WriterThreadState state{dir};
407 {
408 std::scoped_lock lock{m_mutex};
409 state.SetFilename(m_newFilename);
410 m_newFilename.clear();
411 }
412 StartLogFile(state);
413
414 std::error_code ec;
James Kuszmaulcf324122023-01-14 14:07:17 -0800415 std::vector<Buffer> toWrite;
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800416 int freeSpaceCount = 0;
417 int checkExistCount = 0;
418 bool blocked = false;
419 uintmax_t written = 0;
James Kuszmaulcf324122023-01-14 14:07:17 -0800420
421 std::unique_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800422 while (m_state != kShutdown) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800423 bool doFlush = false;
424 auto timeoutTime = std::chrono::steady_clock::now() + periodTime;
425 if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout) {
426 doFlush = true;
427 }
428
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800429 if (m_state == kStopped) {
430 state.Close();
431 continue;
432 }
433
434 bool doStart = false;
435
436 // if file was deleted, recreate it with the same name
437 if (++checkExistCount >= 10) {
438 checkExistCount = 0;
439 lock.unlock();
440 bool exists = fs::exists(state.path, ec);
441 lock.lock();
442 if (!ec && !exists) {
443 state.Close();
444 state.IncrementFilename();
445 WPI_INFO(m_msglog, "Log file deleted, recreating as fresh log '{}'",
446 state.filename);
447 doStart = true;
448 }
449 }
450
451 // start new file if file exceeds 1.8 GB
452 if (written > 1800000000ull) {
453 state.Close();
454 state.IncrementFilename();
455 WPI_INFO(m_msglog, "Log file reached 1.8 GB, starting new file '{}'",
456 state.filename);
457 doStart = true;
458 }
459
460 if (m_state == kStart || doStart) {
461 lock.unlock();
462 StartLogFile(state);
463 lock.lock();
464 if (state.f != fs::kInvalidFile) {
465 // Emit start and schema data records
466 for (auto&& entryInfo : m_entries) {
467 AppendStartRecord(entryInfo.second.id, entryInfo.first(),
468 entryInfo.second.type,
469 m_entryIds[entryInfo.second.id].metadata, 0);
470 if (!entryInfo.second.schemaData.empty()) {
471 StartRecord(entryInfo.second.id, 0,
472 entryInfo.second.schemaData.size(), 0);
473 AppendImpl(entryInfo.second.schemaData);
474 }
475 }
476 }
477 m_state = kActive;
478 written = 0;
479 }
480
481 if (!m_newFilename.empty() && state.f != fs::kInvalidFile) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800482 auto newFilename = std::move(m_newFilename);
483 m_newFilename.clear();
James Kuszmaulcf324122023-01-14 14:07:17 -0800484 // rename
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800485 if (state.filename != newFilename) {
486 lock.unlock();
487 fs::rename(state.path, state.dirPath / newFilename, ec);
488 lock.lock();
James Kuszmaulcf324122023-01-14 14:07:17 -0800489 }
490 if (ec) {
491 WPI_ERROR(m_msglog, "Could not rename log file from '{}' to '{}': {}",
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800492 state.filename, newFilename, ec.message());
James Kuszmaulcf324122023-01-14 14:07:17 -0800493 } else {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800494 WPI_INFO(m_msglog, "Renamed log file from '{}' to '{}'", state.filename,
James Kuszmaulcf324122023-01-14 14:07:17 -0800495 newFilename);
496 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800497 state.SetFilename(newFilename);
James Kuszmaulcf324122023-01-14 14:07:17 -0800498 }
499
500 if (doFlush || m_doFlush) {
501 // flush to file
502 m_doFlush = false;
503 if (m_outgoing.empty()) {
504 continue;
505 }
506 // swap outgoing with empty vector
507 toWrite.swap(m_outgoing);
508
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800509 if (state.f != fs::kInvalidFile && !blocked) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800510 lock.unlock();
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800511
512 // update free space every 10 flushes (in case other things are writing)
513 if (++freeSpaceCount >= 10) {
514 freeSpaceCount = 0;
515 auto freeSpaceInfo = fs::space(state.dirPath, ec);
516 if (!ec) {
517 state.freeSpace = freeSpaceInfo.available;
518 } else {
519 state.freeSpace = UINTMAX_MAX;
520 }
521 }
522
James Kuszmaulcf324122023-01-14 14:07:17 -0800523 // write buffers to file
524 for (auto&& buf : toWrite) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800525 // stop writing when we go below the minimum free space
526 state.freeSpace -= buf.GetData().size();
527 written += buf.GetData().size();
528 if (state.freeSpace < kMinFreeSpace) {
529 [[unlikely]] WPI_ERROR(
530 m_msglog,
531 "Stopped logging due to low free space ({} available)",
532 FormatBytesSize(state.freeSpace));
533 blocked = true;
534 break;
535 }
536 WriteToFile(state.f, buf.GetData(), state.filename, m_msglog);
James Kuszmaulcf324122023-01-14 14:07:17 -0800537 }
538
539 // sync to storage
540#if defined(__linux__)
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800541 ::fdatasync(state.f);
James Kuszmaulcf324122023-01-14 14:07:17 -0800542#elif defined(__APPLE__)
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800543 ::fsync(state.f);
James Kuszmaulcf324122023-01-14 14:07:17 -0800544#endif
545 lock.lock();
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800546 if (blocked) {
547 [[unlikely]] m_state = kPaused;
548 }
James Kuszmaulcf324122023-01-14 14:07:17 -0800549 }
550
551 // release buffers back to free list
552 for (auto&& buf : toWrite) {
553 buf.Clear();
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800554 if (m_free.size() < kMaxFreeCount) {
555 [[likely]] m_free.emplace_back(std::move(buf));
556 }
James Kuszmaulcf324122023-01-14 14:07:17 -0800557 }
558 toWrite.resize(0);
559 }
560 }
James Kuszmaulcf324122023-01-14 14:07:17 -0800561}
562
563void DataLog::WriterThreadMain(
564 std::function<void(std::span<const uint8_t> data)> write) {
565 std::chrono::duration<double> periodTime{m_period};
566
567 // write header (version 1.0)
568 {
569 const uint8_t header[] = {'W', 'P', 'I', 'L', 'O', 'G', 0, 1};
570 write(header);
571 uint8_t extraLen[4];
572 support::endian::write32le(extraLen, m_extraHeader.size());
573 write(extraLen);
574 if (m_extraHeader.size() > 0) {
575 write({reinterpret_cast<const uint8_t*>(m_extraHeader.data()),
576 m_extraHeader.size()});
577 }
578 }
579
580 std::vector<Buffer> toWrite;
581
582 std::unique_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800583 while (m_state != kShutdown) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800584 bool doFlush = false;
585 auto timeoutTime = std::chrono::steady_clock::now() + periodTime;
586 if (m_cond.wait_until(lock, timeoutTime) == std::cv_status::timeout) {
587 doFlush = true;
588 }
589
590 if (doFlush || m_doFlush) {
591 // flush to file
592 m_doFlush = false;
593 if (m_outgoing.empty()) {
594 continue;
595 }
596 // swap outgoing with empty vector
597 toWrite.swap(m_outgoing);
598
599 lock.unlock();
600 // write buffers
601 for (auto&& buf : toWrite) {
602 if (!buf.GetData().empty()) {
603 write(buf.GetData());
604 }
605 }
606 lock.lock();
607
608 // release buffers back to free list
609 for (auto&& buf : toWrite) {
610 buf.Clear();
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800611 if (m_free.size() < kMaxFreeCount) {
612 [[likely]] m_free.emplace_back(std::move(buf));
613 }
James Kuszmaulcf324122023-01-14 14:07:17 -0800614 }
615 toWrite.resize(0);
616 }
617 }
618
619 write({}); // indicate EOF
620}
621
622// Control records use the following format:
623// 1-byte type
624// 4-byte entry
625// rest of data (depending on type)
626
627int DataLog::Start(std::string_view name, std::string_view type,
628 std::string_view metadata, int64_t timestamp) {
629 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800630 return StartImpl(name, type, metadata, timestamp);
631}
632
633int DataLog::StartImpl(std::string_view name, std::string_view type,
634 std::string_view metadata, int64_t timestamp) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800635 auto& entryInfo = m_entries[name];
636 if (entryInfo.id == 0) {
637 entryInfo.id = ++m_lastId;
638 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800639 auto& entryInfo2 = m_entryIds[entryInfo.id];
640 ++entryInfo2.count;
641 if (entryInfo2.count > 1) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800642 if (entryInfo.type != type) {
643 WPI_ERROR(m_msglog,
644 "type mismatch for '{}': was '{}', requested '{}'; ignoring",
645 name, entryInfo.type, type);
646 return 0;
647 }
648 return entryInfo.id;
649 }
650 entryInfo.type = type;
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800651 entryInfo2.metadata = metadata;
652
653 if (m_state != kActive && m_state != kPaused) {
654 [[unlikely]] return entryInfo.id;
655 }
656
657 AppendStartRecord(entryInfo.id, name, type, metadata, timestamp);
658 return entryInfo.id;
659}
660
661void DataLog::AppendStartRecord(int id, std::string_view name,
662 std::string_view type,
663 std::string_view metadata, int64_t timestamp) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800664 size_t strsize = name.size() + type.size() + metadata.size();
665 uint8_t* buf = StartRecord(0, timestamp, 5 + 12 + strsize, 5);
666 *buf++ = impl::kControlStart;
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800667 wpi::support::endian::write32le(buf, id);
James Kuszmaulcf324122023-01-14 14:07:17 -0800668 AppendStringImpl(name);
669 AppendStringImpl(type);
670 AppendStringImpl(metadata);
James Kuszmaulcf324122023-01-14 14:07:17 -0800671}
672
673void DataLog::Finish(int entry, int64_t timestamp) {
674 if (entry <= 0) {
675 return;
676 }
677 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800678 auto& entryInfo2 = m_entryIds[entry];
679 if (entryInfo2.count == 0) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800680 return;
681 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800682 --entryInfo2.count;
683 if (entryInfo2.count != 0) {
James Kuszmaulcf324122023-01-14 14:07:17 -0800684 return;
685 }
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800686 m_entryIds.erase(entry);
687 if (m_state != kActive && m_state != kPaused) {
688 [[unlikely]] return;
689 }
James Kuszmaulcf324122023-01-14 14:07:17 -0800690 uint8_t* buf = StartRecord(0, timestamp, 5, 5);
691 *buf++ = impl::kControlFinish;
692 wpi::support::endian::write32le(buf, entry);
693}
694
695void DataLog::SetMetadata(int entry, std::string_view metadata,
696 int64_t timestamp) {
697 if (entry <= 0) {
698 return;
699 }
700 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800701 m_entryIds[entry].metadata = metadata;
702 if (m_state != kActive && m_state != kPaused) {
703 [[unlikely]] return;
704 }
James Kuszmaulcf324122023-01-14 14:07:17 -0800705 uint8_t* buf = StartRecord(0, timestamp, 5 + 4 + metadata.size(), 5);
706 *buf++ = impl::kControlSetMetadata;
707 wpi::support::endian::write32le(buf, entry);
708 AppendStringImpl(metadata);
709}
710
711uint8_t* DataLog::Reserve(size_t size) {
712 assert(size <= kBlockSize);
713 if (m_outgoing.empty() || size > m_outgoing.back().GetRemaining()) {
714 if (m_free.empty()) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800715 if (m_outgoing.size() >= kMaxBufferCount) {
716 [[unlikely]] WPI_ERROR(
717 m_msglog,
718 "outgoing buffers exceeded threshold, pausing logging--"
719 "consider flushing to disk more frequently (smaller period)");
720 m_state = kPaused;
721 }
James Kuszmaulcf324122023-01-14 14:07:17 -0800722 m_outgoing.emplace_back();
723 } else {
724 m_outgoing.emplace_back(std::move(m_free.back()));
725 m_free.pop_back();
726 }
727 }
728 return m_outgoing.back().Reserve(size);
729}
730
731uint8_t* DataLog::StartRecord(uint32_t entry, uint64_t timestamp,
732 uint32_t payloadSize, size_t reserveSize) {
733 uint8_t* buf = Reserve(kRecordMaxHeaderSize + reserveSize);
734 auto headerLen = WriteRecordHeader(buf, entry, timestamp, payloadSize);
735 m_outgoing.back().Unreserve(kRecordMaxHeaderSize - headerLen);
736 buf += headerLen;
737 return buf;
738}
739
740void DataLog::AppendImpl(std::span<const uint8_t> data) {
741 while (data.size() > kBlockSize) {
742 uint8_t* buf = Reserve(kBlockSize);
743 std::memcpy(buf, data.data(), kBlockSize);
744 data = data.subspan(kBlockSize);
745 }
746 uint8_t* buf = Reserve(data.size());
747 std::memcpy(buf, data.data(), data.size());
748}
749
750void DataLog::AppendStringImpl(std::string_view str) {
751 uint8_t* buf = Reserve(4);
752 wpi::support::endian::write32le(buf, str.size());
753 AppendImpl({reinterpret_cast<const uint8_t*>(str.data()), str.size()});
754}
755
756void DataLog::AppendRaw(int entry, std::span<const uint8_t> data,
757 int64_t timestamp) {
758 if (entry <= 0) {
759 return;
760 }
761 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800762 if (m_state != kActive) {
763 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800764 }
765 StartRecord(entry, timestamp, data.size(), 0);
766 AppendImpl(data);
767}
768
769void DataLog::AppendRaw2(int entry,
770 std::span<const std::span<const uint8_t>> data,
771 int64_t timestamp) {
772 if (entry <= 0) {
773 return;
774 }
775 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800776 if (m_state != kActive) {
777 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800778 }
779 size_t size = 0;
780 for (auto&& chunk : data) {
781 size += chunk.size();
782 }
783 StartRecord(entry, timestamp, size, 0);
784 for (auto chunk : data) {
785 AppendImpl(chunk);
786 }
787}
788
789void DataLog::AppendBoolean(int entry, bool value, int64_t timestamp) {
790 if (entry <= 0) {
791 return;
792 }
793 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800794 if (m_state != kActive) {
795 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800796 }
797 uint8_t* buf = StartRecord(entry, timestamp, 1, 1);
798 buf[0] = value ? 1 : 0;
799}
800
801void DataLog::AppendInteger(int entry, int64_t value, int64_t timestamp) {
802 if (entry <= 0) {
803 return;
804 }
805 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800806 if (m_state != kActive) {
807 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800808 }
809 uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
810 wpi::support::endian::write64le(buf, value);
811}
812
813void DataLog::AppendFloat(int entry, float value, int64_t timestamp) {
814 if (entry <= 0) {
815 return;
816 }
817 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800818 if (m_state != kActive) {
819 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800820 }
821 uint8_t* buf = StartRecord(entry, timestamp, 4, 4);
822 if constexpr (wpi::support::endian::system_endianness() ==
823 wpi::support::little) {
824 std::memcpy(buf, &value, 4);
825 } else {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800826 wpi::support::endian::write32le(buf, wpi::bit_cast<uint32_t>(value));
James Kuszmaulcf324122023-01-14 14:07:17 -0800827 }
828}
829
830void DataLog::AppendDouble(int entry, double value, int64_t timestamp) {
831 if (entry <= 0) {
832 return;
833 }
834 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800835 if (m_state != kActive) {
836 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800837 }
838 uint8_t* buf = StartRecord(entry, timestamp, 8, 8);
839 if constexpr (wpi::support::endian::system_endianness() ==
840 wpi::support::little) {
841 std::memcpy(buf, &value, 8);
842 } else {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800843 wpi::support::endian::write64le(buf, wpi::bit_cast<uint64_t>(value));
James Kuszmaulcf324122023-01-14 14:07:17 -0800844 }
845}
846
847void DataLog::AppendString(int entry, std::string_view value,
848 int64_t timestamp) {
849 AppendRaw(entry,
850 {reinterpret_cast<const uint8_t*>(value.data()), value.size()},
851 timestamp);
852}
853
854void DataLog::AppendBooleanArray(int entry, std::span<const bool> arr,
855 int64_t timestamp) {
856 if (entry <= 0) {
857 return;
858 }
859 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800860 if (m_state != kActive) {
861 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800862 }
863 StartRecord(entry, timestamp, arr.size(), 0);
864 uint8_t* buf;
865 while (arr.size() > kBlockSize) {
866 buf = Reserve(kBlockSize);
867 for (auto val : arr.subspan(0, kBlockSize)) {
868 *buf++ = val ? 1 : 0;
869 }
870 arr = arr.subspan(kBlockSize);
871 }
872 buf = Reserve(arr.size());
873 for (auto val : arr) {
874 *buf++ = val ? 1 : 0;
875 }
876}
877
878void DataLog::AppendBooleanArray(int entry, std::span<const int> arr,
879 int64_t timestamp) {
880 if (entry <= 0) {
881 return;
882 }
883 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800884 if (m_state != kActive) {
885 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800886 }
887 StartRecord(entry, timestamp, arr.size(), 0);
888 uint8_t* buf;
889 while (arr.size() > kBlockSize) {
890 buf = Reserve(kBlockSize);
891 for (auto val : arr.subspan(0, kBlockSize)) {
892 *buf++ = val & 1;
893 }
894 arr = arr.subspan(kBlockSize);
895 }
896 buf = Reserve(arr.size());
897 for (auto val : arr) {
898 *buf++ = val & 1;
899 }
900}
901
902void DataLog::AppendBooleanArray(int entry, std::span<const uint8_t> arr,
903 int64_t timestamp) {
904 AppendRaw(entry, arr, timestamp);
905}
906
907void DataLog::AppendIntegerArray(int entry, std::span<const int64_t> arr,
908 int64_t timestamp) {
909 if constexpr (wpi::support::endian::system_endianness() ==
910 wpi::support::little) {
911 AppendRaw(entry,
912 {reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
913 timestamp);
914 } else {
915 if (entry <= 0) {
916 return;
917 }
918 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800919 if (m_state != kActive) {
920 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800921 }
922 StartRecord(entry, timestamp, arr.size() * 8, 0);
923 uint8_t* buf;
924 while ((arr.size() * 8) > kBlockSize) {
925 buf = Reserve(kBlockSize);
926 for (auto val : arr.subspan(0, kBlockSize / 8)) {
927 wpi::support::endian::write64le(buf, val);
928 buf += 8;
929 }
930 arr = arr.subspan(kBlockSize / 8);
931 }
932 buf = Reserve(arr.size() * 8);
933 for (auto val : arr) {
934 wpi::support::endian::write64le(buf, val);
935 buf += 8;
936 }
937 }
938}
939
940void DataLog::AppendFloatArray(int entry, std::span<const float> arr,
941 int64_t timestamp) {
942 if constexpr (wpi::support::endian::system_endianness() ==
943 wpi::support::little) {
944 AppendRaw(entry,
945 {reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 4},
946 timestamp);
947 } else {
948 if (entry <= 0) {
949 return;
950 }
951 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800952 if (m_state != kActive) {
953 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800954 }
955 StartRecord(entry, timestamp, arr.size() * 4, 0);
956 uint8_t* buf;
957 while ((arr.size() * 4) > kBlockSize) {
958 buf = Reserve(kBlockSize);
959 for (auto val : arr.subspan(0, kBlockSize / 4)) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800960 wpi::support::endian::write32le(buf, wpi::bit_cast<uint32_t>(val));
James Kuszmaulcf324122023-01-14 14:07:17 -0800961 buf += 4;
962 }
963 arr = arr.subspan(kBlockSize / 4);
964 }
965 buf = Reserve(arr.size() * 4);
966 for (auto val : arr) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800967 wpi::support::endian::write32le(buf, wpi::bit_cast<uint32_t>(val));
James Kuszmaulcf324122023-01-14 14:07:17 -0800968 buf += 4;
969 }
970 }
971}
972
973void DataLog::AppendDoubleArray(int entry, std::span<const double> arr,
974 int64_t timestamp) {
975 if constexpr (wpi::support::endian::system_endianness() ==
976 wpi::support::little) {
977 AppendRaw(entry,
978 {reinterpret_cast<const uint8_t*>(arr.data()), arr.size() * 8},
979 timestamp);
980 } else {
981 if (entry <= 0) {
982 return;
983 }
984 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800985 if (m_state != kActive) {
986 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -0800987 }
988 StartRecord(entry, timestamp, arr.size() * 8, 0);
989 uint8_t* buf;
990 while ((arr.size() * 8) > kBlockSize) {
991 buf = Reserve(kBlockSize);
992 for (auto val : arr.subspan(0, kBlockSize / 8)) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -0800993 wpi::support::endian::write64le(buf, wpi::bit_cast<uint64_t>(val));
James Kuszmaulcf324122023-01-14 14:07:17 -0800994 buf += 8;
995 }
996 arr = arr.subspan(kBlockSize / 8);
997 }
998 buf = Reserve(arr.size() * 8);
999 for (auto val : arr) {
James Kuszmaulb13e13f2023-11-22 20:44:04 -08001000 wpi::support::endian::write64le(buf, wpi::bit_cast<uint64_t>(val));
James Kuszmaulcf324122023-01-14 14:07:17 -08001001 buf += 8;
1002 }
1003 }
1004}
1005
1006void DataLog::AppendStringArray(int entry, std::span<const std::string> arr,
1007 int64_t timestamp) {
1008 if (entry <= 0) {
1009 return;
1010 }
1011 // storage: 4-byte array length, each string prefixed by 4-byte length
1012 // calculate total size
1013 size_t size = 4;
1014 for (auto&& str : arr) {
1015 size += 4 + str.size();
1016 }
1017 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -08001018 if (m_state != kActive) {
1019 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -08001020 }
1021 uint8_t* buf = StartRecord(entry, timestamp, size, 4);
1022 wpi::support::endian::write32le(buf, arr.size());
1023 for (auto&& str : arr) {
1024 AppendStringImpl(str);
1025 }
1026}
1027
1028void DataLog::AppendStringArray(int entry,
1029 std::span<const std::string_view> arr,
1030 int64_t timestamp) {
1031 if (entry <= 0) {
1032 return;
1033 }
1034 // storage: 4-byte array length, each string prefixed by 4-byte length
1035 // calculate total size
1036 size_t size = 4;
1037 for (auto&& str : arr) {
1038 size += 4 + str.size();
1039 }
1040 std::scoped_lock lock{m_mutex};
James Kuszmaulb13e13f2023-11-22 20:44:04 -08001041 if (m_state != kActive) {
1042 [[unlikely]] return;
James Kuszmaulcf324122023-01-14 14:07:17 -08001043 }
1044 uint8_t* buf = StartRecord(entry, timestamp, size, 4);
1045 wpi::support::endian::write32le(buf, arr.size());
James Kuszmaulb13e13f2023-11-22 20:44:04 -08001046 for (auto&& sv : arr) {
James Kuszmaulcf324122023-01-14 14:07:17 -08001047 AppendStringImpl(sv);
1048 }
1049}
James Kuszmaulb13e13f2023-11-22 20:44:04 -08001050
1051void DataLog::AppendStringArray(int entry,
1052 std::span<const WPI_DataLog_String> arr,
1053 int64_t timestamp) {
1054 if (entry <= 0) {
1055 return;
1056 }
1057 // storage: 4-byte array length, each string prefixed by 4-byte length
1058 // calculate total size
1059 size_t size = 4;
1060 for (auto&& str : arr) {
1061 size += 4 + str.len;
1062 }
1063 std::scoped_lock lock{m_mutex};
1064 if (m_state != kActive) {
1065 [[unlikely]] return;
1066 }
1067 uint8_t* buf = StartRecord(entry, timestamp, size, 4);
1068 wpi::support::endian::write32le(buf, arr.size());
1069 for (auto&& sv : arr) {
1070 AppendStringImpl(sv.str);
1071 }
1072}
1073
1074extern "C" {
1075
1076struct WPI_DataLog* WPI_DataLog_Create(const char* dir, const char* filename,
1077 double period, const char* extraHeader) {
1078 return reinterpret_cast<WPI_DataLog*>(
1079 new DataLog{dir, filename, period, extraHeader});
1080}
1081
1082struct WPI_DataLog* WPI_DataLog_Create_Func(
1083 void (*write)(void* ptr, const uint8_t* data, size_t len), void* ptr,
1084 double period, const char* extraHeader) {
1085 return reinterpret_cast<WPI_DataLog*>(
1086 new DataLog{[=](auto data) { write(ptr, data.data(), data.size()); },
1087 period, extraHeader});
1088}
1089
1090void WPI_DataLog_Release(struct WPI_DataLog* datalog) {
1091 delete reinterpret_cast<DataLog*>(datalog);
1092}
1093
1094void WPI_DataLog_SetFilename(struct WPI_DataLog* datalog,
1095 const char* filename) {
1096 reinterpret_cast<DataLog*>(datalog)->SetFilename(filename);
1097}
1098
1099void WPI_DataLog_Flush(struct WPI_DataLog* datalog) {
1100 reinterpret_cast<DataLog*>(datalog)->Flush();
1101}
1102
1103void WPI_DataLog_Pause(struct WPI_DataLog* datalog) {
1104 reinterpret_cast<DataLog*>(datalog)->Pause();
1105}
1106
1107void WPI_DataLog_Resume(struct WPI_DataLog* datalog) {
1108 reinterpret_cast<DataLog*>(datalog)->Resume();
1109}
1110
1111void WPI_DataLog_Stop(struct WPI_DataLog* datalog) {
1112 reinterpret_cast<DataLog*>(datalog)->Stop();
1113}
1114
1115int WPI_DataLog_Start(struct WPI_DataLog* datalog, const char* name,
1116 const char* type, const char* metadata,
1117 int64_t timestamp) {
1118 return reinterpret_cast<DataLog*>(datalog)->Start(name, type, metadata,
1119 timestamp);
1120}
1121
1122void WPI_DataLog_Finish(struct WPI_DataLog* datalog, int entry,
1123 int64_t timestamp) {
1124 reinterpret_cast<DataLog*>(datalog)->Finish(entry, timestamp);
1125}
1126
1127void WPI_DataLog_SetMetadata(struct WPI_DataLog* datalog, int entry,
1128 const char* metadata, int64_t timestamp) {
1129 reinterpret_cast<DataLog*>(datalog)->SetMetadata(entry, metadata, timestamp);
1130}
1131
1132void WPI_DataLog_AppendRaw(struct WPI_DataLog* datalog, int entry,
1133 const uint8_t* data, size_t len, int64_t timestamp) {
1134 reinterpret_cast<DataLog*>(datalog)->AppendRaw(entry, {data, len}, timestamp);
1135}
1136
1137void WPI_DataLog_AppendBoolean(struct WPI_DataLog* datalog, int entry,
1138 int value, int64_t timestamp) {
1139 reinterpret_cast<DataLog*>(datalog)->AppendBoolean(entry, value, timestamp);
1140}
1141
1142void WPI_DataLog_AppendInteger(struct WPI_DataLog* datalog, int entry,
1143 int64_t value, int64_t timestamp) {
1144 reinterpret_cast<DataLog*>(datalog)->AppendInteger(entry, value, timestamp);
1145}
1146
1147void WPI_DataLog_AppendFloat(struct WPI_DataLog* datalog, int entry,
1148 float value, int64_t timestamp) {
1149 reinterpret_cast<DataLog*>(datalog)->AppendFloat(entry, value, timestamp);
1150}
1151
1152void WPI_DataLog_AppendDouble(struct WPI_DataLog* datalog, int entry,
1153 double value, int64_t timestamp) {
1154 reinterpret_cast<DataLog*>(datalog)->AppendDouble(entry, value, timestamp);
1155}
1156
1157void WPI_DataLog_AppendString(struct WPI_DataLog* datalog, int entry,
1158 const char* value, size_t len,
1159 int64_t timestamp) {
1160 reinterpret_cast<DataLog*>(datalog)->AppendString(entry, {value, len},
1161 timestamp);
1162}
1163
1164void WPI_DataLog_AppendBooleanArray(struct WPI_DataLog* datalog, int entry,
1165 const int* arr, size_t len,
1166 int64_t timestamp) {
1167 reinterpret_cast<DataLog*>(datalog)->AppendBooleanArray(entry, {arr, len},
1168 timestamp);
1169}
1170
1171void WPI_DataLog_AppendBooleanArrayByte(struct WPI_DataLog* datalog, int entry,
1172 const uint8_t* arr, size_t len,
1173 int64_t timestamp) {
1174 reinterpret_cast<DataLog*>(datalog)->AppendBooleanArray(entry, {arr, len},
1175 timestamp);
1176}
1177
1178void WPI_DataLog_AppendIntegerArray(struct WPI_DataLog* datalog, int entry,
1179 const int64_t* arr, size_t len,
1180 int64_t timestamp) {
1181 reinterpret_cast<DataLog*>(datalog)->AppendIntegerArray(entry, {arr, len},
1182 timestamp);
1183}
1184
1185void WPI_DataLog_AppendFloatArray(struct WPI_DataLog* datalog, int entry,
1186 const float* arr, size_t len,
1187 int64_t timestamp) {
1188 reinterpret_cast<DataLog*>(datalog)->AppendFloatArray(entry, {arr, len},
1189 timestamp);
1190}
1191
1192void WPI_DataLog_AppendDoubleArray(struct WPI_DataLog* datalog, int entry,
1193 const double* arr, size_t len,
1194 int64_t timestamp) {
1195 reinterpret_cast<DataLog*>(datalog)->AppendDoubleArray(entry, {arr, len},
1196 timestamp);
1197}
1198
1199void WPI_DataLog_AppendStringArray(struct WPI_DataLog* datalog, int entry,
1200 const WPI_DataLog_String* arr, size_t len,
1201 int64_t timestamp) {
1202 reinterpret_cast<DataLog*>(datalog)->AppendStringArray(entry, {arr, len},
1203 timestamp);
1204}
1205
1206} // extern "C"