blob: a2441c82bee5925947b63e7be8a3f07c29149dc9 [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"
Austin Schuh893d7f42022-09-16 15:01:35 -070010#include "aos/aos_cli_utils.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080011#include "aos/configuration.h"
Austin Schuhb06f03b2021-02-17 22:00:37 -080012#include "aos/events/logging/log_reader.h"
James Kuszmaul38735e82019-12-07 16:42:06 -080013#include "aos/events/simulated_event_loop.h"
14#include "aos/init.h"
15#include "aos/json_to_flatbuffer.h"
16#include "gflags/gflags.h"
17
James Kuszmaul38735e82019-12-07 16:42:06 -080018DEFINE_string(
19 name, "",
20 "Name to match for printing out channels. Empty means no name filter.");
21DEFINE_string(type, "",
22 "Channel type to match for printing out channels. Empty means no "
23 "type filter.");
Austin Schuh041fe9f2021-10-16 23:01:15 -070024DEFINE_bool(json, false, "If true, print fully valid JSON");
Austin Schuha81454b2020-05-12 19:58:36 -070025DEFINE_bool(fetch, false,
26 "If true, also print out the messages from before the start of the "
27 "log file");
Austin Schuh6f3babe2020-01-26 20:34:50 -080028DEFINE_bool(raw, false,
29 "If true, just print the data out unsorted and unparsed");
Brian Silverman8ff74aa2021-02-05 16:37:15 -080030DEFINE_string(raw_header, "",
31 "If set, the file to read the header from in raw mode");
Austin Schuhff3bc902022-05-11 16:10:57 -070032DEFINE_bool(distributed_clock, false,
33 "If true, print out the distributed time");
Austin Schuha81454b2020-05-12 19:58:36 -070034DEFINE_bool(format_raw, true,
35 "If true and --raw is specified, print out raw data, but use the "
36 "schema to format the data.");
Austin Schuh893d7f42022-09-16 15:01:35 -070037DEFINE_int64(max_vector_size, 100,
Austin Schuhae46f362020-04-11 19:52:56 -070038 "If positive, vectors longer than this will not be printed");
Ravago Jones5cc9df52020-09-02 21:29:58 -070039DEFINE_bool(pretty, false,
40 "If true, pretty print the messages on multiple lines");
Austin Schuh893d7f42022-09-16 15:01:35 -070041DEFINE_bool(
42 pretty_max, false,
43 "If true, expand every field to its own line (expands more than -pretty)");
44DEFINE_bool(print_timestamps, true, "If true, timestamps are printed.");
Austin Schuh569c7f92020-12-11 20:01:42 -080045DEFINE_bool(print, true,
46 "If true, actually print the messages. If false, discard them, "
47 "confirming they can be parsed.");
Tyler Chatowee0afa82021-08-01 22:00:36 -070048DEFINE_uint64(
49 count, 0,
50 "If >0, log_cat will exit after printing this many messages. This "
51 "includes messages from before the start of the log if --fetch is set.");
Austin Schuh7af06d52021-06-28 15:46:59 -070052DEFINE_bool(print_parts_only, false,
53 "If true, only print out the results of logfile sorting.");
Austin Schuh25b17652021-07-21 15:42:56 -070054DEFINE_bool(channels, false,
55 "If true, print out all the configured channels for this log.");
Milind Upadhyay184dfda2022-03-26 15:54:38 -070056DEFINE_double(monotonic_start_time, 0.0,
57 "If set, only print messages sent at or after this many seconds "
58 "after epoch.");
59DEFINE_double(monotonic_end_time, 0.0,
60 "If set, only print messages sent at or before this many seconds "
61 "after epoch.");
Austin Schuhbe6d2962022-11-01 09:24:56 -070062DEFINE_bool(hex, false,
Naman Gupta54047212022-09-23 14:10:30 -070063 "Are integers in the messages printed in hex notation.");
Austin Schuh6f3babe2020-01-26 20:34:50 -080064
Austin Schuh041fe9f2021-10-16 23:01:15 -070065using aos::monotonic_clock;
66namespace chrono = std::chrono;
67
Austin Schuh58646e22021-08-23 23:51:46 -070068// Prints out raw log parts to stdout.
69int PrintRaw(int argc, char **argv) {
Austin Schuhdb605092022-09-26 15:16:53 -070070 if (argc == 1) {
71 CHECK(!FLAGS_raw_header.empty());
72 aos::logger::MessageReader raw_header_reader(FLAGS_raw_header);
73 std::cout << aos::FlatbufferToJson(raw_header_reader.raw_log_file_header(),
74 {.multi_line = FLAGS_pretty,
75 .max_vector_size = static_cast<size_t>(
76 FLAGS_max_vector_size)})
77 << std::endl;
78 return 0;
79 }
80 if (argc != 2 && argc != 1) {
Austin Schuh58646e22021-08-23 23:51:46 -070081 LOG(FATAL) << "Expected 1 logfile as an argument.";
82 }
83 aos::logger::SpanReader reader(argv[1]);
84 absl::Span<const uint8_t> raw_log_file_header_span = reader.ReadMessage();
85
86 if (raw_log_file_header_span == absl::Span<const uint8_t>()) {
87 LOG(WARNING) << "Empty log file on " << reader.filename();
88 return 0;
89 }
90
91 // Now, reproduce the log file header deduplication logic inline so we can
92 // print out all the headers we find.
93 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader> log_file_header(
94 raw_log_file_header_span);
95 if (!log_file_header.Verify()) {
96 LOG(ERROR) << "Header corrupted on " << reader.filename();
97 return 1;
98 }
99 while (true) {
100 absl::Span<const uint8_t> maybe_header_data = reader.PeekMessage();
101 if (maybe_header_data == absl::Span<const uint8_t>()) {
102 break;
103 }
104
105 aos::SizePrefixedFlatbufferSpan<aos::logger::LogFileHeader> maybe_header(
106 maybe_header_data);
107 if (maybe_header.Verify()) {
108 std::cout << aos::FlatbufferToJson(
109 log_file_header, {.multi_line = FLAGS_pretty,
110 .max_vector_size = static_cast<size_t>(
111 FLAGS_max_vector_size)})
112 << std::endl;
113 LOG(WARNING) << "Found duplicate LogFileHeader in " << reader.filename();
114 log_file_header =
115 aos::SizePrefixedFlatbufferVector<aos::logger::LogFileHeader>(
116 maybe_header_data);
117
118 reader.ConsumeMessage();
119 } else {
120 break;
121 }
122 }
123
124 // And now use the final sha256 to match the raw_header.
125 std::optional<aos::logger::MessageReader> raw_header_reader;
126 const aos::logger::LogFileHeader *full_header = &log_file_header.message();
127 if (!FLAGS_raw_header.empty()) {
128 raw_header_reader.emplace(FLAGS_raw_header);
129 std::cout << aos::FlatbufferToJson(full_header,
130 {.multi_line = FLAGS_pretty,
131 .max_vector_size = static_cast<size_t>(
132 FLAGS_max_vector_size)})
133 << std::endl;
134 CHECK_EQ(
135 full_header->configuration_sha256()->string_view(),
136 aos::logger::Sha256(raw_header_reader->raw_log_file_header().span()));
137 full_header = raw_header_reader->log_file_header();
138 }
139
140 if (!FLAGS_print) {
141 return 0;
142 }
143
144 std::cout << aos::FlatbufferToJson(full_header,
145 {.multi_line = FLAGS_pretty,
146 .max_vector_size = static_cast<size_t>(
147 FLAGS_max_vector_size)})
148 << std::endl;
149 CHECK(full_header->has_configuration())
150 << ": Missing configuration! You may want to provide the path to the "
151 "logged configuration file using the --raw_header flag.";
152
153 while (true) {
154 const aos::SizePrefixedFlatbufferSpan<aos::logger::MessageHeader> message(
155 reader.ReadMessage());
156 if (message.span() == absl::Span<const uint8_t>()) {
157 break;
158 }
159 CHECK(message.Verify());
160
161 const auto *const channels = full_header->configuration()->channels();
162 const size_t channel_index = message.message().channel_index();
163 CHECK_LT(channel_index, channels->size());
164 const aos::Channel *const channel = channels->Get(channel_index);
165
166 CHECK(message.Verify()) << absl::BytesToHexString(
167 std::string_view(reinterpret_cast<const char *>(message.span().data()),
168 message.span().size()));
169
170 if (message.message().data() != nullptr) {
171 CHECK(channel->has_schema());
172
173 CHECK(flatbuffers::Verify(
174 *channel->schema(), *channel->schema()->root_table(),
175 message.message().data()->data(), message.message().data()->size()))
176 << ": Corrupted flatbuffer on " << channel->name()->c_str() << " "
177 << channel->type()->c_str();
178 }
179
180 if (FLAGS_format_raw && message.message().data() != nullptr) {
181 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
182 << aos::FlatbufferToJson(message, {.multi_line = FLAGS_pretty,
183 .max_vector_size = 4})
184 << ": "
185 << aos::FlatbufferToJson(
186 channel->schema(), message.message().data()->data(),
187 {FLAGS_pretty,
188 static_cast<size_t>(FLAGS_max_vector_size)})
189 << std::endl;
190 } else {
191 std::cout << aos::configuration::StrippedChannelToString(channel) << " "
192 << aos::FlatbufferToJson(
193 message, {FLAGS_pretty,
194 static_cast<size_t>(FLAGS_max_vector_size)})
195 << std::endl;
196 }
197 }
198 return 0;
199}
200
201// This class prints out all data from a node on a boot.
202class NodePrinter {
203 public:
Naman Gupta54047212022-09-23 14:10:30 -0700204 NodePrinter(aos::EventLoop *event_loop,
205 aos::SimulatedEventLoopFactory *factory, aos::Printer *printer)
Austin Schuh58646e22021-08-23 23:51:46 -0700206 : factory_(factory),
Austin Schuhff3bc902022-05-11 16:10:57 -0700207 node_factory_(factory->GetNodeEventLoopFactory(event_loop->node())),
Austin Schuh58646e22021-08-23 23:51:46 -0700208 event_loop_(event_loop),
Austin Schuh58646e22021-08-23 23:51:46 -0700209 node_name_(
210 event_loop_->node() == nullptr
211 ? ""
Austin Schuh041fe9f2021-10-16 23:01:15 -0700212 : std::string(event_loop->node()->name()->string_view())),
Naman Gupta54047212022-09-23 14:10:30 -0700213 printer_(printer) {
Austin Schuh58646e22021-08-23 23:51:46 -0700214 event_loop_->SkipTimingReport();
215 event_loop_->SkipAosLog();
216
217 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
218 event_loop_->configuration()->channels();
219
Milind Upadhyay184dfda2022-03-26 15:54:38 -0700220 const monotonic_clock::time_point start_time =
221 (FLAGS_monotonic_start_time == 0.0
222 ? monotonic_clock::min_time
223 : monotonic_clock::time_point(
224 std::chrono::duration_cast<monotonic_clock::duration>(
225 std::chrono::duration<double>(
226 FLAGS_monotonic_start_time))));
227 const monotonic_clock::time_point end_time =
228 (FLAGS_monotonic_end_time == 0.0
229 ? monotonic_clock::max_time
230 : monotonic_clock::time_point(
231 std::chrono::duration_cast<monotonic_clock::duration>(
232 std::chrono::duration<double>(
233 FLAGS_monotonic_end_time))));
234
Austin Schuh58646e22021-08-23 23:51:46 -0700235 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
236 const aos::Channel *channel = channels->Get(i);
237 const flatbuffers::string_view name = channel->name()->string_view();
238 const flatbuffers::string_view type = channel->type()->string_view();
239 if (name.find(FLAGS_name) != std::string::npos &&
240 type.find(FLAGS_type) != std::string::npos) {
241 if (!aos::configuration::ChannelIsReadableOnNode(channel,
242 event_loop_->node())) {
243 continue;
244 }
245 VLOG(1) << "Listening on " << name << " " << type;
246
247 CHECK_NOTNULL(channel->schema());
Austin Schuh60e77942022-05-16 17:48:24 -0700248 event_loop_->MakeRawWatcher(channel, [this, channel, start_time,
249 end_time](
250 const aos::Context &context,
251 const void * /*message*/) {
252 if (!FLAGS_print) {
253 return;
254 }
Naman Gupta54047212022-09-23 14:10:30 -0700255 if (FLAGS_count > 0 && printer_->message_count() >= FLAGS_count) {
256 return;
257 }
Austin Schuh58646e22021-08-23 23:51:46 -0700258
Austin Schuh60e77942022-05-16 17:48:24 -0700259 if (!FLAGS_fetch && !started_) {
260 return;
261 }
Austin Schuh58646e22021-08-23 23:51:46 -0700262
Austin Schuh60e77942022-05-16 17:48:24 -0700263 if (context.monotonic_event_time < start_time ||
264 context.monotonic_event_time > end_time) {
265 return;
266 }
Milind Upadhyay184dfda2022-03-26 15:54:38 -0700267
Naman Gupta54047212022-09-23 14:10:30 -0700268 printer_->PrintMessage(node_name_, node_factory_, channel, context);
269 if (FLAGS_count > 0 && printer_->message_count() >= FLAGS_count) {
Austin Schuh60e77942022-05-16 17:48:24 -0700270 factory_->Exit();
271 }
272 });
Austin Schuh58646e22021-08-23 23:51:46 -0700273 }
274 }
275 }
276
277 void SetStarted(bool started, aos::monotonic_clock::time_point monotonic_now,
278 aos::realtime_clock::time_point realtime_now) {
279 started_ = started;
Austin Schuh041fe9f2021-10-16 23:01:15 -0700280 if (FLAGS_json) {
281 return;
282 }
Austin Schuh58646e22021-08-23 23:51:46 -0700283 if (started_) {
284 std::cout << std::endl;
285 std::cout << (event_loop_->node() != nullptr
286 ? (event_loop_->node()->name()->str() + " ")
287 : "")
288 << "Log starting at " << realtime_now << " (" << monotonic_now
289 << ")";
290 std::cout << std::endl << std::endl;
291 } else {
292 std::cout << std::endl;
293 std::cout << (event_loop_->node() != nullptr
294 ? (event_loop_->node()->name()->str() + " ")
295 : "")
296 << "Log shutting down at " << realtime_now << " ("
297 << monotonic_now << ")";
298 std::cout << std::endl << std::endl;
299 }
300 }
301
302 private:
303 struct MessageInfo {
304 std::string node_name;
305 std::unique_ptr<aos::RawFetcher> fetcher;
306 };
307
308 aos::SimulatedEventLoopFactory *factory_;
Austin Schuhff3bc902022-05-11 16:10:57 -0700309 aos::NodeEventLoopFactory *node_factory_;
Austin Schuh58646e22021-08-23 23:51:46 -0700310 aos::EventLoop *event_loop_;
311
Austin Schuh58646e22021-08-23 23:51:46 -0700312 std::string node_name_;
313
314 bool started_ = false;
315
Naman Gupta54047212022-09-23 14:10:30 -0700316 aos::Printer *printer_ = nullptr;
Austin Schuh58646e22021-08-23 23:51:46 -0700317};
318
James Kuszmaul38735e82019-12-07 16:42:06 -0800319int main(int argc, char **argv) {
320 gflags::SetUsageMessage(
Austin Schuh6f3babe2020-01-26 20:34:50 -0800321 "Usage:\n"
322 " log_cat [args] logfile1 logfile2 ...\n"
323 "\n"
James Kuszmaul38735e82019-12-07 16:42:06 -0800324 "This program provides a basic interface to dump data from a logfile to "
325 "stdout. Given a logfile, channel name filter, and type filter, it will "
326 "print all the messages in the logfile matching the filters. The message "
327 "filters work by taking the values of --name and --type and printing any "
328 "channel whose name contains --name as a substr and whose type contains "
329 "--type as a substr. Not specifying --name or --type leaves them free. "
330 "Calling this program without --name or --type specified prints out all "
331 "the logged data.");
332 aos::InitGoogle(&argc, &argv);
333
Austin Schuh6f3babe2020-01-26 20:34:50 -0800334 if (FLAGS_raw) {
Austin Schuh58646e22021-08-23 23:51:46 -0700335 return PrintRaw(argc, argv);
James Kuszmaul38735e82019-12-07 16:42:06 -0800336 }
337
Austin Schuh6f3babe2020-01-26 20:34:50 -0800338 if (argc < 2) {
339 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
340 }
341
Austin Schuh11d43732020-09-21 17:28:30 -0700342 const std::vector<aos::logger::LogFile> logfiles =
Austin Schuh58646e22021-08-23 23:51:46 -0700343 aos::logger::SortParts(aos::logger::FindLogs(argc, argv));
Austin Schuh5212cad2020-09-09 23:12:09 -0700344
Austin Schuhfe3fb342021-01-16 18:50:37 -0800345 for (auto &it : logfiles) {
346 VLOG(1) << it;
Austin Schuh7af06d52021-06-28 15:46:59 -0700347 if (FLAGS_print_parts_only) {
348 std::cout << it << std::endl;
349 }
350 }
351 if (FLAGS_print_parts_only) {
352 return 0;
Austin Schuhfe3fb342021-01-16 18:50:37 -0800353 }
354
Austin Schuh6f3babe2020-01-26 20:34:50 -0800355 aos::logger::LogReader reader(logfiles);
Austin Schuha81454b2020-05-12 19:58:36 -0700356
Austin Schuh25b17652021-07-21 15:42:56 -0700357 if (FLAGS_channels) {
358 const aos::Configuration *config = reader.configuration();
359 for (const aos::Channel *channel : *config->channels()) {
360 std::cout << channel->name()->c_str() << " " << channel->type()->c_str()
361 << '\n';
362 }
363 return 0;
364 }
365
Austin Schuh58646e22021-08-23 23:51:46 -0700366 {
367 bool found_channel = false;
Austin Schuh6f3babe2020-01-26 20:34:50 -0800368 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
Austin Schuh58646e22021-08-23 23:51:46 -0700369 reader.configuration()->channels();
Brian Silverman9891b292020-06-23 16:34:22 -0700370
Austin Schuh6f3babe2020-01-26 20:34:50 -0800371 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
372 const aos::Channel *channel = channels->Get(i);
373 const flatbuffers::string_view name = channel->name()->string_view();
374 const flatbuffers::string_view type = channel->type()->string_view();
375 if (name.find(FLAGS_name) != std::string::npos &&
376 type.find(FLAGS_type) != std::string::npos) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800377 found_channel = true;
378 }
379 }
Austin Schuh58646e22021-08-23 23:51:46 -0700380 if (!found_channel) {
381 LOG(FATAL) << "Could not find any channels";
Austin Schuha81454b2020-05-12 19:58:36 -0700382 }
James Kuszmaul38735e82019-12-07 16:42:06 -0800383 }
384
Naman Gupta54047212022-09-23 14:10:30 -0700385 aos::Printer printer(
386 {
387 .pretty = FLAGS_pretty,
388 .max_vector_size = static_cast<size_t>(FLAGS_max_vector_size),
389 .pretty_max = FLAGS_pretty_max,
390 .print_timestamps = FLAGS_print_timestamps,
391 .json = FLAGS_json,
392 .distributed_clock = FLAGS_distributed_clock,
Austin Schuhbe6d2962022-11-01 09:24:56 -0700393 .hex = FLAGS_hex,
Naman Gupta54047212022-09-23 14:10:30 -0700394 },
395 false);
Austin Schuh58646e22021-08-23 23:51:46 -0700396
397 std::vector<NodePrinter *> printers;
Sanjay Narayananbeb328c2021-09-01 16:24:20 -0700398 printers.resize(aos::configuration::NodesCount(reader.configuration()),
399 nullptr);
400
401 aos::SimulatedEventLoopFactory event_loop_factory(reader.configuration());
402
403 reader.RegisterWithoutStarting(&event_loop_factory);
Austin Schuh58646e22021-08-23 23:51:46 -0700404
405 for (const aos::Node *node :
406 aos::configuration::GetNodes(event_loop_factory.configuration())) {
407 size_t node_index = aos::configuration::GetNodeIndex(
408 event_loop_factory.configuration(), node);
409 // Spin up the printer, and hook up the SetStarted method so that it gets
410 // notified when the log starts and stops.
411 aos::NodeEventLoopFactory *node_factory =
412 event_loop_factory.GetNodeEventLoopFactory(node);
Naman Gupta54047212022-09-23 14:10:30 -0700413 node_factory->OnStartup(
414 [&event_loop_factory, node_factory, &printer, &printers, node_index]() {
415 printers[node_index] = node_factory->AlwaysStart<NodePrinter>(
416 "printer", &event_loop_factory, &printer);
417 });
Austin Schuh58646e22021-08-23 23:51:46 -0700418 node_factory->OnShutdown(
419 [&printers, node_index]() { printers[node_index] = nullptr; });
420
421 reader.OnStart(node, [&printers, node_index, node_factory]() {
422 CHECK(printers[node_index]);
423 printers[node_index]->SetStarted(true, node_factory->monotonic_now(),
424 node_factory->realtime_now());
425 });
426 reader.OnEnd(node, [&printers, node_index, node_factory]() {
427 CHECK(printers[node_index]);
428 printers[node_index]->SetStarted(false, node_factory->monotonic_now(),
429 node_factory->realtime_now());
430 });
Austin Schuha81454b2020-05-12 19:58:36 -0700431 }
432
433 event_loop_factory.Run();
James Kuszmaul38735e82019-12-07 16:42:06 -0800434
Austin Schuh51a92592020-08-09 13:17:00 -0700435 reader.Deregister();
436
James Kuszmaul38735e82019-12-07 16:42:06 -0800437 return 0;
438}