blob: 5b5cdc24b86937c583a4ac0082d59c7eec67e147 [file] [log] [blame]
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "aos/analysis/in_process_plotter.h"
#include "aos/init.h"
#include "aos/util/file.h"
using aos::analysis::Plotter;
DEFINE_bool(all, false, "If true, plot *all* the nodes at once");
DEFINE_bool(bounds, false, "If true, plot the noncausal bounds too.");
DEFINE_bool(samples, true, "If true, plot the samples too.");
DEFINE_string(offsets, "",
"Offsets to add to the monotonic clock for each node. Use the "
"format of node=offset,node=offest");
// Simple C++ application to read the CSV files and use the in process plotter
// to plot them. This smokes the pants off gnuplot in terms of interactivity.
namespace aos {
// Returns all the nodes.
std::vector<std::string> Nodes() {
const std::string start_time_file = aos::util::ReadFileToStringOrDie(
"/tmp/timestamp_noncausal_starttime.csv");
std::vector<std::string_view> nodes = absl::StrSplit(start_time_file, '\n');
std::vector<std::string> formatted_nodes;
for (const std::string_view n : nodes) {
if (n == "") {
continue;
}
std::vector<std::string_view> l = absl::StrSplit(n, ", ");
CHECK_EQ(l.size(), 2u) << "'" << n << "'";
formatted_nodes.emplace_back(l[0]);
}
return formatted_nodes;
}
std::string SampleFile(std::string_view node1, std::string_view node2) {
return absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2,
"_samples.csv");
}
std::pair<std::vector<double>, std::vector<double>> ReadSamples(
std::string_view node1, std::string_view node2, bool flip) {
std::vector<double> samplefile12_t;
std::vector<double> samplefile12_o;
const std::string path = SampleFile(node1, node2);
if (!aos::util::PathExists(path)) {
return {};
}
const std::string file = aos::util::ReadFileToStringOrDie(path);
bool first = true;
std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
samplefile12_t.reserve(lines.size());
for (const std::string_view n : lines) {
if (first) {
first = false;
continue;
}
if (n == "") {
continue;
}
std::vector<std::string_view> l = absl::StrSplit(n, ", ");
if (l.size() != 4u) {
continue;
}
double t;
double o;
CHECK(absl::SimpleAtod(l[0], &t));
CHECK(absl::SimpleAtod(l[1], &o));
samplefile12_t.emplace_back(t);
samplefile12_o.emplace_back(flip ? -o : o);
}
return std::make_pair(samplefile12_t, samplefile12_o);
}
void Offset(std::vector<double> *v, double offset) {
for (double &x : *v) {
x += offset;
}
}
// Returns all the nodes which talk to each other.
std::vector<std::pair<std::string, std::string>> NodeConnections() {
const std::vector<std::string> nodes = Nodes();
std::vector<std::pair<std::string, std::string>> result;
for (size_t i = 1; i < nodes.size(); ++i) {
for (size_t j = 0; j < i; ++j) {
const std::string_view node1 = nodes[j];
const std::string_view node2 = nodes[i];
if (aos::util::PathExists(SampleFile(node1, node2)) ||
aos::util::PathExists(SampleFile(node2, node1))) {
result.emplace_back(node1, node2);
LOG(INFO) << "Found pairing " << node1 << ", " << node2;
}
}
}
return result;
}
// Class to encapsulate the plotter state to make it easy to plot multiple
// connections.
class NodePlotter {
public:
NodePlotter() : nodes_(Nodes()) {
plotter_.AddFigure("Time");
if (!FLAGS_offsets.empty()) {
for (std::string_view nodeoffset : absl::StrSplit(FLAGS_offsets, ',')) {
std::vector<std::string_view> node_offset =
absl::StrSplit(nodeoffset, '=');
CHECK_EQ(node_offset.size(), 2u);
double o;
CHECK(absl::SimpleAtod(node_offset[1], &o));
offset_.emplace(std::string(node_offset[0]), o);
}
}
}
void AddNodes(std::string_view node1, std::string_view node2);
void Serve() {
plotter_.Publish();
plotter_.Spin();
}
private:
std::pair<std::vector<double>, std::vector<double>> ReadLines(
std::string_view node1, std::string_view node2, bool flip);
std::pair<std::vector<double>, std::vector<double>> ReadOffset(
std::string_view node1, std::string_view node2);
double TimeOffset(std::string_view node) {
auto it = offset_.find(std::string(node));
if (it == offset_.end()) {
return 0.0;
} else {
return it->second;
}
}
std::map<std::string, double> offset_;
Plotter plotter_;
std::vector<std::string> nodes_;
};
std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadLines(
std::string_view node1, std::string_view node2, bool flip) {
std::vector<double> samplefile12_t;
std::vector<double> samplefile12_o;
const std::string path =
absl::StrCat("/tmp/timestamp_noncausal_", node1, "_", node2, ".csv");
if (!aos::util::PathExists(path)) {
return {};
}
const std::string file = aos::util::ReadFileToStringOrDie(path);
bool first = true;
std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
samplefile12_t.reserve(lines.size());
for (const std::string_view n : lines) {
if (first) {
first = false;
continue;
}
if (n == "") {
continue;
}
std::vector<std::string_view> l = absl::StrSplit(n, ", ");
if (l.size() != 3u) {
continue;
}
double t;
double o;
CHECK(absl::SimpleAtod(l[0], &t));
CHECK(absl::SimpleAtod(l[2], &o));
samplefile12_t.emplace_back(t);
samplefile12_o.emplace_back(flip ? -o : o);
}
return std::make_pair(samplefile12_t, samplefile12_o);
}
std::pair<std::vector<double>, std::vector<double>> NodePlotter::ReadOffset(
std::string_view node1, std::string_view node2) {
int node1_index = -1;
int node2_index = -1;
{
int index = 0;
for (const std::string &n : nodes_) {
if (n == node1) {
node1_index = index;
}
if (n == node2) {
node2_index = index;
}
++index;
}
}
CHECK_NE(node1_index, -1) << ": Unknown node " << node1;
CHECK_NE(node2_index, -1) << ": Unknown node " << node2;
std::vector<double> offsetfile_t;
std::vector<double> offsetfile_o;
const std::string file =
aos::util::ReadFileToStringOrDie("/tmp/timestamp_noncausal_offsets.csv");
bool first = true;
std::vector<std::string_view> lines = absl::StrSplit(file, '\n');
offsetfile_t.reserve(lines.size());
for (const std::string_view n : lines) {
if (first) {
first = false;
continue;
}
if (n == "") {
continue;
}
std::vector<std::string_view> l = absl::StrSplit(n, ", ");
CHECK_LT(static_cast<size_t>(node1_index + 1), l.size());
CHECK_LT(static_cast<size_t>(node2_index + 1), l.size());
double t;
double o1;
double o2;
CHECK(absl::SimpleAtod(l[0], &t));
CHECK(absl::SimpleAtod(l[1 + node1_index], &o1));
CHECK(absl::SimpleAtod(l[1 + node2_index], &o2));
offsetfile_t.emplace_back(t);
offsetfile_o.emplace_back(o2 - o1);
}
return std::make_pair(offsetfile_t, offsetfile_o);
}
void NodePlotter::AddNodes(std::string_view node1, std::string_view node2) {
const double offset1 = TimeOffset(node1);
const double offset2 = TimeOffset(node2);
std::pair<std::vector<double>, std::vector<double>> samplefile12 =
ReadSamples(node1, node2, false);
std::pair<std::vector<double>, std::vector<double>> samplefile21 =
ReadSamples(node2, node1, true);
std::pair<std::vector<double>, std::vector<double>> noncausalfile12 =
ReadLines(node1, node2, false);
std::pair<std::vector<double>, std::vector<double>> noncausalfile21 =
ReadLines(node2, node1, true);
std::pair<std::vector<double>, std::vector<double>> offsetfile =
ReadOffset(node1, node2);
Offset(&samplefile12.second, offset2 - offset1);
Offset(&samplefile21.second, offset2 - offset1);
Offset(&noncausalfile12.second, offset2 - offset1);
Offset(&noncausalfile21.second, offset2 - offset1);
Offset(&offsetfile.second, offset2 - offset1);
CHECK_EQ(samplefile12.first.size(), samplefile12.second.size());
CHECK_EQ(samplefile21.first.size(), samplefile21.second.size());
CHECK_EQ(noncausalfile12.first.size(), noncausalfile12.second.size());
CHECK_EQ(noncausalfile21.first.size(), noncausalfile21.second.size());
LOG(INFO) << samplefile12.first.size() + samplefile21.first.size() +
noncausalfile12.first.size() + noncausalfile21.first.size()
<< " points";
plotter_.AddLine(offsetfile.first, offsetfile.second,
Plotter::LineOptions{
.label = absl::StrCat("filter ", node2, " ", node1),
// TODO(austin): roboRIO compiler wants all the fields
// filled out, but other compilers don't... Sigh.
.line_style = "*-",
.color = "yellow",
.point_size = 2.0});
if (FLAGS_samples) {
plotter_.AddLine(samplefile12.first, samplefile12.second,
Plotter::LineOptions{
.label = absl::StrCat("sample ", node1, " ", node2),
.line_style = "*",
.color = "purple",
});
plotter_.AddLine(samplefile21.first, samplefile21.second,
Plotter::LineOptions{
.label = absl::StrCat("sample ", node2, " ", node1),
.line_style = "*",
.color = "green",
});
}
if (FLAGS_bounds) {
plotter_.AddLine(
noncausalfile12.first, noncausalfile12.second,
Plotter::LineOptions{.label = absl::StrCat("nc ", node1, " ", node2),
.line_style = "-",
.color = "blue"});
plotter_.AddLine(
noncausalfile21.first, noncausalfile21.second,
Plotter::LineOptions{.label = absl::StrCat("nc ", node2, " ", node1),
.line_style = "-",
.color = "orange"});
}
}
int Main(int argc, const char *const *argv) {
NodePlotter plotter;
if (FLAGS_all) {
const std::vector<std::pair<std::string, std::string>> connections =
NodeConnections();
for (std::pair<std::string, std::string> ab : connections) {
plotter.AddNodes(ab.first, ab.second);
}
if (connections.size() == 0) {
LOG(WARNING) << "No connections found, is something wrong?";
}
} else {
CHECK_EQ(argc, 3);
LOG(INFO) << argv[1];
LOG(INFO) << argv[2];
const std::string_view node1 = argv[1];
const std::string_view node2 = argv[2];
plotter.AddNodes(node1, node2);
}
plotter.Serve();
return 0;
}
} // namespace aos
int main(int argc, char **argv) {
aos::InitGoogle(&argc, &argv);
aos::Main(argc, argv);
}