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