blob: eef07538523356715e5a8fe99666699ea28fe626 [file] [log] [blame]
Stephan Massaltf84cf812019-12-31 14:14:50 -08001#include <iomanip>
2#include <iostream>
James Kuszmaulc7bb1652022-06-22 11:18:49 -07003#include <queue>
Stephan Massaltf84cf812019-12-31 14:14:50 -08004
Austin Schuhc99e1392021-03-30 22:59:24 -07005#include "absl/strings/str_format.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07006#include "gflags/gflags.h"
7
Austin Schuhb06f03b2021-02-17 22:00:37 -08008#include "aos/events/logging/log_reader.h"
Stephan Massaltf84cf812019-12-31 14:14:50 -08009#include "aos/events/simulated_event_loop.h"
10#include "aos/init.h"
11#include "aos/json_to_flatbuffer.h"
12#include "aos/time/time.h"
Stephan Massaltf84cf812019-12-31 14:14:50 -080013
Stephan Massaltf84cf812019-12-31 14:14:50 -080014DEFINE_string(
15 name, "",
16 "Name to match for printing out channels. Empty means no name filter.");
17
Austin Schuh6f3babe2020-01-26 20:34:50 -080018DEFINE_string(node, "", "Node to print stats out for.");
19
milind upadhyay38fe3cd2021-03-27 15:30:53 -070020DEFINE_bool(excessive_size_only, false,
21 "Only print channels that have a set max message size that is more "
22 "than double of the max message size.");
23
Philipp Schradera1214ef2023-08-10 11:50:22 -070024DEFINE_double(
25 run_for, 0.0,
26 "If set to a positive value, only process the log for this many seconds. "
27 "Otherwise, process the log until the end of the log.");
28
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -070029// This class implements a histogram for tracking message period
30// percentiles.
Austin Schuhc99e1392021-03-30 22:59:24 -070031class Histogram {
32 public:
33 Histogram(size_t buckets = 1024)
34 : max_value_bucket_(0.01), values_(buckets, 0.0), counts_(buckets, 0) {}
35
36 // Adds a new sample to the histogram, potentially downsampling the existing
37 // data.
38 void Add(double value) {
39 if (value < max_value_bucket_) {
40 const ssize_t bucket = static_cast<size_t>(
41 std::floor(value * values_.size() / max_value_bucket_));
42 CHECK_GE(bucket, 0);
43 CHECK_LT(bucket, static_cast<ssize_t>(values_.size()));
44 values_[bucket] += value;
45 if (all_counts_ == 0 || value > max_value_) {
46 max_value_ = value;
47 }
48 if (all_counts_ == 0 || value < min_value_) {
49 min_value_ = value;
50 }
51 ++counts_[bucket];
52 ++all_counts_;
53 } else {
54 // Double all the bucket sizes by merging adjacent buckets and doubling
55 // the max value. If this isn't enough, we'll recurse inside Add and
56 // do it again until it fits.
57 max_value_bucket_ *= 2.0;
58 for (size_t bucket = 0; bucket < values_.size() / 2; ++bucket) {
59 values_[bucket] = values_[bucket * 2] + values_[bucket * 2 + 1];
60 counts_[bucket] = counts_[bucket * 2] + counts_[bucket * 2 + 1];
61 }
62 for (size_t bucket = values_.size() / 2; bucket < values_.size();
63 ++bucket) {
64 values_[bucket] = 0.0;
65 counts_[bucket] = 0;
66 }
67 Add(value);
68 }
69 }
70
71 // Prints out the percentiles for a couple of critical numbers.
72 std::string Percentile() const {
73 const size_t percentile5 = all_counts_ / 20;
74 double percentile5_value = 0.0;
75 const size_t percentile50 = all_counts_ / 2;
76 double percentile50_value = 0.0;
77 const size_t percentile95 = all_counts_ - percentile5;
78 double percentile95_value = 0.0;
79
80 size_t count = 0;
81 for (size_t i = 0; i < values_.size(); ++i) {
82 if (count < percentile5 && count + counts_[i] >= percentile5) {
83 percentile5_value = values_[i] / counts_[i];
84 }
85 if (count < percentile50 && count + counts_[i] >= percentile50) {
86 percentile50_value = values_[i] / counts_[i];
87 }
88 if (count < percentile95 && count + counts_[i] >= percentile95) {
89 percentile95_value = values_[i] / counts_[i];
90 }
91 count += counts_[i];
92 }
93
94 // Assume here that these are periods in seconds. Convert to ms for
95 // readability. This isn't super generic, but that's fine for now.
96 return absl::StrFormat(
97 "[max %.3fms 95%%:%.3fms 50%%:%.3fms 5%%:%.3fms min %.3fms]",
98 max_value_ * 1000., percentile95_value * 1000.,
99 percentile50_value * 1000., percentile5_value * 1000.,
100 min_value_ * 1000.);
101 }
102
103 private:
104 // The size of the largest bucket. Used to figure out which bucket something
105 // goes into.
106 double max_value_bucket_;
107 // Max and min values overall we have seen.
108 double max_value_ = 0;
109 double min_value_ = 0;
110 // A list of the sum of values and counts for those per bucket.
111 std::vector<double> values_;
112 std::vector<size_t> counts_;
113 // Total number of samples.
114 size_t all_counts_ = 0;
115};
116
117class ChannelStats {
118 public:
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700119 ChannelStats(const aos::Channel *channel, const aos::Node *destination_node,
120 aos::SimulatedEventLoopFactory *factory)
121 : channel_(channel),
122 config_(factory->configuration()),
123 factory_(factory),
124 destination_node_(destination_node) {
125 // Multi-node channel
126 if (channel_->has_source_node() && channel_->has_destination_nodes() &&
127 channel_->destination_nodes()->size() > 0) {
128 CHECK(destination_node_)
129 << "Should have destination node for forwarded channel: "
130 << channel_->name()->string_view();
131 source_node_ = aos::configuration::GetNode(
132 config_, channel_->source_node()->string_view());
133 CHECK(source_node_) << "Node not in config: "
134 << channel_->source_node()->string_view();
135 }
136 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700137
138 // Adds a sample to the statistics.
139 void Add(const aos::Context &context) {
140 max_message_size_ = std::max(max_message_size_, context.size);
141 total_message_size_ += context.size;
142 total_num_messages_++;
143 channel_end_time_ = context.realtime_event_time;
144 first_message_time_ =
145 std::min(first_message_time_, context.monotonic_event_time);
146 if (current_message_time_ != aos::monotonic_clock::min_time) {
147 histogram_.Add(std::chrono::duration<double>(
148 context.monotonic_event_time - current_message_time_)
149 .count());
150 }
151 current_message_time_ = context.monotonic_event_time;
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700152 channel_storage_duration_messages_.push(current_message_time_);
153 while (channel_storage_duration_messages_.front() +
Austin Schuhfff9c3a2023-06-16 18:48:23 -0700154 aos::configuration::ChannelStorageDuration(config_, channel_) <=
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700155 current_message_time_) {
156 channel_storage_duration_messages_.pop();
157 }
158 max_messages_per_period_ = std::max(
159 max_messages_per_period_, channel_storage_duration_messages_.size());
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700160
161 // Only count latency if this message is forwarded and the remote time was
162 // filled
163 if (source_node_ != nullptr &&
164 context.monotonic_remote_time != context.monotonic_event_time) {
165 // Convert times to distributed clock so they can be compared across nodes
166 const aos::distributed_clock::time_point remote_time =
167 factory_->GetNodeEventLoopFactory(source_node_)
168 ->ToDistributedClock(context.monotonic_remote_time);
169
170 const aos::distributed_clock::time_point event_time =
171 factory_->GetNodeEventLoopFactory(destination_node_)
172 ->ToDistributedClock(context.monotonic_event_time);
173 // Add the current latency to the sum
174 total_latency_ += event_time - remote_time;
175
176 num_messages_with_remote_++;
177 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700178 }
179
180 std::string Percentile() const { return histogram_.Percentile(); }
181
182 double SecondsActive() const {
183 return aos::time::DurationInSeconds(current_message_time_ -
184 first_message_time_);
185 }
186
187 size_t max_message_size() const { return max_message_size_; }
188 size_t total_num_messages() const { return total_num_messages_; }
189
190 double avg_messages_per_sec() const {
191 return total_num_messages_ / SecondsActive();
192 }
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700193 double max_messages_per_sec() const {
194 return max_messages_per_period_ /
195 std::min(SecondsActive(),
Austin Schuhfff9c3a2023-06-16 18:48:23 -0700196 std::chrono::duration<double>(
197 aos::configuration::ChannelStorageDuration(config_,
198 channel_))
199 .count());
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700200 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700201 size_t avg_message_size() const {
202 return total_message_size_ / total_num_messages_;
203 }
James Kuszmaul6abc7442021-10-24 13:46:04 -0700204 size_t avg_message_bandwidth() const {
205 return total_message_size_ / SecondsActive();
206 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700207
208 aos::realtime_clock::time_point channel_end_time() const {
209 return channel_end_time_;
210 }
211
212 const aos::Channel *channel() const { return channel_; }
213
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700214 std::string AvgLatency() {
215 if (num_messages_with_remote_ == 0) {
216 return "";
217 }
218
219 std::stringstream ss;
220 ss << std::setprecision(3);
221
222 const double avg_latency =
223 std::chrono::duration<double, std::milli>(total_latency_).count() /
224 num_messages_with_remote_;
225 ss << '[' << source_node_->name()->string_view() << "->"
226 << destination_node_->name()->string_view() << " " << avg_latency
227 << "ms latency avg]";
228
229 return ss.str();
230 }
231
Austin Schuhc99e1392021-03-30 22:59:24 -0700232 private:
Stephan Massaltf84cf812019-12-31 14:14:50 -0800233 // pointer to the channel for which stats are collected
Austin Schuhc99e1392021-03-30 22:59:24 -0700234 const aos::Channel *channel_;
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700235 const aos::Configuration *config_;
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700236 aos::SimulatedEventLoopFactory *factory_;
Austin Schuhc99e1392021-03-30 22:59:24 -0700237 aos::realtime_clock::time_point channel_end_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800238 aos::realtime_clock::min_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700239 aos::monotonic_clock::time_point first_message_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800240 // needs to be higher than time in the logfile!
241 aos::monotonic_clock::max_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700242 aos::monotonic_clock::time_point current_message_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800243 aos::monotonic_clock::min_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700244
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700245 // Buffer of the last N seconds of messages, for N = channel_storage_duration.
246 std::queue<aos::monotonic_clock::time_point>
247 channel_storage_duration_messages_;
248 size_t max_messages_per_period_ = 0;
249
Stephan Massaltf84cf812019-12-31 14:14:50 -0800250 // channel stats to collect per channel
Austin Schuhc99e1392021-03-30 22:59:24 -0700251 int total_num_messages_ = 0;
252 size_t max_message_size_ = 0;
253 size_t total_message_size_ = 0;
254
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700255 // Count of messages which had remote timestamps
256 size_t num_messages_with_remote_ = 0;
257 // Sum of latencies in all messages sent on this channel if multinode
258 aos::distributed_clock::duration total_latency_;
259
Austin Schuhc99e1392021-03-30 22:59:24 -0700260 Histogram histogram_;
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700261
262 const aos::Node *source_node_ = nullptr;
263 const aos::Node *destination_node_;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800264};
265
266struct LogfileStats {
267 // All relevant stats on to logfile level
268 size_t logfile_length = 0;
269 int total_log_messages = 0;
270 aos::realtime_clock::time_point logfile_end_time =
271 aos::realtime_clock::min_time;
272};
273
274int main(int argc, char **argv) {
275 gflags::SetUsageMessage(
Ravago Jones8bab1842020-12-12 17:36:39 -0800276 "Usage: \n"
277 " log_stats [args] logfile1 logfile2 ...\n"
Stephan Massaltf84cf812019-12-31 14:14:50 -0800278 "This program provides statistics on a given log file. Supported "
279 "statistics are:\n"
280 " - Logfile start time;\n"
281 " - Total messages per channel/type;\n"
282 " - Max message size per channel/type;\n"
283 " - Frequency of messages per second;\n"
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700284 " - Total logfile size and number of messages;\n"
285 " - Average latency per forwarded channel/type.\n"
286 "Pass a logfile (path/filename) and use --name "
Stephan Massaltf84cf812019-12-31 14:14:50 -0800287 "flag to specify a channel to listen on.");
288
289 aos::InitGoogle(&argc, &argv);
290
Ravago Jones8bab1842020-12-12 17:36:39 -0800291 if (argc < 2) {
292 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
293 }
294
Austin Schuh95460cc2023-06-26 11:53:10 -0700295 aos::logger::LogReader reader(
296 aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
Ravago Jones8bab1842020-12-12 17:36:39 -0800297
Stephan Massaltf84cf812019-12-31 14:14:50 -0800298 LogfileStats logfile_stats;
299 std::vector<ChannelStats> channel_stats;
300
Stephan Massaltf84cf812019-12-31 14:14:50 -0800301 aos::SimulatedEventLoopFactory log_reader_factory(reader.configuration());
302 reader.Register(&log_reader_factory);
303
Austin Schuh6f3babe2020-01-26 20:34:50 -0800304 const aos::Node *node = nullptr;
305
306 if (aos::configuration::MultiNode(reader.configuration())) {
307 if (FLAGS_node.empty()) {
308 LOG(INFO) << "Need a --node specified. The log file has:";
Austin Schuh07676622021-01-21 18:59:17 -0800309 for (const aos::Node *node : reader.LoggedNodes()) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800310 LOG(INFO) << " " << node->name()->string_view();
311 }
Austin Schuh8c7f14b2021-01-21 19:01:54 -0800312 reader.Deregister();
Austin Schuh6f3babe2020-01-26 20:34:50 -0800313 return 1;
314 } else {
315 node = aos::configuration::GetNode(reader.configuration(), FLAGS_node);
316 }
317 }
318
Stephan Massaltf84cf812019-12-31 14:14:50 -0800319 // Make an eventloop for retrieving stats
320 std::unique_ptr<aos::EventLoop> stats_event_loop =
Austin Schuh6f3babe2020-01-26 20:34:50 -0800321 log_reader_factory.MakeEventLoop("logstats", node);
Stephan Massaltf84cf812019-12-31 14:14:50 -0800322 stats_event_loop->SkipTimingReport();
Tyler Chatow67ddb032020-01-12 14:30:04 -0800323 stats_event_loop->SkipAosLog();
Stephan Massaltf84cf812019-12-31 14:14:50 -0800324
325 // Read channel info and store in vector
326 bool found_channel = false;
327 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
328 reader.configuration()->channels();
329
330 int it = 0; // iterate through the channel_stats
331 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
332 const aos::Channel *channel = channels->Get(i);
Austin Schuh76db3fa2020-03-07 17:02:44 -0800333 if (!aos::configuration::ChannelIsReadableOnNode(
334 channel, stats_event_loop->node())) {
335 continue;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800336 }
Austin Schuh76db3fa2020-03-07 17:02:44 -0800337
338 if (channel->name()->string_view().find(FLAGS_name) == std::string::npos) {
339 continue;
340 }
341
342 // Add a record to the stats vector.
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700343 channel_stats.push_back({channel, node, &log_reader_factory});
Austin Schuh76db3fa2020-03-07 17:02:44 -0800344 // Lambda to read messages and parse for information
Brian Silvermanad1bce02020-03-05 14:23:01 -0800345 stats_event_loop->MakeRawNoArgWatcher(
346 channel,
347 [&logfile_stats, &channel_stats, it](const aos::Context &context) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700348 channel_stats[it].Add(context);
349
350 // Update the overall logfile statistics
Brian Silvermanad1bce02020-03-05 14:23:01 -0800351 logfile_stats.logfile_length += context.size;
352 });
Austin Schuh76db3fa2020-03-07 17:02:44 -0800353 it++;
354 // TODO (Stephan): Frequency of messages per second
355 // - Sliding window
356 // - Max / Deviation
357 found_channel = true;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800358 }
359 if (!found_channel) {
360 LOG(FATAL) << "Could not find any channels";
361 }
362
Philipp Schradera1214ef2023-08-10 11:50:22 -0700363 if (FLAGS_run_for > 0.0) {
364 log_reader_factory.RunFor(
365 std::chrono::duration_cast<std::chrono::nanoseconds>(
366 std::chrono::duration<double>(FLAGS_run_for)));
367 } else {
368 log_reader_factory.Run();
369 }
Stephan Massaltf84cf812019-12-31 14:14:50 -0800370
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700371 std::cout << std::endl;
Austin Schuhc99e1392021-03-30 22:59:24 -0700372
Stephan Massaltf84cf812019-12-31 14:14:50 -0800373 // Print out the stats per channel and for the logfile
374 for (size_t i = 0; i != channel_stats.size(); i++) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700375 if (!FLAGS_excessive_size_only ||
376 (channel_stats[i].max_message_size() * 2) <
377 static_cast<size_t>(channel_stats[i].channel()->max_size())) {
378 if (channel_stats[i].total_num_messages() > 0) {
379 std::cout << channel_stats[i].channel()->name()->string_view() << " "
380 << channel_stats[i].channel()->type()->string_view() << "\n";
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700381
Austin Schuhc99e1392021-03-30 22:59:24 -0700382 logfile_stats.total_log_messages +=
383 channel_stats[i].total_num_messages();
384 logfile_stats.logfile_end_time =
385 std::max(logfile_stats.logfile_end_time,
386 channel_stats[i].channel_end_time());
387
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700388 if (!FLAGS_excessive_size_only) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700389 std::cout << " " << channel_stats[i].total_num_messages()
390 << " msgs, " << channel_stats[i].avg_messages_per_sec()
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700391 << "hz avg, " << channel_stats[i].max_messages_per_sec()
392 << "hz max, " << channel_stats[i].channel()->frequency()
393 << "hz configured max";
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700394 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700395 std::cout << " " << channel_stats[i].avg_message_size()
Austin Schuh60e77942022-05-16 17:48:24 -0700396 << " bytes avg, " << channel_stats[i].avg_message_bandwidth()
James Kuszmaul6abc7442021-10-24 13:46:04 -0700397 << " bytes/sec avg, " << channel_stats[i].max_message_size()
Austin Schuhc99e1392021-03-30 22:59:24 -0700398 << " bytes max / " << channel_stats[i].channel()->max_size()
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700399 << "bytes, " << channel_stats[i].Percentile() << ", "
400 << channel_stats[i].AvgLatency();
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700401 std::cout << std::endl;
402 }
Stephan Massaltf84cf812019-12-31 14:14:50 -0800403 }
404 }
405 std::cout << std::setfill('-') << std::setw(80) << "-"
Ravago Jones8bab1842020-12-12 17:36:39 -0800406 << "\nLogfile statistics:\n"
Austin Schuh76db3fa2020-03-07 17:02:44 -0800407 << "Log starts at:\t" << reader.realtime_start_time(node) << "\n"
Stephan Massaltf84cf812019-12-31 14:14:50 -0800408 << "Log ends at:\t" << logfile_stats.logfile_end_time << "\n"
409 << "Log file size:\t" << logfile_stats.logfile_length << "\n"
410 << "Total messages:\t" << logfile_stats.total_log_messages << "\n";
411
412 // Cleanup the created processes
413 reader.Deregister();
Austin Schuhae87e312020-08-01 16:15:01 -0700414
Stephan Massaltf84cf812019-12-31 14:14:50 -0800415 return 0;
416}