blob: 9c5f2dd256fdb6ba1bed9c7e8598baa6b7a59d60 [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;
51
Austin Schuh69d0b732022-07-20 21:19:32 -070052 const std::string file =
53 aos::util::ReadFileToStringOrDie(SampleFile(node1, node2));
Austin Schuh59d93f42022-07-18 16:52:32 -070054 bool first = true;
55 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
56 samplefile12_t.reserve(lines.size());
57 for (const std::string_view n : lines) {
58 if (first) {
59 first = false;
60 continue;
61 }
62 if (n == "") {
63 continue;
64 }
65
66 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
67 CHECK_EQ(l.size(), 4u);
68 double t;
69 double o;
70 CHECK(absl::SimpleAtod(l[0], &t));
71 CHECK(absl::SimpleAtod(l[1], &o));
72 samplefile12_t.emplace_back(t);
73 samplefile12_o.emplace_back(flip ? -o : o);
74 }
75 return std::make_pair(samplefile12_t, samplefile12_o);
76}
77
Austin Schuh69d0b732022-07-20 21:19:32 -070078void Offset(std::vector<double> *v, double offset) {
79 for (double &x : *v) {
80 x += offset;
81 }
82}
83
84// Returns all the nodes which talk to each other.
85std::vector<std::pair<std::string, std::string>> NodeConnections() {
86 const std::vector<std::string> nodes = Nodes();
87 std::vector<std::pair<std::string, std::string>> result;
88 for (size_t i = 1; i < nodes.size(); ++i) {
89 for (size_t j = 0; j < i; ++j) {
90 const std::string_view node1 = nodes[j];
91 const std::string_view node2 = nodes[i];
92 if (aos::util::PathExists(SampleFile(node1, node2))) {
93 result.emplace_back(node1, node2);
94 LOG(INFO) << "Found pairing " << node1 << ", " << node2;
95 }
96 }
97 }
98 return result;
99}
100
101// Class to encapsulate the plotter state to make it easy to plot multiple
102// connections.
103class NodePlotter {
104 public:
105 NodePlotter() : nodes_(Nodes()) {
106 plotter_.AddFigure("Time");
107 if (!FLAGS_offsets.empty()) {
108 for (std::string_view nodeoffset : absl::StrSplit(FLAGS_offsets, ',')) {
109 std::vector<std::string_view> node_offset =
110 absl::StrSplit(nodeoffset, '=');
111 CHECK_EQ(node_offset.size(), 2u);
112 double o;
113 CHECK(absl::SimpleAtod(node_offset[1], &o));
114 offset_.emplace(std::string(node_offset[0]), o);
115 }
116 }
117 }
118
119 void AddNodes(std::string_view node1, std::string_view node2);
120
121 void Serve() {
122 plotter_.Publish();
123 plotter_.Spin();
124 }
125
126 private:
127 std::pair<std::vector<double>, std::vector<double>> ReadLines(
128 std::string_view node1, std::string_view node2, bool flip);
129
130 std::pair<std::vector<double>, std::vector<double>> ReadOffset(
131 std::string_view node1, std::string_view node2);
132
133 double TimeOffset(std::string_view node) {
134 auto it = offset_.find(std::string(node));
135 if (it == offset_.end()) {
136 return 0.0;
137 } else {
138 return it->second;
139 }
140 }
141
142 std::map<std::string, double> offset_;
143
144 Plotter plotter_;
145
146 std::vector<std::string> nodes_;
147};
148
149std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadLines(
Austin Schuh59d93f42022-07-18 16:52:32 -0700150 std::string_view node1, std::string_view node2, bool flip) {
151 std::vector<double> samplefile12_t;
152 std::vector<double> samplefile12_o;
153
154 const std::string file = aos::util::ReadFileToStringOrDie(
155 absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2, ".csv"));
156 bool first = true;
157 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
158 samplefile12_t.reserve(lines.size());
159 for (const std::string_view n : lines) {
160 if (first) {
161 first = false;
162 continue;
163 }
164 if (n == "") {
165 continue;
166 }
167
168 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
169 CHECK_EQ(l.size(), 3u);
170 double t;
171 double o;
172 CHECK(absl::SimpleAtod(l[0], &t));
173 CHECK(absl::SimpleAtod(l[2], &o));
174 samplefile12_t.emplace_back(t);
175 samplefile12_o.emplace_back(flip ? -o : o);
176 }
177 return std::make_pair(samplefile12_t, samplefile12_o);
178}
179
Austin Schuh69d0b732022-07-20 21:19:32 -0700180std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadOffset(
Austin Schuh59d93f42022-07-18 16:52:32 -0700181 std::string_view node1, std::string_view node2) {
182 int node1_index = -1;
183 int node2_index = -1;
184
185 {
Austin Schuh59d93f42022-07-18 16:52:32 -0700186 int index = 0;
Austin Schuh69d0b732022-07-20 21:19:32 -0700187 for (const std::string &n : nodes_) {
188 if (n == node1) {
Austin Schuh59d93f42022-07-18 16:52:32 -0700189 node1_index = index;
190 }
Austin Schuh69d0b732022-07-20 21:19:32 -0700191 if (n == node2) {
Austin Schuh59d93f42022-07-18 16:52:32 -0700192 node2_index = index;
193 }
194 ++index;
195 }
196 }
197 CHECK_NE(node1_index, -1) << ": Unknown node " << node1;
198 CHECK_NE(node2_index, -1) << ": Unknown node " << node2;
199 std::vector<double> offsetfile_t;
200 std::vector<double> offsetfile_o;
201
202 const std::string file =
203 aos::util::ReadFileToStringOrDie("/tmp/timestamp_noncausal_offsets.csv");
204 bool first = true;
205 std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
206 offsetfile_t.reserve(lines.size());
207 for (const std::string_view n : lines) {
208 if (first) {
209 first = false;
210 continue;
211 }
212 if (n == "") {
213 continue;
214 }
215
216 std::vector<std::string_view> l = absl::StrSplit(n, ", ");
217 CHECK_LT(static_cast<size_t>(node1_index + 1), l.size());
218 CHECK_LT(static_cast<size_t>(node2_index + 1), l.size());
219 double t;
220 double o1;
221 double o2;
222 CHECK(absl::SimpleAtod(l[0], &t));
223 CHECK(absl::SimpleAtod(l[1 + node1_index], &o1));
224 CHECK(absl::SimpleAtod(l[1 + node2_index], &o2));
225 offsetfile_t.emplace_back(t);
226 offsetfile_o.emplace_back(o2 - o1);
227 }
228 return std::make_pair(offsetfile_t, offsetfile_o);
229}
230
Austin Schuh69d0b732022-07-20 21:19:32 -0700231void NodePlotter::AddNodes(std::string_view node1, std::string_view node2) {
232 const double offset1 = TimeOffset(node1);
233 const double offset2 = TimeOffset(node2);
234
235 std::pair<std::vector<double>, std::vector<double>> samplefile12 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700236 ReadSamples(node1, node2, false);
Austin Schuh69d0b732022-07-20 21:19:32 -0700237 std::pair<std::vector<double>, std::vector<double>> samplefile21 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700238 ReadSamples(node2, node1, true);
239
Austin Schuh69d0b732022-07-20 21:19:32 -0700240 std::pair<std::vector<double>, std::vector<double>> noncausalfile12 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700241 ReadLines(node1, node2, false);
Austin Schuh69d0b732022-07-20 21:19:32 -0700242 std::pair<std::vector<double>, std::vector<double>> noncausalfile21 =
Austin Schuh59d93f42022-07-18 16:52:32 -0700243 ReadLines(node2, node1, true);
244
Austin Schuh69d0b732022-07-20 21:19:32 -0700245 std::pair<std::vector<double>, std::vector<double>> offsetfile =
Austin Schuh59d93f42022-07-18 16:52:32 -0700246 ReadOffset(node1, node2);
247
Austin Schuh69d0b732022-07-20 21:19:32 -0700248 Offset(&samplefile12.second, offset2 - offset1);
249 Offset(&samplefile21.second, offset2 - offset1);
250 Offset(&noncausalfile12.second, offset2 - offset1);
251 Offset(&noncausalfile21.second, offset2 - offset1);
252 Offset(&offsetfile.second, offset2 - offset1);
253
Austin Schuh59d93f42022-07-18 16:52:32 -0700254 CHECK_EQ(samplefile12.first.size(), samplefile12.second.size());
255 CHECK_EQ(samplefile21.first.size(), samplefile21.second.size());
256 CHECK_EQ(noncausalfile12.first.size(), noncausalfile12.second.size());
257 CHECK_EQ(noncausalfile21.first.size(), noncausalfile21.second.size());
258
259 LOG(INFO) << samplefile12.first.size() + samplefile21.first.size() +
260 noncausalfile12.first.size() + noncausalfile21.first.size()
261 << " points";
Austin Schuh59d93f42022-07-18 16:52:32 -0700262
Austin Schuh69d0b732022-07-20 21:19:32 -0700263 plotter_.AddLine(offsetfile.first, offsetfile.second,
Austin Schuh59d93f42022-07-18 16:52:32 -0700264 Plotter::LineOptions{
265 .label = absl::StrCat("filter ", node2, " ", node1),
266 // TODO(austin): roboRIO compiler wants all the fields
267 // filled out, but other compilers don't... Sigh.
268 .line_style = "*-",
Austin Schuh69d0b732022-07-20 21:19:32 -0700269 .color = "yellow",
270 .point_size = 2.0});
271
272 if (FLAGS_samples) {
273 plotter_.AddLine(samplefile12.first, samplefile12.second,
274 Plotter::LineOptions{
275 .label = absl::StrCat("sample ", node1, " ", node2),
276 .line_style = "*",
277 .color = "purple",
278 });
279 plotter_.AddLine(samplefile21.first, samplefile21.second,
280 Plotter::LineOptions{
281 .label = absl::StrCat("sample ", node2, " ", node1),
282 .line_style = "*",
283 .color = "green",
284 });
285 }
286
287 if (FLAGS_bounds) {
288 plotter_.AddLine(
289 noncausalfile12.first, noncausalfile12.second,
290 Plotter::LineOptions{.label = absl::StrCat("nc ", node1, " ", node2),
291 .line_style = "-",
292 .color = "blue"});
293 plotter_.AddLine(
294 noncausalfile21.first, noncausalfile21.second,
295 Plotter::LineOptions{.label = absl::StrCat("nc ", node2, " ", node1),
296 .line_style = "-",
297 .color = "orange"});
298 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700299}
300
301int Main(int argc, const char *const *argv) {
Austin Schuh69d0b732022-07-20 21:19:32 -0700302 NodePlotter plotter;
Austin Schuh59d93f42022-07-18 16:52:32 -0700303
Austin Schuh69d0b732022-07-20 21:19:32 -0700304 if (FLAGS_all) {
305 for (std::pair<std::string, std::string> ab : NodeConnections()) {
306 plotter.AddNodes(ab.first, ab.second);
307 }
308 } else {
309 CHECK_EQ(argc, 3);
Austin Schuh59d93f42022-07-18 16:52:32 -0700310
Austin Schuh69d0b732022-07-20 21:19:32 -0700311 LOG(INFO) << argv[1];
312 LOG(INFO) << argv[2];
Austin Schuh59d93f42022-07-18 16:52:32 -0700313
Austin Schuh69d0b732022-07-20 21:19:32 -0700314 const std::string_view node1 = argv[1];
315 const std::string_view node2 = argv[2];
Austin Schuh59d93f42022-07-18 16:52:32 -0700316
Austin Schuh69d0b732022-07-20 21:19:32 -0700317 plotter.AddNodes(node1, node2);
318 }
Austin Schuh59d93f42022-07-18 16:52:32 -0700319
Austin Schuh69d0b732022-07-20 21:19:32 -0700320 plotter.Serve();
Austin Schuh59d93f42022-07-18 16:52:32 -0700321
322 return 0;
323}
324
325} // namespace aos
326
327int main(int argc, char **argv) {
328 aos::InitGoogle(&argc, &argv);
329
330 aos::Main(argc, argv);
331}