diff --git a/common/include/villas/hist.hpp b/common/include/villas/hist.hpp index b4a2b68b5..11adef22d 100644 --- a/common/include/villas/hist.hpp +++ b/common/include/villas/hist.hpp @@ -1,4 +1,4 @@ -/** Histogram functions. +/** Histogram class. * * @file * @author Steffen Vogel @@ -28,13 +28,81 @@ #include -#define HIST_HEIGHT (LOG_WIDTH - 55) -#define HIST_SEQ 17 +#define HEIGHT (LOG_WIDTH - 55) +#define SEQ 17 -typedef uintmax_t hist_cnt_t; +namespace villas { /** Histogram structure used to collect statistics. */ -struct hist { +class Hist { + +public: + using cnt_t = uintmax_t; + + /** Initialize struct hist with supplied values and allocate memory for buckets. */ + Hist(int buckets, cnt_t warmup); + + /** Free the dynamically allocated memory. */ + ~Hist(); + + /** Reset all counters and values back to zero. */ + void reset(); + + /** Count a value within its corresponding bucket. */ + void put(double value); + + /** Calcluate the variance of all counted values. */ + double getVar() const; + + /** Calculate the mean average of all counted values. */ + double getMean() const; + + /** Calculate the standard derivation of all counted values. */ + double getStddev() const; + + /** Print all statistical properties of distribution including a graphilcal plot of the histogram. */ + void print(const int details) const; + + /** Print ASCII style plot of histogram */ + void plot() const; + + /** Dump histogram data in Matlab format. + * + * @return The string containing the dump. The caller is responsible to free() the buffer. + */ + char * dump() const; + + /** Prints Matlab struct containing all infos to file. */ + int dumpMatlab(FILE *f) const; + + /** Write the histogram in JSON format to fiel \p f. */ + int dumpJson(FILE *f) const; + + /** Build a libjansson / JSON object of the histogram. */ + json_t * toJson() const; + + double getHigh() const + { + return high; + } + double getLow() const + { + return low; + } + double getHighest() const + { + return highest; + } + double getLowest() const + { + return lowest; + } + double getLast() const + { + return last; + } + +protected: double resolution; /**< The distance between two adjacent buckets. */ double high; /**< The value of the highest bucket. */ @@ -46,60 +114,16 @@ struct hist { int length; /**< The number of buckets in #data. */ - hist_cnt_t total; /**< Total number of counted values. */ - hist_cnt_t warmup; /**< Number of values which are used during warmup phase. */ + cnt_t total; /**< Total number of counted values. */ + cnt_t warmup; /**< Number of values which are used during warmup phase. */ - hist_cnt_t higher; /**< The number of values which are higher than #high. */ - hist_cnt_t lower; /**< The number of values which are lower than #low. */ + cnt_t higher; /**< The number of values which are higher than #high. */ + cnt_t lower; /**< The number of values which are lower than #low. */ - hist_cnt_t *data; /**< Pointer to dynamically allocated array of size length. */ + + cnt_t *data; /**< Pointer to dynamically allocated array of size length. */ double _m[2], _s[2]; /**< Private variables for online variance calculation */ }; -#define hist_last(h) ((h)->last) -#define hist_highest(h) ((h)->highest) -#define hist_lowest(h) ((h)->lowest) -#define hist_total(h) ((h)->total) - -/** Initialize struct hist with supplied values and allocate memory for buckets. */ -int hist_init(struct hist *h, int buckets, hist_cnt_t warmup); - -/** Free the dynamically allocated memory. */ -int hist_destroy(struct hist *h); - -/** Reset all counters and values back to zero. */ -void hist_reset(struct hist *h); - -/** Count a value within its corresponding bucket. */ -void hist_put(struct hist *h, double value); - -/** Calcluate the variance of all counted values. */ -double hist_var(const struct hist *h); - -/** Calculate the mean average of all counted values. */ -double hist_mean(const struct hist *h); - -/** Calculate the standard derivation of all counted values. */ -double hist_stddev(const struct hist *h); - -/** Print all statistical properties of distribution including a graphilcal plot of the histogram. */ -void hist_print(const struct hist *h, int details); - -/** Print ASCII style plot of histogram */ -void hist_plot(const struct hist *h); - -/** Dump histogram data in Matlab format. - * - * @return The string containing the dump. The caller is responsible to free() the buffer. - */ -char * hist_dump(const struct hist *h); - -/** Prints Matlab struct containing all infos to file. */ -int hist_dump_matlab(const struct hist *h, FILE *f); - -/** Write the histogram in JSON format to fiel \p f. */ -int hist_dump_json(const struct hist *h, FILE *f); - -/** Build a libjansson / JSON object of the histogram. */ -json_t * hist_json(const struct hist *h); +} /* namespace villas */ diff --git a/common/lib/hist.cpp b/common/lib/hist.cpp index c559931e2..888410ab4 100644 --- a/common/lib/hist.cpp +++ b/common/lib/hist.cpp @@ -1,4 +1,4 @@ -/** Histogram functions. +/** Histogram class. * * @author Steffen Vogel * @copyright 2014-2019, Institute for Automation of Complex Power Systems, EONERC @@ -34,141 +34,134 @@ using namespace villas::utils; -#define VAL(h, i) ((h)->low + (i) * (h)->resolution) -#define INDEX(h, v) round((v - (h)->low) / (h)->resolution) +namespace villas { -int hist_init(struct hist *h, int buckets, hist_cnt_t warmup) +Hist::Hist(int buckets, Hist::cnt_t wu) { - h->length = buckets; - h->warmup = warmup; + length = buckets; + warmup = wu; - h->data = (hist_cnt_t *) (buckets ? alloc(h->length * sizeof(hist_cnt_t)) : nullptr); + data = (Hist::cnt_t *) (buckets ? alloc(length * sizeof(Hist::cnt_t)) : nullptr); - hist_reset(h); - - return 0; + Hist::reset(); } -int hist_destroy(struct hist *h) +Hist::~Hist() { - if (h->data) { - free(h->data); - h->data = nullptr; - } - - return 0; + if (data) + free(data); } -void hist_put(struct hist *h, double value) +void Hist::put(double value) { - h->last = value; + last = value; /* Update min/max */ - if (value > h->highest) - h->highest = value; - if (value < h->lowest) - h->lowest = value; + if (value > highest) + highest = value; + if (value < lowest) + lowest = value; - if (h->total < h->warmup) { + if (total < warmup) { } - else if (h->total == h->warmup) { - h->low = hist_mean(h) - 3 * hist_stddev(h); - h->high = hist_mean(h) + 3 * hist_stddev(h); - h->resolution = (h->high - h->low) / h->length; + else if (total == warmup) { + low = getMean() - 3 * getStddev(); + high = getMean() + 3 * getStddev(); + resolution = (high - low) / length; } else { - int idx = INDEX(h, value); + int idx = round((value - low) / resolution); /* Check bounds and increment */ - if (idx >= h->length) - h->higher++; + if (idx >= length) + higher++; else if (idx < 0) - h->lower++; - else if (h->data != nullptr) - h->data[idx]++; + lower++; + else if (data != nullptr) + data[idx]++; } - h->total++; + total++; /* Online / running calculation of variance and mean * by Donald Knuth’s Art of Computer Programming, Vol 2, page 232, 3rd edition */ - if (h->total == 1) { - h->_m[1] = h->_m[0] = value; - h->_s[1] = 0.0; + if (total == 1) { + _m[1] = _m[0] = value; + _s[1] = 0.0; } else { - h->_m[0] = h->_m[1] + (value - h->_m[1]) / h->total; - h->_s[0] = h->_s[1] + (value - h->_m[1]) * (value - h->_m[0]); + _m[0] = _m[1] + (value - _m[1]) / total; + _s[0] = _s[1] + (value - _m[1]) * (value - _m[0]); /* Set up for next iteration */ - h->_m[1] = h->_m[0]; - h->_s[1] = h->_s[0]; + _m[1] = _m[0]; + _s[1] = _s[0]; } } -void hist_reset(struct hist *h) +void Hist::reset() { - h->total = 0; - h->higher = 0; - h->lower = 0; + total = 0; + higher = 0; + lower = 0; - h->highest = -DBL_MAX; - h->lowest = DBL_MAX; + highest = -DBL_MAX; + lowest = DBL_MAX; - if (h->data) - memset(h->data, 0, h->length * sizeof(unsigned)); + if (data) + memset(data, 0, length * sizeof(unsigned)); } -double hist_mean(const struct hist *h) +double Hist::getMean() const { - return (h->total > 0) ? h->_m[0] : NAN; + return (total > 0) ? _m[0] : NAN; } -double hist_var(const struct hist *h) +double Hist::getVar() const { - return (h->total > 1) ? h->_s[0] / (h->total - 1) : NAN; + return (total > 1) ? _s[0] / (total - 1) : NAN; } -double hist_stddev(const struct hist *h) +double Hist::getStddev() const { - return sqrt(hist_var(h)); + return sqrt(getVar()); } -void hist_print(const struct hist *h, int details) +void Hist::print(int details) const { - if (h->total > 0) { - hist_cnt_t missed = h->total - h->higher - h->lower; + if (total > 0) { + Hist::cnt_t missed = total - higher - lower; - info("Counted values: %ju (%ju between %f and %f)", h->total, missed, h->low, h->high); - info("Highest: %g", h->highest); - info("Lowest: %g", h->lowest); - info("Mu: %g", hist_mean(h)); - info("1/Mu: %g", 1.0/hist_mean(h)); - info("Variance: %g", hist_var(h)); - info("Stddev: %g", hist_stddev(h)); + info("Counted values: %ju (%ju between %f and %f)", total, missed, low, high); + info("Highest: %g", highest); + info("Lowest: %g", lowest); + info("Mu: %g", getMean()); + info("1/Mu: %g", 1.0 / getMean()); + info("Variance: %g", getVar()); + info("Stddev: %g", getStddev()); - if (details > 0 && h->total - h->higher - h->lower > 0) { - char *buf = hist_dump(h); + if (details > 0 && total - higher - lower > 0) { + char *buf =dump(); info("Matlab: %s", buf); free(buf); - hist_plot(h); + plot(); } } else - info("Counted values: %ju", h->total); + info("Counted values: %ju", total); } -void hist_plot(const struct hist *h) +void Hist::plot() const { - hist_cnt_t max = 1; + Hist::cnt_t max = 1; /* Get highest bar */ - for (int i = 0; i < h->length; i++) { - if (h->data[i] > max) - max = h->data[i]; + for (int i = 0; i < length; i++) { + if (data[i] > max) + max = data[i]; } std::vector cols = { @@ -182,9 +175,9 @@ void hist_plot(const struct hist *h) /* Print plot */ table.header(); - for (int i = 0; i < h->length; i++) { - double value = VAL(h, i); - hist_cnt_t cnt = h->data[i]; + for (int i = 0; i < length; i++) { + double value = low + (i) * resolution; + Hist::cnt_t cnt = data[i]; int bar = cols[2].getWidth() * ((double) cnt / max); char *buf = strf("%s", ""); @@ -197,47 +190,47 @@ void hist_plot(const struct hist *h) } } -char * hist_dump(const struct hist *h) +char * Hist::dump() const { char *buf = (char *) alloc(128); strcatf(&buf, "[ "); - for (int i = 0; i < h->length; i++) - strcatf(&buf, "%ju ", h->data[i]); + for (int i = 0; i < length; i++) + strcatf(&buf, "%ju ", data[i]); strcatf(&buf, "]"); return buf; } -json_t * hist_json(const struct hist *h) +json_t * Hist::toJson() const { json_t *json_buckets, *json_hist; json_hist = json_pack("{ s: f, s: f, s: i }", - "low", h->low, - "high", h->high, - "total", h->total + "low", low, + "high", high, + "total", total ); - if (h->total > 0) { + if (total > 0) { json_object_update(json_hist, json_pack("{ s: i, s: i, s: f, s: f, s: f, s: f, s: f }", - "higher", h->higher, - "lower", h->lower, - "highest", h->highest, - "lowest", h->lowest, - "mean", hist_mean(h), - "variance", hist_var(h), - "stddev", hist_stddev(h) + "higher", higher, + "lower", lower, + "highest", highest, + "lowest", lowest, + "mean", getMean(), + "variance", getVar(), + "stddev", getStddev() )); } - if (h->total - h->lower - h->higher > 0) { + if (total - lower - higher > 0) { json_buckets = json_array(); - for (int i = 0; i < h->length; i++) - json_array_append(json_buckets, json_integer(h->data[i])); + for (int i = 0; i < length; i++) + json_array_append(json_buckets, json_integer(data[i])); json_object_set(json_hist, "buckets", json_buckets); } @@ -245,9 +238,9 @@ json_t * hist_json(const struct hist *h) return json_hist; } -int hist_dump_json(const struct hist *h, FILE *f) +int Hist::dumpJson(FILE *f) const { - json_t *j = hist_json(h); + json_t *j = Hist::toJson(); int ret = json_dumpf(j, f, 0); @@ -256,29 +249,31 @@ int hist_dump_json(const struct hist *h, FILE *f) return ret; } -int hist_dump_matlab(const struct hist *h, FILE *f) +int Hist::dumpMatlab(FILE *f) const { fprintf(f, "struct("); - fprintf(f, "'low', %f, ", h->low); - fprintf(f, "'high', %f, ", h->high); - fprintf(f, "'total', %ju, ", h->total); - fprintf(f, "'higher', %ju, ", h->higher); - fprintf(f, "'lower', %ju, ", h->lower); - fprintf(f, "'highest', %f, ", h->highest); - fprintf(f, "'lowest', %f, ", h->lowest); - fprintf(f, "'mean', %f, ", hist_mean(h)); - fprintf(f, "'variance', %f, ", hist_var(h)); - fprintf(f, "'stddev', %f, ", hist_stddev(h)); + fprintf(f, "'low', %f, ", low); + fprintf(f, "'high', %f, ", high); + fprintf(f, "'total', %ju, ", total); + fprintf(f, "'higher', %ju, ", higher); + fprintf(f, "'lower', %ju, ", lower); + fprintf(f, "'highest', %f, ", highest); + fprintf(f, "'lowest', %f, ", lowest); + fprintf(f, "'mean', %f, ", getMean()); + fprintf(f, "'variance', %f, ", getVar()); + fprintf(f, "'stddev', %f, ", getStddev()); - if (h->total - h->lower - h->higher > 0) { - char *buf = hist_dump(h); + if (total - lower - higher > 0) { + char *buf = dump(); fprintf(f, "'buckets', %s", buf); free(buf); } else - fprintf(f, "'buckets', zeros(1, %d)", h->length); + fprintf(f, "'buckets', zeros(1, %d)", length); fprintf(f, ")"); return 0; } + +} /* namespace villas */ diff --git a/common/tests/unit/hist.cpp b/common/tests/unit/hist.cpp index 541d55709..41ec005ef 100644 --- a/common/tests/unit/hist.cpp +++ b/common/tests/unit/hist.cpp @@ -27,21 +27,18 @@ const double test_data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; +using namespace villas; + TestSuite(hist, .description = "Histogram"); Test(hist, simple) { - struct hist h; - int ret; - ret = hist_init(&h, 0, 0); - cr_assert_eq(ret, 0); + Hist h(0, 0); for (unsigned i = 0; i < ARRAY_LEN(test_data); i++) - hist_put(&h, test_data[i]); + h.put(test_data[i]); - cr_assert_float_eq(hist_mean(&h), 5.5, 1e-6); - cr_assert_float_eq(hist_var(&h), 9.1666, 1e-3,); - cr_assert_float_eq(hist_stddev(&h), 3.027650, 1e-6); - - hist_destroy(&h); + cr_assert_float_eq(h.getMean(), 5.5, 1e-6); + cr_assert_float_eq(h.getVar(), 9.1666, 1e-3,); + cr_assert_float_eq(h.getStddev(), 3.027650, 1e-6); }