blob: 4efb8dfb1a61ca66f1f652484446839f66c724cf [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"
Austin Schuhb06f03b2021-02-17 22:00:37 -08006#include "aos/events/logging/log_reader.h"
Stephan Massaltf84cf812019-12-31 14:14:50 -08007#include "aos/events/simulated_event_loop.h"
8#include "aos/init.h"
9#include "aos/json_to_flatbuffer.h"
10#include "aos/time/time.h"
11#include "gflags/gflags.h"
12
Stephan Massaltf84cf812019-12-31 14:14:50 -080013DEFINE_string(
14 name, "",
15 "Name to match for printing out channels. Empty means no name filter.");
16
Austin Schuh6f3babe2020-01-26 20:34:50 -080017DEFINE_string(node, "", "Node to print stats out for.");
18
milind upadhyay38fe3cd2021-03-27 15:30:53 -070019DEFINE_bool(excessive_size_only, false,
20 "Only print channels that have a set max message size that is more "
21 "than double of the max message size.");
22
Austin Schuhc99e1392021-03-30 22:59:24 -070023// This class implements a histogram for tracking message period percentiles.
24class Histogram {
25 public:
26 Histogram(size_t buckets = 1024)
27 : max_value_bucket_(0.01), values_(buckets, 0.0), counts_(buckets, 0) {}
28
29 // Adds a new sample to the histogram, potentially downsampling the existing
30 // data.
31 void Add(double value) {
32 if (value < max_value_bucket_) {
33 const ssize_t bucket = static_cast<size_t>(
34 std::floor(value * values_.size() / max_value_bucket_));
35 CHECK_GE(bucket, 0);
36 CHECK_LT(bucket, static_cast<ssize_t>(values_.size()));
37 values_[bucket] += value;
38 if (all_counts_ == 0 || value > max_value_) {
39 max_value_ = value;
40 }
41 if (all_counts_ == 0 || value < min_value_) {
42 min_value_ = value;
43 }
44 ++counts_[bucket];
45 ++all_counts_;
46 } else {
47 // Double all the bucket sizes by merging adjacent buckets and doubling
48 // the max value. If this isn't enough, we'll recurse inside Add and
49 // do it again until it fits.
50 max_value_bucket_ *= 2.0;
51 for (size_t bucket = 0; bucket < values_.size() / 2; ++bucket) {
52 values_[bucket] = values_[bucket * 2] + values_[bucket * 2 + 1];
53 counts_[bucket] = counts_[bucket * 2] + counts_[bucket * 2 + 1];
54 }
55 for (size_t bucket = values_.size() / 2; bucket < values_.size();
56 ++bucket) {
57 values_[bucket] = 0.0;
58 counts_[bucket] = 0;
59 }
60 Add(value);
61 }
62 }
63
64 // Prints out the percentiles for a couple of critical numbers.
65 std::string Percentile() const {
66 const size_t percentile5 = all_counts_ / 20;
67 double percentile5_value = 0.0;
68 const size_t percentile50 = all_counts_ / 2;
69 double percentile50_value = 0.0;
70 const size_t percentile95 = all_counts_ - percentile5;
71 double percentile95_value = 0.0;
72
73 size_t count = 0;
74 for (size_t i = 0; i < values_.size(); ++i) {
75 if (count < percentile5 && count + counts_[i] >= percentile5) {
76 percentile5_value = values_[i] / counts_[i];
77 }
78 if (count < percentile50 && count + counts_[i] >= percentile50) {
79 percentile50_value = values_[i] / counts_[i];
80 }
81 if (count < percentile95 && count + counts_[i] >= percentile95) {
82 percentile95_value = values_[i] / counts_[i];
83 }
84 count += counts_[i];
85 }
86
87 // Assume here that these are periods in seconds. Convert to ms for
88 // readability. This isn't super generic, but that's fine for now.
89 return absl::StrFormat(
90 "[max %.3fms 95%%:%.3fms 50%%:%.3fms 5%%:%.3fms min %.3fms]",
91 max_value_ * 1000., percentile95_value * 1000.,
92 percentile50_value * 1000., percentile5_value * 1000.,
93 min_value_ * 1000.);
94 }
95
96 private:
97 // The size of the largest bucket. Used to figure out which bucket something
98 // goes into.
99 double max_value_bucket_;
100 // Max and min values overall we have seen.
101 double max_value_ = 0;
102 double min_value_ = 0;
103 // A list of the sum of values and counts for those per bucket.
104 std::vector<double> values_;
105 std::vector<size_t> counts_;
106 // Total number of samples.
107 size_t all_counts_ = 0;
108};
109
110class ChannelStats {
111 public:
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700112 ChannelStats(const aos::Channel *channel, const aos::Configuration *config)
113 : channel_(channel), config_(config) {}
Austin Schuhc99e1392021-03-30 22:59:24 -0700114
115 // Adds a sample to the statistics.
116 void Add(const aos::Context &context) {
117 max_message_size_ = std::max(max_message_size_, context.size);
118 total_message_size_ += context.size;
119 total_num_messages_++;
120 channel_end_time_ = context.realtime_event_time;
121 first_message_time_ =
122 std::min(first_message_time_, context.monotonic_event_time);
123 if (current_message_time_ != aos::monotonic_clock::min_time) {
124 histogram_.Add(std::chrono::duration<double>(
125 context.monotonic_event_time - current_message_time_)
126 .count());
127 }
128 current_message_time_ = context.monotonic_event_time;
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700129 channel_storage_duration_messages_.push(current_message_time_);
130 while (channel_storage_duration_messages_.front() +
131 std::chrono::nanoseconds(config_->channel_storage_duration()) <=
132 current_message_time_) {
133 channel_storage_duration_messages_.pop();
134 }
135 max_messages_per_period_ = std::max(
136 max_messages_per_period_, channel_storage_duration_messages_.size());
Austin Schuhc99e1392021-03-30 22:59:24 -0700137 }
138
139 std::string Percentile() const { return histogram_.Percentile(); }
140
141 double SecondsActive() const {
142 return aos::time::DurationInSeconds(current_message_time_ -
143 first_message_time_);
144 }
145
146 size_t max_message_size() const { return max_message_size_; }
147 size_t total_num_messages() const { return total_num_messages_; }
148
149 double avg_messages_per_sec() const {
150 return total_num_messages_ / SecondsActive();
151 }
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700152 double max_messages_per_sec() const {
153 return max_messages_per_period_ /
154 std::min(SecondsActive(),
155 1e-9 * config_->channel_storage_duration());
156 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700157 size_t avg_message_size() const {
158 return total_message_size_ / total_num_messages_;
159 }
James Kuszmaul6abc7442021-10-24 13:46:04 -0700160 size_t avg_message_bandwidth() const {
161 return total_message_size_ / SecondsActive();
162 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700163
164 aos::realtime_clock::time_point channel_end_time() const {
165 return channel_end_time_;
166 }
167
168 const aos::Channel *channel() const { return channel_; }
169
170 private:
Stephan Massaltf84cf812019-12-31 14:14:50 -0800171 // pointer to the channel for which stats are collected
Austin Schuhc99e1392021-03-30 22:59:24 -0700172 const aos::Channel *channel_;
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700173 const aos::Configuration *config_;
Austin Schuhc99e1392021-03-30 22:59:24 -0700174 aos::realtime_clock::time_point channel_end_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800175 aos::realtime_clock::min_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700176 aos::monotonic_clock::time_point first_message_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800177 // needs to be higher than time in the logfile!
178 aos::monotonic_clock::max_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700179 aos::monotonic_clock::time_point current_message_time_ =
Stephan Massaltf84cf812019-12-31 14:14:50 -0800180 aos::monotonic_clock::min_time;
Austin Schuhc99e1392021-03-30 22:59:24 -0700181
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700182 // Buffer of the last N seconds of messages, for N = channel_storage_duration.
183 std::queue<aos::monotonic_clock::time_point>
184 channel_storage_duration_messages_;
185 size_t max_messages_per_period_ = 0;
186
Stephan Massaltf84cf812019-12-31 14:14:50 -0800187 // channel stats to collect per channel
Austin Schuhc99e1392021-03-30 22:59:24 -0700188 int total_num_messages_ = 0;
189 size_t max_message_size_ = 0;
190 size_t total_message_size_ = 0;
191
192 Histogram histogram_;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800193};
194
195struct LogfileStats {
196 // All relevant stats on to logfile level
197 size_t logfile_length = 0;
198 int total_log_messages = 0;
199 aos::realtime_clock::time_point logfile_end_time =
200 aos::realtime_clock::min_time;
201};
202
203int main(int argc, char **argv) {
204 gflags::SetUsageMessage(
Ravago Jones8bab1842020-12-12 17:36:39 -0800205 "Usage: \n"
206 " log_stats [args] logfile1 logfile2 ...\n"
Stephan Massaltf84cf812019-12-31 14:14:50 -0800207 "This program provides statistics on a given log file. Supported "
208 "statistics are:\n"
209 " - Logfile start time;\n"
210 " - Total messages per channel/type;\n"
211 " - Max message size per channel/type;\n"
212 " - Frequency of messages per second;\n"
213 " - Total logfile size and number of messages.\n"
214 "Use --logfile flag to select a logfile (path/filename) and use --name "
215 "flag to specify a channel to listen on.");
216
217 aos::InitGoogle(&argc, &argv);
218
Ravago Jones8bab1842020-12-12 17:36:39 -0800219 if (argc < 2) {
220 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
221 }
222
223 // find logfiles
224 std::vector<std::string> unsorted_logfiles =
225 aos::logger::FindLogs(argc, argv);
226
227 // sort logfiles
228 const std::vector<aos::logger::LogFile> logfiles =
229 aos::logger::SortParts(unsorted_logfiles);
230
231 // open logfiles
232 aos::logger::LogReader reader(logfiles);
233
Stephan Massaltf84cf812019-12-31 14:14:50 -0800234 LogfileStats logfile_stats;
235 std::vector<ChannelStats> channel_stats;
236
Stephan Massaltf84cf812019-12-31 14:14:50 -0800237 aos::SimulatedEventLoopFactory log_reader_factory(reader.configuration());
238 reader.Register(&log_reader_factory);
239
Austin Schuh6f3babe2020-01-26 20:34:50 -0800240 const aos::Node *node = nullptr;
241
242 if (aos::configuration::MultiNode(reader.configuration())) {
243 if (FLAGS_node.empty()) {
244 LOG(INFO) << "Need a --node specified. The log file has:";
Austin Schuh07676622021-01-21 18:59:17 -0800245 for (const aos::Node *node : reader.LoggedNodes()) {
Austin Schuh6f3babe2020-01-26 20:34:50 -0800246 LOG(INFO) << " " << node->name()->string_view();
247 }
Austin Schuh8c7f14b2021-01-21 19:01:54 -0800248 reader.Deregister();
Austin Schuh6f3babe2020-01-26 20:34:50 -0800249 return 1;
250 } else {
251 node = aos::configuration::GetNode(reader.configuration(), FLAGS_node);
252 }
253 }
254
Stephan Massaltf84cf812019-12-31 14:14:50 -0800255 // Make an eventloop for retrieving stats
256 std::unique_ptr<aos::EventLoop> stats_event_loop =
Austin Schuh6f3babe2020-01-26 20:34:50 -0800257 log_reader_factory.MakeEventLoop("logstats", node);
Stephan Massaltf84cf812019-12-31 14:14:50 -0800258 stats_event_loop->SkipTimingReport();
Tyler Chatow67ddb032020-01-12 14:30:04 -0800259 stats_event_loop->SkipAosLog();
Stephan Massaltf84cf812019-12-31 14:14:50 -0800260
261 // Read channel info and store in vector
262 bool found_channel = false;
263 const flatbuffers::Vector<flatbuffers::Offset<aos::Channel>> *channels =
264 reader.configuration()->channels();
265
266 int it = 0; // iterate through the channel_stats
267 for (flatbuffers::uoffset_t i = 0; i < channels->size(); i++) {
268 const aos::Channel *channel = channels->Get(i);
Austin Schuh76db3fa2020-03-07 17:02:44 -0800269 if (!aos::configuration::ChannelIsReadableOnNode(
270 channel, stats_event_loop->node())) {
271 continue;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800272 }
Austin Schuh76db3fa2020-03-07 17:02:44 -0800273
274 if (channel->name()->string_view().find(FLAGS_name) == std::string::npos) {
275 continue;
276 }
277
278 // Add a record to the stats vector.
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700279 channel_stats.push_back({channel, reader.configuration()});
Austin Schuh76db3fa2020-03-07 17:02:44 -0800280 // Lambda to read messages and parse for information
Brian Silvermanad1bce02020-03-05 14:23:01 -0800281 stats_event_loop->MakeRawNoArgWatcher(
282 channel,
283 [&logfile_stats, &channel_stats, it](const aos::Context &context) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700284 channel_stats[it].Add(context);
285
286 // Update the overall logfile statistics
Brian Silvermanad1bce02020-03-05 14:23:01 -0800287 logfile_stats.logfile_length += context.size;
288 });
Austin Schuh76db3fa2020-03-07 17:02:44 -0800289 it++;
290 // TODO (Stephan): Frequency of messages per second
291 // - Sliding window
292 // - Max / Deviation
293 found_channel = true;
Stephan Massaltf84cf812019-12-31 14:14:50 -0800294 }
295 if (!found_channel) {
296 LOG(FATAL) << "Could not find any channels";
297 }
298
299 log_reader_factory.Run();
300
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700301 std::cout << std::endl;
Austin Schuhc99e1392021-03-30 22:59:24 -0700302
Stephan Massaltf84cf812019-12-31 14:14:50 -0800303 // Print out the stats per channel and for the logfile
304 for (size_t i = 0; i != channel_stats.size(); i++) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700305 if (!FLAGS_excessive_size_only ||
306 (channel_stats[i].max_message_size() * 2) <
307 static_cast<size_t>(channel_stats[i].channel()->max_size())) {
308 if (channel_stats[i].total_num_messages() > 0) {
309 std::cout << channel_stats[i].channel()->name()->string_view() << " "
310 << channel_stats[i].channel()->type()->string_view() << "\n";
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700311
Austin Schuhc99e1392021-03-30 22:59:24 -0700312 logfile_stats.total_log_messages +=
313 channel_stats[i].total_num_messages();
314 logfile_stats.logfile_end_time =
315 std::max(logfile_stats.logfile_end_time,
316 channel_stats[i].channel_end_time());
317
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700318 if (!FLAGS_excessive_size_only) {
Austin Schuhc99e1392021-03-30 22:59:24 -0700319 std::cout << " " << channel_stats[i].total_num_messages()
320 << " msgs, " << channel_stats[i].avg_messages_per_sec()
James Kuszmaulc7bb1652022-06-22 11:18:49 -0700321 << "hz avg, " << channel_stats[i].max_messages_per_sec()
322 << "hz max, " << channel_stats[i].channel()->frequency()
323 << "hz configured max";
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700324 }
Austin Schuhc99e1392021-03-30 22:59:24 -0700325 std::cout << " " << channel_stats[i].avg_message_size()
Austin Schuh60e77942022-05-16 17:48:24 -0700326 << " bytes avg, " << channel_stats[i].avg_message_bandwidth()
James Kuszmaul6abc7442021-10-24 13:46:04 -0700327 << " bytes/sec avg, " << channel_stats[i].max_message_size()
Austin Schuhc99e1392021-03-30 22:59:24 -0700328 << " bytes max / " << channel_stats[i].channel()->max_size()
329 << "bytes " << channel_stats[i].Percentile();
milind upadhyay38fe3cd2021-03-27 15:30:53 -0700330 std::cout << std::endl;
331 }
Stephan Massaltf84cf812019-12-31 14:14:50 -0800332 }
333 }
334 std::cout << std::setfill('-') << std::setw(80) << "-"
Ravago Jones8bab1842020-12-12 17:36:39 -0800335 << "\nLogfile statistics:\n"
Austin Schuh76db3fa2020-03-07 17:02:44 -0800336 << "Log starts at:\t" << reader.realtime_start_time(node) << "\n"
Stephan Massaltf84cf812019-12-31 14:14:50 -0800337 << "Log ends at:\t" << logfile_stats.logfile_end_time << "\n"
338 << "Log file size:\t" << logfile_stats.logfile_length << "\n"
339 << "Total messages:\t" << logfile_stats.total_log_messages << "\n";
340
341 // Cleanup the created processes
342 reader.Deregister();
Austin Schuhae87e312020-08-01 16:15:01 -0700343
Stephan Massaltf84cf812019-12-31 14:14:50 -0800344 return 0;
345}