blob: c03dc3686ca77d825e2088d85b3b291d059fe886 [file] [log] [blame]
Austin Schuh30f74292021-08-13 17:25:26 -07001#include <iomanip>
2#include <iostream>
3
Austin Schuh99f7c6a2024-06-25 22:07:44 -07004#include "absl/flags/flag.h"
5#include "absl/flags/usage.h"
Austin Schuh30f74292021-08-13 17:25:26 -07006#include "absl/strings/str_format.h"
7#include "absl/strings/str_split.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07008
Austin Schuh30f74292021-08-13 17:25:26 -07009#include "aos/events/logging/log_reader.h"
10#include "aos/events/simulated_event_loop.h"
11#include "aos/init.h"
12#include "aos/json_to_flatbuffer.h"
13#include "aos/time/time.h"
Austin Schuh30f74292021-08-13 17:25:26 -070014
Austin Schuh99f7c6a2024-06-25 22:07:44 -070015ABSL_FLAG(std::string, skip, "", "Applications to skip, seperated by ;");
Austin Schuh30f74292021-08-13 17:25:26 -070016
17struct ChannelState {
18 const aos::Channel *channel = nullptr;
19 double frequency_sum = 0.0;
20 size_t frequency_count = 0;
21};
22
23// List of channels for an application.
24struct Application {
25 std::vector<ChannelState> watchers;
26 std::vector<ChannelState> fetchers;
27 std::vector<ChannelState> senders;
28};
29
30// List of all applications connected to a channel.
31struct ChannelConnections {
32 std::vector<std::pair<std::string, double>> senders;
33 std::vector<std::string> watchers;
34 std::vector<std::string> fetchers;
35};
36
37int main(int argc, char **argv) {
Austin Schuh99f7c6a2024-06-25 22:07:44 -070038 absl::SetProgramUsageMessage(
Austin Schuh30f74292021-08-13 17:25:26 -070039 "Usage: \n"
40 " aos_graph_channels [args] logfile1 logfile2 ...\n"
41 "\n"
42 "The output is in dot format. Typical usage will be to pipe the results "
43 "to dot\n"
44 "\n"
45 " aos_graph_channels ./log/ | dot -Tx11");
46
47 aos::InitGoogle(&argc, &argv);
48
49 if (argc < 2) {
50 LOG(FATAL) << "Expected at least 1 logfile as an argument.";
51 }
52
Austin Schuh99f7c6a2024-06-25 22:07:44 -070053 const std::vector<std::string> skip_list =
54 absl::StrSplit(absl::GetFlag(FLAGS_skip), ";");
Austin Schuh30f74292021-08-13 17:25:26 -070055 aos::logger::LogReader reader(
56 aos::logger::SortParts(aos::logger::FindLogs(argc, argv)));
57
58 aos::SimulatedEventLoopFactory factory(reader.configuration());
59 reader.Register(&factory);
60
61 // Now: hook everything up to grab all the timing reports and extract them
62 // into the Application data structure.
63 std::map<std::string, Application> applications;
64
65 std::vector<std::unique_ptr<aos::EventLoop>> loops;
66 for (const aos::Node *node :
67 aos::configuration::GetNodes(factory.configuration())) {
68 std::unique_ptr<aos::EventLoop> event_loop =
69 factory.MakeEventLoop("timing_reports", node);
70 event_loop->SkipTimingReport();
71 event_loop->SkipAosLog();
72
73 event_loop->MakeWatcher("/aos", [&](const aos::timing::Report
74 &timing_report) {
75 if (std::find(skip_list.begin(), skip_list.end(),
76 timing_report.name()->str()) != skip_list.end()) {
77 return;
78 }
79 // Make an application if one doesn't exist.
80 auto it = applications.find(timing_report.name()->str());
81 if (it == applications.end()) {
82 it = applications.emplace(timing_report.name()->str(), Application())
83 .first;
84 }
85
86 // Add watcher state.
87 if (timing_report.has_watchers()) {
88 for (const aos::timing::Watcher *watcher : *timing_report.watchers()) {
89 const aos::Channel *channel =
90 factory.configuration()->channels()->Get(
91 watcher->channel_index());
92 auto watcher_it = std::find_if(
93 it->second.watchers.begin(), it->second.watchers.end(),
94 [&](const ChannelState &c) { return c.channel == channel; });
95 if (watcher_it == it->second.watchers.end()) {
96 it->second.watchers.push_back(ChannelState{.channel = channel,
97 .frequency_sum = 0.0,
98 .frequency_count = 0});
99 watcher_it = it->second.watchers.end() - 1;
100 }
101 watcher_it->frequency_sum += watcher->count();
102 ++watcher_it->frequency_count;
103 }
104 }
105
106 // Add sender state.
107 if (timing_report.has_senders()) {
108 for (const aos::timing::Sender *sender : *timing_report.senders()) {
109 const aos::Channel *channel =
110 factory.configuration()->channels()->Get(sender->channel_index());
111 auto sender_it = std::find_if(
112 it->second.senders.begin(), it->second.senders.end(),
113 [&](const ChannelState &c) { return c.channel == channel; });
114 if (sender_it == it->second.senders.end()) {
115 it->second.senders.push_back(ChannelState{.channel = channel,
116 .frequency_sum = 0.0,
117 .frequency_count = 0});
118 sender_it = it->second.senders.end() - 1;
119 }
120 sender_it->frequency_sum += sender->count();
121 ++sender_it->frequency_count;
122 }
123 }
124
125 // Add fetcher state.
126 if (timing_report.has_fetchers()) {
127 for (const aos::timing::Fetcher *fetcher : *timing_report.fetchers()) {
128 const aos::Channel *channel =
129 factory.configuration()->channels()->Get(
130 fetcher->channel_index());
131 auto fetcher_it = std::find_if(
132 it->second.fetchers.begin(), it->second.fetchers.end(),
133 [&](const ChannelState &c) { return c.channel == channel; });
134 if (fetcher_it == it->second.fetchers.end()) {
135 it->second.fetchers.push_back(ChannelState{.channel = channel,
136 .frequency_sum = 0.0,
137 .frequency_count = 0});
138 fetcher_it = it->second.fetchers.end() - 1;
139 }
140 fetcher_it->frequency_sum += fetcher->count();
141 ++fetcher_it->frequency_count;
142 }
143 }
144 });
145 loops.emplace_back(std::move(event_loop));
146 }
147
148 factory.Run();
149
150 reader.Deregister();
151
152 // Now, we need to flip this graph on it's head to deduplicate and draw the
153 // correct graph. Build it all up as a list of applications per channel.
154 std::map<const aos::Channel *, ChannelConnections> connections;
155 for (const std::pair<const std::string, Application> &app : applications) {
156 for (const ChannelState &state : app.second.senders) {
157 auto it = connections.find(state.channel);
158 if (it == connections.end()) {
159 it = connections.emplace(state.channel, ChannelConnections()).first;
160 }
161
162 it->second.senders.emplace_back(std::make_pair(
163 app.first, state.frequency_count == 0
164 ? 0.0
165 : state.frequency_sum / state.frequency_count));
166 }
167 for (const ChannelState &state : app.second.watchers) {
168 auto it = connections.find(state.channel);
169 if (it == connections.end()) {
170 it = connections.emplace(state.channel, ChannelConnections()).first;
171 }
172
173 it->second.watchers.emplace_back(app.first);
174 }
175 for (const ChannelState &state : app.second.fetchers) {
176 auto it = connections.find(state.channel);
177 if (it == connections.end()) {
178 it = connections.emplace(state.channel, ChannelConnections()).first;
179 }
180
181 it->second.fetchers.emplace_back(app.first);
182 }
183 }
184
185 const std::vector<std::string> color_list = {
186 "red", "blue", "orange", "green", "violet", "gold3", "magenta"};
187
188 // Now generate graphvis compatible output.
189 std::stringstream graph_out;
190 graph_out << "digraph g {" << std::endl;
Austin Schuhf71d1ee2021-10-18 16:32:19 -0700191 for (const std::pair<const aos::Channel *const, ChannelConnections> &c :
Austin Schuh30f74292021-08-13 17:25:26 -0700192 connections) {
193 const std::string channel = absl::StrCat(
194 c.first->name()->string_view(), "\n", c.first->type()->string_view());
195 for (const std::pair<std::string, double> &sender : c.second.senders) {
196 graph_out << "\t\"" << sender.first << "\" -> \"" << channel
197 << "\" [label=\"" << sender.second << "\" color=\""
198 << color_list[0]
199 << "\" weight=" << static_cast<int>(sender.second) << "];"
200 << std::endl;
201 }
202 for (const std::string &watcher : c.second.watchers) {
203 graph_out << "\t\"" << channel << "\" -> \"" << watcher << "\" [color=\""
204 << color_list[1] << "\"];" << std::endl;
205 }
206 for (const std::string &watcher : c.second.fetchers) {
207 graph_out << "\t\"" << channel << "\" -> \"" << watcher << "\" [color=\""
208 << color_list[2] << "\"];" << std::endl;
209 }
210 }
211
212 size_t index = 0;
213 for (const std::pair<const std::string, Application> &app : applications) {
214 graph_out << "\t\"" << app.first << "\" [color=\"" << color_list[index]
215 << "\" shape=box style=filled];" << std::endl;
216 ++index;
217 if (index >= color_list.size()) {
218 index = 0;
219 }
220 }
221 graph_out << "}" << std::endl;
222
223 std::cout << graph_out.str();
224
225 return 0;
226}