blob: 82e8031001e5d0002f4714f2ea32f6daf3aee730 [file] [log] [blame]
Austin Schuh99f7c6a2024-06-25 22:07:44 -07001#include "absl/flags/flag.h"
Austin Schuh59d93f42022-07-18 16:52:32 -07002#include "absl/strings/str_cat.h"
3#include "absl/strings/str_split.h"
Philipp Schrader790cb542023-07-05 21:06:52 -07004
Stephan Pleines85b295c2024-02-04 17:50:26 -08005#include "aos/analysis/in_process_plotter.h"
Austin Schuh59d93f42022-07-18 16:52:32 -07006#include "aos/init.h"
7#include "aos/util/file.h"
Austin Schuh59d93f42022-07-18 16:52:32 -07008
Stephan Pleines9e40c8e2024-02-07 20:58:28 -08009using aos::analysis::Plotter;
Austin Schuh59d93f42022-07-18 16:52:32 -070010
Austin Schuh99f7c6a2024-06-25 22:07:44 -070011ABSL_FLAG(bool, all, false, "If true, plot *all* the nodes at once");
12ABSL_FLAG(bool, bounds, false, "If true, plot the noncausal bounds too.");
13ABSL_FLAG(bool, samples, true, "If true, plot the samples too.");
Austin Schuh69d0b732022-07-20 21:19:32 -070014
Austin Schuh99f7c6a2024-06-25 22:07:44 -070015ABSL_FLAG(std::string, offsets, "",
16 "Offsets to add to the monotonic clock for each node. Use the "
17 "format of node=offset,node=offest");
Austin Schuh69d0b732022-07-20 21:19:32 -070018
Austin Schuh59d93f42022-07-18 16:52:32 -070019// Simple C++ application to read the CSV files and use the in process plotter
20// to plot them. This smokes the pants off gnuplot in terms of interactivity.
21
22namespace aos {
23
Austin Schuh69d0b732022-07-20 21:19:32 -070024// Returns all the nodes.
25std::vector<std::string> Nodes() {
26 const std::string start_time_file = aos::util::ReadFileToStringOrDie(
27 "/tmp/timestamp_noncausal_starttime.csv");
28 std::vector<std::string_view> nodes = absl::StrSplit(start_time_file, '\n');
29
30 std::vector<std::string> formatted_nodes;
31 for (const std::string_view n : nodes) {
32 if (n == "") {
33 continue;
34 }
35
36 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
37 CHECK_EQ(l.size(), 2u) << "'" << n << "'";
38 formatted_nodes.emplace_back(l[0]);
39 }
40
41 return formatted_nodes;
42}
43
44std::string SampleFile(std::string_view node1, std::string_view node2) {
45 return absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2,
46 "_samples.csv");
47}
48
Austin Schuh59d93f42022-07-18 16:52:32 -070049std::pair<std::vector<double>, std::vector<double>> ReadSamples(
50 std::string_view node1, std::string_view node2, bool flip) {
51 std::vector<double> samplefile12_t;
52 std::vector<double> samplefile12_o;
Austin Schuh2c899242023-02-17 12:19:57 -080053 const std::string path = SampleFile(node1, node2);
Austin Schuh59d93f42022-07-18 16:52:32 -070054
Austin Schuh2c899242023-02-17 12:19:57 -080055 if (!aos::util::PathExists(path)) {
56 return {};
57 }
58
59 const std::string file = aos::util::ReadFileToStringOrDie(path);
Austin Schuh59d93f42022-07-18 16:52:32 -070060 bool first = true;
61 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
62 samplefile12_t.reserve(lines.size());
63 for (const std::string_view n : lines) {
64 if (first) {
65 first = false;
66 continue;
67 }
68 if (n == "") {
69 continue;
70 }
71
72 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
Austin Schuh8c2e8c72022-08-06 18:31:19 -070073 if (l.size() != 4u) {
74 continue;
75 }
Austin Schuh59d93f42022-07-18 16:52:32 -070076 double t;
77 double o;
78 CHECK(absl::SimpleAtod(l[0], &t));
79 CHECK(absl::SimpleAtod(l[1], &o));
80 samplefile12_t.emplace_back(t);
81 samplefile12_o.emplace_back(flip ? -o : o);
82 }
83 return std::make_pair(samplefile12_t, samplefile12_o);
84}
85
Austin Schuh69d0b732022-07-20 21:19:32 -070086void Offset(std::vector<double> *v, double offset) {
87 for (double &x : *v) {
88 x += offset;
89 }
90}
91
92// Returns all the nodes which talk to each other.
93std::vector<std::pair<std::string, std::string>> NodeConnections() {
94 const std::vector<std::string> nodes = Nodes();
95 std::vector<std::pair<std::string, std::string>> result;
96 for (size_t i = 1; i < nodes.size(); ++i) {
97 for (size_t j = 0; j < i; ++j) {
98 const std::string_view node1 = nodes[j];
99 const std::string_view node2 = nodes[i];
Austin Schuh2c899242023-02-17 12:19:57 -0800100 if (aos::util::PathExists(SampleFile(node1, node2)) ||
101 aos::util::PathExists(SampleFile(node2, node1))) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700102 result.emplace_back(node1, node2);
103 LOG(INFO) << "Found pairing " << node1 << ", " << node2;
104 }
105 }
106 }
107 return result;
108}
109
110// Class to encapsulate the plotter state to make it easy to plot multiple
111// connections.
112class NodePlotter {
113 public:
114 NodePlotter() : nodes_(Nodes()) {
115 plotter_.AddFigure("Time");
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700116 if (!absl::GetFlag(FLAGS_offsets).empty()) {
117 for (std::string_view nodeoffset :
118 absl::StrSplit(absl::GetFlag(FLAGS_offsets), ',')) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700119 std::vector<std::string_view> node_offset =
120 absl::StrSplit(nodeoffset, '=');
121 CHECK_EQ(node_offset.size(), 2u);
122 double o;
123 CHECK(absl::SimpleAtod(node_offset[1], &o));
124 offset_.emplace(std::string(node_offset[0]), o);
125 }
126 }
127 }
128
129 void AddNodes(std::string_view node1, std::string_view node2);
130
131 void Serve() {
132 plotter_.Publish();
133 plotter_.Spin();
134 }
135
136 private:
137 std::pair<std::vector<double>, std::vector<double>> ReadLines(
138 std::string_view node1, std::string_view node2, bool flip);
139
140 std::pair<std::vector<double>, std::vector<double>> ReadOffset(
141 std::string_view node1, std::string_view node2);
142
143 double TimeOffset(std::string_view node) {
144 auto it = offset_.find(std::string(node));
145 if (it == offset_.end()) {
146 return 0.0;
147 } else {
148 return it->second;
149 }
150 }
151
152 std::map<std::string, double> offset_;
153
154 Plotter plotter_;
155
156 std::vector<std::string> nodes_;
157};
158
159std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadLines(
Austin Schuh59d93f42022-07-18 16:52:32 -0700160 std::string_view node1, std::string_view node2, bool flip) {
161 std::vector<double> samplefile12_t;
162 std::vector<double> samplefile12_o;
Philipp Schrader790cb542023-07-05 21:06:52 -0700163 const std::string path =
164 absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2, ".csv");
Austin Schuh59d93f42022-07-18 16:52:32 -0700165
Austin Schuh2c899242023-02-17 12:19:57 -0800166 if (!aos::util::PathExists(path)) {
167 return {};
168 }
169
170 const std::string file = aos::util::ReadFileToStringOrDie(path);
Austin Schuh59d93f42022-07-18 16:52:32 -0700171 bool first = true;
172 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
173 samplefile12_t.reserve(lines.size());
174 for (const std::string_view n : lines) {
175 if (first) {
176 first = false;
177 continue;
178 }
179 if (n == "") {
180 continue;
181 }
182
183 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
Austin Schuh8c2e8c72022-08-06 18:31:19 -0700184 if (l.size() != 3u) {
185 continue;
186 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700187 double t;
188 double o;
189 CHECK(absl::SimpleAtod(l[0], &t));
190 CHECK(absl::SimpleAtod(l[2], &o));
191 samplefile12_t.emplace_back(t);
192 samplefile12_o.emplace_back(flip ? -o : o);
193 }
194 return std::make_pair(samplefile12_t, samplefile12_o);
195}
196
Austin Schuh69d0b732022-07-20 21:19:32 -0700197std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadOffset(
Austin Schuh59d93f42022-07-18 16:52:32 -0700198 std::string_view node1, std::string_view node2) {
199 int node1_index = -1;
200 int node2_index = -1;
201
202 {
Austin Schuh59d93f42022-07-18 16:52:32 -0700203 int index = 0;
Austin Schuh69d0b732022-07-20 21:19:32 -0700204 for (const std::string &n : nodes_) {
205 if (n == node1) {
Austin Schuh59d93f42022-07-18 16:52:32 -0700206 node1_index = index;
207 }
Austin Schuh69d0b732022-07-20 21:19:32 -0700208 if (n == node2) {
Austin Schuh59d93f42022-07-18 16:52:32 -0700209 node2_index = index;
210 }
211 ++index;
212 }
213 }
214 CHECK_NE(node1_index, -1) << ": Unknown node " << node1;
215 CHECK_NE(node2_index, -1) << ": Unknown node " << node2;
216 std::vector<double> offsetfile_t;
217 std::vector<double> offsetfile_o;
218
219 const std::string file =
220 aos::util::ReadFileToStringOrDie("/tmp/timestamp_noncausal_offsets.csv");
221 bool first = true;
222 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
223 offsetfile_t.reserve(lines.size());
224 for (const std::string_view n : lines) {
225 if (first) {
226 first = false;
227 continue;
228 }
229 if (n == "") {
230 continue;
231 }
232
233 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
234 CHECK_LT(static_cast<size_t>(node1_index + 1), l.size());
235 CHECK_LT(static_cast<size_t>(node2_index + 1), l.size());
236 double t;
237 double o1;
238 double o2;
239 CHECK(absl::SimpleAtod(l[0], &t));
240 CHECK(absl::SimpleAtod(l[1 + node1_index], &o1));
241 CHECK(absl::SimpleAtod(l[1 + node2_index], &o2));
242 offsetfile_t.emplace_back(t);
243 offsetfile_o.emplace_back(o2 - o1);
244 }
245 return std::make_pair(offsetfile_t, offsetfile_o);
246}
247
Austin Schuh69d0b732022-07-20 21:19:32 -0700248void NodePlotter::AddNodes(std::string_view node1, std::string_view node2) {
249 const double offset1 = TimeOffset(node1);
250 const double offset2 = TimeOffset(node2);
251
252 std::pair<std::vector<double>, std::vector<double>> samplefile12 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700253 ReadSamples(node1, node2, false);
Austin Schuh69d0b732022-07-20 21:19:32 -0700254 std::pair<std::vector<double>, std::vector<double>> samplefile21 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700255 ReadSamples(node2, node1, true);
256
Austin Schuh69d0b732022-07-20 21:19:32 -0700257 std::pair<std::vector<double>, std::vector<double>> noncausalfile12 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700258 ReadLines(node1, node2, false);
Austin Schuh69d0b732022-07-20 21:19:32 -0700259 std::pair<std::vector<double>, std::vector<double>> noncausalfile21 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700260 ReadLines(node2, node1, true);
261
Austin Schuh69d0b732022-07-20 21:19:32 -0700262 std::pair<std::vector<double>, std::vector<double>> offsetfile =
Austin Schuh59d93f42022-07-18 16:52:32 -0700263 ReadOffset(node1, node2);
264
Austin Schuh69d0b732022-07-20 21:19:32 -0700265 Offset(&samplefile12.second, offset2 - offset1);
266 Offset(&samplefile21.second, offset2 - offset1);
267 Offset(&noncausalfile12.second, offset2 - offset1);
268 Offset(&noncausalfile21.second, offset2 - offset1);
269 Offset(&offsetfile.second, offset2 - offset1);
270
Austin Schuh59d93f42022-07-18 16:52:32 -0700271 CHECK_EQ(samplefile12.first.size(), samplefile12.second.size());
272 CHECK_EQ(samplefile21.first.size(), samplefile21.second.size());
273 CHECK_EQ(noncausalfile12.first.size(), noncausalfile12.second.size());
274 CHECK_EQ(noncausalfile21.first.size(), noncausalfile21.second.size());
275
276 LOG(INFO) << samplefile12.first.size() + samplefile21.first.size() +
277 noncausalfile12.first.size() + noncausalfile21.first.size()
278 << " points";
Austin Schuh59d93f42022-07-18 16:52:32 -0700279
Austin Schuh69d0b732022-07-20 21:19:32 -0700280 plotter_.AddLine(offsetfile.first, offsetfile.second,
Austin Schuh59d93f42022-07-18 16:52:32 -0700281 Plotter::LineOptions{
282 .label = absl::StrCat("filter ", node2, " ", node1),
283 // TODO(austin): roboRIO compiler wants all the fields
284 // filled out, but other compilers don't... Sigh.
285 .line_style = "*-",
Austin Schuh69d0b732022-07-20 21:19:32 -0700286 .color = "yellow",
287 .point_size = 2.0});
288
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700289 if (absl::GetFlag(FLAGS_samples)) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700290 plotter_.AddLine(samplefile12.first, samplefile12.second,
291 Plotter::LineOptions{
292 .label = absl::StrCat("sample ", node1, " ", node2),
293 .line_style = "*",
294 .color = "purple",
295 });
296 plotter_.AddLine(samplefile21.first, samplefile21.second,
297 Plotter::LineOptions{
298 .label = absl::StrCat("sample ", node2, " ", node1),
299 .line_style = "*",
300 .color = "green",
301 });
302 }
303
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700304 if (absl::GetFlag(FLAGS_bounds)) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700305 plotter_.AddLine(
306 noncausalfile12.first, noncausalfile12.second,
307 Plotter::LineOptions{.label = absl::StrCat("nc ", node1, " ", node2),
308 .line_style = "-",
309 .color = "blue"});
310 plotter_.AddLine(
311 noncausalfile21.first, noncausalfile21.second,
312 Plotter::LineOptions{.label = absl::StrCat("nc ", node2, " ", node1),
313 .line_style = "-",
314 .color = "orange"});
315 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700316}
317
318int Main(int argc, const char *const *argv) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700319 NodePlotter plotter;
Austin Schuh59d93f42022-07-18 16:52:32 -0700320
Austin Schuh99f7c6a2024-06-25 22:07:44 -0700321 if (absl::GetFlag(FLAGS_all)) {
Austin Schuh2c899242023-02-17 12:19:57 -0800322 const std::vector<std::pair<std::string, std::string>> connections =
323 NodeConnections();
324 for (std::pair<std::string, std::string> ab : connections) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700325 plotter.AddNodes(ab.first, ab.second);
326 }
Austin Schuh2c899242023-02-17 12:19:57 -0800327 if (connections.size() == 0) {
328 LOG(WARNING) << "No connections found, is something wrong?";
329 }
Austin Schuh69d0b732022-07-20 21:19:32 -0700330 } else {
331 CHECK_EQ(argc, 3);
Austin Schuh59d93f42022-07-18 16:52:32 -0700332
Austin Schuh69d0b732022-07-20 21:19:32 -0700333 LOG(INFO) << argv[1];
334 LOG(INFO) << argv[2];
Austin Schuh59d93f42022-07-18 16:52:32 -0700335
Austin Schuh69d0b732022-07-20 21:19:32 -0700336 const std::string_view node1 = argv[1];
337 const std::string_view node2 = argv[2];
Austin Schuh59d93f42022-07-18 16:52:32 -0700338
Austin Schuh69d0b732022-07-20 21:19:32 -0700339 plotter.AddNodes(node1, node2);
340 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700341
Austin Schuh69d0b732022-07-20 21:19:32 -0700342 plotter.Serve();
Austin Schuh59d93f42022-07-18 16:52:32 -0700343
344 return 0;
345}
346
347} // namespace aos
348
349int main(int argc, char **argv) {
350 aos::InitGoogle(&argc, &argv);
351
352 aos::Main(argc, argv);
353}