blob: bbed18f1e2b4a3d8f1513fee376c8a915871cbb6 [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
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -070024// This class implements a histogram for tracking message period
25// percentiles.
Austin Schuhc99e1392021-03-30 22:59:24 -070026class Histogram {
27 public:
28 Histogram(size_t buckets = 1024)
29 : max_value_bucket_(0.01), values_(buckets, 0.0), counts_(buckets, 0) {}
30
31 // Adds a new sample to the histogram, potentially downsampling the existing
32 // data.
33 void Add(double value) {
34 if (value < max_value_bucket_) {
35 const ssize_t bucket = static_cast<size_t>(
36 std::floor(value * values_.size() / max_value_bucket_));
37 CHECK_GE(bucket, 0);
38 CHECK_LT(bucket, static_cast<ssize_t>(values_.size()));
39 values_[bucket] += value;
40 if (all_counts_ == 0 || value > max_value_) {
41 max_value_ = value;
42 }
43 if (all_counts_ == 0 || value < min_value_) {
44 min_value_ = value;
45 }
46 ++counts_[bucket];
47 ++all_counts_;
48 } else {
49 // Double all the bucket sizes by merging adjacent buckets and doubling
50 // the max value. If this isn't enough, we'll recurse inside Add and
51 // do it again until it fits.
52 max_value_bucket_ *= 2.0;
53 for (size_t bucket = 0; bucket < values_.size() / 2; ++bucket) {
54 values_[bucket] = values_[bucket * 2] + values_[bucket * 2 + 1];
55 counts_[bucket] = counts_[bucket * 2] + counts_[bucket * 2 + 1];
56 }
57 for (size_t bucket = values_.size() / 2; bucket < values_.size();
58 ++bucket) {
59 values_[bucket] = 0.0;
60 counts_[bucket] = 0;
61 }
62 Add(value);
63 }
64 }
65
66 // Prints out the percentiles for a couple of critical numbers.
67 std::string Percentile() const {
68 const size_t percentile5 = all_counts_ / 20;
69 double percentile5_value = 0.0;
70 const size_t percentile50 = all_counts_ / 2;
71 double percentile50_value = 0.0;
72 const size_t percentile95 = all_counts_ - percentile5;
73 double percentile95_value = 0.0;
74
75 size_t count = 0;
76 for (size_t i = 0; i < values_.size(); ++i) {
77 if (count < percentile5 && count + counts_[i] >= percentile5) {
78 percentile5_value = values_[i] / counts_[i];
79 }
80 if (count < percentile50 && count + counts_[i] >= percentile50) {
81 percentile50_value = values_[i] / counts_[i];
82 }
83 if (count < percentile95 && count + counts_[i] >= percentile95) {
84 percentile95_value = values_[i] / counts_[i];
85 }
86 count += counts_[i];
87 }
88
89 // Assume here that these are periods in seconds. Convert to ms for
90 // readability. This isn't super generic, but that's fine for now.
91 return absl::StrFormat(
92 "[max %.3fms 95%%:%.3fms 50%%:%.3fms 5%%:%.3fms min %.3fms]",
93 max_value_ * 1000., percentile95_value * 1000.,
94 percentile50_value * 1000., percentile5_value * 1000.,
95 min_value_ * 1000.);
96 }
97
98 private:
99 // The size of the largest bucket. Used to figure out which bucket something
100 // goes into.
101 double max_value_bucket_;
102 // Max and min values overall we have seen.
103 double max_value_ = 0;
104 double min_value_ = 0;
105 // A list of the sum of values and counts for those per bucket.
106 std::vector<double> values_;
107 std::vector<size_t> counts_;
108 // Total number of samples.
109 size_t all_counts_ = 0;
110};
111
112class ChannelStats {
113 public:
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700114 ChannelStats(const aos::Channel *channel, const aos::Node *destination_node,
115 aos::SimulatedEventLoopFactory *factory)
116 : channel_(channel),
117 config_(factory->configuration()),
118 factory_(factory),
119 destination_node_(destination_node) {
120 // Multi-node channel
121 if (channel_->has_source_node() && channel_->has_destination_nodes() &&
122 channel_->destination_nodes()->size() > 0) {
123 CHECK(destination_node_)
124 << "Should have destination node for forwarded channel: "
125 << channel_->name()->string_view();
126 source_node_ = aos::configuration::GetNode(
127 config_, channel_->source_node()->string_view());
128 CHECK(source_node_) << "Node not in config: "
129 << channel_->source_node()->string_view();
130 }
131 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700132
133 // Adds a sample to the statistics.
134 void Add(const aos::Context &context) {
135 max_message_size_ = std::max(max_message_size_, context.size);
136 total_message_size_ += context.size;
137 total_num_messages_++;
138 channel_end_time_ = context.realtime_event_time;
139 first_message_time_ =
140 std::min(first_message_time_, context.monotonic_event_time);
141 if (current_message_time_ != aos::monotonic_clock::min_time) {
142 histogram_.Add(std::chrono::duration<double>(
143 context.monotonic_event_time - current_message_time_)
144 .count());
145 }
146 current_message_time_ = context.monotonic_event_time;
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700147 channel_storage_duration_messages_.push(current_message_time_);
148 while (channel_storage_duration_messages_.front() +
Austin Schuhfff9c3a2023-06-16 18:48:23 -0700149 aos::configuration::ChannelStorageDuration(config_, channel_) <=
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700150 current_message_time_) {
151 channel_storage_duration_messages_.pop();
152 }
153 max_messages_per_period_ = std::max(
154 max_messages_per_period_, channel_storage_duration_messages_.size());
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700155
156 // Only count latency if this message is forwarded and the remote time was
157 // filled
158 if (source_node_ != nullptr &&
159 context.monotonic_remote_time != context.monotonic_event_time) {
160 // Convert times to distributed clock so they can be compared across nodes
161 const aos::distributed_clock::time_point remote_time =
162 factory_->GetNodeEventLoopFactory(source_node_)
163 ->ToDistributedClock(context.monotonic_remote_time);
164
165 const aos::distributed_clock::time_point event_time =
166 factory_->GetNodeEventLoopFactory(destination_node_)
167 ->ToDistributedClock(context.monotonic_event_time);
168 // Add the current latency to the sum
169 total_latency_ += event_time - remote_time;
170
171 num_messages_with_remote_++;
172 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700173 }
174
175 std::string Percentile() const { return histogram_.Percentile(); }
176
177 double SecondsActive() const {
178 return aos::time::DurationInSeconds(current_message_time_ -
179 first_message_time_);
180 }
181
182 size_t max_message_size() const { return max_message_size_; }
183 size_t total_num_messages() const { return total_num_messages_; }
184
185 double avg_messages_per_sec() const {
186 return total_num_messages_ / SecondsActive();
187 }
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700188 double max_messages_per_sec() const {
189 return max_messages_per_period_ /
190 std::min(SecondsActive(),
Austin Schuhfff9c3a2023-06-16 18:48:23 -0700191 std::chrono::duration<double>(
192 aos::configuration::ChannelStorageDuration(config_,
193 channel_))
194 .count());
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700195 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700196 size_t avg_message_size() const {
197 return total_message_size_ / total_num_messages_;
198 }
James Kuszmaul6abc7442021-10-24 13:46:04 -0700199 size_t avg_message_bandwidth() const {
200 return total_message_size_ / SecondsActive();
201 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700202
203 aos::realtime_clock::time_point channel_end_time() const {
204 return channel_end_time_;
205 }
206
207 const aos::Channel *channel() const { return channel_; }
208
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700209 std::string AvgLatency() {
210 if (num_messages_with_remote_ == 0) {
211 return "";
212 }
213
214 std::stringstream ss;
215 ss << std::setprecision(3);
216
217 const double avg_latency =
218 std::chrono::duration<double, std::milli>(total_latency_).count() /
219 num_messages_with_remote_;
220 ss << '[' << source_node_->name()->string_view() << "->"
221 << destination_node_->name()->string_view() << " " << avg_latency
222 << "ms latency avg]";
223
224 return ss.str();
225 }
226
Austin Schuhc99e1392021-03-30 22:59:24 -0700227 private:
Stephan Massaltf84cf812019-12-31 14:14:50 -0800228 // pointer to the channel for which stats are collected
Austin Schuhc99e1392021-03-30 22:59:24 -0700229 const aos::Channel *channel_;
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700230 const aos::Configuration *config_;
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700231 aos::SimulatedEventLoopFactory *factory_;
Austin Schuhc99e1392021-03-30 22:59:24 -0700232 aos::realtime_clock::time_point channel_end_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800233 aos::realtime_clock::min_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700234 aos::monotonic_clock::time_point first_message_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800235 // needs to be higher than time in the logfile!
236 aos::monotonic_clock::max_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700237 aos::monotonic_clock::time_point current_message_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800238 aos::monotonic_clock::min_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700239
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700240 // Buffer of the last N seconds of messages, for N = channel_storage_duration.
241 std::queue<aos::monotonic_clock::time_point>
242 channel_storage_duration_messages_;
243 size_t max_messages_per_period_ = 0;
244
Stephan Massaltf84cf812019-12-31 14:14:50 -0800245 // channel stats to collect per channel
Austin Schuhc99e1392021-03-30 22:59:24 -0700246 int total_num_messages_ = 0;
247 size_t max_message_size_ = 0;
248 size_t total_message_size_ = 0;
249
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700250 // Count of messages which had remote timestamps
251 size_t num_messages_with_remote_ = 0;
252 // Sum of latencies in all messages sent on this channel if multinode
253 aos::distributed_clock::duration total_latency_;
254
Austin Schuhc99e1392021-03-30 22:59:24 -0700255 Histogram histogram_;
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700256
257 const aos::Node *source_node_ = nullptr;
258 const aos::Node *destination_node_;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800259};
260
261struct LogfileStats {
262 // All relevant stats on to logfile level
263 size_t logfile_length = 0;
264 int total_log_messages = 0;
265 aos::realtime_clock::time_point logfile_end_time =
266 aos::realtime_clock::min_time;
267};
268
269int main(int argc, char **argv) {
270 gflags::SetUsageMessage(
Ravago Jones8bab1842020-12-12 17:36:39 -0800271 "Usage: \n"
272 " log_stats [args] logfile1 logfile2 ...\n"
Stephan Massaltf84cf812019-12-31 14:14:50 -0800273 "This program provides statistics on a given log file. Supported "
274 "statistics are:\n"
275 " - Logfile start time;\n"
276 " - Total messages per channel/type;\n"
277 " - Max message size per channel/type;\n"
278 " - Frequency of messages per second;\n"
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700279 " - Total logfile size and number of messages;\n"
280 " - Average latency per forwarded channel/type.\n"
281 "Pass a logfile (path/filename) and use --name "
Stephan Massaltf84cf812019-12-31 14:14:50 -0800282 "flag to specify a channel to listen on.");
283
284 aos::InitGoogle(&argc, &argv);
285
Ravago Jones8bab1842020-12-12 17:36:39 -0800286 if (argc < 2) {
287 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
288 }
289
Austin Schuh95460cc2023-06-26 11:53:10 -0700290 aos::logger::LogReader reader(
291 aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
Ravago Jones8bab1842020-12-12 17:36:39 -0800292
Stephan Massaltf84cf812019-12-31 14:14:50 -0800293 LogfileStats logfile_stats;
294 std::vector<ChannelStats> channel_stats;
295
Stephan Massaltf84cf812019-12-31 14:14:50 -0800296 aos::SimulatedEventLoopFactory log_reader_factory(reader.configuration());
297 reader.Register(&log_reader_factory);
298
Austin Schuh6f3babe2020-01-26 20:34:50 -0800299 const aos::Node *node = nullptr;
300
301 if (aos::configuration::MultiNode(reader.configuration())) {
302 if (FLAGS_node.empty()) {
303 LOG(INFO) << "Need a --node specified. The log file has:";
Austin Schuh07676622021-01-21 18:59:17 -0800304 for (const aos::Node *node : reader.LoggedNodes()) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800305 LOG(INFO) << " " << node->name()->string_view();
306 }
Austin Schuh8c7f14b2021-01-21 19:01:54 -0800307 reader.Deregister();
Austin Schuh6f3babe2020-01-26 20:34:50 -0800308 return 1;
309 } else {
310 node = aos::configuration::GetNode(reader.configuration(), FLAGS_node);
311 }
312 }
313
Stephan Massaltf84cf812019-12-31 14:14:50 -0800314 // Make an eventloop for retrieving stats
315 std::unique_ptr<aos::EventLoop> stats_event_loop =
Austin Schuh6f3babe2020-01-26 20:34:50 -0800316 log_reader_factory.MakeEventLoop("logstats", node);
Stephan Massaltf84cf812019-12-31 14:14:50 -0800317 stats_event_loop->SkipTimingReport();
Tyler Chatow67ddb032020-01-12 14:30:04 -0800318 stats_event_loop->SkipAosLog();
Stephan Massaltf84cf812019-12-31 14:14:50 -0800319
320 // Read channel info and store in vector
321 bool found_channel = false;
322 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
323 reader.configuration()->channels();
324
325 int it = 0; // iterate through the channel_stats
326 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
327 const aos::Channel *channel = channels->Get(i);
Austin Schuh76db3fa2020-03-07 17:02:44 -0800328 if (!aos::configuration::ChannelIsReadableOnNode(
329 channel, stats_event_loop->node())) {
330 continue;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800331 }
Austin Schuh76db3fa2020-03-07 17:02:44 -0800332
333 if (channel->name()->string_view().find(FLAGS_name) == std::string::npos) {
334 continue;
335 }
336
337 // Add a record to the stats vector.
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700338 channel_stats.push_back({channel, node, &log_reader_factory});
Austin Schuh76db3fa2020-03-07 17:02:44 -0800339 // Lambda to read messages and parse for information
Brian Silvermanad1bce02020-03-05 14:23:01 -0800340 stats_event_loop->MakeRawNoArgWatcher(
341 channel,
342 [&logfile_stats, &channel_stats, it](const aos::Context &context) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700343 channel_stats[it].Add(context);
344
345 // Update the overall logfile statistics
Brian Silvermanad1bce02020-03-05 14:23:01 -0800346 logfile_stats.logfile_length += context.size;
347 });
Austin Schuh76db3fa2020-03-07 17:02:44 -0800348 it++;
349 // TODO (Stephan): Frequency of messages per second
350 // - Sliding window
351 // - Max / Deviation
352 found_channel = true;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800353 }
354 if (!found_channel) {
355 LOG(FATAL) << "Could not find any channels";
356 }
357
358 log_reader_factory.Run();
359
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700360 std::cout << std::endl;
Austin Schuhc99e1392021-03-30 22:59:24 -0700361
Stephan Massaltf84cf812019-12-31 14:14:50 -0800362 // Print out the stats per channel and for the logfile
363 for (size_t i = 0; i != channel_stats.size(); i++) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700364 if (!FLAGS_excessive_size_only ||
365 (channel_stats[i].max_message_size() * 2) <
366 static_cast<size_t>(channel_stats[i].channel()->max_size())) {
367 if (channel_stats[i].total_num_messages() > 0) {
368 std::cout << channel_stats[i].channel()->name()->string_view() << " "
369 << channel_stats[i].channel()->type()->string_view() << "\n";
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700370
Austin Schuhc99e1392021-03-30 22:59:24 -0700371 logfile_stats.total_log_messages +=
372 channel_stats[i].total_num_messages();
373 logfile_stats.logfile_end_time =
374 std::max(logfile_stats.logfile_end_time,
375 channel_stats[i].channel_end_time());
376
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700377 if (!FLAGS_excessive_size_only) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700378 std::cout << " " << channel_stats[i].total_num_messages()
379 << " msgs, " << channel_stats[i].avg_messages_per_sec()
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700380 << "hz avg, " << channel_stats[i].max_messages_per_sec()
381 << "hz max, " << channel_stats[i].channel()->frequency()
382 << "hz configured max";
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700383 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700384 std::cout << " " << channel_stats[i].avg_message_size()
Austin Schuh60e77942022-05-16 17:48:24 -0700385 << " bytes avg, " << channel_stats[i].avg_message_bandwidth()
James Kuszmaul6abc7442021-10-24 13:46:04 -0700386 << " bytes/sec avg, " << channel_stats[i].max_message_size()
Austin Schuhc99e1392021-03-30 22:59:24 -0700387 << " bytes max / " << channel_stats[i].channel()->max_size()
Milind Upadhyay3d13a1a2022-08-04 10:58:32 -0700388 << "bytes, " << channel_stats[i].Percentile() << ", "
389 << channel_stats[i].AvgLatency();
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700390 std::cout << std::endl;
391 }
Stephan Massaltf84cf812019-12-31 14:14:50 -0800392 }
393 }
394 std::cout << std::setfill('-') << std::setw(80) << "-"
Ravago Jones8bab1842020-12-12 17:36:39 -0800395 << "\nLogfile statistics:\n"
Austin Schuh76db3fa2020-03-07 17:02:44 -0800396 << "Log starts at:\t" << reader.realtime_start_time(node) << "\n"
Stephan Massaltf84cf812019-12-31 14:14:50 -0800397 << "Log ends at:\t" << logfile_stats.logfile_end_time << "\n"
398 << "Log file size:\t" << logfile_stats.logfile_length << "\n"
399 << "Total messages:\t" << logfile_stats.total_log_messages << "\n";
400
401 // Cleanup the created processes
402 reader.Deregister();
Austin Schuhae87e312020-08-01 16:15:01 -0700403
Stephan Massaltf84cf812019-12-31 14:14:50 -0800404 return 0;
405}