blob: 232b84204b38b318606a4229eb2c50ff2bbea038 [file] [log] [blame]
Austin Schuhba20ea72021-01-21 16:47:01 -08001#include <iostream>
2#include <string>
3#include <vector>
4
5#include "aos/events/logging/logfile_sorting.h"
6#include "aos/events/logging/logfile_utils.h"
7#include "aos/init.h"
8#include "aos/network/multinode_timestamp_filter.h"
9#include "gflags/gflags.h"
10
11DECLARE_bool(timestamps_to_csv);
12DEFINE_bool(skip_order_validation, false,
13 "If true, ignore any out of orderness in replay");
14
15namespace aos::logger {
16
17namespace chrono = std::chrono;
18
19std::string LogFileVectorToString(std::vector<logger::LogFile> log_files) {
20 std::stringstream ss;
Austin Schuh05dad8b2021-01-27 22:47:23 -080021 for (const auto &f : log_files) {
Austin Schuhba20ea72021-01-21 16:47:01 -080022 ss << f << "\n";
23 }
24 return ss.str();
25}
26
27int Main(int argc, char **argv) {
28 const std::vector<std::string> unsorted_logfiles = FindLogs(argc, argv);
29 const std::vector<LogFile> log_files = SortParts(unsorted_logfiles);
30
31 CHECK_GT(log_files.size(), 0u);
32 // Validate that we have the same config everwhere. This will be true if
33 // all the parts were sorted together and the configs match.
34 const Configuration *config = nullptr;
35 for (const LogFile &log_file : log_files) {
36 if (config == nullptr) {
37 config = log_file.config.get();
38 } else {
39 CHECK_EQ(config, log_file.config.get());
40 }
41 }
42
43 CHECK(configuration::MultiNode(config))
44 << ": Timestamps only make sense in a multi-node world.";
45
46 // Now, build up all the TimestampMapper classes to read and sort the data.
47 std::vector<std::unique_ptr<TimestampMapper>> mappers;
48
49 for (const Node *node : configuration::GetNodes(config)) {
50 std::vector<LogParts> filtered_parts =
51 FilterPartsForNode(log_files, node->name()->string_view());
52
53 // Confirm that all the parts are from the same boot if there are enough
54 // parts to not be from the same boot.
55 if (!filtered_parts.empty()) {
56 for (size_t i = 1; i < filtered_parts.size(); ++i) {
57 CHECK_EQ(filtered_parts[i].source_boot_uuid,
58 filtered_parts[0].source_boot_uuid)
59 << ": Found parts from different boots "
60 << LogFileVectorToString(log_files);
61 }
62
63 // Filter the parts relevant to each node when building the mapper.
64 mappers.emplace_back(
65 std::make_unique<TimestampMapper>(std::move(filtered_parts)));
66 } else {
67 mappers.emplace_back(nullptr);
68 }
69 }
70
71 // Now, build up the estimator used to solve for time.
72 message_bridge::MultiNodeNoncausalOffsetEstimator multinode_estimator(
73 config, config, FLAGS_skip_order_validation, chrono::seconds(0));
74
Austin Schuhe639ea12021-01-25 13:00:22 -080075 {
76 std::vector<TimestampMapper *> timestamp_mappers;
77 for (std::unique_ptr<TimestampMapper> &mapper : mappers) {
78 timestamp_mappers.emplace_back(mapper.get());
79 }
80 multinode_estimator.SetTimestampMappers(std::move(timestamp_mappers));
81 }
82
Austin Schuhba20ea72021-01-21 16:47:01 -080083 // To make things more like the logger and faster, cache the node + channel ->
84 // filter mapping in a set of vectors.
85 std::vector<std::vector<message_bridge::NoncausalOffsetEstimator *>> filters;
86 filters.resize(configuration::NodesCount(config));
87
88 for (const Node *node : configuration::GetNodes(config)) {
89 const size_t node_index = configuration::GetNodeIndex(config, node);
90 filters[node_index].resize(config->channels()->size(), nullptr);
91 for (size_t channel_index = 0; channel_index < config->channels()->size();
92 ++channel_index) {
93 const Channel *channel = config->channels()->Get(channel_index);
94
95 if (!configuration::ChannelIsSendableOnNode(channel, node) &&
96 configuration::ChannelIsReadableOnNode(channel, node)) {
97 // We've got a message which is being forwarded to this node.
98 const Node *source_node = configuration::GetNode(
99 config, channel->source_node()->string_view());
100 filters[node_index][channel_index] =
101 multinode_estimator.GetFilter(node, source_node);
102 }
103 }
104 }
105
Austin Schuhe639ea12021-01-25 13:00:22 -0800106 multinode_estimator.CheckGraph();
107
Austin Schuhba20ea72021-01-21 16:47:01 -0800108 // Now, read all the timestamps for each node. This is simpler than the
109 // logger on purpose. It loads in *all* the timestamps in 1 go per node,
110 // ignoring memory usage.
111 for (const Node *node : configuration::GetNodes(config)) {
112 LOG(INFO) << "Reading all data for " << node->name()->string_view();
113 const size_t node_index = configuration::GetNodeIndex(config, node);
114 TimestampMapper *timestamp_mapper = mappers[node_index].get();
115 if (timestamp_mapper == nullptr) {
116 continue;
117 }
118 while (true) {
119 TimestampedMessage *m = timestamp_mapper->Front();
120 if (m == nullptr) {
121 break;
122 }
Austin Schuhba20ea72021-01-21 16:47:01 -0800123 timestamp_mapper->PopFront();
124 }
125 }
126
127 // Don't get clever. Use the first time as the start time. Note: this is
128 // different than how log_cat and others work.
129 std::optional<std::tuple<distributed_clock::time_point,
130 std::vector<monotonic_clock::time_point>>>
131 next_timestamp = multinode_estimator.NextTimestamp();
132 CHECK(next_timestamp);
133 LOG(INFO) << "Starting at:";
134 for (const Node *node : configuration::GetNodes(config)) {
135 const size_t node_index = configuration::GetNodeIndex(config, node);
136 LOG(INFO) << " " << node->name()->string_view() << " -> "
137 << std::get<1>(*next_timestamp)[node_index];
138 }
139
140 multinode_estimator.Start(std::get<1>(*next_timestamp));
141
142 // As we pull off all the timestamps, the time problem is continually solved,
143 // filling in the CSV files.
144 while (true) {
145 std::optional<std::tuple<distributed_clock::time_point,
146 std::vector<monotonic_clock::time_point>>>
147 next_timestamp = multinode_estimator.NextTimestamp();
148 if (!next_timestamp) {
149 break;
150 }
151 }
152
153 return 0;
154}
155
156} // namespace aos::logger
157
158int main(int argc, char **argv) {
159 FLAGS_timestamps_to_csv = true;
160 gflags::SetUsageMessage(
161 "Usage:\n"
162 " timestamp_extractor [args] logfile1 logfile2 ...\n\nThis program "
163 "dumps out all the timestamps from a set of log files for plotting. Use "
164 "--skip_order_validation to skip any time estimation problems we find.");
165 aos::InitGoogle(&argc, &argv);
166
167 return aos::logger::Main(argc, argv);
168}