blob: ddc8381f849f998c1bfeeaa57510f9afd98ffc5a [file] [log] [blame]
Austin Schuh59d93f42022-07-18 16:52:32 -07001#include "absl/strings/str_cat.h"
2#include "absl/strings/str_split.h"
3#include "aos/init.h"
4#include "aos/util/file.h"
5#include "frc971/analysis/in_process_plotter.h"
6
7using frc971::analysis::Plotter;
8
Austin Schuh69d0b732022-07-20 21:19:32 -07009DEFINE_bool(all, false, "If true, plot *all* the nodes at once");
10DEFINE_bool(bounds, false, "If true, plot the noncausal bounds too.");
11DEFINE_bool(samples, true, "If true, plot the samples too.");
12
13DEFINE_string(offsets, "",
14 "Offsets to add to the monotonic clock for each node. Use the "
15 "format of node=offset,node=offest");
16
Austin Schuh59d93f42022-07-18 16:52:32 -070017// Simple C++ application to read the CSV files and use the in process plotter
18// to plot them. This smokes the pants off gnuplot in terms of interactivity.
19
20namespace aos {
21
Austin Schuh69d0b732022-07-20 21:19:32 -070022// Returns all the nodes.
23std::vector<std::string> Nodes() {
24 const std::string start_time_file = aos::util::ReadFileToStringOrDie(
25 "/tmp/timestamp_noncausal_starttime.csv");
26 std::vector<std::string_view> nodes = absl::StrSplit(start_time_file, '\n');
27
28 std::vector<std::string> formatted_nodes;
29 for (const std::string_view n : nodes) {
30 if (n == "") {
31 continue;
32 }
33
34 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
35 CHECK_EQ(l.size(), 2u) << "'" << n << "'";
36 formatted_nodes.emplace_back(l[0]);
37 }
38
39 return formatted_nodes;
40}
41
42std::string SampleFile(std::string_view node1, std::string_view node2) {
43 return absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2,
44 "_samples.csv");
45}
46
Austin Schuh59d93f42022-07-18 16:52:32 -070047std::pair<std::vector<double>, std::vector<double>> ReadSamples(
48 std::string_view node1, std::string_view node2, bool flip) {
49 std::vector<double> samplefile12_t;
50 std::vector<double> samplefile12_o;
Austin Schuh2c899242023-02-17 12:19:57 -080051 const std::string path = SampleFile(node1, node2);
Austin Schuh59d93f42022-07-18 16:52:32 -070052
Austin Schuh2c899242023-02-17 12:19:57 -080053 if (!aos::util::PathExists(path)) {
54 return {};
55 }
56
57 const std::string file = aos::util::ReadFileToStringOrDie(path);
Austin Schuh59d93f42022-07-18 16:52:32 -070058 bool first = true;
59 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
60 samplefile12_t.reserve(lines.size());
61 for (const std::string_view n : lines) {
62 if (first) {
63 first = false;
64 continue;
65 }
66 if (n == "") {
67 continue;
68 }
69
70 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
Austin Schuh8c2e8c72022-08-06 18:31:19 -070071 if (l.size() != 4u) {
72 continue;
73 }
Austin Schuh59d93f42022-07-18 16:52:32 -070074 double t;
75 double o;
76 CHECK(absl::SimpleAtod(l[0], &t));
77 CHECK(absl::SimpleAtod(l[1], &o));
78 samplefile12_t.emplace_back(t);
79 samplefile12_o.emplace_back(flip ? -o : o);
80 }
81 return std::make_pair(samplefile12_t, samplefile12_o);
82}
83
Austin Schuh69d0b732022-07-20 21:19:32 -070084void Offset(std::vector<double> *v, double offset) {
85 for (double &x : *v) {
86 x += offset;
87 }
88}
89
90// Returns all the nodes which talk to each other.
91std::vector<std::pair<std::string, std::string>> NodeConnections() {
92 const std::vector<std::string> nodes = Nodes();
93 std::vector<std::pair<std::string, std::string>> result;
94 for (size_t i = 1; i < nodes.size(); ++i) {
95 for (size_t j = 0; j < i; ++j) {
96 const std::string_view node1 = nodes[j];
97 const std::string_view node2 = nodes[i];
Austin Schuh2c899242023-02-17 12:19:57 -080098 if (aos::util::PathExists(SampleFile(node1, node2)) ||
99 aos::util::PathExists(SampleFile(node2, node1))) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700100 result.emplace_back(node1, node2);
101 LOG(INFO) << "Found pairing " << node1 << ", " << node2;
102 }
103 }
104 }
105 return result;
106}
107
108// Class to encapsulate the plotter state to make it easy to plot multiple
109// connections.
110class NodePlotter {
111 public:
112 NodePlotter() : nodes_(Nodes()) {
113 plotter_.AddFigure("Time");
114 if (!FLAGS_offsets.empty()) {
115 for (std::string_view nodeoffset : absl::StrSplit(FLAGS_offsets, ',')) {
116 std::vector<std::string_view> node_offset =
117 absl::StrSplit(nodeoffset, '=');
118 CHECK_EQ(node_offset.size(), 2u);
119 double o;
120 CHECK(absl::SimpleAtod(node_offset[1], &o));
121 offset_.emplace(std::string(node_offset[0]), o);
122 }
123 }
124 }
125
126 void AddNodes(std::string_view node1, std::string_view node2);
127
128 void Serve() {
129 plotter_.Publish();
130 plotter_.Spin();
131 }
132
133 private:
134 std::pair<std::vector<double>, std::vector<double>> ReadLines(
135 std::string_view node1, std::string_view node2, bool flip);
136
137 std::pair<std::vector<double>, std::vector<double>> ReadOffset(
138 std::string_view node1, std::string_view node2);
139
140 double TimeOffset(std::string_view node) {
141 auto it = offset_.find(std::string(node));
142 if (it == offset_.end()) {
143 return 0.0;
144 } else {
145 return it->second;
146 }
147 }
148
149 std::map<std::string, double> offset_;
150
151 Plotter plotter_;
152
153 std::vector<std::string> nodes_;
154};
155
156std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadLines(
Austin Schuh59d93f42022-07-18 16:52:32 -0700157 std::string_view node1, std::string_view node2, bool flip) {
158 std::vector<double> samplefile12_t;
159 std::vector<double> samplefile12_o;
Austin Schuh2c899242023-02-17 12:19:57 -0800160 const std::string path = absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2, ".csv");
Austin Schuh59d93f42022-07-18 16:52:32 -0700161
Austin Schuh2c899242023-02-17 12:19:57 -0800162 if (!aos::util::PathExists(path)) {
163 return {};
164 }
165
166 const std::string file = aos::util::ReadFileToStringOrDie(path);
Austin Schuh59d93f42022-07-18 16:52:32 -0700167 bool first = true;
168 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
169 samplefile12_t.reserve(lines.size());
170 for (const std::string_view n : lines) {
171 if (first) {
172 first = false;
173 continue;
174 }
175 if (n == "") {
176 continue;
177 }
178
179 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
Austin Schuh8c2e8c72022-08-06 18:31:19 -0700180 if (l.size() != 3u) {
181 continue;
182 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700183 double t;
184 double o;
185 CHECK(absl::SimpleAtod(l[0], &t));
186 CHECK(absl::SimpleAtod(l[2], &o));
187 samplefile12_t.emplace_back(t);
188 samplefile12_o.emplace_back(flip ? -o : o);
189 }
190 return std::make_pair(samplefile12_t, samplefile12_o);
191}
192
Austin Schuh69d0b732022-07-20 21:19:32 -0700193std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadOffset(
Austin Schuh59d93f42022-07-18 16:52:32 -0700194 std::string_view node1, std::string_view node2) {
195 int node1_index = -1;
196 int node2_index = -1;
197
198 {
Austin Schuh59d93f42022-07-18 16:52:32 -0700199 int index = 0;
Austin Schuh69d0b732022-07-20 21:19:32 -0700200 for (const std::string &n : nodes_) {
201 if (n == node1) {
Austin Schuh59d93f42022-07-18 16:52:32 -0700202 node1_index = index;
203 }
Austin Schuh69d0b732022-07-20 21:19:32 -0700204 if (n == node2) {
Austin Schuh59d93f42022-07-18 16:52:32 -0700205 node2_index = index;
206 }
207 ++index;
208 }
209 }
210 CHECK_NE(node1_index, -1) << ": Unknown node " << node1;
211 CHECK_NE(node2_index, -1) << ": Unknown node " << node2;
212 std::vector<double> offsetfile_t;
213 std::vector<double> offsetfile_o;
214
215 const std::string file =
216 aos::util::ReadFileToStringOrDie("/tmp/timestamp_noncausal_offsets.csv");
217 bool first = true;
218 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
219 offsetfile_t.reserve(lines.size());
220 for (const std::string_view n : lines) {
221 if (first) {
222 first = false;
223 continue;
224 }
225 if (n == "") {
226 continue;
227 }
228
229 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
230 CHECK_LT(static_cast<size_t>(node1_index + 1), l.size());
231 CHECK_LT(static_cast<size_t>(node2_index + 1), l.size());
232 double t;
233 double o1;
234 double o2;
235 CHECK(absl::SimpleAtod(l[0], &t));
236 CHECK(absl::SimpleAtod(l[1 + node1_index], &o1));
237 CHECK(absl::SimpleAtod(l[1 + node2_index], &o2));
238 offsetfile_t.emplace_back(t);
239 offsetfile_o.emplace_back(o2 - o1);
240 }
241 return std::make_pair(offsetfile_t, offsetfile_o);
242}
243
Austin Schuh69d0b732022-07-20 21:19:32 -0700244void NodePlotter::AddNodes(std::string_view node1, std::string_view node2) {
245 const double offset1 = TimeOffset(node1);
246 const double offset2 = TimeOffset(node2);
247
248 std::pair<std::vector<double>, std::vector<double>> samplefile12 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700249 ReadSamples(node1, node2, false);
Austin Schuh69d0b732022-07-20 21:19:32 -0700250 std::pair<std::vector<double>, std::vector<double>> samplefile21 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700251 ReadSamples(node2, node1, true);
252
Austin Schuh69d0b732022-07-20 21:19:32 -0700253 std::pair<std::vector<double>, std::vector<double>> noncausalfile12 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700254 ReadLines(node1, node2, false);
Austin Schuh69d0b732022-07-20 21:19:32 -0700255 std::pair<std::vector<double>, std::vector<double>> noncausalfile21 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700256 ReadLines(node2, node1, true);
257
Austin Schuh69d0b732022-07-20 21:19:32 -0700258 std::pair<std::vector<double>, std::vector<double>> offsetfile =
Austin Schuh59d93f42022-07-18 16:52:32 -0700259 ReadOffset(node1, node2);
260
Austin Schuh69d0b732022-07-20 21:19:32 -0700261 Offset(&samplefile12.second, offset2 - offset1);
262 Offset(&samplefile21.second, offset2 - offset1);
263 Offset(&noncausalfile12.second, offset2 - offset1);
264 Offset(&noncausalfile21.second, offset2 - offset1);
265 Offset(&offsetfile.second, offset2 - offset1);
266
Austin Schuh59d93f42022-07-18 16:52:32 -0700267 CHECK_EQ(samplefile12.first.size(), samplefile12.second.size());
268 CHECK_EQ(samplefile21.first.size(), samplefile21.second.size());
269 CHECK_EQ(noncausalfile12.first.size(), noncausalfile12.second.size());
270 CHECK_EQ(noncausalfile21.first.size(), noncausalfile21.second.size());
271
272 LOG(INFO) << samplefile12.first.size() + samplefile21.first.size() +
273 noncausalfile12.first.size() + noncausalfile21.first.size()
274 << " points";
Austin Schuh59d93f42022-07-18 16:52:32 -0700275
Austin Schuh69d0b732022-07-20 21:19:32 -0700276 plotter_.AddLine(offsetfile.first, offsetfile.second,
Austin Schuh59d93f42022-07-18 16:52:32 -0700277 Plotter::LineOptions{
278 .label = absl::StrCat("filter ", node2, " ", node1),
279 // TODO(austin): roboRIO compiler wants all the fields
280 // filled out, but other compilers don't... Sigh.
281 .line_style = "*-",
Austin Schuh69d0b732022-07-20 21:19:32 -0700282 .color = "yellow",
283 .point_size = 2.0});
284
285 if (FLAGS_samples) {
286 plotter_.AddLine(samplefile12.first, samplefile12.second,
287 Plotter::LineOptions{
288 .label = absl::StrCat("sample ", node1, " ", node2),
289 .line_style = "*",
290 .color = "purple",
291 });
292 plotter_.AddLine(samplefile21.first, samplefile21.second,
293 Plotter::LineOptions{
294 .label = absl::StrCat("sample ", node2, " ", node1),
295 .line_style = "*",
296 .color = "green",
297 });
298 }
299
300 if (FLAGS_bounds) {
301 plotter_.AddLine(
302 noncausalfile12.first, noncausalfile12.second,
303 Plotter::LineOptions{.label = absl::StrCat("nc ", node1, " ", node2),
304 .line_style = "-",
305 .color = "blue"});
306 plotter_.AddLine(
307 noncausalfile21.first, noncausalfile21.second,
308 Plotter::LineOptions{.label = absl::StrCat("nc ", node2, " ", node1),
309 .line_style = "-",
310 .color = "orange"});
311 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700312}
313
314int Main(int argc, const char *const *argv) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700315 NodePlotter plotter;
Austin Schuh59d93f42022-07-18 16:52:32 -0700316
Austin Schuh69d0b732022-07-20 21:19:32 -0700317 if (FLAGS_all) {
Austin Schuh2c899242023-02-17 12:19:57 -0800318 const std::vector<std::pair<std::string, std::string>> connections =
319 NodeConnections();
320 for (std::pair<std::string, std::string> ab : connections) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700321 plotter.AddNodes(ab.first, ab.second);
322 }
Austin Schuh2c899242023-02-17 12:19:57 -0800323 if (connections.size() == 0) {
324 LOG(WARNING) << "No connections found, is something wrong?";
325 }
Austin Schuh69d0b732022-07-20 21:19:32 -0700326 } else {
327 CHECK_EQ(argc, 3);
Austin Schuh59d93f42022-07-18 16:52:32 -0700328
Austin Schuh69d0b732022-07-20 21:19:32 -0700329 LOG(INFO) << argv[1];
330 LOG(INFO) << argv[2];
Austin Schuh59d93f42022-07-18 16:52:32 -0700331
Austin Schuh69d0b732022-07-20 21:19:32 -0700332 const std::string_view node1 = argv[1];
333 const std::string_view node2 = argv[2];
Austin Schuh59d93f42022-07-18 16:52:32 -0700334
Austin Schuh69d0b732022-07-20 21:19:32 -0700335 plotter.AddNodes(node1, node2);
336 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700337
Austin Schuh69d0b732022-07-20 21:19:32 -0700338 plotter.Serve();
Austin Schuh59d93f42022-07-18 16:52:32 -0700339
340 return 0;
341}
342
343} // namespace aos
344
345int main(int argc, char **argv) {
346 aos::InitGoogle(&argc, &argv);
347
348 aos::Main(argc, argv);
349}