blob: dcacb68663eef7eb6cd3bc43c9686150f205307a [file] [log] [blame]
James Kuszmaul4ed5fb12022-03-22 15:20:04 -07001#ifndef AOS_UTIL_MCAP_LOGGER_H_
2#define AOS_UTIL_MCAP_LOGGER_H_
3
4#include "aos/configuration_generated.h"
5#include "aos/events/event_loop.h"
6#include "aos/fast_string_builder.h"
7#include "aos/flatbuffer_utils.h"
8#include "single_include/nlohmann/json.hpp"
9
10namespace aos {
11
12// Produces a JSON Schema (https://json-schema.org/) for a given flatbuffer
13// type. If recursion_level is set, will include a $schema attribute indicating
14// the schema definition being used (this is used to allow for recursion).
15//
16// Note that this is pretty bare-bones, so, e.g., we don't distinguish between
17// structs and tables when generating the JSON schema, so we don't bother to
18// mark struct fields as required.
19enum class JsonSchemaRecursion {
20 kTopLevel,
21 kNested,
22};
23nlohmann::json JsonSchemaForFlatbuffer(
24 const FlatbufferType &type,
25 JsonSchemaRecursion recursion_level = JsonSchemaRecursion::kTopLevel);
26
27// Generates an MCAP file, per the specification at
28// https://github.com/foxglove/mcap/tree/main/docs/specification
James Kuszmaulb3fba252022-04-06 15:13:31 -070029// This currently generates an uncompressed logfile with full message indexing
30// available, to be able to support Foxglove fully.
James Kuszmaul4ed5fb12022-03-22 15:20:04 -070031class McapLogger {
32 public:
33 McapLogger(EventLoop *event_loop, const std::string &output_path);
34 ~McapLogger();
35
36 private:
37 enum class OpCode {
38 kHeader = 0x01,
39 kFooter = 0x02,
40 kSchema = 0x03,
41 kChannel = 0x04,
42 kMessage = 0x05,
James Kuszmaulb3fba252022-04-06 15:13:31 -070043 kChunk = 0x06,
44 kMessageIndex = 0x07,
45 kChunkIndex = 0x08,
46 kAttachment = 0x09,
47 kAttachmentIndex = 0x0A,
48 kStatistics = 0x0B,
49 kMetadata = 0x0C,
50 kMetadataIndex = 0x0D,
51 kSummaryOffset = 0x0E,
James Kuszmaul4ed5fb12022-03-22 15:20:04 -070052 kDataEnd = 0x0F,
53 };
James Kuszmaulb3fba252022-04-06 15:13:31 -070054 // Stores information associated with a SummaryOffset entry (an offset to the
55 // start of a section within Summary section, which allows readers to quickly
56 // find all the indices/channel definitions/etc. for a given log).
57 struct SummaryOffset {
58 OpCode op_code;
59 // Offset from the start of the file.
60 uint64_t offset;
61 // Total length of the section, in bytes.
62 uint64_t size;
63 };
64 // Information needed to build a ChunkIndex entry.
65 struct ChunkIndex {
66 // Earliest and latest message times within the Chunk being referenced.
67 aos::monotonic_clock::time_point start_time;
68 aos::monotonic_clock::time_point end_time;
69 // Offset from the start of the file to the start of the relevant Chunk.
70 uint64_t offset;
71 // Total size of the Chunk, in bytes.
72 uint64_t chunk_size;
73 // Total size of the records portion of the Chunk, in bytes.
74 uint64_t records_size;
75 // Mapping of channel IDs to the MessageIndex entry for that channel within
76 // the referenced Chunk. The MessageIndex is referenced by an offset from
77 // the start of the file.
78 std::map<uint16_t, uint64_t> message_index_offsets;
79 // Total size, in bytes, of all the MessageIndex entries for this Chunk
80 // together (note that they are required to be contiguous).
81 uint64_t message_index_size;
82 };
83 enum class RegisterHandlers { kYes, kNo };
James Kuszmaul4ed5fb12022-03-22 15:20:04 -070084 // Helpers to write each type of relevant record.
85 void WriteMagic();
86 void WriteHeader();
James Kuszmaulb3fba252022-04-06 15:13:31 -070087 void WriteFooter(uint64_t summary_offset, uint64_t summary_offset_offset);
James Kuszmaul4ed5fb12022-03-22 15:20:04 -070088 void WriteDataEnd();
89 void WriteSchema(const uint16_t id, const aos::Channel *channel);
90 void WriteChannel(const uint16_t id, const uint16_t schema_id,
91 const aos::Channel *channel);
92 void WriteMessage(uint16_t channel_id, const Channel *channel,
James Kuszmaulb3fba252022-04-06 15:13:31 -070093 const Context &context, std::ostream *output);
94 void WriteChunk();
95
96 // The helpers for writing records which appear in the Summary section will
97 // return SummaryOffset's so that they can be referenced in the SummaryOffset
98 // section.
99 SummaryOffset WriteChunkIndices();
100 SummaryOffset WriteStatistics();
101 std::vector<SummaryOffset> WriteSchemasAndChannels(
102 RegisterHandlers register_handlers);
103 void WriteSummaryOffset(const SummaryOffset &offset);
James Kuszmaul4ed5fb12022-03-22 15:20:04 -0700104
105 // Writes an MCAP record to the output file.
James Kuszmaulb3fba252022-04-06 15:13:31 -0700106 void WriteRecord(OpCode op, std::string_view record, std::ostream *ostream);
107 void WriteRecord(OpCode op, std::string_view record) {
108 WriteRecord(op, record, &output_);
109 }
110 // Adds an MCAP-spec string/byte-array/map/array of pairs/fixed-size integer
111 // to a buffer.
James Kuszmaul4ed5fb12022-03-22 15:20:04 -0700112 static void AppendString(FastStringBuilder *builder, std::string_view string);
James Kuszmaulb3fba252022-04-06 15:13:31 -0700113 static void AppendBytes(FastStringBuilder *builder, std::string_view bytes);
114 static void AppendChannelMap(FastStringBuilder *builder,
115 const std::map<uint16_t, uint64_t> &map);
116 static void AppendMessageIndices(
117 FastStringBuilder *builder,
118 const std::vector<std::pair<uint64_t, uint64_t>> &messages);
James Kuszmaul4ed5fb12022-03-22 15:20:04 -0700119 static void AppendInt16(FastStringBuilder *builder, uint16_t val);
120 static void AppendInt32(FastStringBuilder *builder, uint32_t val);
121 static void AppendInt64(FastStringBuilder *builder, uint64_t val);
122
James Kuszmaulb3fba252022-04-06 15:13:31 -0700123 aos::EventLoop *event_loop_;
James Kuszmaul4ed5fb12022-03-22 15:20:04 -0700124 std::ofstream output_;
James Kuszmaulb3fba252022-04-06 15:13:31 -0700125 // Buffer containing serialized message data for the currently-being-built
126 // chunk.
127 std::stringstream current_chunk_;
James Kuszmaul4ed5fb12022-03-22 15:20:04 -0700128 FastStringBuilder string_builder_;
James Kuszmaulb3fba252022-04-06 15:13:31 -0700129
130 // Earliest message observed in this logfile.
131 std::optional<aos::monotonic_clock::time_point> earliest_message_;
132 // Earliest message observed in the current chunk.
133 std::optional<aos::monotonic_clock::time_point> earliest_chunk_message_;
134 // Latest message observed.
135 aos::monotonic_clock::time_point latest_message_ =
136 aos::monotonic_clock::min_time;
137 // Count of all messages on each channel, indexed by channel ID.
138 std::map<uint16_t, uint64_t> message_counts_;
139 // MessageIndex's for each message. The std::map is indexed by channel ID. The
140 // vector is then a series of pairs of (timestamp, offset from start of
141 // current_chunk_).
142 std::map<uint16_t, std::vector<std::pair<uint64_t, uint64_t>>>
143 message_indices_;
144 // ChunkIndex's for all fully written Chunks.
145 std::vector<ChunkIndex> chunk_indices_;
James Kuszmaul4ed5fb12022-03-22 15:20:04 -0700146};
147} // namespace aos
148#endif // AOS_UTIL_MCAP_LOGGER_H_