diff --git a/common/include/villas/table.hpp b/common/include/villas/table.hpp index 555dd3141..5ef77ec81 100644 --- a/common/include/villas/table.hpp +++ b/common/include/villas/table.hpp @@ -10,6 +10,8 @@ #include #include +#include + #include namespace villas { @@ -26,12 +28,14 @@ class TableColumn { protected: int _width; // The real width of this column. Calculated by Table::resize(). - int width; // Width of the column. + int width; // Width of the column. + int precision; // Precision of the column, used for floating point values. public: - TableColumn(int w, enum Alignment a, const std::string &t, + TableColumn(int w, int p, enum Alignment a, const std::string &t, const std::string &f, const std::string &u = "") - : _width(0), width(w), title(t), format(f), unit(u), align(a) {} + : _width(0), width(w), precision(p), title(t), format(f), unit(u), + align(a) {} std::string title; // The title as shown in the table header. std::string format; // The format which is used to print the table rows. @@ -46,10 +50,11 @@ class Table { protected: int resize(int w); + void updateRowFormat(); int width; - std::vector columns; + std::string rowFormat; Logger logger; @@ -61,7 +66,15 @@ class Table { void header(); // Print table rows. - void row(int count, ...); + template void row(const Args &...args) { + auto logWidth = Log::getInstance().getWidth(); + if (width != logWidth) { + resize(logWidth); + header(); + } + + logger->info(fmt::runtime(rowFormat), args...); + } int getWidth() const { return width; } }; diff --git a/common/lib/hist.cpp b/common/lib/hist.cpp index c18c13519..de1a2278e 100644 --- a/common/lib/hist.cpp +++ b/common/lib/hist.cpp @@ -129,9 +129,9 @@ void Hist::plot(Logger logger) const { Hist::cnt_t max = *std::max_element(data.begin(), data.end()); std::vector cols = { - {-9, TableColumn::Alignment::RIGHT, "Value", "%+9.3g"}, - {-6, TableColumn::Alignment::RIGHT, "Count", "%6ju"}, - {0, TableColumn::Alignment::LEFT, "Plot", "%s", "occurrences"}}; + {-9, 3, TableColumn::Alignment::RIGHT, "Value", "g"}, + {-6, -1, TableColumn::Alignment::RIGHT, "Count", "d"}, + {0, -1, TableColumn::Alignment::LEFT, "Plot", "s", "occurrences"}}; Table table = Table(logger, cols); @@ -147,7 +147,7 @@ void Hist::plot(Logger logger) const { for (int i = 0; i < bar; i++) buf = strcatf(&buf, "\u2588"); - table.row(3, value, cnt, buf); + table.row(value, cnt, buf); free(buf); } diff --git a/common/lib/log.cpp b/common/lib/log.cpp index c1536b6e1..572a5065a 100644 --- a/common/lib/log.cpp +++ b/common/lib/log.cpp @@ -57,10 +57,15 @@ Log::Log(Level lvl) : level(lvl), pattern("%H:%M:%S %^%-4t%$ %-16n %v") { } int Log::getWidth() { - int width = Terminal::getCols() - 50; - - if (!prefix.empty()) - width -= prefix.length(); + int width = Terminal::getCols(); + + width -= 8; // Timestamp + width -= 1; // Space + width -= 4; // Level + width -= 1; // Space + width -= 16; // Name + width -= 1; // Space + width -= prefix.length(); return width; } diff --git a/common/lib/table.cpp b/common/lib/table.cpp index 8c1b50002..eb727366f 100644 --- a/common/lib/table.cpp +++ b/common/lib/table.cpp @@ -25,118 +25,116 @@ using namespace villas::utils; #endif int Table::resize(int w) { - int norm, flex, fixed, total; - width = w; - norm = 0; - flex = 0; - fixed = 0; - total = width - columns.size() * 2; + float norm = 0; + float flex = 0; + float fixed = 0; + float total = width - columns.size() * 3; // Column separators and whitespace // Normalize width - for (unsigned i = 0; i < columns.size(); i++) { - if (columns[i].width > 0) - norm += columns[i].width; - if (columns[i].width == 0) + for (auto &column : columns) { + if (column.width > 0) { + norm += column.width; + } else if (column.width == 0) { flex++; - if (columns[i].width < 0) - fixed += -1 * columns[i].width; + } else if (column.width < 0) { + fixed += -1 * column.width; + } } - for (unsigned i = 0; i < columns.size(); i++) { - if (columns[i].width > 0) - columns[i]._width = columns[i].width * (float)(total - fixed) / norm; - if (columns[i].width == 0) - columns[i]._width = (float)(total - fixed) / flex; - if (columns[i].width < 0) - columns[i]._width = -1 * columns[i].width; + int total_used = 0; + + for (auto &column : columns) { + if (column.width > 0) { + column._width = column.width * (total - fixed) / norm; + } else if (column.width == 0) { + column._width = (total - fixed) / flex; + } else if (column.width < 0) { + column._width = -1 * column.width; + } + + total_used += column._width; } + // Distribute remaining space over flex columns + for (auto &column : columns) { + if (total_used >= total) { + break; + } + + if (column.width >= 0) { + column._width++; + total_used++; + } + } + + updateRowFormat(); + return 0; } -void Table::header() { - if (width != Log::getInstance().getWidth()) - resize(Log::getInstance().getWidth()); +void Table::updateRowFormat() { + rowFormat.clear(); - char *line1 = nullptr; - char *line2 = nullptr; - char *line3 = nullptr; + for (unsigned i = 0; i < columns.size(); ++i) { + auto &column = columns[i]; - for (unsigned i = 0; i < columns.size(); i++) { - int w, u; - char *col, *unit; - - col = strf(CLR_BLD("%s"), columns[i].title.c_str()); - unit = columns[i].unit.size() ? strf(CLR_YEL("%s"), columns[i].unit.c_str()) - : strf(""); - - w = columns[i]._width + strlen(col) - strlenp(col); - u = columns[i]._width + strlen(unit) - strlenp(unit); - - if (columns[i].align == TableColumn::Alignment::LEFT) { - strcatf(&line1, " %-*.*s" ANSI_RESET, w, w, col); - strcatf(&line2, " %-*.*s" ANSI_RESET, u, u, unit); - } else { - strcatf(&line1, " %*.*s" ANSI_RESET, w, w, col); - strcatf(&line2, " %*.*s" ANSI_RESET, u, u, unit); - } + rowFormat += " {:"; + rowFormat += column.align == TableColumn::Alignment::LEFT ? "<" : ">"; + rowFormat += std::to_string(column._width); - for (int j = 0; j < columns[i]._width + 2; j++) { - strcatf(&line3, "%s", BOX_LR); + if (column.precision >= 0) { + rowFormat += fmt::format(".{}", column.precision); + } else if (column.format == "s") { + rowFormat += fmt::format(".{}", column._width); } + rowFormat += column.format; + rowFormat += "}"; + if (i != columns.size() - 1) { - strcatf(&line1, " %s", BOX_UD); - strcatf(&line2, " %s", BOX_UD); - strcatf(&line3, "%s", BOX_UDLR); + rowFormat += BOX_UD; } - - free(col); - free(unit); } - - logger->info("{}", line1); - logger->info("{}", line2); - logger->info("{}", line3); - - free(line1); - free(line2); - free(line3); } -void Table::row(int count, ...) { - if (width != Log::getInstance().getWidth()) { - resize(Log::getInstance().getWidth()); - header(); +void Table::header() { + auto logWidth = Log::getInstance().getWidth(); + if (width != logWidth) { + resize(logWidth); } - va_list args; - va_start(args, count); - - char *line = nullptr; - - for (unsigned i = 0; i < columns.size(); ++i) { - char *col = vstrf(columns[i].format.c_str(), args); - - int l = strlenp(col); - int r = strlen(col); - int w = columns[i]._width + r - l; + std::string line1; + std::string line2; + std::string line3; + line1.reserve(width); + line2.reserve(width); + line3.reserve(width); - if (columns[i].align == TableColumn::Alignment::LEFT) - strcatf(&line, " %-*.*s " ANSI_RESET, w, w, col); - else - strcatf(&line, " %*.*s " ANSI_RESET, w, w, col); - - if (i != columns.size() - 1) - strcatf(&line, BOX_UD); + for (unsigned i = 0; i < columns.size(); i++) { + auto &column = columns[i]; + + auto leftAligned = column.align == TableColumn::Alignment::LEFT; + line1 += fmt::format(leftAligned ? CLR_BLD(" {0:<{1}.{1}s}") + : CLR_BLD(" {0:>{1}.{1}s}"), + column.title, column._width); + line2 += fmt::format(leftAligned ? CLR_YEL(" {0:<{1}.{1}s}") + : CLR_YEL(" {0:>{1}.{1}s}"), + column.unit, column._width); + + for (int j = 0; j < column._width + 2; j++) { + line3 += BOX_LR; + } - free(col); + if (i != columns.size() - 1) { + line1 += " " BOX_UD; + line2 += " " BOX_UD; + line3 += BOX_UDLR; + } } - va_end(args); - - logger->info("{}", line); - free(line); + logger->info("{}", line1); + logger->info("{}", line2); + logger->info("{}", line3); } diff --git a/etc/examples/hooks/stats.conf b/etc/examples/hooks/stats.conf index 983498f1a..53142e269 100644 --- a/etc/examples/hooks/stats.conf +++ b/etc/examples/hooks/stats.conf @@ -1,28 +1,31 @@ # SPDX-FileCopyrightText: 2014-2023 Institute for Automation of Complex Power Systems, RWTH Aachen University # SPDX-License-Identifier: Apache-2.0 +stats = 1 + nodes = { - udp_node = { - type = "socket" + signal_node = { + type = "signal" + signal = "mixed" + values = 5 + rate = 50 in = { - address = "*:12000" - hooks = ( { type = "stats" verbose = true - warmup = 100 + warmup = 10 buckets = 25 - - output = "stats.log" - format = "json" } ) } - out = { - address = "127.0.0.1:12000" - } } } + +paths = ( + { + in = "signal_node" + } +) diff --git a/lib/stats.cpp b/lib/stats.cpp index 7a0aa780c..c92d3da05 100644 --- a/lib/stats.cpp +++ b/lib/stats.cpp @@ -51,18 +51,18 @@ std::unordered_map Stats::types = { {Stats::Type::TOTAL, {"total", SignalType::INTEGER}}}; std::vector Stats::columns = { - {10, TableColumn::Alignment::LEFT, "Node", "%s"}, - {10, TableColumn::Alignment::RIGHT, "Recv", "%ju", "pkts"}, - {10, TableColumn::Alignment::RIGHT, "Sent", "%ju", "pkts"}, - {10, TableColumn::Alignment::RIGHT, "Drop", "%ju", "pkts"}, - {10, TableColumn::Alignment::RIGHT, "Skip", "%ju", "pkts"}, - {10, TableColumn::Alignment::RIGHT, "OWD last", "%lf", "secs"}, - {10, TableColumn::Alignment::RIGHT, "OWD mean", "%lf", "secs"}, - {10, TableColumn::Alignment::RIGHT, "Rate last", "%lf", "pkt/sec"}, - {10, TableColumn::Alignment::RIGHT, "Rate mean", "%lf", "pkt/sec"}, - {10, TableColumn::Alignment::RIGHT, "Age mean", "%lf", "secs"}, - {10, TableColumn::Alignment::RIGHT, "Age Max", "%lf", "sec"}, - {8, TableColumn::Alignment::RIGHT, "Signals", "%ju", "signals"}}; + {10, -1, TableColumn::Alignment::LEFT, "Node", "s"}, + {10, -1, TableColumn::Alignment::RIGHT, "Recv", "d", "pkts"}, + {10, -1, TableColumn::Alignment::RIGHT, "Sent", "d", "pkts"}, + {10, -1, TableColumn::Alignment::RIGHT, "Drop", "d", "pkts"}, + {10, -1, TableColumn::Alignment::RIGHT, "Skip", "d", "pkts"}, + {10, -1, TableColumn::Alignment::RIGHT, "OWD last", "f", "secs"}, + {10, -1, TableColumn::Alignment::RIGHT, "OWD mean", "f", "secs"}, + {10, -1, TableColumn::Alignment::RIGHT, "Rate last", "f", "pkt/sec"}, + {10, -1, TableColumn::Alignment::RIGHT, "Rate mean", "f", "pkt/sec"}, + {10, -1, TableColumn::Alignment::RIGHT, "Age mean", "f", "secs"}, + {10, -1, TableColumn::Alignment::RIGHT, "Age Max", "f", "sec"}, + {-7, -1, TableColumn::Alignment::RIGHT, "Signals", "d", "signals"}}; enum Stats::Format Stats::lookupFormat(const std::string &str) { if (str == "human") @@ -142,7 +142,7 @@ void Stats::printPeriodic(FILE *f, enum Format fmt, node::Node *n) const { switch (fmt) { case Format::HUMAN: setupTable(); - table->row(11, n->getNameShort().c_str(), + table->row(n->getNameShort(), (uintmax_t)histograms.at(Metric::OWD).getTotal(), (uintmax_t)histograms.at(Metric::AGE).getTotal(), (uintmax_t)histograms.at(Metric::SMPS_REORDERED).getTotal(),