blob: 3e1dd1e808991302781ca63e152cc5672b2875eb [file] [log] [blame]
Brian Silverman9891b292020-06-23 16:34:22 -07001#include <algorithm>
James Kuszmaul38735e82019-12-07 16:42:06 -08002#include <iostream>
Brian Silverman9891b292020-06-23 16:34:22 -07003#include <memory>
4#include <optional>
5#include <string>
6#include <string_view>
7#include <vector>
James Kuszmaul38735e82019-12-07 16:42:06 -08008
Austin Schuh0e8db662021-07-06 10:43:47 -07009#include "absl/strings/escaping.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080010#include "aos/configuration.h"
Austin Schuhb06f03b2021-02-17 22:00:37 -080011#include "aos/events/logging/log_reader.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080012#include "aos/events/simulated_event_loop.h"
13#include "aos/init.h"
14#include "aos/json_to_flatbuffer.h"
15#include "gflags/gflags.h"
16
James Kuszmaul38735e82019-12-07 16:42:06 -080017DEFINE_string(
18 name, "",
19 "Name to match for printing out channels. Empty means no name filter.");
20DEFINE_string(type, "",
21 "Channel type to match for printing out channels. Empty means no "
22 "type filter.");
Austin Schuh041fe9f2021-10-16 23:01:15 -070023DEFINE_bool(json, false, "If true, print fully valid JSON");
Austin Schuha81454b2020-05-12 19:58:36 -070024DEFINE_bool(fetch, false,
25 "If true, also print out the messages from before the start of the "
26 "log file");
Austin Schuh6f3babe2020-01-26 20:34:50 -080027DEFINE_bool(raw, false,
28 "If true, just print the data out unsorted and unparsed");
Brian Silverman8ff74aa2021-02-05 16:37:15 -080029DEFINE_string(raw_header, "",
30 "If set, the file to read the header from in raw mode");
Austin Schuhff3bc902022-05-11 16:10:57 -070031DEFINE_bool(distributed_clock, false,
32 "If true, print out the distributed time");
Austin Schuha81454b2020-05-12 19:58:36 -070033DEFINE_bool(format_raw, true,
34 "If true and --raw is specified, print out raw data, but use the "
35 "schema to format the data.");
Austin Schuhae46f362020-04-11 19:52:56 -070036DEFINE_int32(max_vector_size, 100,
37 "If positive, vectors longer than this will not be printed");
Ravago Jones5cc9df52020-09-02 21:29:58 -070038DEFINE_bool(pretty, false,
39 "If true, pretty print the messages on multiple lines");
Austin Schuh569c7f92020-12-11 20:01:42 -080040DEFINE_bool(print, true,
41 "If true, actually print the messages. If false, discard them, "
42 "confirming they can be parsed.");
Tyler Chatowee0afa82021-08-01 22:00:36 -070043DEFINE_uint64(
44 count, 0,
45 "If >0, log_cat will exit after printing this many messages. This "
46 "includes messages from before the start of the log if --fetch is set.");
Austin Schuh7af06d52021-06-28 15:46:59 -070047DEFINE_bool(print_parts_only, false,
48 "If true, only print out the results of logfile sorting.");
Austin Schuh25b17652021-07-21 15:42:56 -070049DEFINE_bool(channels, false,
50 "If true, print out all the configured channels for this log.");
Milind Upadhyay184dfda2022-03-26 15:54:38 -070051DEFINE_double(monotonic_start_time, 0.0,
52 "If set, only print messages sent at or after this many seconds "
53 "after epoch.");
54DEFINE_double(monotonic_end_time, 0.0,
55 "If set, only print messages sent at or before this many seconds "
56 "after epoch.");
Austin Schuh6f3babe2020-01-26 20:34:50 -080057
Austin Schuh041fe9f2021-10-16 23:01:15 -070058using aos::monotonic_clock;
59namespace chrono = std::chrono;
60
61void StreamSeconds(std::ostream &stream,
62 const aos::monotonic_clock::time_point now) {
63 if (now < monotonic_clock::epoch()) {
64 chrono::seconds seconds =
65 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
66
67 stream << "-" << -seconds.count() << "." << std::setfill('0')
68 << std::setw(9)
69 << chrono::duration_cast<chrono::nanoseconds>(seconds -
70 now.time_since_epoch())
71 .count();
72 } else {
73 chrono::seconds seconds =
74 chrono::duration_cast<chrono::seconds>(now.time_since_epoch());
75 stream << seconds.count() << "." << std::setfill('0') << std::setw(9)
76 << chrono::duration_cast<chrono::nanoseconds>(
77 now.time_since_epoch() - seconds)
78 .count();
79 }
80}
81
Brian Silverman9891b292020-06-23 16:34:22 -070082// Print the flatbuffer out to stdout, both to remove the unnecessary cruft from
83// glog and to allow the user to readily redirect just the logged output
84// independent of any debugging information on stderr.
Austin Schuhff3bc902022-05-11 16:10:57 -070085void PrintMessage(const std::string_view node_name,
86 aos::NodeEventLoopFactory *node_factory,
87 const aos::Channel *channel, const aos::Context &context,
Austin Schuh746690f2020-08-01 16:15:57 -070088 aos::FastStringBuilder *builder) {
89 builder->Reset();
Austin Schuha9df9ad2021-06-16 14:49:39 -070090 CHECK(flatbuffers::Verify(*channel->schema(),
91 *channel->schema()->root_table(),
92 static_cast<const uint8_t *>(context.data),
93 static_cast<size_t>(context.size)))
94 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
95 << channel->type()->c_str();
96
Ravago Jones5cc9df52020-09-02 21:29:58 -070097 aos::FlatbufferToJson(
98 builder, channel->schema(), static_cast<const uint8_t *>(context.data),
99 {FLAGS_pretty, static_cast<size_t>(FLAGS_max_vector_size)});
Austin Schuh746690f2020-08-01 16:15:57 -0700100
Austin Schuh041fe9f2021-10-16 23:01:15 -0700101 if (FLAGS_json) {
102 std::cout << "{";
103 if (!node_name.empty()) {
104 std::cout << "\"node\": \"" << node_name << "\", ";
105 }
106 std::cout << "\"monotonic_event_time\": ";
107 StreamSeconds(std::cout, context.monotonic_event_time);
108 std::cout << ", \"realtime_event_time\": \"" << context.realtime_event_time
109 << "\", ";
110
111 if (context.monotonic_remote_time != context.monotonic_event_time) {
112 std::cout << "\"monotonic_remote_time\": ";
113 StreamSeconds(std::cout, context.monotonic_remote_time);
114 std::cout << ", \"realtime_remote_time\": \""
115 << context.realtime_remote_time << "\", ";
116 }
117
118 std::cout << "\"channel\": "
119 << aos::configuration::StrippedChannelToString(channel)
120 << ", \"data\": " << *builder << "}" << std::endl;
Austin Schuha81454b2020-05-12 19:58:36 -0700121 } else {
Austin Schuhff3bc902022-05-11 16:10:57 -0700122 if (FLAGS_distributed_clock) {
123 std::cout << node_factory->ToDistributedClock(
124 context.monotonic_event_time)
125 << " ";
126 }
Austin Schuh041fe9f2021-10-16 23:01:15 -0700127 if (!node_name.empty()) {
128 std::cout << node_name << " ";
129 }
130 if (context.monotonic_remote_time != context.monotonic_event_time) {
131 std::cout << context.realtime_event_time << " ("
132 << context.monotonic_event_time << ") sent "
133 << context.realtime_remote_time << " ("
134 << context.monotonic_remote_time << ") "
135 << channel->name()->c_str() << ' ' << channel->type()->c_str()
136 << ": " << *builder << std::endl;
137 } else {
138 std::cout << context.realtime_event_time << " ("
139 << context.monotonic_event_time << ") "
140 << channel->name()->c_str() << ' ' << channel->type()->c_str()
141 << ": " << *builder << std::endl;
142 }
Austin Schuha81454b2020-05-12 19:58:36 -0700143 }
144}
145
Austin Schuh58646e22021-08-23 23:51:46 -0700146// Prints out raw log parts to stdout.
147int PrintRaw(int argc, char **argv) {
148 if (argc != 2) {
149 LOG(FATAL) << "Expected 1 logfile as an argument.";
150 }
151 aos::logger::SpanReader reader(argv[1]);
152 absl::Span<const uint8_t> raw_log_file_header_span = reader.ReadMessage();
153
154 if (raw_log_file_header_span == absl::Span<const uint8_t>()) {
155 LOG(WARNING) << "Empty log file on " << reader.filename();
156 return 0;
157 }
158
159 // Now, reproduce the log file header deduplication logic inline so we can
160 // print out all the headers we find.
161 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader> log_file_header(
162 raw_log_file_header_span);
163 if (!log_file_header.Verify()) {
164 LOG(ERROR) << "Header corrupted on " << reader.filename();
165 return 1;
166 }
167 while (true) {
168 absl::Span<const uint8_t> maybe_header_data = reader.PeekMessage();
169 if (maybe_header_data == absl::Span<const uint8_t>()) {
170 break;
171 }
172
173 aos::SizePrefixedFlatbufferSpan<aos::logger::LogFileHeader> maybe_header(
174 maybe_header_data);
175 if (maybe_header.Verify()) {
176 std::cout << aos::FlatbufferToJson(
177 log_file_header, {.multi_line = FLAGS_pretty,
178 .max_vector_size = static_cast<size_t>(
179 FLAGS_max_vector_size)})
180 << std::endl;
181 LOG(WARNING) << "Found duplicate LogFileHeader in " << reader.filename();
182 log_file_header =
183 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader>(
184 maybe_header_data);
185
186 reader.ConsumeMessage();
187 } else {
188 break;
189 }
190 }
191
192 // And now use the final sha256 to match the raw_header.
193 std::optional<aos::logger::MessageReader> raw_header_reader;
194 const aos::logger::LogFileHeader *full_header = &log_file_header.message();
195 if (!FLAGS_raw_header.empty()) {
196 raw_header_reader.emplace(FLAGS_raw_header);
197 std::cout << aos::FlatbufferToJson(full_header,
198 {.multi_line = FLAGS_pretty,
199 .max_vector_size = static_cast<size_t>(
200 FLAGS_max_vector_size)})
201 << std::endl;
202 CHECK_EQ(
203 full_header->configuration_sha256()->string_view(),
204 aos::logger::Sha256(raw_header_reader->raw_log_file_header().span()));
205 full_header = raw_header_reader->log_file_header();
206 }
207
208 if (!FLAGS_print) {
209 return 0;
210 }
211
212 std::cout << aos::FlatbufferToJson(full_header,
213 {.multi_line = FLAGS_pretty,
214 .max_vector_size = static_cast<size_t>(
215 FLAGS_max_vector_size)})
216 << std::endl;
217 CHECK(full_header->has_configuration())
218 << ": Missing configuration! You may want to provide the path to the "
219 "logged configuration file using the --raw_header flag.";
220
221 while (true) {
222 const aos::SizePrefixedFlatbufferSpan<aos::logger::MessageHeader> message(
223 reader.ReadMessage());
224 if (message.span() == absl::Span<const uint8_t>()) {
225 break;
226 }
227 CHECK(message.Verify());
228
229 const auto *const channels = full_header->configuration()->channels();
230 const size_t channel_index = message.message().channel_index();
231 CHECK_LT(channel_index, channels->size());
232 const aos::Channel *const channel = channels->Get(channel_index);
233
234 CHECK(message.Verify()) << absl::BytesToHexString(
235 std::string_view(reinterpret_cast<const char *>(message.span().data()),
236 message.span().size()));
237
238 if (message.message().data() != nullptr) {
239 CHECK(channel->has_schema());
240
241 CHECK(flatbuffers::Verify(
242 *channel->schema(), *channel->schema()->root_table(),
243 message.message().data()->data(), message.message().data()->size()))
244 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
245 << channel->type()->c_str();
246 }
247
248 if (FLAGS_format_raw && message.message().data() != nullptr) {
249 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
250 << aos::FlatbufferToJson(message, {.multi_line = FLAGS_pretty,
251 .max_vector_size = 4})
252 << ": "
253 << aos::FlatbufferToJson(
254 channel->schema(), message.message().data()->data(),
255 {FLAGS_pretty,
256 static_cast<size_t>(FLAGS_max_vector_size)})
257 << std::endl;
258 } else {
259 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
260 << aos::FlatbufferToJson(
261 message, {FLAGS_pretty,
262 static_cast<size_t>(FLAGS_max_vector_size)})
263 << std::endl;
264 }
265 }
266 return 0;
267}
268
269// This class prints out all data from a node on a boot.
270class NodePrinter {
271 public:
272 NodePrinter(aos::EventLoop *event_loop, uint64_t *message_print_counter,
273 aos::SimulatedEventLoopFactory *factory,
274 aos::FastStringBuilder *builder)
275 : factory_(factory),
Austin Schuhff3bc902022-05-11 16:10:57 -0700276 node_factory_(factory->GetNodeEventLoopFactory(event_loop->node())),
Austin Schuh58646e22021-08-23 23:51:46 -0700277 event_loop_(event_loop),
278 message_print_counter_(message_print_counter),
279 node_name_(
280 event_loop_->node() == nullptr
281 ? ""
Austin Schuh041fe9f2021-10-16 23:01:15 -0700282 : std::string(event_loop->node()->name()->string_view())),
Austin Schuh58646e22021-08-23 23:51:46 -0700283 builder_(builder) {
284 event_loop_->SkipTimingReport();
285 event_loop_->SkipAosLog();
286
287 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
288 event_loop_->configuration()->channels();
289
Milind Upadhyay184dfda2022-03-26 15:54:38 -0700290 const monotonic_clock::time_point start_time =
291 (FLAGS_monotonic_start_time == 0.0
292 ? monotonic_clock::min_time
293 : monotonic_clock::time_point(
294 std::chrono::duration_cast<monotonic_clock::duration>(
295 std::chrono::duration<double>(
296 FLAGS_monotonic_start_time))));
297 const monotonic_clock::time_point end_time =
298 (FLAGS_monotonic_end_time == 0.0
299 ? monotonic_clock::max_time
300 : monotonic_clock::time_point(
301 std::chrono::duration_cast<monotonic_clock::duration>(
302 std::chrono::duration<double>(
303 FLAGS_monotonic_end_time))));
304
Austin Schuh58646e22021-08-23 23:51:46 -0700305 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
306 const aos::Channel *channel = channels->Get(i);
307 const flatbuffers::string_view name = channel->name()->string_view();
308 const flatbuffers::string_view type = channel->type()->string_view();
309 if (name.find(FLAGS_name) != std::string::npos &&
310 type.find(FLAGS_type) != std::string::npos) {
311 if (!aos::configuration::ChannelIsReadableOnNode(channel,
312 event_loop_->node())) {
313 continue;
314 }
315 VLOG(1) << "Listening on " << name << " " << type;
316
317 CHECK_NOTNULL(channel->schema());
Austin Schuh60e77942022-05-16 17:48:24 -0700318 event_loop_->MakeRawWatcher(channel, [this, channel, start_time,
319 end_time](
320 const aos::Context &context,
321 const void * /*message*/) {
322 if (!FLAGS_print) {
323 return;
324 }
Austin Schuh58646e22021-08-23 23:51:46 -0700325
Austin Schuh60e77942022-05-16 17:48:24 -0700326 if (!FLAGS_fetch && !started_) {
327 return;
328 }
Austin Schuh58646e22021-08-23 23:51:46 -0700329
Austin Schuh60e77942022-05-16 17:48:24 -0700330 if (context.monotonic_event_time < start_time ||
331 context.monotonic_event_time > end_time) {
332 return;
333 }
Milind Upadhyay184dfda2022-03-26 15:54:38 -0700334
Austin Schuh60e77942022-05-16 17:48:24 -0700335 PrintMessage(node_name_, node_factory_, channel, context, builder_);
336 ++(*message_print_counter_);
337 if (FLAGS_count > 0 && *message_print_counter_ >= FLAGS_count) {
338 factory_->Exit();
339 }
340 });
Austin Schuh58646e22021-08-23 23:51:46 -0700341 }
342 }
343 }
344
345 void SetStarted(bool started, aos::monotonic_clock::time_point monotonic_now,
346 aos::realtime_clock::time_point realtime_now) {
347 started_ = started;
Austin Schuh041fe9f2021-10-16 23:01:15 -0700348 if (FLAGS_json) {
349 return;
350 }
Austin Schuh58646e22021-08-23 23:51:46 -0700351 if (started_) {
352 std::cout << std::endl;
353 std::cout << (event_loop_->node() != nullptr
354 ? (event_loop_->node()->name()->str() + " ")
355 : "")
356 << "Log starting at " << realtime_now << " (" << monotonic_now
357 << ")";
358 std::cout << std::endl << std::endl;
359 } else {
360 std::cout << std::endl;
361 std::cout << (event_loop_->node() != nullptr
362 ? (event_loop_->node()->name()->str() + " ")
363 : "")
364 << "Log shutting down at " << realtime_now << " ("
365 << monotonic_now << ")";
366 std::cout << std::endl << std::endl;
367 }
368 }
369
370 private:
371 struct MessageInfo {
372 std::string node_name;
373 std::unique_ptr<aos::RawFetcher> fetcher;
374 };
375
376 aos::SimulatedEventLoopFactory *factory_;
Austin Schuhff3bc902022-05-11 16:10:57 -0700377 aos::NodeEventLoopFactory *node_factory_;
Austin Schuh58646e22021-08-23 23:51:46 -0700378 aos::EventLoop *event_loop_;
379
380 uint64_t *message_print_counter_ = nullptr;
381
382 std::string node_name_;
383
384 bool started_ = false;
385
386 aos::FastStringBuilder *builder_;
387};
388
James Kuszmaul38735e82019-12-07 16:42:06 -0800389int main(int argc, char **argv) {
390 gflags::SetUsageMessage(
Austin Schuh6f3babe2020-01-26 20:34:50 -0800391 "Usage:\n"
392 " log_cat [args] logfile1 logfile2 ...\n"
393 "\n"
James Kuszmaul38735e82019-12-07 16:42:06 -0800394 "This program provides a basic interface to dump data from a logfile to "
395 "stdout. Given a logfile, channel name filter, and type filter, it will "
396 "print all the messages in the logfile matching the filters. The message "
397 "filters work by taking the values of --name and --type and printing any "
398 "channel whose name contains --name as a substr and whose type contains "
399 "--type as a substr. Not specifying --name or --type leaves them free. "
400 "Calling this program without --name or --type specified prints out all "
401 "the logged data.");
402 aos::InitGoogle(&argc, &argv);
403
Austin Schuh6f3babe2020-01-26 20:34:50 -0800404 if (FLAGS_raw) {
Austin Schuh58646e22021-08-23 23:51:46 -0700405 return PrintRaw(argc, argv);
James Kuszmaul38735e82019-12-07 16:42:06 -0800406 }
407
Austin Schuh6f3babe2020-01-26 20:34:50 -0800408 if (argc < 2) {
409 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
410 }
411
Austin Schuh11d43732020-09-21 17:28:30 -0700412 const std::vector<aos::logger::LogFile> logfiles =
Austin Schuh58646e22021-08-23 23:51:46 -0700413 aos::logger::SortParts(aos::logger::FindLogs(argc, argv));
Austin Schuh5212cad2020-09-09 23:12:09 -0700414
Austin Schuhfe3fb342021-01-16 18:50:37 -0800415 for (auto &it : logfiles) {
416 VLOG(1) << it;
Austin Schuh7af06d52021-06-28 15:46:59 -0700417 if (FLAGS_print_parts_only) {
418 std::cout << it << std::endl;
419 }
420 }
421 if (FLAGS_print_parts_only) {
422 return 0;
Austin Schuhfe3fb342021-01-16 18:50:37 -0800423 }
424
Austin Schuh6f3babe2020-01-26 20:34:50 -0800425 aos::logger::LogReader reader(logfiles);
Austin Schuha81454b2020-05-12 19:58:36 -0700426
Austin Schuh25b17652021-07-21 15:42:56 -0700427 if (FLAGS_channels) {
428 const aos::Configuration *config = reader.configuration();
429 for (const aos::Channel *channel : *config->channels()) {
430 std::cout << channel->name()->c_str() << " " << channel->type()->c_str()
431 << '\n';
432 }
433 return 0;
434 }
435
Austin Schuh58646e22021-08-23 23:51:46 -0700436 {
437 bool found_channel = false;
Austin Schuh6f3babe2020-01-26 20:34:50 -0800438 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Austin Schuh58646e22021-08-23 23:51:46 -0700439 reader.configuration()->channels();
Brian Silverman9891b292020-06-23 16:34:22 -0700440
Austin Schuh6f3babe2020-01-26 20:34:50 -0800441 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
442 const aos::Channel *channel = channels->Get(i);
443 const flatbuffers::string_view name = channel->name()->string_view();
444 const flatbuffers::string_view type = channel->type()->string_view();
445 if (name.find(FLAGS_name) != std::string::npos &&
446 type.find(FLAGS_type) != std::string::npos) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800447 found_channel = true;
448 }
449 }
Austin Schuh58646e22021-08-23 23:51:46 -0700450 if (!found_channel) {
451 LOG(FATAL) << "Could not find any channels";
Austin Schuha81454b2020-05-12 19:58:36 -0700452 }
James Kuszmaul38735e82019-12-07 16:42:06 -0800453 }
454
Austin Schuh58646e22021-08-23 23:51:46 -0700455 aos::FastStringBuilder builder;
James Kuszmaul912af072020-10-31 16:06:54 -0700456
Austin Schuh58646e22021-08-23 23:51:46 -0700457 uint64_t message_print_counter = 0;
458
459 std::vector<NodePrinter *> printers;
Sanjay Narayananbeb328c2021-09-01 16:24:20 -0700460 printers.resize(aos::configuration::NodesCount(reader.configuration()),
461 nullptr);
462
463 aos::SimulatedEventLoopFactory event_loop_factory(reader.configuration());
464
465 reader.RegisterWithoutStarting(&event_loop_factory);
Austin Schuh58646e22021-08-23 23:51:46 -0700466
467 for (const aos::Node *node :
468 aos::configuration::GetNodes(event_loop_factory.configuration())) {
469 size_t node_index = aos::configuration::GetNodeIndex(
470 event_loop_factory.configuration(), node);
471 // Spin up the printer, and hook up the SetStarted method so that it gets
472 // notified when the log starts and stops.
473 aos::NodeEventLoopFactory *node_factory =
474 event_loop_factory.GetNodeEventLoopFactory(node);
475 node_factory->OnStartup([&event_loop_factory, node_factory,
476 &message_print_counter, &builder, &printers,
477 node_index]() {
478 printers[node_index] = node_factory->AlwaysStart<NodePrinter>(
479 "printer", &message_print_counter, &event_loop_factory, &builder);
480 });
481 node_factory->OnShutdown(
482 [&printers, node_index]() { printers[node_index] = nullptr; });
483
484 reader.OnStart(node, [&printers, node_index, node_factory]() {
485 CHECK(printers[node_index]);
486 printers[node_index]->SetStarted(true, node_factory->monotonic_now(),
487 node_factory->realtime_now());
488 });
489 reader.OnEnd(node, [&printers, node_index, node_factory]() {
490 CHECK(printers[node_index]);
491 printers[node_index]->SetStarted(false, node_factory->monotonic_now(),
492 node_factory->realtime_now());
493 });
Austin Schuha81454b2020-05-12 19:58:36 -0700494 }
495
496 event_loop_factory.Run();
James Kuszmaul38735e82019-12-07 16:42:06 -0800497
Austin Schuh51a92592020-08-09 13:17:00 -0700498 reader.Deregister();
499
James Kuszmaul38735e82019-12-07 16:42:06 -0800500 return 0;
501}