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