blob: 5b5cdc24b86937c583a4ac0082d59c7eec67e147 [file] [log] [blame]
Austin Schuh59d93f42022-07-18 16:52:32 -07001#include "absl/strings/str_cat.h"
2#include "absl/strings/str_split.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07003
Stephan Pleines85b295c2024-02-04 17:50:26 -08004#include "aos/analysis/in_process_plotter.h"
Austin Schuh59d93f42022-07-18 16:52:32 -07005#include "aos/init.h"
6#include "aos/util/file.h"
Austin Schuh59d93f42022-07-18 16:52:32 -07007
Stephan Pleines9e40c8e2024-02-07 20:58:28 -08008using aos::analysis::Plotter;
Austin Schuh59d93f42022-07-18 16:52:32 -07009
Austin Schuh69d0b732022-07-20 21:19:32 -070010DEFINE_bool(all, false, "If true, plot *all* the nodes at once");
11DEFINE_bool(bounds, false, "If true, plot the noncausal bounds too.");
12DEFINE_bool(samples, true, "If true, plot the samples too.");
13
14DEFINE_string(offsets, "",
15 "Offsets to add to the monotonic clock for each node. Use the "
16 "format of node=offset,node=offest");
17
Austin Schuh59d93f42022-07-18 16:52:32 -070018// Simple C++ application to read the CSV files and use the in process plotter
19// to plot them. This smokes the pants off gnuplot in terms of interactivity.
20
21namespace aos {
22
Austin Schuh69d0b732022-07-20 21:19:32 -070023// Returns all the nodes.
24std::vector<std::string> Nodes() {
25 const std::string start_time_file = aos::util::ReadFileToStringOrDie(
26 "/tmp/timestamp_noncausal_starttime.csv");
27 std::vector<std::string_view> nodes = absl::StrSplit(start_time_file, '\n');
28
29 std::vector<std::string> formatted_nodes;
30 for (const std::string_view n : nodes) {
31 if (n == "") {
32 continue;
33 }
34
35 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
36 CHECK_EQ(l.size(), 2u) << "'" << n << "'";
37 formatted_nodes.emplace_back(l[0]);
38 }
39
40 return formatted_nodes;
41}
42
43std::string SampleFile(std::string_view node1, std::string_view node2) {
44 return absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2,
45 "_samples.csv");
46}
47
Austin Schuh59d93f42022-07-18 16:52:32 -070048std::pair<std::vector<double>, std::vector<double>> ReadSamples(
49 std::string_view node1, std::string_view node2, bool flip) {
50 std::vector<double> samplefile12_t;
51 std::vector<double> samplefile12_o;
Austin Schuh2c899242023-02-17 12:19:57 -080052 const std::string path = SampleFile(node1, node2);
Austin Schuh59d93f42022-07-18 16:52:32 -070053
Austin Schuh2c899242023-02-17 12:19:57 -080054 if (!aos::util::PathExists(path)) {
55 return {};
56 }
57
58 const std::string file = aos::util::ReadFileToStringOrDie(path);
Austin Schuh59d93f42022-07-18 16:52:32 -070059 bool first = true;
60 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
61 samplefile12_t.reserve(lines.size());
62 for (const std::string_view n : lines) {
63 if (first) {
64 first = false;
65 continue;
66 }
67 if (n == "") {
68 continue;
69 }
70
71 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
Austin Schuh8c2e8c72022-08-06 18:31:19 -070072 if (l.size() != 4u) {
73 continue;
74 }
Austin Schuh59d93f42022-07-18 16:52:32 -070075 double t;
76 double o;
77 CHECK(absl::SimpleAtod(l[0], &t));
78 CHECK(absl::SimpleAtod(l[1], &o));
79 samplefile12_t.emplace_back(t);
80 samplefile12_o.emplace_back(flip ? -o : o);
81 }
82 return std::make_pair(samplefile12_t, samplefile12_o);
83}
84
Austin Schuh69d0b732022-07-20 21:19:32 -070085void Offset(std::vector<double> *v, double offset) {
86 for (double &x : *v) {
87 x += offset;
88 }
89}
90
91// Returns all the nodes which talk to each other.
92std::vector<std::pair<std::string, std::string>> NodeConnections() {
93 const std::vector<std::string> nodes = Nodes();
94 std::vector<std::pair<std::string, std::string>> result;
95 for (size_t i = 1; i < nodes.size(); ++i) {
96 for (size_t j = 0; j < i; ++j) {
97 const std::string_view node1 = nodes[j];
98 const std::string_view node2 = nodes[i];
Austin Schuh2c899242023-02-17 12:19:57 -080099 if (aos::util::PathExists(SampleFile(node1, node2)) ||
100 aos::util::PathExists(SampleFile(node2, node1))) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700101 result.emplace_back(node1, node2);
102 LOG(INFO) << "Found pairing " << node1 << ", " << node2;
103 }
104 }
105 }
106 return result;
107}
108
109// Class to encapsulate the plotter state to make it easy to plot multiple
110// connections.
111class NodePlotter {
112 public:
113 NodePlotter() : nodes_(Nodes()) {
114 plotter_.AddFigure("Time");
115 if (!FLAGS_offsets.empty()) {
116 for (std::string_view nodeoffset : absl::StrSplit(FLAGS_offsets, ',')) {
117 std::vector<std::string_view> node_offset =
118 absl::StrSplit(nodeoffset, '=');
119 CHECK_EQ(node_offset.size(), 2u);
120 double o;
121 CHECK(absl::SimpleAtod(node_offset[1], &o));
122 offset_.emplace(std::string(node_offset[0]), o);
123 }
124 }
125 }
126
127 void AddNodes(std::string_view node1, std::string_view node2);
128
129 void Serve() {
130 plotter_.Publish();
131 plotter_.Spin();
132 }
133
134 private:
135 std::pair<std::vector<double>, std::vector<double>> ReadLines(
136 std::string_view node1, std::string_view node2, bool flip);
137
138 std::pair<std::vector<double>, std::vector<double>> ReadOffset(
139 std::string_view node1, std::string_view node2);
140
141 double TimeOffset(std::string_view node) {
142 auto it = offset_.find(std::string(node));
143 if (it == offset_.end()) {
144 return 0.0;
145 } else {
146 return it->second;
147 }
148 }
149
150 std::map<std::string, double> offset_;
151
152 Plotter plotter_;
153
154 std::vector<std::string> nodes_;
155};
156
157std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadLines(
Austin Schuh59d93f42022-07-18 16:52:32 -0700158 std::string_view node1, std::string_view node2, bool flip) {
159 std::vector<double> samplefile12_t;
160 std::vector<double> samplefile12_o;
Philipp Schrader790cb542023-07-05 21:06:52 -0700161 const std::string path =
162 absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2, ".csv");
Austin Schuh59d93f42022-07-18 16:52:32 -0700163
Austin Schuh2c899242023-02-17 12:19:57 -0800164 if (!aos::util::PathExists(path)) {
165 return {};
166 }
167
168 const std::string file = aos::util::ReadFileToStringOrDie(path);
Austin Schuh59d93f42022-07-18 16:52:32 -0700169 bool first = true;
170 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
171 samplefile12_t.reserve(lines.size());
172 for (const std::string_view n : lines) {
173 if (first) {
174 first = false;
175 continue;
176 }
177 if (n == "") {
178 continue;
179 }
180
181 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
Austin Schuh8c2e8c72022-08-06 18:31:19 -0700182 if (l.size() != 3u) {
183 continue;
184 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700185 double t;
186 double o;
187 CHECK(absl::SimpleAtod(l[0], &t));
188 CHECK(absl::SimpleAtod(l[2], &o));
189 samplefile12_t.emplace_back(t);
190 samplefile12_o.emplace_back(flip ? -o : o);
191 }
192 return std::make_pair(samplefile12_t, samplefile12_o);
193}
194
Austin Schuh69d0b732022-07-20 21:19:32 -0700195std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadOffset(
Austin Schuh59d93f42022-07-18 16:52:32 -0700196 std::string_view node1, std::string_view node2) {
197 int node1_index = -1;
198 int node2_index = -1;
199
200 {
Austin Schuh59d93f42022-07-18 16:52:32 -0700201 int index = 0;
Austin Schuh69d0b732022-07-20 21:19:32 -0700202 for (const std::string &n : nodes_) {
203 if (n == node1) {
Austin Schuh59d93f42022-07-18 16:52:32 -0700204 node1_index = index;
205 }
Austin Schuh69d0b732022-07-20 21:19:32 -0700206 if (n == node2) {
Austin Schuh59d93f42022-07-18 16:52:32 -0700207 node2_index = index;
208 }
209 ++index;
210 }
211 }
212 CHECK_NE(node1_index, -1) << ": Unknown node " << node1;
213 CHECK_NE(node2_index, -1) << ": Unknown node " << node2;
214 std::vector<double> offsetfile_t;
215 std::vector<double> offsetfile_o;
216
217 const std::string file =
218 aos::util::ReadFileToStringOrDie("/tmp/timestamp_noncausal_offsets.csv");
219 bool first = true;
220 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
221 offsetfile_t.reserve(lines.size());
222 for (const std::string_view n : lines) {
223 if (first) {
224 first = false;
225 continue;
226 }
227 if (n == "") {
228 continue;
229 }
230
231 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
232 CHECK_LT(static_cast<size_t>(node1_index + 1), l.size());
233 CHECK_LT(static_cast<size_t>(node2_index + 1), l.size());
234 double t;
235 double o1;
236 double o2;
237 CHECK(absl::SimpleAtod(l[0], &t));
238 CHECK(absl::SimpleAtod(l[1 + node1_index], &o1));
239 CHECK(absl::SimpleAtod(l[1 + node2_index], &o2));
240 offsetfile_t.emplace_back(t);
241 offsetfile_o.emplace_back(o2 - o1);
242 }
243 return std::make_pair(offsetfile_t, offsetfile_o);
244}
245
Austin Schuh69d0b732022-07-20 21:19:32 -0700246void NodePlotter::AddNodes(std::string_view node1, std::string_view node2) {
247 const double offset1 = TimeOffset(node1);
248 const double offset2 = TimeOffset(node2);
249
250 std::pair<std::vector<double>, std::vector<double>> samplefile12 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700251 ReadSamples(node1, node2, false);
Austin Schuh69d0b732022-07-20 21:19:32 -0700252 std::pair<std::vector<double>, std::vector<double>> samplefile21 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700253 ReadSamples(node2, node1, true);
254
Austin Schuh69d0b732022-07-20 21:19:32 -0700255 std::pair<std::vector<double>, std::vector<double>> noncausalfile12 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700256 ReadLines(node1, node2, false);
Austin Schuh69d0b732022-07-20 21:19:32 -0700257 std::pair<std::vector<double>, std::vector<double>> noncausalfile21 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700258 ReadLines(node2, node1, true);
259
Austin Schuh69d0b732022-07-20 21:19:32 -0700260 std::pair<std::vector<double>, std::vector<double>> offsetfile =
Austin Schuh59d93f42022-07-18 16:52:32 -0700261 ReadOffset(node1, node2);
262
Austin Schuh69d0b732022-07-20 21:19:32 -0700263 Offset(&samplefile12.second, offset2 - offset1);
264 Offset(&samplefile21.second, offset2 - offset1);
265 Offset(&noncausalfile12.second, offset2 - offset1);
266 Offset(&noncausalfile21.second, offset2 - offset1);
267 Offset(&offsetfile.second, offset2 - offset1);
268
Austin Schuh59d93f42022-07-18 16:52:32 -0700269 CHECK_EQ(samplefile12.first.size(), samplefile12.second.size());
270 CHECK_EQ(samplefile21.first.size(), samplefile21.second.size());
271 CHECK_EQ(noncausalfile12.first.size(), noncausalfile12.second.size());
272 CHECK_EQ(noncausalfile21.first.size(), noncausalfile21.second.size());
273
274 LOG(INFO) << samplefile12.first.size() + samplefile21.first.size() +
275 noncausalfile12.first.size() + noncausalfile21.first.size()
276 << " points";
Austin Schuh59d93f42022-07-18 16:52:32 -0700277
Austin Schuh69d0b732022-07-20 21:19:32 -0700278 plotter_.AddLine(offsetfile.first, offsetfile.second,
Austin Schuh59d93f42022-07-18 16:52:32 -0700279 Plotter::LineOptions{
280 .label = absl::StrCat("filter ", node2, " ", node1),
281 // TODO(austin): roboRIO compiler wants all the fields
282 // filled out, but other compilers don't... Sigh.
283 .line_style = "*-",
Austin Schuh69d0b732022-07-20 21:19:32 -0700284 .color = "yellow",
285 .point_size = 2.0});
286
287 if (FLAGS_samples) {
288 plotter_.AddLine(samplefile12.first, samplefile12.second,
289 Plotter::LineOptions{
290 .label = absl::StrCat("sample ", node1, " ", node2),
291 .line_style = "*",
292 .color = "purple",
293 });
294 plotter_.AddLine(samplefile21.first, samplefile21.second,
295 Plotter::LineOptions{
296 .label = absl::StrCat("sample ", node2, " ", node1),
297 .line_style = "*",
298 .color = "green",
299 });
300 }
301
302 if (FLAGS_bounds) {
303 plotter_.AddLine(
304 noncausalfile12.first, noncausalfile12.second,
305 Plotter::LineOptions{.label = absl::StrCat("nc ", node1, " ", node2),
306 .line_style = "-",
307 .color = "blue"});
308 plotter_.AddLine(
309 noncausalfile21.first, noncausalfile21.second,
310 Plotter::LineOptions{.label = absl::StrCat("nc ", node2, " ", node1),
311 .line_style = "-",
312 .color = "orange"});
313 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700314}
315
316int Main(int argc, const char *const *argv) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700317 NodePlotter plotter;
Austin Schuh59d93f42022-07-18 16:52:32 -0700318
Austin Schuh69d0b732022-07-20 21:19:32 -0700319 if (FLAGS_all) {
Austin Schuh2c899242023-02-17 12:19:57 -0800320 const std::vector<std::pair<std::string, std::string>> connections =
321 NodeConnections();
322 for (std::pair<std::string, std::string> ab : connections) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700323 plotter.AddNodes(ab.first, ab.second);
324 }
Austin Schuh2c899242023-02-17 12:19:57 -0800325 if (connections.size() == 0) {
326 LOG(WARNING) << "No connections found, is something wrong?";
327 }
Austin Schuh69d0b732022-07-20 21:19:32 -0700328 } else {
329 CHECK_EQ(argc, 3);
Austin Schuh59d93f42022-07-18 16:52:32 -0700330
Austin Schuh69d0b732022-07-20 21:19:32 -0700331 LOG(INFO) << argv[1];
332 LOG(INFO) << argv[2];
Austin Schuh59d93f42022-07-18 16:52:32 -0700333
Austin Schuh69d0b732022-07-20 21:19:32 -0700334 const std::string_view node1 = argv[1];
335 const std::string_view node2 = argv[2];
Austin Schuh59d93f42022-07-18 16:52:32 -0700336
Austin Schuh69d0b732022-07-20 21:19:32 -0700337 plotter.AddNodes(node1, node2);
338 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700339
Austin Schuh69d0b732022-07-20 21:19:32 -0700340 plotter.Serve();
Austin Schuh59d93f42022-07-18 16:52:32 -0700341
342 return 0;
343}
344
345} // namespace aos
346
347int main(int argc, char **argv) {
348 aos::InitGoogle(&argc, &argv);
349
350 aos::Main(argc, argv);
351}