From c842914bc563f7f7a0bdb9cce1f6058f35eadd11 Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Sun, 23 Jun 2019 13:35:42 +0200 Subject: [PATCH] stats: port to C++ --- include/villas/mapping.h | 6 +- include/villas/node.h | 3 +- include/villas/nodes/stats.hpp | 6 +- include/villas/stats.h | 120 --------------- include/villas/stats.hpp | 132 ++++++++++++++++ lib/api/actions/nodes.cpp | 4 +- lib/api/actions/stats.cpp | 4 +- lib/hooks/stats.cpp | 49 +++--- lib/mapping.cpp | 16 +- lib/node.cpp | 3 +- lib/nodes/rtp.cpp | 9 +- lib/nodes/stats.cpp | 26 ++-- lib/signal.cpp | 3 +- lib/stats.cpp | 268 +++++++++++++++------------------ lib/super_node.cpp | 2 +- tests/unit/mapping.cpp | 10 +- 16 files changed, 318 insertions(+), 343 deletions(-) delete mode 100644 include/villas/stats.h create mode 100644 include/villas/stats.hpp diff --git a/include/villas/mapping.h b/include/villas/mapping.h index eb816b76b..6859109de 100644 --- a/include/villas/mapping.h +++ b/include/villas/mapping.h @@ -25,7 +25,7 @@ #include -#include +#include #include /* Forward declarations */ @@ -70,8 +70,8 @@ struct mapping_entry { } data; struct { - enum stats_metric metric; - enum stats_type type; + enum villas::Stats::Metric metric; + enum villas::Stats::Type type; } stats; struct { diff --git a/include/villas/node.h b/include/villas/node.h index 1b831ad72..955986912 100644 --- a/include/villas/node.h +++ b/include/villas/node.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #if defined(LIBNL3_ROUTE_FOUND) && defined(__linux__) @@ -67,7 +68,7 @@ struct node { uint64_t sequence; /**< This is a counter of received samples, in case the node-type does not generate sequence numbers itself. */ - struct stats *stats; /**< Statistic counters. This is a pointer to the statistic hooks private data. */ + villas::Stats *stats; /**< Statistic counters. This is a pointer to the statistic hooks private data. */ struct node_direction in, out; diff --git a/include/villas/nodes/stats.hpp b/include/villas/nodes/stats.hpp index 612c1ae03..27a507397 100644 --- a/include/villas/nodes/stats.hpp +++ b/include/villas/nodes/stats.hpp @@ -32,7 +32,7 @@ #include #include -#include +#include #include #include @@ -40,8 +40,8 @@ struct stats_node_signal { struct node *node; char *node_str; - enum stats_metric metric; - enum stats_type type; + enum villas::Stats::Metric metric; + enum villas::Stats::Type type; }; struct stats_node { diff --git a/include/villas/stats.h b/include/villas/stats.h deleted file mode 100644 index 231398f60..000000000 --- a/include/villas/stats.h +++ /dev/null @@ -1,120 +0,0 @@ -/** Statistic collection. - * - * @file - * @author Steffen Vogel - * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC - * @license GNU General Public License (version 3) - * - * VILLASnode - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - *********************************************************************************/ - -#pragma once - -#include -#include - -#include -#include -#include - -/* Forward declarations */ -struct sample; -struct node; - -enum stats_format { - STATS_FORMAT_HUMAN, - STATS_FORMAT_JSON, - STATS_FORMAT_MATLAB -}; - -enum stats_metric { - STATS_METRIC_INVALID = -1, - - STATS_METRIC_SMPS_SKIPPED, /**< Counter for skipped samples due to hooks. */ - STATS_METRIC_SMPS_REORDERED, /**< Counter for reordered samples. */ - - /* Timings */ - STATS_METRIC_GAP_SAMPLE, /**< Histogram for inter sample timestamps (as sent by remote). */ - STATS_METRIC_GAP_RECEIVED, /**< Histogram for inter sample arrival time (as seen by this instance). */ - STATS_METRIC_OWD, /**< Histogram for one-way-delay (OWD) of received samples. */ - STATS_METRIC_AGE, /**< Processing time of packets within VILLASnode. */ - - /* RTP metrics */ - STATS_METRIC_RTP_LOSS_FRACTION, /**< Fraction lost since last RTP SR/RR. */ - STATS_METRIC_RTP_PKTS_LOST, /**< Cumul. no. pkts lost. */ - STATS_METRIC_RTP_JITTER, /**< Interarrival jitter. */ - - /* Always last */ - STATS_METRIC_COUNT /**< Just here to have an updated number of statistics. */ -}; - -enum stats_type { - STATS_TYPE_INVALID = -1, - STATS_TYPE_LAST, - STATS_TYPE_HIGHEST, - STATS_TYPE_LOWEST, - STATS_TYPE_MEAN, - STATS_TYPE_VAR, - STATS_TYPE_STDDEV, - STATS_TYPE_TOTAL, - STATS_TYPE_COUNT -}; - -struct stats_metric_description { - const char *name; - enum stats_metric metric; - const char *unit; - const char *desc; -}; - -struct stats_type_description { - const char *name; - enum stats_type type; - enum signal_type signal_type; -}; - -struct stats { - enum state state; - villas::Hist histograms[STATS_METRIC_COUNT]; -}; - -extern struct stats_metric_description stats_metrics[]; -extern struct stats_type_description stats_types[]; - -int stats_lookup_format(const char *str); - -enum stats_metric stats_lookup_metric(const char *str); - -enum stats_type stats_lookup_type(const char *str); - -int stats_init(struct stats *s, int buckets, int warmup); - -int stats_destroy(struct stats *s); - -void stats_update(struct stats *s, enum stats_metric id, double val); - -json_t * stats_json(struct stats *s); - -void stats_reset(struct stats *s); - -void stats_print_header(enum stats_format fmt); - -void stats_print_periodic(struct stats *s, FILE *f, enum stats_format fmt, struct node *p); - -void stats_print(struct stats *s, FILE *f, enum stats_format fmt, int verbose); - -union signal_data stats_get_value(const struct stats *s, enum stats_metric sm, enum stats_type st); - diff --git a/include/villas/stats.hpp b/include/villas/stats.hpp new file mode 100644 index 000000000..65128b63f --- /dev/null +++ b/include/villas/stats.hpp @@ -0,0 +1,132 @@ +/** Statistic collection. + * + * @file + * @author Steffen Vogel + * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC + * @license GNU General Public License (version 3) + * + * VILLASnode + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *********************************************************************************/ + +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +/* Forward declarations */ +struct sample; +struct node; + +namespace villas { + +class Stats { + +public: + enum class Format { + HUMAN, + JSON, + MATLAB + }; + + enum class Metric { + SMPS_SKIPPED, /**< Counter for skipped samples due to hooks. */ + SMPS_REORDERED, /**< Counter for reordered samples. */ + + /* Timings */ + GAP_SAMPLE, /**< Histogram for inter sample timestamps (as sent by remote). */ + GAP_RECEIVED, /**< Histogram for inter sample arrival time (as seen by this instance). */ + OWD, /**< Histogram for one-way-delay (OWD) of received samples. */ + AGE, /**< Processing time of packets within VILLASnode. */ + + /* RTP metrics */ + RTP_LOSS_FRACTION, /**< Fraction lost since last RTP SR/RR. */ + RTP_PKTS_LOST, /**< Cumul. no. pkts lost. */ + RTP_JITTER /**< Interarrival jitter. */ + }; + + enum class Type { + LAST, + HIGHEST, + LOWEST, + MEAN, + VAR, + STDDEV, + TOTAL + }; + +protected: + std::unordered_map histograms; + + struct MetricDescription { + const char *name; + const char *unit; + const char *desc; + }; + + struct TypeDescription { + const char *name; + enum signal_type signal_type; + }; + + static std::shared_ptr table; + + static void setupTable(); + +public: + + Stats(int buckets, int warmup); + + static + enum Format lookupFormat(const std::string &str); + + static + enum Metric lookupMetric(const std::string &str); + + static + enum Type lookupType(const std::string &str); + + void update(enum Metric id, double val); + + void reset(); + + json_t * toJson() const; + + static + void printHeader(enum Format fmt); + + void printPeriodic(FILE *f, enum Format fmt, struct node *p) const; + + void print(FILE *f, enum Format fmt, int verbose) const; + + union signal_data getValue(enum Metric sm, enum Type st) const; + + const villas::Hist & getHistogram(enum Metric sm) const; + + static std::unordered_map metrics; + static std::unordered_map types; + static std::vector columns; +}; + +} /* namespace villas */ diff --git a/lib/api/actions/nodes.cpp b/lib/api/actions/nodes.cpp index d1b6f0359..1e81d7e7e 100644 --- a/lib/api/actions/nodes.cpp +++ b/lib/api/actions/nodes.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include @@ -58,7 +58,7 @@ public: ); if (n->stats) - json_object_set_new(json_node, "stats", stats_json(n->stats)); + json_object_set_new(json_node, "stats", n->stats->toJson()); /* Add all additional fields of node here. * This can be used for metadata */ diff --git a/lib/api/actions/stats.cpp b/lib/api/actions/stats.cpp index 1e251d18b..f2a2c6d4e 100644 --- a/lib/api/actions/stats.cpp +++ b/lib/api/actions/stats.cpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include #include @@ -58,7 +58,7 @@ public: if (n->stats) { if (reset) { - stats_reset(n->stats); + n->stats->reset(); info("Stats resetted for node %s", node_name(n)); } } diff --git a/lib/hooks/stats.cpp b/lib/hooks/stats.cpp index 258ddef5a..21708efa5 100644 --- a/lib/hooks/stats.cpp +++ b/lib/hooks/stats.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include @@ -53,11 +53,11 @@ public: virtual int process(sample *smp) { - stats *s = node->stats; + Stats *s = node->stats; timespec now = time_now(); - stats_update(s, STATS_METRIC_AGE, time_delta(&smp->ts.received, &now)); + s->update(Stats::Metric::AGE, time_delta(&smp->ts.received, &now)); return HOOK_OK; } @@ -96,22 +96,22 @@ public: virtual int process(sample *smp) { - stats *s = node->stats; + Stats *s = node->stats; if (last) { if (smp->flags & last->flags & SAMPLE_HAS_TS_RECEIVED) - stats_update(s, STATS_METRIC_GAP_RECEIVED, time_delta(&last->ts.received, &smp->ts.received)); + s->update(Stats::Metric::GAP_RECEIVED, time_delta(&last->ts.received, &smp->ts.received)); if (smp->flags & last->flags & SAMPLE_HAS_TS_ORIGIN) - stats_update(s, STATS_METRIC_GAP_SAMPLE, time_delta(&last->ts.origin, &smp->ts.origin)); + s->update(Stats::Metric::GAP_SAMPLE, time_delta(&last->ts.origin, &smp->ts.origin)); if ((smp->flags & SAMPLE_HAS_TS_ORIGIN) && (smp->flags & SAMPLE_HAS_TS_RECEIVED)) - stats_update(s, STATS_METRIC_OWD, time_delta(&smp->ts.origin, &smp->ts.received)); + s->update(Stats::Metric::OWD, time_delta(&smp->ts.origin, &smp->ts.received)); if (smp->flags & last->flags & SAMPLE_HAS_SEQUENCE) { int dist = smp->sequence - (int32_t) last->sequence; if (dist != 1) - stats_update(s, STATS_METRIC_SMPS_REORDERED, dist); + s->update(Stats::Metric::SMPS_REORDERED, dist); } } @@ -129,12 +129,12 @@ public: class StatsHook : public Hook { protected: - struct stats stats; + Stats stats; StatsReadHook *readHook; StatsWriteHook *writeHook; - enum stats_format format; + enum Stats::Format format; int verbose; int warmup; int buckets; @@ -146,20 +146,14 @@ public: StatsHook(struct path *p, struct node *n, int fl, int prio, bool en = true) : Hook(p, n, fl, prio, en), - format(STATS_FORMAT_HUMAN), + stats(buckets, warmup), + format(Stats::Format::HUMAN), verbose(0), warmup(500), buckets(20), output(nullptr), uri(nullptr) { - int ret; - - stats.state = STATE_DESTROYED; - ret = stats_init(&stats, buckets, warmup); - if (ret) - throw RuntimeError("Failed to initialize stats"); - /* Register statistic object to path. * * This allows the path code to update statistics. */ @@ -178,8 +172,7 @@ public: if (uri) free(uri); - if (stats.state != STATE_DESTROYED) - stats_destroy(&stats); + stats.~Stats(); } virtual void start() @@ -199,7 +192,7 @@ public: { assert(state == STATE_STARTED); - stats_print(&stats, uri ? output->file : stdout, format, verbose); + stats.print(uri ? output->file : stdout, format, verbose); if (uri) afclose(output); @@ -211,19 +204,19 @@ public: { assert(state == STATE_STARTED); - stats_reset(&stats); + stats.reset(); } virtual void periodic() { assert(state == STATE_STARTED); - stats_print_periodic(&stats, uri ? output->file : stdout, format, node); + stats.printPeriodic(uri ? output->file : stdout, format, node); } virtual void parse(json_t *cfg) { - int ret, fmt; + int ret; json_error_t err; assert(state != STATE_STARTED); @@ -242,11 +235,11 @@ public: throw ConfigError(cfg, err, "node-config-hook-stats"); if (f) { - fmt = stats_lookup_format(f); - if (fmt < 0) + try { + format = Stats::lookupFormat(f); + } catch (std::invalid_argument &e) { throw ConfigError(cfg, "node-config-hook-stats", "Invalid statistic output format: {}", f); - - format = static_cast(fmt); + } } if (u) diff --git a/lib/mapping.cpp b/lib/mapping.cpp index 45e77e8fc..8e00a251f 100644 --- a/lib/mapping.cpp +++ b/lib/mapping.cpp @@ -29,6 +29,7 @@ #include #include +using namespace villas; using namespace villas::utils; int mapping_parse_str(struct mapping_entry *me, const char *str, struct vlist *nodes) @@ -76,13 +77,8 @@ int mapping_parse_str(struct mapping_entry *me, const char *str, struct vlist *n if (!type) goto invalid_format; - me->stats.metric = stats_lookup_metric(metric); - if (me->stats.metric < 0) - goto invalid_format; - - me->stats.type = stats_lookup_type(type); - if (me->stats.type < 0) - goto invalid_format; + me->stats.metric = Stats::lookupMetric(metric); + me->stats.type = Stats::lookupType(type); } else if (!strcmp(type, "hdr")) { me->type = MAPPING_TYPE_HEADER; @@ -245,7 +241,7 @@ int mapping_update(const struct mapping_entry *me, struct sample *remapped, cons switch (me->type) { case MAPPING_TYPE_STATS: - remapped->data[me->offset] = stats_get_value(me->node->stats, me->stats.metric, me->stats.type); + remapped->data[me->offset] = me->node->stats->getValue(me->stats.metric, me->stats.type); break; case MAPPING_TYPE_TIMESTAMP: { @@ -350,8 +346,8 @@ int mapping_to_str(const struct mapping_entry *me, unsigned index, char **str) switch (me->type) { case MAPPING_TYPE_STATS: strcatf(str, "stats.%s.%s", - stats_metrics[me->stats.metric].name, - stats_types[me->stats.type].name + Stats::metrics[me->stats.metric].name, + Stats::types[me->stats.type].name ); break; diff --git a/lib/node.cpp b/lib/node.cpp index a2b9bbd94..7b78e007c 100644 --- a/lib/node.cpp +++ b/lib/node.cpp @@ -43,6 +43,7 @@ #include #endif /* WITH_NETEM */ +using namespace villas; using namespace villas::utils; int node_init(struct node *n, struct node_type *vt) @@ -427,7 +428,7 @@ int node_read(struct node *n, struct sample *smps[], unsigned cnt, unsigned *rel int skipped = nread - rread; if (skipped > 0 && n->stats != nullptr) { - stats_update(n->stats, STATS_METRIC_SMPS_SKIPPED, skipped); + n->stats->update(Stats::Metric::SMPS_SKIPPED, skipped); } debug(LOG_NODE | 5, "Received %u samples from node %s of which %d have been skipped", nread, node_name(n), skipped); diff --git a/lib/nodes/rtp.cpp b/lib/nodes/rtp.cpp index 4a2cbc2db..d7ea79a32 100644 --- a/lib/nodes/rtp.cpp +++ b/lib/nodes/rtp.cpp @@ -42,7 +42,7 @@ extern "C" { #include #include #include -#include +#include #include #include #include @@ -53,6 +53,7 @@ extern "C" { static pthread_t re_pthread; +using namespace villas; using namespace villas::node; using namespace villas::utils; @@ -300,9 +301,9 @@ static void rtcp_handler(const struct sa *src, struct rtcp_msg *msg, void *arg) rtp_aimd(n, loss_frac); if (n->stats) { - stats_update(n->stats, STATS_METRIC_RTP_PKTS_LOST, rr->lost); - stats_update(n->stats, STATS_METRIC_RTP_LOSS_FRACTION, loss_frac); - stats_update(n->stats, STATS_METRIC_RTP_JITTER, rr->jitter); + n->stats->update(Stats::Metric::RTP_PKTS_LOST, rr->lost); + n->stats->update(Stats::Metric::RTP_LOSS_FRACTION, loss_frac); + n->stats->update(Stats::Metric::RTP_JITTER, rr->jitter); } r->logger->info("RTCP: rr: num_rrs={}, loss_frac={}, pkts_lost={}, jitter={}", r->rtcp.num_rrs, loss_frac, rr->lost, rr->jitter); diff --git a/lib/nodes/stats.cpp b/lib/nodes/stats.cpp index d1ee7db6d..55316bf35 100644 --- a/lib/nodes/stats.cpp +++ b/lib/nodes/stats.cpp @@ -25,13 +25,12 @@ #include #include #include -#include +#include #include #include #include -#define STATS_METRICS 6 - +using namespace villas; using namespace villas::node; using namespace villas::utils; @@ -72,13 +71,8 @@ int stats_node_signal_parse(struct stats_node_signal *s, json_t *cfg) if (!type) goto invalid_format; - s->metric = stats_lookup_metric(metric); - if (s->metric < 0) - goto invalid_format; - - s->type = stats_lookup_type(type); - if (s->type < 0) - goto invalid_format; + s->metric = Stats::lookupMetric(metric); + s->type = Stats::lookupType(type); s->node_str = strdup(node); @@ -196,16 +190,16 @@ int stats_node_parse(struct node *n, json_t *cfg) error("Failed to parse statistics signal definition of node %s", node_name(n)); if (!sig->name) { - const char *metric = stats_metrics[stats_sig->metric].name; - const char *type = stats_types[stats_sig->type].name; + const char *metric = Stats::metrics[stats_sig->metric].name; + const char *type = Stats::types[stats_sig->type].name; sig->name = strf("%s.%s.%s", stats_sig->node_str, metric, type); } if (!sig->unit) - sig->unit = strdup(stats_metrics[stats_sig->metric].unit); + sig->unit = strdup(Stats::metrics[stats_sig->metric].unit); - if (sig->type != stats_types[stats_sig->type].signal_type) + if (sig->type != Stats::types[stats_sig->type].signal_type) error("Invalid type for signal %zu in node %s", i, node_name(n)); vlist_push(&s->signals, stats_sig); @@ -226,14 +220,14 @@ int stats_node_read(struct node *n, struct sample *smps[], unsigned cnt, unsigne unsigned len = MIN(vlist_length(&s->signals), smps[0]->capacity); for (size_t i = 0; i < len; i++) { - struct stats *st; + Stats *st; struct stats_node_signal *sig = (struct stats_node_signal *) vlist_at(&s->signals, i); st = sig->node->stats; if (!st) return -1; - smps[0]->data[i] = stats_get_value(st, sig->metric, sig->type); + smps[0]->data[i] = st->getValue(sig->metric, sig->type); } smps[0]->length = len; diff --git a/lib/signal.cpp b/lib/signal.cpp index 3e41fb902..2b42a775d 100644 --- a/lib/signal.cpp +++ b/lib/signal.cpp @@ -28,6 +28,7 @@ #include #include +using namespace villas; using namespace villas::utils; int signal_init(struct signal *s) @@ -57,7 +58,7 @@ int signal_init_from_mapping(struct signal *s, const struct mapping_entry *me, u switch (me->type) { case MAPPING_TYPE_STATS: - s->type = stats_types[me->stats.type].signal_type; + s->type = Stats::types[me->stats.type].signal_type; break; case MAPPING_TYPE_HEADER: diff --git a/lib/stats.cpp b/lib/stats.cpp index c930571fb..557772bc9 100644 --- a/lib/stats.cpp +++ b/lib/stats.cpp @@ -22,7 +22,7 @@ #include -#include +#include #include #include #include @@ -34,180 +34,155 @@ using namespace villas; using namespace villas::utils; -struct stats_metric_description stats_metrics[] = { - { "skipped", STATS_METRIC_SMPS_SKIPPED, "samples", "Skipped samples and the distance between them" }, - { "reordered", STATS_METRIC_SMPS_REORDERED, "samples", "Reordered samples and the distance between them" }, - { "gap_sent", STATS_METRIC_GAP_SAMPLE, "seconds", "Inter-message timestamps (as sent by remote)" }, - { "gap_received", STATS_METRIC_GAP_RECEIVED, "seconds", "Inter-message arrival time (as received by this instance)" }, - { "owd", STATS_METRIC_OWD, "seconds", "One-way-delay (OWD) of received messages" }, - { "age", STATS_METRIC_AGE, "seconds", "Processing time of packets within the from receive to sent" }, - { "rtp.loss_fraction", STATS_METRIC_RTP_LOSS_FRACTION, "percent", "Fraction lost since last RTP SR/RR." }, - { "rtp.pkts_lost", STATS_METRIC_RTP_PKTS_LOST, "packets", "Cumulative number of packtes lost" }, - { "rtp.jitter", STATS_METRIC_RTP_JITTER, "seconds", "Interarrival jitter" }, +std::unordered_map Stats::metrics = { + { Stats::Metric::SMPS_SKIPPED, { "skipped", "samples", "Skipped samples and the distance between them" }}, + { Stats::Metric::SMPS_REORDERED, { "reordered", "samples", "Reordered samples and the distance between them" }}, + { Stats::Metric::GAP_SAMPLE, { "gap_sent", "seconds", "Inter-message timestamps (as sent by remote)" }}, + { Stats::Metric::GAP_RECEIVED, { "gap_received", "seconds", "Inter-message arrival time (as received by this instance)" }}, + { Stats::Metric::OWD, { "owd", "seconds", "One-way-delay (OWD) of received messages" }}, + { Stats::Metric::AGE, { "age", "seconds", "Processing time of packets within the from receive to sent" }}, + { Stats::Metric::RTP_LOSS_FRACTION, { "rtp.loss_fraction", "percent", "Fraction lost since last RTP SR/RR." }}, + { Stats::Metric::RTP_PKTS_LOST, { "rtp.pkts_lost", "packets", "Cumulative number of packtes lost" }}, + { Stats::Metric::RTP_JITTER, { "rtp.jitter", "seconds", "Interarrival jitter" }}, }; -struct stats_type_description stats_types[] = { - { "last", STATS_TYPE_LAST, SIGNAL_TYPE_FLOAT }, - { "highest", STATS_TYPE_HIGHEST, SIGNAL_TYPE_FLOAT }, - { "lowest", STATS_TYPE_LOWEST, SIGNAL_TYPE_FLOAT }, - { "mean", STATS_TYPE_MEAN, SIGNAL_TYPE_FLOAT }, - { "var", STATS_TYPE_VAR, SIGNAL_TYPE_FLOAT }, - { "stddev", STATS_TYPE_STDDEV, SIGNAL_TYPE_FLOAT }, - { "total", STATS_TYPE_TOTAL, SIGNAL_TYPE_INTEGER } +std::unordered_map Stats::types = { + { Stats::Type::LAST, { "last", SIGNAL_TYPE_FLOAT }}, + { Stats::Type::HIGHEST, { "highest", SIGNAL_TYPE_FLOAT }}, + { Stats::Type::LOWEST, { "lowest", SIGNAL_TYPE_FLOAT }}, + { Stats::Type::MEAN, { "mean", SIGNAL_TYPE_FLOAT }}, + { Stats::Type::VAR, { "var", SIGNAL_TYPE_FLOAT }}, + { Stats::Type::STDDEV, { "stddev", SIGNAL_TYPE_FLOAT }}, + { Stats::Type::TOTAL, { "total", SIGNAL_TYPE_INTEGER }} }; -int stats_lookup_format(const char *str) +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" } +}; + +enum Stats::Format Stats::lookupFormat(const std::string &str) { - if (!strcmp(str, "human")) - return STATS_FORMAT_HUMAN; - else if (!strcmp(str, "json")) - return STATS_FORMAT_JSON; - else if (!strcmp(str, "matlab")) - return STATS_FORMAT_MATLAB; - else - return -1; + if (str == "human") + return Format::HUMAN; + else if (str == "json") + return Format::JSON; + else if (str == "matlab") + return Format::MATLAB; + + throw std::invalid_argument("Invalid format"); } -enum stats_metric stats_lookup_metric(const char *str) +enum Stats::Metric Stats::lookupMetric(const std::string &str) { - for (int i = 0; i < STATS_METRIC_COUNT; i++) { - struct stats_metric_description *d = &stats_metrics[i]; - - if (!strcmp(str, d->name)) - return d->metric; + for (auto m : metrics) { + if (str == m.second.name) + return m.first; } - return STATS_METRIC_INVALID; + throw std::invalid_argument("Invalid metric"); } -enum stats_type stats_lookup_type(const char *str) +enum Stats::Type Stats::lookupType(const std::string &str) { - for (int i = 0; i < STATS_TYPE_COUNT; i++) { - struct stats_type_description *d = &stats_types[i]; - - if (!strcmp(str, d->name)) - return d->type; + for (auto t : types) { + if (str == t.second.name) + return t.first; } - return STATS_TYPE_INVALID; + throw std::invalid_argument("Invalid type"); } -int stats_init(struct stats *s, int buckets, int warmup) +Stats::Stats(int buckets, int warmup) { - assert(s->state == STATE_DESTROYED); - - for (int i = 0; i < STATS_METRIC_COUNT; i++) - new (&s->histograms[i]) Hist(buckets, warmup); - - s->state = STATE_INITIALIZED; - - return 0; + for (auto m : metrics) + histograms[m.first] = Hist(buckets, warmup); } -int stats_destroy(struct stats *s) +void Stats::update(enum Metric m, double val) { - assert(s->state != STATE_DESTROYED); - - for (int i = 0; i < STATS_METRIC_COUNT; i++) - s->histograms[i].~Hist(); - - s->state = STATE_DESTROYED; - - return 0; + histograms[m].put(val); } -void stats_update(struct stats *s, enum stats_metric id, double val) +void Stats::reset() { - assert(s->state == STATE_INITIALIZED); - - s->histograms[id].put(val); + for (auto m : metrics) + histograms[m.first].reset(); } -json_t * stats_json(struct stats *s) +json_t * Stats::toJson() const { - assert(s->state == STATE_INITIALIZED); - json_t *obj = json_object(); - for (int i = 0; i < STATS_METRIC_COUNT; i++) { - struct stats_metric_description *d = &stats_metrics[i]; - const Hist &h = s->histograms[i]; + for (auto m : metrics) { + const Hist &h = histograms.at(m.first); - json_object_set_new(obj, d->name, h.toJson()); + json_object_set_new(obj, m.second.name, h.toJson()); } return obj; } -void stats_reset(struct stats *s) -{ - assert(s->state == STATE_INITIALIZED); - - for (int i = 0; i < STATS_METRIC_COUNT; i++) - s->histograms[i].reset(); -} - -static std::vector stats_columns = { - { 10, TableColumn::align::LEFT, "Node", "%s" }, - { 10, TableColumn::align::RIGHT, "Recv", "%ju", "pkts" }, - { 10, TableColumn::align::RIGHT, "Sent", "%ju", "pkts" }, - { 10, TableColumn::align::RIGHT, "Drop", "%ju", "pkts" }, - { 10, TableColumn::align::RIGHT, "Skip", "%ju", "pkts" }, - { 10, TableColumn::align::RIGHT, "OWD last", "%lf", "secs" }, - { 10, TableColumn::align::RIGHT, "OWD mean", "%lf", "secs" }, - { 10, TableColumn::align::RIGHT, "Rate last", "%lf", "pkt/sec" }, - { 10, TableColumn::align::RIGHT, "Rate mean", "%lf", "pkt/sec" }, - { 10, TableColumn::align::RIGHT, "Age mean", "%lf", "secs" }, - { 10, TableColumn::align::RIGHT, "Age Max", "%lf", "sec" } -}; - -static Table stats_table = Table(stats_columns); - -void stats_print_header(enum stats_format fmt) +void Stats::printHeader(enum Format fmt) { switch (fmt) { - case STATS_FORMAT_HUMAN: - stats_table.header(); + case Format::HUMAN: + setupTable(); + table->header(); break; default: { } } } -void stats_print_periodic(struct stats *s, FILE *f, enum stats_format fmt, struct node *n) +void Stats::setupTable() { - assert(s->state == STATE_INITIALIZED); + if (!table) + table = std::make_shared
(columns); +} +void Stats::printPeriodic(FILE *f, enum Format fmt, struct node *n) const +{ switch (fmt) { - case STATS_FORMAT_HUMAN: - stats_table.row(11, + case Format::HUMAN: + setupTable(); + table->row(11, node_name_short(n), - (uintmax_t) s->histograms[STATS_METRIC_OWD].getTotal(), - (uintmax_t) s->histograms[STATS_METRIC_AGE].getTotal(), - (uintmax_t) s->histograms[STATS_METRIC_SMPS_REORDERED].getTotal(), - (uintmax_t) s->histograms[STATS_METRIC_SMPS_SKIPPED].getTotal(), - (double) s->histograms[STATS_METRIC_OWD].getLast(), - (double) s->histograms[STATS_METRIC_OWD].getMean(), - (double) 1.0 / s->histograms[STATS_METRIC_GAP_RECEIVED].getLast(), - (double) 1.0 / s->histograms[STATS_METRIC_GAP_RECEIVED].getMean(), - (double) s->histograms[STATS_METRIC_AGE].getMean(), - (double) s->histograms[STATS_METRIC_AGE].getHighest() + (uintmax_t) histograms.at(Metric::OWD).getTotal(), + (uintmax_t) histograms.at(Metric::AGE).getTotal(), + (uintmax_t) histograms.at(Metric::SMPS_REORDERED).getTotal(), + (uintmax_t) histograms.at(Metric::SMPS_SKIPPED).getTotal(), + (double) histograms.at(Metric::OWD).getLast(), + (double) histograms.at(Metric::OWD).getMean(), + (double) 1.0 / histograms.at(Metric::GAP_RECEIVED).getLast(), + (double) 1.0 / histograms.at(Metric::GAP_RECEIVED).getMean(), + (double) histograms.at(Metric::AGE).getMean(), + (double) histograms.at(Metric::AGE).getHighest() ); break; - case STATS_FORMAT_JSON: { + case Format::JSON: { json_t *json_stats = json_pack("{ s: s, s: i, s: i, s: i, s: i, s: f, s: f, s: f, s: f, s: f, s: f }", "node", node_name(n), - "recv", s->histograms[STATS_METRIC_OWD].getTotal(), - "sent", s->histograms[STATS_METRIC_AGE].getTotal(), - "dropped", s->histograms[STATS_METRIC_SMPS_REORDERED].getTotal(), - "skipped", s->histograms[STATS_METRIC_SMPS_SKIPPED].getTotal(), - "owd_last", 1.0 / s->histograms[STATS_METRIC_OWD].getLast(), - "owd_mean", 1.0 / s->histograms[STATS_METRIC_OWD].getMean(), - "rate_last", 1.0 / s->histograms[STATS_METRIC_GAP_SAMPLE].getLast(), - "rate_mean", 1.0 / s->histograms[STATS_METRIC_GAP_SAMPLE].getMean(), - "age_mean", s->histograms[STATS_METRIC_AGE].getMean(), - "age_max", s->histograms[STATS_METRIC_AGE].getHighest() + "recv", histograms.at(Metric::OWD).getTotal(), + "sent", histograms.at(Metric::AGE).getTotal(), + "dropped", histograms.at(Metric::SMPS_REORDERED).getTotal(), + "skipped", histograms.at(Metric::SMPS_SKIPPED).getTotal(), + "owd_last", 1.0 / histograms.at(Metric::OWD).getLast(), + "owd_mean", 1.0 / histograms.at(Metric::OWD).getMean(), + "rate_last", 1.0 / histograms.at(Metric::GAP_SAMPLE).getLast(), + "rate_mean", 1.0 / histograms.at(Metric::GAP_SAMPLE).getMean(), + "age_mean", histograms.at(Metric::AGE).getMean(), + "age_max", histograms.at(Metric::AGE).getHighest() ); json_dumpf(json_stats, f, 0); break; @@ -217,64 +192,56 @@ void stats_print_periodic(struct stats *s, FILE *f, enum stats_format fmt, struc } } -void stats_print(struct stats *s, FILE *f, enum stats_format fmt, int verbose) +void Stats::print(FILE *f, enum Format fmt, int verbose) const { - assert(s->state == STATE_INITIALIZED); - switch (fmt) { - case STATS_FORMAT_HUMAN: - for (int i = 0; i < STATS_METRIC_COUNT; i++) { - struct stats_metric_description *d = &stats_metrics[i]; - - info("%s: %s", d->name, d->desc); - s->histograms[i].print(verbose); + case Format::HUMAN: + for (auto m : metrics) { + info("%s: %s", m.second.name, m.second.desc); + histograms.at(m.first).print(verbose); } break; - case STATS_FORMAT_JSON: { - json_t *json_stats = stats_json(s); - json_dumpf(json_stats, f, 0); + case Format::JSON: + json_dumpf(toJson(), f, 0); fflush(f); break; - } default: { } } } -union signal_data stats_get_value(const struct stats *s, enum stats_metric sm, enum stats_type st) +union signal_data Stats::getValue(enum Metric sm, enum Type st) const { - assert(s->state == STATE_INITIALIZED); - - const Hist &h = s->histograms[sm]; + const Hist &h = histograms.at(sm); union signal_data d; switch (st) { - case STATS_TYPE_TOTAL: + case Type::TOTAL: d.i = h.getTotal(); break; - case STATS_TYPE_LAST: + case Type::LAST: d.f = h.getLast(); break; - case STATS_TYPE_HIGHEST: + case Type::HIGHEST: d.f = h.getHighest(); break; - case STATS_TYPE_LOWEST: + case Type::LOWEST: d.f = h.getLowest(); break; - case STATS_TYPE_MEAN: + case Type::MEAN: d.f = h.getMean(); break; - case STATS_TYPE_STDDEV: + case Type::STDDEV: d.f = h.getStddev(); break; - case STATS_TYPE_VAR: + case Type::VAR: d.f = h.getVar(); break; @@ -284,3 +251,10 @@ union signal_data stats_get_value(const struct stats *s, enum stats_metric sm, e return d; } + +const Hist & Stats::getHistogram(enum Metric sm) const +{ + return histograms.at(sm); +} + +std::shared_ptr
Stats::table = std::shared_ptr
(); diff --git a/lib/super_node.cpp b/lib/super_node.cpp index 918ba2131..c9b55917c 100644 --- a/lib/super_node.cpp +++ b/lib/super_node.cpp @@ -382,7 +382,7 @@ void SuperNode::start() if (ret) throw RuntimeError("Failed to create timer"); - stats_print_header(STATS_FORMAT_HUMAN); + Stats::printHeader(Stats::Format::HUMAN); state = STATE_STARTED; } diff --git a/tests/unit/mapping.cpp b/tests/unit/mapping.cpp index c8b1a54db..ed6dd6c0d 100644 --- a/tests/unit/mapping.cpp +++ b/tests/unit/mapping.cpp @@ -28,6 +28,8 @@ #include #include +using namespace villas; + Test(mapping, parse_nodes) { int ret; @@ -73,8 +75,8 @@ Test(mapping, parse_nodes) cr_assert_eq(ret, 0); cr_assert_eq(m.node, vlist_lookup(&nodes, "cherry")); cr_assert_eq(m.type, MAPPING_TYPE_STATS); - cr_assert_eq(m.stats.metric, STATS_METRIC_OWD); - cr_assert_eq(m.stats.type, STATS_TYPE_MEAN); + cr_assert_eq(m.stats.metric, Stats::Metric::OWD); + cr_assert_eq(m.stats.type, Stats::Type::MEAN); ret = mapping_parse_str(&m, "carrot.data[1-2]", &nodes); cr_assert_eq(ret, 0); @@ -126,8 +128,8 @@ Test(mapping, parse) ret = mapping_parse_str(&m, "stats.owd.mean", nullptr); cr_assert_eq(ret, 0); cr_assert_eq(m.type, MAPPING_TYPE_STATS); - cr_assert_eq(m.stats.metric, STATS_METRIC_OWD); - cr_assert_eq(m.stats.type, STATS_TYPE_MEAN); + cr_assert_eq(m.stats.metric, Stats::Metric::OWD); + cr_assert_eq(m.stats.type, Stats::Type::MEAN); ret = mapping_parse_str(&m, "data[1-2]", nullptr); cr_assert_eq(ret, 0);