#include "base/stats/group.hh"
#include "base/stats/info.hh"
#include "base/stats/output.hh"
+#include "base/stats/storage.hh"
#include "base/stats/types.hh"
#include "base/cast.hh"
#include "base/cprintf.hh"
#include "base/intmath.hh"
#include "base/str.hh"
#include "base/types.hh"
-// For curTick().
-#include "sim/core.hh"
/* A namespace for all of the Statistics */
namespace Stats {
Result total() const { return this->s.total(); }
};
-struct StorageParams
-{
- virtual ~StorageParams();
-};
-
class InfoAccess
{
private:
//
//////////////////////////////////////////////////////////////////////
-/**
- * Templatized storage and interface for a simple scalar stat.
- */
-class StatStor
-{
- private:
- /** The statistic value. */
- Counter data;
-
- public:
- struct Params : public StorageParams {};
-
- public:
- /**
- * Builds this storage element and calls the base constructor of the
- * datatype.
- */
- StatStor(Info *info)
- : data(Counter())
- { }
-
- /**
- * The the stat to the given value.
- * @param val The new value.
- */
- void set(Counter val) { data = val; }
- /**
- * Increment the stat by the given value.
- * @param val The new value.
- */
- void inc(Counter val) { data += val; }
- /**
- * Decrement the stat by the given value.
- * @param val The new value.
- */
- void dec(Counter val) { data -= val; }
- /**
- * Return the value of this stat as its base type.
- * @return The value of this stat.
- */
- Counter value() const { return data; }
- /**
- * Return the value of this stat as a result type.
- * @return The value of this stat.
- */
- Result result() const { return (Result)data; }
- /**
- * Prepare stat data for dumping or serialization
- */
- void prepare(Info *info) { }
- /**
- * Reset stat value to default
- */
- void reset(Info *info) { data = Counter(); }
-
- /**
- * @return true if zero value
- */
- bool zero() const { return data == Counter(); }
-};
-
-/**
- * Templatized storage and interface to a per-tick average stat. This keeps
- * a current count and updates a total (count * ticks) when this count
- * changes. This allows the quick calculation of a per tick count of the item
- * being watched. This is good for keeping track of residencies in structures
- * among other things.
- */
-class AvgStor
-{
- private:
- /** The current count. */
- Counter current;
- /** The tick of the last reset */
- Tick lastReset;
- /** The total count for all tick. */
- mutable Result total;
- /** The tick that current last changed. */
- mutable Tick last;
-
- public:
- struct Params : public StorageParams {};
-
- public:
- /**
- * Build and initializes this stat storage.
- */
- AvgStor(Info *info)
- : current(0), lastReset(0), total(0), last(0)
- { }
-
- /**
- * Set the current count to the one provided, update the total and last
- * set values.
- * @param val The new count.
- */
- void
- set(Counter val)
- {
- total += current * (curTick() - last);
- last = curTick();
- current = val;
- }
-
- /**
- * Increment the current count by the provided value, calls set.
- * @param val The amount to increment.
- */
- void inc(Counter val) { set(current + val); }
-
- /**
- * Deccrement the current count by the provided value, calls set.
- * @param val The amount to decrement.
- */
- void dec(Counter val) { set(current - val); }
-
- /**
- * Return the current count.
- * @return The current count.
- */
- Counter value() const { return current; }
-
- /**
- * Return the current average.
- * @return The current average.
- */
- Result
- result() const
- {
- assert(last == curTick());
- return (Result)(total + current) / (Result)(curTick() - lastReset + 1);
- }
-
- /**
- * @return true if zero value
- */
- bool zero() const { return total == 0.0; }
-
- /**
- * Prepare stat data for dumping or serialization
- */
- void
- prepare(Info *info)
- {
- total += current * (curTick() - last);
- last = curTick();
- }
-
- /**
- * Reset stat value to default
- */
- void
- reset(Info *info)
- {
- total = 0.0;
- last = curTick();
- lastReset = curTick();
- }
-
-};
-
/**
* Implementation of a scalar stat. The type of stat is determined by the
* Storage template.
// Non formula statistics
//
//////////////////////////////////////////////////////////////////////
-/** The parameters for a distribution stat. */
-struct DistParams : public StorageParams
-{
- const DistType type;
- DistParams(DistType t) : type(t) {}
-};
-
-/**
- * Templatized storage and interface for a distribution stat.
- */
-class DistStor
-{
- public:
- /** The parameters for a distribution stat. */
- struct Params : public DistParams
- {
- /** The minimum value to track. */
- Counter min;
- /** The maximum value to track. */
- Counter max;
- /** The number of entries in each bucket. */
- Counter bucket_size;
- /** The number of buckets. Equal to (max-min)/bucket_size. */
- size_type buckets;
-
- Params() : DistParams(Dist), min(0), max(0), bucket_size(0),
- buckets(0) {}
- };
-
- private:
- /** The minimum value to track. */
- Counter min_track;
- /** The maximum value to track. */
- Counter max_track;
- /** The number of entries in each bucket. */
- Counter bucket_size;
-
- /** The smallest value sampled. */
- Counter min_val;
- /** The largest value sampled. */
- Counter max_val;
- /** The number of values sampled less than min. */
- Counter underflow;
- /** The number of values sampled more than max. */
- Counter overflow;
- /** The current sum. */
- Counter sum;
- /** The sum of squares. */
- Counter squares;
- /** The number of samples. */
- Counter samples;
- /** Counter for each bucket. */
- VCounter cvec;
-
- public:
- DistStor(Info *info)
- : cvec(safe_cast<const Params *>(info->storageParams)->buckets)
- {
- reset(info);
- }
-
- /**
- * Add a value to the distribution for the given number of times.
- * @param val The value to add.
- * @param number The number of times to add the value.
- */
- void
- sample(Counter val, int number)
- {
- if (val < min_track)
- underflow += number;
- else if (val > max_track)
- overflow += number;
- else {
- size_type index =
- (size_type)std::floor((val - min_track) / bucket_size);
- assert(index < size());
- cvec[index] += number;
- }
-
- if (val < min_val)
- min_val = val;
-
- if (val > max_val)
- max_val = val;
-
- sum += val * number;
- squares += val * val * number;
- samples += number;
- }
-
- /**
- * Return the number of buckets in this distribution.
- * @return the number of buckets.
- */
- size_type size() const { return cvec.size(); }
-
- /**
- * Returns true if any calls to sample have been made.
- * @return True if any values have been sampled.
- */
- bool
- zero() const
- {
- return samples == Counter();
- }
-
- void
- prepare(Info *info, DistData &data)
- {
- const Params *params = safe_cast<const Params *>(info->storageParams);
-
- assert(params->type == Dist);
- data.type = params->type;
- data.min = params->min;
- data.max = params->max;
- data.bucket_size = params->bucket_size;
-
- data.min_val = (min_val == CounterLimits::max()) ? 0 : min_val;
- data.max_val = (max_val == CounterLimits::min()) ? 0 : max_val;
- data.underflow = underflow;
- data.overflow = overflow;
-
- data.cvec.resize(params->buckets);
- for (off_type i = 0; i < params->buckets; ++i)
- data.cvec[i] = cvec[i];
-
- data.sum = sum;
- data.squares = squares;
- data.samples = samples;
- }
-
- /**
- * Reset stat value to default
- */
- void
- reset(Info *info)
- {
- const Params *params = safe_cast<const Params *>(info->storageParams);
- min_track = params->min;
- max_track = params->max;
- bucket_size = params->bucket_size;
-
- min_val = CounterLimits::max();
- max_val = CounterLimits::min();
- underflow = Counter();
- overflow = Counter();
-
- size_type size = cvec.size();
- for (off_type i = 0; i < size; ++i)
- cvec[i] = Counter();
-
- sum = Counter();
- squares = Counter();
- samples = Counter();
- }
-};
-
-/**
- * Templatized storage and interface for a histogram stat.
- */
-class HistStor
-{
- public:
- /** The parameters for a distribution stat. */
- struct Params : public DistParams
- {
- /** The number of buckets.. */
- size_type buckets;
-
- Params() : DistParams(Hist), buckets(0) {}
- };
-
- private:
- /** The minimum value to track. */
- Counter min_bucket;
- /** The maximum value to track. */
- Counter max_bucket;
- /** The number of entries in each bucket. */
- Counter bucket_size;
-
- /** The current sum. */
- Counter sum;
- /** The sum of logarithm of each sample, used to compute geometric mean. */
- Counter logs;
- /** The sum of squares. */
- Counter squares;
- /** The number of samples. */
- Counter samples;
- /** Counter for each bucket. */
- VCounter cvec;
-
- public:
- HistStor(Info *info)
- : cvec(safe_cast<const Params *>(info->storageParams)->buckets)
- {
- reset(info);
- }
-
- void grow_up();
- void grow_out();
- void grow_convert();
- void add(HistStor *);
-
- /**
- * Add a value to the distribution for the given number of times.
- * @param val The value to add.
- * @param number The number of times to add the value.
- */
- void
- sample(Counter val, int number)
- {
- assert(min_bucket < max_bucket);
- if (val < min_bucket) {
- if (min_bucket == 0)
- grow_convert();
-
- while (val < min_bucket)
- grow_out();
- } else if (val >= max_bucket + bucket_size) {
- if (min_bucket == 0) {
- while (val >= max_bucket + bucket_size)
- grow_up();
- } else {
- while (val >= max_bucket + bucket_size)
- grow_out();
- }
- }
-
- size_type index =
- (int64_t)std::floor((val - min_bucket) / bucket_size);
-
- assert(index < size());
- cvec[index] += number;
-
- sum += val * number;
- squares += val * val * number;
- logs += log(val) * number;
- samples += number;
- }
-
- /**
- * Return the number of buckets in this distribution.
- * @return the number of buckets.
- */
- size_type size() const { return cvec.size(); }
-
- /**
- * Returns true if any calls to sample have been made.
- * @return True if any values have been sampled.
- */
- bool
- zero() const
- {
- return samples == Counter();
- }
-
- void
- prepare(Info *info, DistData &data)
- {
- const Params *params = safe_cast<const Params *>(info->storageParams);
-
- assert(params->type == Hist);
- data.type = params->type;
- data.min = min_bucket;
- data.max = max_bucket + bucket_size - 1;
- data.bucket_size = bucket_size;
-
- data.min_val = min_bucket;
- data.max_val = max_bucket;
-
- int buckets = params->buckets;
- data.cvec.resize(buckets);
- for (off_type i = 0; i < buckets; ++i)
- data.cvec[i] = cvec[i];
-
- data.sum = sum;
- data.logs = logs;
- data.squares = squares;
- data.samples = samples;
- }
-
- /**
- * Reset stat value to default
- */
- void
- reset(Info *info)
- {
- const Params *params = safe_cast<const Params *>(info->storageParams);
- min_bucket = 0;
- max_bucket = params->buckets - 1;
- bucket_size = 1;
-
- size_type size = cvec.size();
- for (off_type i = 0; i < size; ++i)
- cvec[i] = Counter();
-
- sum = Counter();
- squares = Counter();
- samples = Counter();
- logs = Counter();
- }
-};
-
-/**
- * Templatized storage and interface for a distribution that calculates mean
- * and variance.
- */
-class SampleStor
-{
- public:
- struct Params : public DistParams
- {
- Params() : DistParams(Deviation) {}
- };
-
- private:
- /** The current sum. */
- Counter sum;
- /** The sum of squares. */
- Counter squares;
- /** The number of samples. */
- Counter samples;
-
- public:
- /**
- * Create and initialize this storage.
- */
- SampleStor(Info *info)
- : sum(Counter()), squares(Counter()), samples(Counter())
- { }
-
- /**
- * Add a value the given number of times to this running average.
- * Update the running sum and sum of squares, increment the number of
- * values seen by the given number.
- * @param val The value to add.
- * @param number The number of times to add the value.
- */
- void
- sample(Counter val, int number)
- {
- sum += val * number;
- squares += val * val * number;
- samples += number;
- }
-
- /**
- * Return the number of entries in this stat, 1
- * @return 1.
- */
- size_type size() const { return 1; }
-
- /**
- * Return true if no samples have been added.
- * @return True if no samples have been added.
- */
- bool zero() const { return samples == Counter(); }
-
- void
- prepare(Info *info, DistData &data)
- {
- const Params *params = safe_cast<const Params *>(info->storageParams);
-
- assert(params->type == Deviation);
- data.type = params->type;
- data.sum = sum;
- data.squares = squares;
- data.samples = samples;
- }
-
- /**
- * Reset stat value to default
- */
- void
- reset(Info *info)
- {
- sum = Counter();
- squares = Counter();
- samples = Counter();
- }
-};
-
-/**
- * Templatized storage for distribution that calculates per tick mean and
- * variance.
- */
-class AvgSampleStor
-{
- public:
- struct Params : public DistParams
- {
- Params() : DistParams(Deviation) {}
- };
-
- private:
- /** Current total. */
- Counter sum;
- /** Current sum of squares. */
- Counter squares;
-
- public:
- /**
- * Create and initialize this storage.
- */
- AvgSampleStor(Info *info)
- : sum(Counter()), squares(Counter())
- {}
-
- /**
- * Add a value to the distribution for the given number of times.
- * Update the running sum and sum of squares.
- * @param val The value to add.
- * @param number The number of times to add the value.
- */
- void
- sample(Counter val, int number)
- {
- sum += val * number;
- squares += val * val * number;
- }
-
- /**
- * Return the number of entries, in this case 1.
- * @return 1.
- */
- size_type size() const { return 1; }
-
- /**
- * Return true if no samples have been added.
- * @return True if the sum is zero.
- */
- bool zero() const { return sum == Counter(); }
-
- void
- prepare(Info *info, DistData &data)
- {
- const Params *params = safe_cast<const Params *>(info->storageParams);
-
- assert(params->type == Deviation);
- data.type = params->type;
- data.sum = sum;
- data.squares = squares;
- data.samples = curTick();
- }
-
- /**
- * Reset stat value to default
- */
- void
- reset(Info *info)
- {
- sum = Counter();
- squares = Counter();
- }
-};
/**
* Implementation of a distribution stat. The type of distribution is
* Add the argument distribution to the this distribution.
*/
void add(DistBase &d) { data()->add(d.data()); }
-
};
template <class Stat>
}
};
-/**
- * Templatized storage and interface for a sparse histogram stat.
- */
-class SparseHistStor
-{
- public:
- /** The parameters for a sparse histogram stat. */
- struct Params : public DistParams
- {
- Params() : DistParams(Hist) {}
- };
-
- private:
- /** Counter for number of samples */
- Counter samples;
- /** Counter for each bucket. */
- MCounter cmap;
-
- public:
- SparseHistStor(Info *info)
- {
- reset(info);
- }
-
- /**
- * Add a value to the distribution for the given number of times.
- * @param val The value to add.
- * @param number The number of times to add the value.
- */
- void
- sample(Counter val, int number)
- {
- cmap[val] += number;
- samples += number;
- }
-
- /**
- * Return the number of buckets in this distribution.
- * @return the number of buckets.
- */
- size_type size() const { return cmap.size(); }
-
- /**
- * Returns true if any calls to sample have been made.
- * @return True if any values have been sampled.
- */
- bool
- zero() const
- {
- return samples == Counter();
- }
-
- void
- prepare(Info *info, SparseHistData &data)
- {
- MCounter::iterator it;
- data.cmap.clear();
- for (it = cmap.begin(); it != cmap.end(); it++) {
- data.cmap[(*it).first] = (*it).second;
- }
-
- data.samples = samples;
- }
-
- /**
- * Reset stat value to default
- */
- void
- reset(Info *info)
- {
- cmap.clear();
- samples = 0;
- }
-};
-
class SparseHistogram : public SparseHistBase<SparseHistogram, SparseHistStor>
{
public:
--- /dev/null
+/*
+ * Copyright (c) 2021 Daniel R. Carvalho
+ * Copyright (c) 2003-2005 The Regents of The University of Michigan
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __BASE_STATS_STORAGE_HH__
+#define __BASE_STATS_STORAGE_HH__
+
+#include <cassert>
+
+#include "base/cast.hh"
+#include "base/logging.hh"
+#include "base/stats/info.hh"
+#include "base/stats/types.hh"
+// For curTick().
+#include "sim/core.hh"
+
+namespace Stats {
+
+struct StorageParams
+{
+ virtual ~StorageParams() = default;
+};
+
+/**
+ * Templatized storage and interface for a simple scalar stat.
+ */
+class StatStor
+{
+ private:
+ /** The statistic value. */
+ Counter data;
+
+ public:
+ struct Params : public StorageParams {};
+
+ /**
+ * Builds this storage element and calls the base constructor of the
+ * datatype.
+ */
+ StatStor(Info *info)
+ : data(Counter())
+ { }
+
+ /**
+ * The the stat to the given value.
+ * @param val The new value.
+ */
+ void set(Counter val) { data = val; }
+
+ /**
+ * Increment the stat by the given value.
+ * @param val The new value.
+ */
+ void inc(Counter val) { data += val; }
+
+ /**
+ * Decrement the stat by the given value.
+ * @param val The new value.
+ */
+ void dec(Counter val) { data -= val; }
+
+ /**
+ * Return the value of this stat as its base type.
+ * @return The value of this stat.
+ */
+ Counter value() const { return data; }
+
+ /**
+ * Return the value of this stat as a result type.
+ * @return The value of this stat.
+ */
+ Result result() const { return (Result)data; }
+
+ /**
+ * Prepare stat data for dumping or serialization
+ */
+ void prepare(Info *info) { }
+
+ /**
+ * Reset stat value to default
+ */
+ void reset(Info *info) { data = Counter(); }
+
+ /**
+ * @return true if zero value
+ */
+ bool zero() const { return data == Counter(); }
+};
+
+/**
+ * Templatized storage and interface to a per-tick average stat. This keeps
+ * a current count and updates a total (count * ticks) when this count
+ * changes. This allows the quick calculation of a per tick count of the item
+ * being watched. This is good for keeping track of residencies in structures
+ * among other things.
+ */
+class AvgStor
+{
+ private:
+ /** The current count. */
+ Counter current;
+ /** The tick of the last reset */
+ Tick lastReset;
+ /** The total count for all tick. */
+ mutable Result total;
+ /** The tick that current last changed. */
+ mutable Tick last;
+
+ public:
+ struct Params : public StorageParams {};
+
+ /**
+ * Build and initializes this stat storage.
+ */
+ AvgStor(Info *info)
+ : current(0), lastReset(0), total(0), last(0)
+ { }
+
+ /**
+ * Set the current count to the one provided, update the total and last
+ * set values.
+ * @param val The new count.
+ */
+ void
+ set(Counter val)
+ {
+ total += current * (curTick() - last);
+ last = curTick();
+ current = val;
+ }
+
+ /**
+ * Increment the current count by the provided value, calls set.
+ * @param val The amount to increment.
+ */
+ void inc(Counter val) { set(current + val); }
+
+ /**
+ * Deccrement the current count by the provided value, calls set.
+ * @param val The amount to decrement.
+ */
+ void dec(Counter val) { set(current - val); }
+
+ /**
+ * Return the current count.
+ * @return The current count.
+ */
+ Counter value() const { return current; }
+
+ /**
+ * Return the current average.
+ * @return The current average.
+ */
+ Result
+ result() const
+ {
+ assert(last == curTick());
+ return (Result)(total + current) / (Result)(curTick() - lastReset + 1);
+ }
+
+ /**
+ * @return true if zero value
+ */
+ bool zero() const { return total == 0.0; }
+
+ /**
+ * Prepare stat data for dumping or serialization
+ */
+ void
+ prepare(Info *info)
+ {
+ total += current * (curTick() - last);
+ last = curTick();
+ }
+
+ /**
+ * Reset stat value to default
+ */
+ void
+ reset(Info *info)
+ {
+ total = 0.0;
+ last = curTick();
+ lastReset = curTick();
+ }
+
+};
+
+/** The parameters for a distribution stat. */
+struct DistParams : public StorageParams
+{
+ const DistType type;
+ DistParams(DistType t) : type(t) {}
+};
+
+/**
+ * Templatized storage and interface for a distribution stat. A distribution
+ * uses buckets to keep track of values within a given range. All other
+ * values, although accounted for on the overall calculations, are not tracked
+ * in buckets themselves; two special counters, underflow and overflow store
+ * the number of occurrences of such values.
+ */
+class DistStor
+{
+ private:
+ /** The minimum value to track. */
+ Counter min_track;
+ /** The maximum value to track. */
+ Counter max_track;
+ /** The number of entries in each bucket. */
+ Counter bucket_size;
+
+ /** The smallest value sampled. */
+ Counter min_val;
+ /** The largest value sampled. */
+ Counter max_val;
+ /** The number of values sampled less than min. */
+ Counter underflow;
+ /** The number of values sampled more than max. */
+ Counter overflow;
+ /** The current sum. */
+ Counter sum;
+ /** The sum of squares. */
+ Counter squares;
+ /** The number of samples. */
+ Counter samples;
+ /** Counter for each bucket. */
+ VCounter cvec;
+
+ public:
+ /** The parameters for a distribution stat. */
+ struct Params : public DistParams
+ {
+ /** The minimum value to track. */
+ Counter min;
+ /** The maximum value to track. */
+ Counter max;
+ /** The number of entries in each bucket. */
+ Counter bucket_size;
+ /** The number of buckets. Equal to (max-min)/bucket_size. */
+ size_type buckets;
+
+ Params() : DistParams(Dist), min(0), max(0), bucket_size(0),
+ buckets(0) {}
+ };
+
+ DistStor(Info *info)
+ : cvec(safe_cast<const Params *>(info->storageParams)->buckets)
+ {
+ reset(info);
+ }
+
+ /**
+ * Add a value to the distribution for the given number of times.
+ * @param val The value to add.
+ * @param number The number of times to add the value.
+ */
+ void sample(Counter val, int number);
+
+ /**
+ * Return the number of buckets in this distribution.
+ * @return the number of buckets.
+ */
+ size_type size() const { return cvec.size(); }
+
+ /**
+ * Returns true if any calls to sample have been made.
+ * @return True if any values have been sampled.
+ */
+ bool
+ zero() const
+ {
+ return samples == Counter();
+ }
+
+ void
+ prepare(Info *info, DistData &data)
+ {
+ const Params *params = safe_cast<const Params *>(info->storageParams);
+
+ assert(params->type == Dist);
+ data.type = params->type;
+ data.min = params->min;
+ data.max = params->max;
+ data.bucket_size = params->bucket_size;
+
+ data.min_val = (min_val == CounterLimits::max()) ? 0 : min_val;
+ data.max_val = (max_val == CounterLimits::min()) ? 0 : max_val;
+ data.underflow = underflow;
+ data.overflow = overflow;
+
+ data.cvec.resize(params->buckets);
+ for (off_type i = 0; i < params->buckets; ++i)
+ data.cvec[i] = cvec[i];
+
+ data.sum = sum;
+ data.squares = squares;
+ data.samples = samples;
+ }
+
+ /**
+ * Reset stat value to default
+ */
+ void
+ reset(Info *info)
+ {
+ const Params *params = safe_cast<const Params *>(info->storageParams);
+ min_track = params->min;
+ max_track = params->max;
+ bucket_size = params->bucket_size;
+
+ min_val = CounterLimits::max();
+ max_val = CounterLimits::min();
+ underflow = Counter();
+ overflow = Counter();
+
+ size_type size = cvec.size();
+ for (off_type i = 0; i < size; ++i)
+ cvec[i] = Counter();
+
+ sum = Counter();
+ squares = Counter();
+ samples = Counter();
+ }
+};
+
+/**
+ * Templatized storage and interface for a histogram stat.
+ *
+ * The number of buckets is fixed on initialization; however, the bucket size
+ * isn't. That means that when samples that are outside the current range are
+ * seen, the bucket size will be increased so that each bucket can hold a
+ * bigger range of values. When that happens, the bucket's contents are re-
+ * located.
+ *
+ * The min and max bucket values can only be, respectively, decreased and
+ * increased when sampling. If this wasn't true, samples that were previously
+ * within the buclet range could not be anymore within the valid range, making
+ * the storage's state incoherent. These values are set back to their initial
+ * states on reset().
+ *
+ * The bucket range always is zero-centric. While the storage does not
+ * contain negative values the bucket range will keep its lower bound at
+ * zero, doubling the upper bound when needed; However, as soon a negative
+ * value is sampled, zero becomes the lower bound of the middle (rounded up)
+ * bucket. Although this means that the histogram will not be symmetric if
+ * negative values are sampled, it makes it possible to grow the buckets
+ * without keeping track of the individual values.
+ *
+ * This happens because if zero was not a lower or upper bound, when its
+ * value was doubled, the lower and upper bound of the bucket containing
+ * zero would intersect with middle values of the previous and next buckets.
+ * For example, if the bucket containing zero has range [-2,2[, therefore
+ * its neighbor buckets would have ranges at [-6,-2[ and [2,6[. When the
+ * buckets are grown, the zero bucket would grow its range to [-4,4[, which
+ * cannot be easily extracted from the neighor buckets.
+ */
+class HistStor
+{
+ private:
+ /** Lower bound of the first bucket's range. */
+ Counter min_bucket;
+ /** Lower bound of the last bucket's range. */
+ Counter max_bucket;
+ /** The number of entries in each bucket. */
+ Counter bucket_size;
+
+ /** The current sum. */
+ Counter sum;
+ /** The sum of logarithm of each sample, used to compute geometric mean. */
+ Counter logs;
+ /** The sum of squares. */
+ Counter squares;
+ /** The number of samples. */
+ Counter samples;
+ /** Counter for each bucket. */
+ VCounter cvec;
+
+ /**
+ * Given a bucket size B, and a range of values [0, N], this function
+ * doubles the bucket size to double the range of values towards the
+ * positive infinite; that is, double the upper range of this storage
+ * so that the range becomes [0, 2*N].
+ *
+ * Because the bucket size is doubled, the buckets contents are rearranged,
+ * since the original range of values is mapped to the lower half buckets.
+ */
+ void growUp();
+
+ /**
+ * Given a bucket size B, and a range of values [M, N], where M < 0, this
+ * function doubles the bucket size to double the range of values towards
+ * both positive and negative infinites; that is, it doubles both the lower
+ * and the upper range of this storage so that the range becomes
+ * [2*M, 2*N].
+ *
+ * Because the bucket size is doubled, the buckets contents are
+ * rearranged, and the original range of values are redistributed to free
+ * buckets for the newly appended ranges.
+ */
+ void growOut();
+
+ /**
+ * Given a bucket size B, and a range of values [0, N], this function
+ * doubles the bucket size to double the range of values towards the
+ * negative infinite; that is, it doubles the lower range of this
+ * storage so that the middle buckes contaihs zero as a lower bound. As
+ * such, the storage range becomes [-N, N+B] if there is an odd number
+ * of buckets, and [-N-B, N+B] if there is an even number of buckets.
+ *
+ * Because the bucket size is doubled, the buckets contents are
+ * rearranged, and the original range of values are redistributed to free
+ * buckets for the newly appended ranges.
+ */
+ void growDown();
+
+ public:
+ /** The parameters for a distribution stat. */
+ struct Params : public DistParams
+ {
+ /** The number of buckets. */
+ size_type buckets;
+
+ Params() : DistParams(Hist), buckets(0) {}
+ };
+
+ HistStor(Info *info)
+ : cvec(safe_cast<const Params *>(info->storageParams)->buckets)
+ {
+ fatal_if(cvec.size() == 1,
+ "There must be at least two buckets in a histogram");
+ reset(info);
+ }
+
+ /**
+ * Adds the contents of the given storage to this storage.
+ * @param other The other storage to be added.
+ */
+ void add(HistStor *other);
+
+ /**
+ * Add a value to the distribution for the given number of times.
+ * @param val The value to add.
+ * @param number The number of times to add the value.
+ */
+ void sample(Counter val, int number);
+
+ /**
+ * Return the number of buckets in this distribution.
+ * @return the number of buckets.
+ */
+ size_type size() const { return cvec.size(); }
+
+ /**
+ * Returns true if any calls to sample have been made.
+ * @return True if any values have been sampled.
+ */
+ bool
+ zero() const
+ {
+ return samples == Counter();
+ }
+
+ void
+ prepare(Info *info, DistData &data)
+ {
+ const Params *params = safe_cast<const Params *>(info->storageParams);
+
+ assert(params->type == Hist);
+ data.type = params->type;
+ data.min = min_bucket;
+ data.max = max_bucket + bucket_size - 1;
+ data.bucket_size = bucket_size;
+
+ data.min_val = min_bucket;
+ data.max_val = max_bucket;
+
+ int buckets = params->buckets;
+ data.cvec.resize(buckets);
+ for (off_type i = 0; i < buckets; ++i)
+ data.cvec[i] = cvec[i];
+
+ data.sum = sum;
+ data.logs = logs;
+ data.squares = squares;
+ data.samples = samples;
+ }
+
+ /**
+ * Reset stat value to default
+ */
+ void
+ reset(Info *info)
+ {
+ const Params *params = safe_cast<const Params *>(info->storageParams);
+ min_bucket = 0;
+ max_bucket = params->buckets - 1;
+ bucket_size = 1;
+
+ size_type size = cvec.size();
+ for (off_type i = 0; i < size; ++i)
+ cvec[i] = Counter();
+
+ sum = Counter();
+ squares = Counter();
+ samples = Counter();
+ logs = Counter();
+ }
+};
+
+/**
+ * Templatized storage and interface for a distribution that calculates mean
+ * and variance.
+ */
+class SampleStor
+{
+ private:
+ /** The current sum. */
+ Counter sum;
+ /** The sum of squares. */
+ Counter squares;
+ /** The number of samples. */
+ Counter samples;
+
+ public:
+ struct Params : public DistParams
+ {
+ Params() : DistParams(Deviation) {}
+ };
+
+ /**
+ * Create and initialize this storage.
+ */
+ SampleStor(Info *info)
+ : sum(Counter()), squares(Counter()), samples(Counter())
+ { }
+
+ /**
+ * Add a value the given number of times to this running average.
+ * Update the running sum and sum of squares, increment the number of
+ * values seen by the given number.
+ * @param val The value to add.
+ * @param number The number of times to add the value.
+ */
+ void
+ sample(Counter val, int number)
+ {
+ sum += val * number;
+ squares += val * val * number;
+ samples += number;
+ }
+
+ /**
+ * Return the number of entries in this stat, 1
+ * @return 1.
+ */
+ size_type size() const { return 1; }
+
+ /**
+ * Return true if no samples have been added.
+ * @return True if no samples have been added.
+ */
+ bool zero() const { return samples == Counter(); }
+
+ void
+ prepare(Info *info, DistData &data)
+ {
+ const Params *params = safe_cast<const Params *>(info->storageParams);
+
+ assert(params->type == Deviation);
+ data.type = params->type;
+ data.sum = sum;
+ data.squares = squares;
+ data.samples = samples;
+ }
+
+ /**
+ * Reset stat value to default
+ */
+ void
+ reset(Info *info)
+ {
+ sum = Counter();
+ squares = Counter();
+ samples = Counter();
+ }
+};
+
+/**
+ * Templatized storage for distribution that calculates per tick mean and
+ * variance.
+ */
+class AvgSampleStor
+{
+ private:
+ /** Current total. */
+ Counter sum;
+ /** Current sum of squares. */
+ Counter squares;
+
+ public:
+ struct Params : public DistParams
+ {
+ Params() : DistParams(Deviation) {}
+ };
+
+ /**
+ * Create and initialize this storage.
+ */
+ AvgSampleStor(Info *info)
+ : sum(Counter()), squares(Counter())
+ {}
+
+ /**
+ * Add a value to the distribution for the given number of times.
+ * Update the running sum and sum of squares.
+ * @param val The value to add.
+ * @param number The number of times to add the value.
+ */
+ void
+ sample(Counter val, int number)
+ {
+ sum += val * number;
+ squares += val * val * number;
+ }
+
+ /**
+ * Return the number of entries, in this case 1.
+ * @return 1.
+ */
+ size_type size() const { return 1; }
+
+ /**
+ * Return true if no samples have been added.
+ * @return True if the sum is zero.
+ */
+ bool zero() const { return sum == Counter(); }
+
+ void
+ prepare(Info *info, DistData &data)
+ {
+ const Params *params = safe_cast<const Params *>(info->storageParams);
+
+ assert(params->type == Deviation);
+ data.type = params->type;
+ data.sum = sum;
+ data.squares = squares;
+ data.samples = curTick();
+ }
+
+ /**
+ * Reset stat value to default
+ */
+ void
+ reset(Info *info)
+ {
+ sum = Counter();
+ squares = Counter();
+ }
+};
+
+/**
+ * Templatized storage and interface for a sparse histogram stat. There
+ * is no actual limit on the number of buckets, and each of them has a size
+ * of 1, meaning that samples are individually recorded, and there is no
+ * need to keep track of the samples that occur in between two distant
+ * sampled values.
+ */
+class SparseHistStor
+{
+ private:
+ /** Counter for number of samples */
+ Counter samples;
+ /** Counter for each bucket. */
+ MCounter cmap;
+
+ public:
+ /** The parameters for a sparse histogram stat. */
+ struct Params : public DistParams
+ {
+ Params() : DistParams(Hist) {}
+ };
+
+ SparseHistStor(Info *info)
+ {
+ reset(info);
+ }
+
+ /**
+ * Add a value to the distribution for the given number of times.
+ * @param val The value to add.
+ * @param number The number of times to add the value.
+ */
+ void
+ sample(Counter val, int number)
+ {
+ cmap[val] += number;
+ samples += number;
+ }
+
+ /**
+ * Return the number of buckets in this distribution.
+ * @return the number of buckets.
+ */
+ size_type size() const { return cmap.size(); }
+
+ /**
+ * Returns true if any calls to sample have been made.
+ * @return True if any values have been sampled.
+ */
+ bool
+ zero() const
+ {
+ return samples == Counter();
+ }
+
+ void
+ prepare(Info *info, SparseHistData &data)
+ {
+ MCounter::iterator it;
+ data.cmap.clear();
+ for (it = cmap.begin(); it != cmap.end(); it++) {
+ data.cmap[(*it).first] = (*it).second;
+ }
+
+ data.samples = samples;
+ }
+
+ /**
+ * Reset stat value to default
+ */
+ void
+ reset(Info *info)
+ {
+ cmap.clear();
+ samples = 0;
+ }
+};
+
+} // namespace Stats
+
+#endif // __BASE_STATS_STORAGE_HH__
--- /dev/null
+/*
+ * Copyright (c) 2021 Daniel R. Carvalho
+ * All rights reserved
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <gtest/gtest-spi.h>
+#include <gtest/gtest.h>
+
+#include <cmath>
+
+#include "base/gtest/cur_tick_fake.hh"
+#include "base/stats/storage.hh"
+
+// Instantiate the fake class to have a valid curTick of 0
+GTestTickHandler tickHandler;
+
+/** Increases the current tick by one. */
+void increaseTick() { tickHandler.setCurTick(curTick() + 1); }
+
+/** A pair of value and its number of samples, used for sampling. */
+struct ValueSamples
+{
+ Stats::Counter value;
+ Stats::Counter numSamples;
+
+ ValueSamples(Stats::Counter value, Stats::Counter num_samples)
+ : value(value), numSamples(num_samples)
+ {
+ }
+};
+
+/**
+ * A mocked info class.
+ * @todo There is no real dependency on the info class, so this must be
+ * removed on a cleanup.
+ */
+class MockInfo : public Stats::Info
+{
+ public:
+ MockInfo(Stats::StorageParams* storage_params)
+ : Stats::Info()
+ {
+ this->storageParams = storage_params;
+ }
+ ~MockInfo() = default;
+
+ bool check() const override { return true; }
+ void prepare() override { }
+ void reset() override { }
+ bool zero() const override { return true; }
+ void visit(Stats::Output &visitor) override { }
+};
+
+/** Test setting and getting a value to the storage. */
+TEST(StatsStatStorTest, SetValueResult)
+{
+ Stats::StatStor stor(nullptr);
+ Stats::Counter val;
+
+ val = 10;
+ stor.set(val);
+ ASSERT_EQ(stor.value(), val);
+ ASSERT_EQ(stor.result(), Stats::Result(val));
+
+ val = 1234;
+ stor.set(val);
+ ASSERT_EQ(stor.value(), val);
+ ASSERT_EQ(stor.result(), Stats::Result(val));
+}
+
+/** Test if prepare does not change the value. */
+TEST(StatsStatStorTest, Prepare)
+{
+ Stats::StatStor stor(nullptr);
+ Stats::Counter val;
+
+ val = 10;
+ stor.set(val);
+ stor.prepare(nullptr);
+ ASSERT_EQ(stor.value(), val);
+ ASSERT_EQ(stor.result(), Stats::Result(val));
+}
+
+/** Test whether incrementing and decrementing work as expected. */
+TEST(StatsStatStorTest, IncDec)
+{
+ Stats::StatStor stor(nullptr);
+ Stats::Counter diff_val = 10;
+ Stats::Counter val = 0;
+
+ stor.inc(diff_val);
+ val += diff_val;
+ ASSERT_EQ(stor.value(), val);
+
+ stor.inc(diff_val);
+ val += diff_val;
+ ASSERT_EQ(stor.value(), val);
+
+ stor.dec(diff_val);
+ val -= diff_val;
+ ASSERT_EQ(stor.value(), val);
+
+ stor.dec(diff_val);
+ val -= diff_val;
+ ASSERT_EQ(stor.value(), val);
+}
+
+/**
+ * Test whether zero is correctly set as the reset value. The test order is
+ * to check if it is initially zero on creation, then it is made non zero,
+ * and finally reset to zero.
+ */
+TEST(StatsStatStorTest, ZeroReset)
+{
+ Stats::StatStor stor(nullptr);
+ Stats::Counter val = 10;
+
+ ASSERT_TRUE(stor.zero());
+
+ stor.reset(nullptr);
+ ASSERT_TRUE(stor.zero());
+
+ stor.reset(nullptr);
+ stor.inc(val);
+ ASSERT_FALSE(stor.zero());
+}
+
+/** Test setting and getting a value to the storage. */
+TEST(StatsAvgStorTest, SetValueResult)
+{
+ Stats::AvgStor stor(nullptr);
+ Stats::Counter val;
+ Stats::Result total = 0;
+ Tick last_reset = 0;
+ Tick last_tick = 0;
+
+ val = 10;
+ stor.set(val);
+ last_tick = curTick();
+ ASSERT_EQ(stor.value(), val);
+ ASSERT_EQ(stor.result(), Stats::Result(total + val) /
+ Stats::Result(curTick() - last_reset + 1));
+ increaseTick();
+
+ total += val * (curTick() - last_tick);
+ val = 1234;
+ stor.set(val);
+ last_tick = curTick();
+ ASSERT_EQ(stor.value(), val);
+ ASSERT_EQ(stor.result(), Stats::Result(total + val) /
+ Stats::Result(curTick() - last_reset + 1));
+ increaseTick();
+}
+
+/**
+ * Test whether getting the result in a different tick triggers an assertion.
+ */
+TEST(StatsAvgStorDeathTest, Result)
+{
+ Stats::AvgStor stor(nullptr);
+ increaseTick();
+ ASSERT_DEATH(stor.result(), ".+");
+}
+
+/**
+ * Test whether getting the result in a different tick does not trigger an
+ * assertion if storage is prepared.
+ */
+TEST(StatsAvgStorTest, Prepare)
+{
+ Stats::AvgStor stor(nullptr);
+ Stats::Counter val = 10;
+ Stats::Result total = 0;
+ Tick last_reset = 0;
+ Tick last_tick = 0;
+
+ val = 10;
+ stor.set(val);
+ last_tick = curTick();
+ ASSERT_EQ(stor.value(), val);
+ ASSERT_EQ(stor.result(), Stats::Result(total + val) /
+ Stats::Result(curTick() - last_reset + 1));
+ increaseTick();
+
+ total += val * (curTick() - last_tick);
+ stor.prepare(nullptr);
+ last_tick = curTick();
+ ASSERT_EQ(stor.value(), val);
+ ASSERT_EQ(stor.result(), Stats::Result(total + val) /
+ Stats::Result(curTick() - last_reset + 1));
+ increaseTick();
+}
+
+/** Test whether incrementing and decrementing work as expected. */
+TEST(StatsAvgStorTest, IncDec)
+{
+ Stats::AvgStor stor(nullptr);
+ Stats::Counter diff_val = 10;
+ Stats::Counter val = 0;
+
+ stor.set(diff_val);
+ val += diff_val;
+ ASSERT_EQ(stor.value(), val);
+
+ stor.inc(diff_val);
+ val += diff_val;
+ ASSERT_EQ(stor.value(), val);
+
+ stor.inc(diff_val);
+ val += diff_val;
+ ASSERT_EQ(stor.value(), val);
+
+ stor.dec(diff_val);
+ val -= diff_val;
+ ASSERT_EQ(stor.value(), val);
+
+ stor.dec(diff_val);
+ val -= diff_val;
+ ASSERT_EQ(stor.value(), val);
+}
+
+/**
+ * Test whether zero is correctly set as the reset value. The test order is
+ * to check if it is initially zero on creation, then it is made non zero,
+ * and finally reset to zero.
+ */
+TEST(StatsAvgStorTest, ZeroReset)
+{
+ Stats::AvgStor stor(nullptr);
+ Stats::Counter val = 10;
+
+ ASSERT_TRUE(stor.zero());
+
+ stor.reset(nullptr);
+ ASSERT_TRUE(stor.zero());
+
+ // Set current value to val, reset total and increase tick, so that the
+ // next call to set will update the total to be different from zero
+ stor.inc(val);
+ stor.reset(nullptr);
+ increaseTick();
+ stor.inc(val);
+ ASSERT_FALSE(stor.zero());
+}
+
+/**
+ * Test that an assertion is thrown when no bucket size is provided before
+ * sampling.
+ */
+TEST(StatsDistStorDeathTest, NoBucketSize)
+{
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+ Stats::DistStor::Params params;
+ MockInfo info(¶ms);
+ Stats::DistStor stor(&info);
+ ASSERT_DEATH(stor.sample(val, num_samples), ".+");
+}
+
+/**
+ * Test whether zero is correctly set as the reset value. The test order is
+ * to check if it is initially zero on creation, then it is made non zero,
+ * and finally reset to zero.
+ */
+TEST(StatsDistStorTest, ZeroReset)
+{
+ Stats::DistStor::Params params;
+ params.bucket_size = 10;
+ MockInfo info(¶ms);
+ Stats::DistStor stor(&info);
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+
+ ASSERT_TRUE(stor.zero());
+
+ stor.reset(&info);
+ stor.sample(val, num_samples);
+ ASSERT_FALSE(stor.zero());
+
+ stor.reset(&info);
+ ASSERT_TRUE(stor.zero());
+}
+
+/**
+ * Test that the size of this storage is equal to its counters vector's size,
+ * and that after it has been set, nothing can modify it.
+ */
+TEST(StatsDistStorTest, Size)
+{
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+ Stats::Counter size = 20;
+ Stats::DistData data;
+
+ Stats::DistStor::Params params;
+ params.bucket_size = 1;
+ params.buckets = size;
+ MockInfo info(¶ms);
+ Stats::DistStor stor(&info);
+
+ ASSERT_EQ(stor.size(), size);
+ stor.sample(val, num_samples);
+ ASSERT_EQ(stor.size(), size);
+ stor.prepare(&info, data);
+ ASSERT_EQ(stor.size(), size);
+ stor.reset(&info);
+ ASSERT_EQ(stor.size(), size);
+ stor.zero();
+ ASSERT_EQ(stor.size(), size);
+}
+
+/**
+ * Compare both dist datas to see if their contents match.
+ *
+ * @param data The data being tested.
+ * @param expected_data The ground truth.
+ * @param no_log Whether log should not be compared.
+ */
+void
+checkExpectedDistData(const Stats::DistData& data,
+ const Stats::DistData& expected_data, bool no_log = true)
+{
+ ASSERT_EQ(data.type, expected_data.type);
+ ASSERT_EQ(data.min, expected_data.min);
+ ASSERT_EQ(data.max, expected_data.max);
+ ASSERT_EQ(data.bucket_size, expected_data.bucket_size);
+ ASSERT_EQ(data.min_val, expected_data.min_val);
+ ASSERT_EQ(data.max_val, expected_data.max_val);
+ ASSERT_EQ(data.sum, expected_data.sum);
+ ASSERT_EQ(data.squares, expected_data.squares);
+ if (!no_log) {
+ ASSERT_EQ(data.logs, expected_data.logs);
+ }
+ ASSERT_EQ(data.samples, expected_data.samples);
+ ASSERT_EQ(data.cvec.size(), expected_data.cvec.size());
+ for (int i = 0; i < expected_data.cvec.size(); i++) {
+ ASSERT_EQ(data.cvec[i], expected_data.cvec[i]);
+ }
+}
+
+/**
+ * Auxiliary function that finishes preparing the DistStor's expected values,
+ * perform the calls to the storage's sample, and compares the expected data.
+ *
+ * @param params The params containing the number of buckets.
+ * @param values The value-num_sample pairs to be sampled.
+ * @param num_values Number of values in the values array.
+ * @param expected_data Expected data after sampling, with the following values
+ * setup to the expected values: bucket_size, min, max_val, and cvec.
+ */
+void
+prepareCheckDistStor(Stats::DistStor::Params& params, ValueSamples* values,
+ int num_values, Stats::DistData& expected_data)
+{
+ MockInfo info(¶ms);
+ Stats::DistStor stor(&info);
+
+ Stats::Counter val;
+ Stats::DistData data;
+
+ expected_data.min = params.min;
+ expected_data.max = params.max;
+ expected_data.sum = 0;
+ expected_data.squares = 0;
+ expected_data.logs = 0;
+ expected_data.samples = 0;
+
+ // Populate storage with more data
+ for (int i = 0; i < num_values; i++) {
+ stor.sample(values[i].value, values[i].numSamples);
+
+ val = values[i].value * values[i].numSamples;
+ expected_data.sum += val;
+ expected_data.squares += values[i].value * val;
+ expected_data.samples += values[i].numSamples;
+ }
+ stor.prepare(&info, data);
+
+ // DistStor does not use log
+ checkExpectedDistData(data, expected_data, true);
+}
+
+/** Test setting and getting value from storage. */
+TEST(StatsDistStorTest, SamplePrepareSingle)
+{
+ Stats::DistStor::Params params;
+ params.min = 0;
+ params.max = 99;
+ params.bucket_size = 5;
+ params.buckets = 20;
+
+ ValueSamples values[] = {{10, 5}};
+ int num_values = sizeof(values) / sizeof(ValueSamples);
+
+ // Setup expected data
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Dist;
+ expected_data.bucket_size = params.bucket_size;
+ expected_data.underflow = 0;
+ expected_data.overflow = 0;
+ expected_data.min_val = 10;
+ expected_data.max_val = 10;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[2] = 5;
+
+ prepareCheckDistStor(params, values, num_values, expected_data);
+}
+
+/** Test setting and getting value from storage with multiple values. */
+TEST(StatsDistStorTest, SamplePrepareMultiple)
+{
+ Stats::DistStor::Params params;
+ params.min = 0;
+ params.max = 99;
+ params.bucket_size = 5;
+ params.buckets = 20;
+
+ // There are 20 buckets: [0,5[, [5,10[, [10,15[, ..., [95,100[.
+ // We test that values that pass the maximum bucket value (1234, 12345678,
+ // 100) are added to the overflow counter, and that the ones below the
+ // minimum bucket value (-10, -1) are added to the underflow counter.
+ // The extremes (0 and 99) are added to check if they go to the first and
+ // last buckets.
+ ValueSamples values[] = {{10, 5}, {1234, 2}, {12345678, 99}, {-10, 4},
+ {17, 17}, {52, 63}, {18, 11}, {0, 1}, {99, 15}, {-1, 200}, {100, 50}};
+ int num_values = sizeof(values) / sizeof(ValueSamples);
+
+ // Setup variables that should always match params' values
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Dist;
+ expected_data.min_val = -10;
+ expected_data.max_val = 12345678;
+ expected_data.bucket_size = params.bucket_size;
+ expected_data.underflow = 204;
+ expected_data.overflow = 151;
+ expected_data.sum = 0;
+ expected_data.squares = 0;
+ expected_data.samples = 0;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 1;
+ expected_data.cvec[2] = 5;
+ expected_data.cvec[3] = 17+11;
+ expected_data.cvec[10] = 63;
+ expected_data.cvec[19] = 15;
+
+ prepareCheckDistStor(params, values, num_values, expected_data);
+}
+
+/** Test resetting storage. */
+TEST(StatsDistStorTest, Reset)
+{
+ Stats::DistStor::Params params;
+ params.min = 0;
+ params.max = 99;
+ params.bucket_size = 5;
+ params.buckets = 20;
+ MockInfo info(¶ms);
+ Stats::DistStor stor(&info);
+
+ // Populate storage with random samples
+ ValueSamples values[] = {{10, 5}, {1234, 2}, {12345678, 99}, {-10, 4},
+ {17, 17}, {52, 63}, {18, 11}, {0, 1}, {99, 15}, {-1, 200}, {100, 50}};
+ int num_values = sizeof(values) / sizeof(ValueSamples);
+ for (int i = 0; i < num_values; i++) {
+ stor.sample(values[i].value, values[i].numSamples);
+ }
+
+ // Reset storage, and make sure all data has been cleared
+ stor.reset(&info);
+ Stats::DistData data;
+ stor.prepare(&info, data);
+
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Dist;
+ expected_data.bucket_size = params.bucket_size;
+ expected_data.underflow = 0;
+ expected_data.overflow = 0;
+ expected_data.min = params.min;
+ expected_data.max = params.max;
+ expected_data.min_val = 0;
+ expected_data.max_val = 0;
+ expected_data.sum = 0;
+ expected_data.squares = 0;
+ expected_data.samples = 0;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+
+ checkExpectedDistData(data, expected_data, true);
+}
+
+/**
+ * Test that an assertion is thrown when no bucket size is provided before
+ * sampling.
+ */
+TEST(StatsHistStorDeathTest, NoBucketSize)
+{
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+
+ // If no bucket size is specified, it is 0 by default
+ Stats::HistStor::Params params;
+ MockInfo info(¶ms);
+ Stats::HistStor stor(&info);
+ ASSERT_DEATH(stor.sample(val, num_samples), ".+");
+}
+
+/**
+ * Test whether zero is correctly set as the reset value. The test order is
+ * to check if it is initially zero on creation, then it is made non zero,
+ * and finally reset to zero.
+ */
+TEST(StatsHistStorTest, ZeroReset)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 10;
+ MockInfo info(¶ms);
+ Stats::HistStor stor(&info);
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+
+ ASSERT_TRUE(stor.zero());
+
+ stor.reset(&info);
+ stor.sample(val, num_samples);
+ ASSERT_FALSE(stor.zero());
+
+ stor.reset(&info);
+ ASSERT_TRUE(stor.zero());
+}
+
+/**
+ * Test that the size of this storage is equal to its counters vector's size,
+ * and that after it has been set, nothing can modify it.
+ */
+TEST(StatsHistStorTest, Size)
+{
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+ Stats::DistData data;
+ Stats::size_type sizes[] = {2, 10, 1234};
+
+ // If no bucket size is specified, it is 0 by default
+ {
+ Stats::HistStor::Params params;
+ MockInfo info(¶ms);
+ Stats::HistStor stor(&info);
+
+ ASSERT_EQ(stor.size(), 0);
+ stor.prepare(&info, data);
+ ASSERT_EQ(stor.size(), 0);
+ stor.reset(&info);
+ ASSERT_EQ(stor.size(), 0);
+ stor.zero();
+ ASSERT_EQ(stor.size(), 0);
+ }
+
+ for (int i = 0; i < (sizeof(sizes) / sizeof(Stats::size_type)); i++) {
+ Stats::HistStor::Params params;
+ params.buckets = sizes[i];
+ MockInfo info(¶ms);
+ Stats::HistStor stor(&info);
+
+ ASSERT_EQ(stor.size(), sizes[i]);
+ stor.sample(val, num_samples);
+ ASSERT_EQ(stor.size(), sizes[i]);
+ stor.prepare(&info, data);
+ ASSERT_EQ(stor.size(), sizes[i]);
+ stor.reset(&info);
+ ASSERT_EQ(stor.size(), sizes[i]);
+ stor.zero();
+ ASSERT_EQ(stor.size(), sizes[i]);
+ }
+}
+
+/**
+ * Auxiliary function that finishes preparing the HistStor's expected values,
+ * perform the calls to the storage's sample, and compares the expected data.
+ *
+ * @param params The params containing the number of buckets.
+ * @param values The value-num_sample pairs to be sampled.
+ * @param num_values Number of values in the values array.
+ * @param expected_data Expected data after sampling, with the following values
+ * setup to the expected values: bucket_size, min, max_val, and cvec.
+ */
+void
+prepareCheckHistStor(Stats::HistStor::Params& params, ValueSamples* values,
+ int num_values, Stats::DistData& expected_data)
+{
+ MockInfo info(¶ms);
+ Stats::HistStor stor(&info);
+
+ Stats::Counter val;
+ Stats::DistData data;
+ bool no_log = false;
+
+ expected_data.min_val = expected_data.min;
+ expected_data.max = expected_data.max_val + expected_data.bucket_size - 1;
+ expected_data.sum = 0;
+ expected_data.squares = 0;
+ expected_data.logs = 0;
+ expected_data.samples = 0;
+
+ // Populate storage with more data
+ for (int i = 0; i < num_values; i++) {
+ stor.sample(values[i].value, values[i].numSamples);
+
+ val = values[i].value * values[i].numSamples;
+ expected_data.sum += val;
+ expected_data.squares += values[i].value * val;
+ if (values[i].value < 0) {
+ // Negative values don't have log, so mark log check to be skipped
+ no_log = true;
+ } else {
+ expected_data.logs +=
+ std::log(values[i].value) * values[i].numSamples;
+ }
+ expected_data.samples += values[i].numSamples;
+ }
+ stor.prepare(&info, data);
+ checkExpectedDistData(data, expected_data, no_log);
+}
+
+/**
+ * Test samples that fit in the initial buckets, and therefore do not need
+ * to grow up.
+ */
+TEST(StatsHistStorTest, SamplePrepareFit)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+
+ // Setup expected data for the hand-carved values given. The final buckets
+ // will be divided at:
+ // Bkt0=[0,1[ , Bkt1=[1,2[, Bkt2=[2,3[, Bkt3=[3,4[
+ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {3, 4}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 1;
+ expected_data.min = 0;
+ expected_data.max_val = 3;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 5;
+ expected_data.cvec[1] = 2;
+ expected_data.cvec[2] = 99;
+ expected_data.cvec[3] = 4;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/**
+ * Test samples that do not fit in the initial buckets, and therefore have
+ * to grow up once.
+ */
+TEST(StatsHistStorTest, SamplePrepareSingleGrowUp)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+
+ // Setup expected data for the hand-carved values given. Since there
+ // are four buckets, and the highest value is 4, the bucket size will
+ // grow to be 2. The final buckets will be divided at:
+ // Bkt0=[0,2[ , Bkt1=[2,4[, Bkt2=[4,6[, Bkt3=[6,8[
+ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {4, 4}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 2;
+ expected_data.min = 0;
+ expected_data.max_val = 6;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 5+2;
+ expected_data.cvec[1] = 99;
+ expected_data.cvec[2] = 4;
+ expected_data.cvec[3] = 0;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/**
+ * Test samples that do not fit in the initial buckets, and therefore have
+ * to grow up a few times.
+ */
+TEST(StatsHistStorTest, SamplePrepareMultipleGrowUp)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+
+ // Setup expected data for the hand-carved values given. Since there
+ // are four buckets, and the highest value is 4, the bucket size will
+ // grow thrice to become 8. The final buckets will be divided at:
+ // Bkt0=[0,8[ , Bkt1=[8,16[, Bkt2=[16,24[, Bkt3=[24,32[
+ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {16, 4}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 8;
+ expected_data.min = 0;
+ expected_data.max_val = 24;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 5+2+99;
+ expected_data.cvec[1] = 0;
+ expected_data.cvec[2] = 4;
+ expected_data.cvec[3] = 0;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/**
+ * Test samples that have a negative value, and therefore do not fit in the
+ * initial buckets. Since this involves using negative values, the logs
+ * become irrelevant.
+ */
+TEST(StatsHistStorTest, SamplePrepareGrowDownOddBuckets)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 5;
+
+ // Setup expected data for the hand-carved values given. Since there
+ // is a negative value, the min bucket will change, and the bucket size
+ // will grow to be 2. The final buckets will be divided at:
+ // Bkt0=[-4,-2[ , Bkt1=[-2,-0[, Bkt2=[0,2[, Bkt3=[2,4[, Bkt4=[4,6[
+ ValueSamples values[] =
+ {{0, 5}, {1, 2}, {2, 99}, {3, 12}, {4, 33}, {-1, 4}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 2;
+ expected_data.min = -4;
+ expected_data.max_val = 4;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 0;
+ expected_data.cvec[1] = 4;
+ expected_data.cvec[2] = 5+2;
+ expected_data.cvec[3] = 99+12;
+ expected_data.cvec[4] = 33;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/**
+ * Test samples that have a negative value, and therefore do not fit in the
+ * initial buckets. Since this involves using negative values, the logs
+ * become irrelevant.
+ */
+TEST(StatsHistStorTest, SamplePrepareGrowDownEvenBuckets)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+
+ // Setup expected data for the hand-carved values given. Since there
+ // is a negative value, the min bucket will change, and the bucket size
+ // will grow to be 2. The final buckets will be divided at:
+ // Bkt0=[-4,-2[ , Bkt1=[-2,0[, Bkt2=[0,2[, Bkt3=[2,4[
+ ValueSamples values[] = {{0, 5}, {1, 2}, {2, 99}, {-1, 4}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 2;
+ expected_data.min = -4;
+ expected_data.max_val = 2;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 0;
+ expected_data.cvec[1] = 4;
+ expected_data.cvec[2] = 5+2;
+ expected_data.cvec[3] = 99;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/**
+ * Test samples that have one low negative value, and therefore do not fit
+ * in the initial buckets and have to grow down a few times. Since this
+ * involves using negative values, the logs become irrelevant.
+ */
+TEST(StatsHistStorTest, SamplePrepareGrowDownGrowOutOddBuckets)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 5;
+
+ // Setup expected data for the hand-carved values given. Since there
+ // is a negative value, the min bucket will change, and the bucket size
+ // will grow to be 8. The final buckets will be divided at:
+ // Bkt0=[-16,-8[ , Bkt1=[-8,0[, Bkt2=[0,8[, Bkt3=[8,16[, Bkt4=[16,24[
+ ValueSamples values[] =
+ {{0, 5}, {1, 2}, {2, 99}, {3, 12}, {4, 33}, {-12, 4}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 8;
+ expected_data.min = -16;
+ expected_data.max_val = 16;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 4;
+ expected_data.cvec[1] = 0;
+ expected_data.cvec[2] = 5+2+99+12+33;
+ expected_data.cvec[3] = 0;
+ expected_data.cvec[4] = 0;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/**
+ * Test samples that have one low negative value, and therefore do not fit
+ * in the initial buckets and have to grow down a few times. Since this
+ * involves using negative values, the logs become irrelevant.
+ */
+TEST(StatsHistStorTest, SamplePrepareGrowDownGrowOutEvenBuckets)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+
+ // Setup expected data for the hand-carved values given. Since there
+ // is a negative value, the min bucket will change, and the bucket size
+ // will grow to be 8. The final buckets will be divided at:
+ // Bkt0=[-16,-8[ , Bkt1=[-8,0[, Bkt2=[0,8[, Bkt3=[8,16[
+ ValueSamples values[] =
+ {{0, 5}, {1, 2}, {2, 99}, {3, 12}, {-12, 4}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 8;
+ expected_data.min = -16;
+ expected_data.max_val = 8;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 4;
+ expected_data.cvec[1] = 0;
+ expected_data.cvec[2] = 5+2+99+12;
+ expected_data.cvec[3] = 0;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/**
+ * Test a complex sample set with negative values, and therefore multiple
+ * grows will happen. Since this involves using negative values, the logs
+ * become irrelevant.
+ */
+TEST(StatsHistStorTest, SamplePrepareMultipleGrowOddBuckets)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 5;
+
+ // Setup expected data for the hand-carved values given. This adds quite
+ // a few positive and negative samples, and the bucket size will grow to
+ // be 64. The final buckets will be divided at:
+ // Bkt0=[-128,-64[ , Bkt1=[-64,0[, Bkt2=[0,64[, Bkt3=[64,128[,
+ // Bkt4=[128,192[
+ ValueSamples values[] =
+ {{0, 5}, {7, 2}, {31, 99}, {-8, 12}, {127, 4}, {-120, 53}, {-50, 1}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 64;
+ expected_data.min = -128;
+ expected_data.max_val = 128;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 53;
+ expected_data.cvec[1] = 12+1;
+ expected_data.cvec[2] = 5+2+99;
+ expected_data.cvec[3] = 4;
+ expected_data.cvec[4] = 0;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/**
+ * Test a complex sample set with negative values, and therefore multiple
+ * grows will happen. Since this involves using negative values, the logs
+ * become irrelevant.
+ */
+TEST(StatsHistStorTest, SamplePrepareMultipleGrowEvenBuckets)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+
+ // Setup expected data for the hand-carved values given. This adds quite
+ // a few positive and negative samples, and the bucket size will grow to
+ // be 64. The final buckets will be divided at:
+ // Bkt0=[-128,-64[ , Bkt1=[-64,0[, Bkt2=[0,64[, Bkt3=[64,128[
+ ValueSamples values[] =
+ {{0, 5}, {7, 2}, {31, 99}, {-8, 12}, {127, 4}, {-120, 53}, {-50, 1}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 64;
+ expected_data.min = -128;
+ expected_data.max_val = 64;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 53;
+ expected_data.cvec[1] = 12+1;
+ expected_data.cvec[2] = 5+2+99;
+ expected_data.cvec[3] = 4;
+
+ prepareCheckHistStor(params, values, num_values, expected_data);
+}
+
+/** Test resetting storage. */
+TEST(StatsHistStorTest, Reset)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+ MockInfo info(¶ms);
+ Stats::HistStor stor(&info);
+
+ // Setup expected data for the hand-carved values given. This adds quite
+ // a few positive and negative samples, and the bucket size will grow to
+ // be 64. The final buckets will be divided at:
+ // Bkt0=[-128,-64[ , Bkt1=[-64,0[, Bkt2=[0,64[, Bkt3=[64,128[
+ ValueSamples values[] =
+ {{0, 5}, {7, 2}, {31, 99}, {-8, 12}, {127, 4}, {-120, 53}, {-50, 1}};
+ const int num_values = sizeof(values) / sizeof(ValueSamples);
+ for (int i = 0; i < num_values; i++) {
+ stor.sample(values[i].value, values[i].numSamples);
+ }
+
+ // Reset storage, and make sure all data has been cleared:
+ // Bkt0=[0,1[ , Bkt1=[1,2[, Bkt2=[2,3[, Bkt3=[3,4[
+ stor.reset(&info);
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 1;
+ expected_data.min = 0;
+ expected_data.max_val = 3;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ prepareCheckHistStor(params, values, 0, expected_data);
+}
+
+/** Test whether adding storages with different sizes triggers an assertion. */
+TEST(StatsHistStorDeathTest, AddDifferentSize)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+ MockInfo info(¶ms);
+ Stats::HistStor stor(&info);
+
+ Stats::HistStor::Params params2;
+ params2.buckets = 5;
+ MockInfo info2(¶ms2);
+ Stats::HistStor stor2(&info2);
+
+ ASSERT_DEATH(stor.add(&stor2), ".+");
+}
+
+/** Test whether adding storages with different min triggers an assertion. */
+TEST(StatsHistStorDeathTest, AddDifferentMin)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+ MockInfo info(¶ms);
+ Stats::HistStor stor(&info);
+ stor.sample(-1, 3);
+
+ // On creation, the storage's min is zero
+ Stats::HistStor::Params params2;
+ params2.buckets = 4;
+ MockInfo info2(¶ms2);
+ Stats::HistStor stor2(&info2);
+
+ ASSERT_DEATH(stor.add(&stor2), ".+");
+}
+
+/** Test merging two histograms. */
+TEST(StatsHistStorTest, Add)
+{
+ Stats::HistStor::Params params;
+ params.buckets = 4;
+ MockInfo info(¶ms);
+
+ // Setup first storage. Buckets are:
+ // Bkt0=[0,16[, Bkt1=[16,32[, Bkt2=[32,48[, Bkt3=[58,64[
+ Stats::HistStor stor(&info);
+ ValueSamples values[] = {{0, 5}, {3, 2}, {20, 37}, {32, 18}};
+ int num_values = sizeof(values) / sizeof(ValueSamples);
+ for (int i = 0; i < num_values; i++) {
+ stor.sample(values[i].value, values[i].numSamples);
+ }
+ Stats::DistData data;
+ stor.prepare(&info, data);
+
+ // Setup second storage. Buckets are:
+ // Bkt0=[0,32[, Bkt1=[32,64[, Bkt2=[64,96[, Bkt3=[96,128[
+ Stats::HistStor stor2(&info);
+ ValueSamples values2[] = {{10, 10}, {0, 1}, {80, 4}, {17, 100}, {95, 79}};
+ int num_values2 = sizeof(values2) / sizeof(ValueSamples);
+ for (int i = 0; i < num_values2; i++) {
+ stor2.sample(values2[i].value, values2[i].numSamples);
+ }
+ Stats::DistData data2;
+ stor2.prepare(&info, data2);
+
+ // Perform the merge
+ stor.add(&stor2);
+ Stats::DistData merge_data;
+ stor.prepare(&info, merge_data);
+
+ // Setup expected data. Buckets are:
+ // Bkt0=[0,32[, Bkt1=[32,64[, Bkt2=[64,96[, Bkt3=[96,128[
+ Stats::DistData expected_data;
+ expected_data.type = Stats::Hist;
+ expected_data.bucket_size = 32;
+ expected_data.min = 0;
+ expected_data.max = 127;
+ expected_data.min_val = 0;
+ expected_data.max_val = 96;
+ expected_data.cvec.clear();
+ expected_data.cvec.resize(params.buckets);
+ expected_data.cvec[0] = 5+2+37+10+1+100;
+ expected_data.cvec[1] = 18;
+ expected_data.cvec[2] = 4+79;
+ expected_data.cvec[3] = 0;
+ expected_data.sum = data.sum + data2.sum;
+ expected_data.squares = data.squares + data2.squares;
+ expected_data.logs = data.squares + data2.logs;
+ expected_data.samples = data.samples + data2.samples;
+
+ // Compare results
+ checkExpectedDistData(merge_data, expected_data, false);
+}
+
+/**
+ * Test whether zero is correctly set as the reset value. The test order is
+ * to check if it is initially zero on creation, then it is made non zero,
+ * and finally reset to zero.
+ */
+TEST(StatsSampleStorTest, ZeroReset)
+{
+ Stats::SampleStor stor(nullptr);
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+
+ ASSERT_TRUE(stor.zero());
+
+ stor.reset(nullptr);
+ stor.sample(val, num_samples);
+ ASSERT_FALSE(stor.zero());
+
+ stor.reset(nullptr);
+ ASSERT_TRUE(stor.zero());
+}
+
+/** Test setting and getting value from storage. */
+TEST(StatsSampleStorTest, SamplePrepare)
+{
+ Stats::SampleStor stor(nullptr);
+ ValueSamples values[] = {{10, 5}, {1234, 2}, {0xFFFFFFFF, 18}};
+ int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::Counter val;
+ Stats::DistData data;
+ Stats::DistData expected_data;
+ Stats::DistParams params(Stats::Deviation);
+ MockInfo info(¶ms);
+
+ // Simple test with one value being sampled
+ stor.sample(values[0].value, values[0].numSamples);
+ stor.prepare(&info, data);
+ val = values[0].value * values[0].numSamples;
+ expected_data.type = Stats::Deviation;
+ expected_data.sum = val;
+ expected_data.squares = values[0].value * val;
+ expected_data.samples = values[0].numSamples;
+ ASSERT_EQ(data.type, expected_data.type);
+ ASSERT_EQ(data.sum, expected_data.sum);
+ ASSERT_EQ(data.squares, expected_data.squares);
+ ASSERT_EQ(data.samples, expected_data.samples);
+
+ // Reset storage, and make sure all data has been cleared
+ expected_data.sum = 0;
+ expected_data.squares = 0;
+ expected_data.samples = 0;
+ stor.reset(nullptr);
+ stor.prepare(&info, data);
+ ASSERT_EQ(data.type, expected_data.type);
+ ASSERT_EQ(data.sum, expected_data.sum);
+ ASSERT_EQ(data.squares, expected_data.squares);
+ ASSERT_EQ(data.samples, expected_data.samples);
+
+ // Populate storage with more data
+ for (int i = 0; i < num_values; i++) {
+ stor.sample(values[i].value, values[i].numSamples);
+
+ val = values[i].value * values[i].numSamples;
+ expected_data.sum += val;
+ expected_data.squares += values[i].value * val;
+ expected_data.samples += values[i].numSamples;
+ }
+ stor.prepare(&info, data);
+ ASSERT_EQ(data.type, expected_data.type);
+ ASSERT_EQ(data.sum, expected_data.sum);
+ ASSERT_EQ(data.squares, expected_data.squares);
+ ASSERT_EQ(data.samples, expected_data.samples);
+}
+
+/** The size is always 1, no matter which functions have been called. */
+TEST(StatsSampleStorTest, Size)
+{
+ Stats::SampleStor stor(nullptr);
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+ Stats::DistData data;
+ Stats::DistParams params(Stats::Deviation);
+ MockInfo info(¶ms);
+
+ ASSERT_EQ(stor.size(), 1);
+ stor.sample(val, num_samples);
+ ASSERT_EQ(stor.size(), 1);
+ stor.prepare(&info, data);
+ ASSERT_EQ(stor.size(), 1);
+ stor.reset(nullptr);
+ ASSERT_EQ(stor.size(), 1);
+ stor.zero();
+ ASSERT_EQ(stor.size(), 1);
+}
+
+/**
+ * Test whether zero is correctly set as the reset value. The test order is
+ * to check if it is initially zero on creation, then it is made non zero,
+ * and finally reset to zero.
+ */
+TEST(StatsAvgSampleStorTest, ZeroReset)
+{
+ Stats::AvgSampleStor stor(nullptr);
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+
+ ASSERT_TRUE(stor.zero());
+
+ stor.reset(nullptr);
+ stor.sample(val, num_samples);
+ ASSERT_FALSE(stor.zero());
+
+ stor.reset(nullptr);
+ ASSERT_TRUE(stor.zero());
+}
+
+/** Test setting and getting value from storage. */
+TEST(StatsAvgSampleStorTest, SamplePrepare)
+{
+ Stats::AvgSampleStor stor(nullptr);
+ ValueSamples values[] = {{10, 5}, {1234, 2}, {0xFFFFFFFF, 18}};
+ int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::Counter val;
+ Stats::DistData data;
+ Stats::DistData expected_data;
+ Stats::DistParams params(Stats::Deviation);
+ MockInfo info(¶ms);
+
+ // Simple test with one value being sampled
+ stor.sample(values[0].value, values[0].numSamples);
+ stor.prepare(&info, data);
+ val = values[0].value * values[0].numSamples;
+ expected_data.type = Stats::Deviation;
+ expected_data.sum = val;
+ expected_data.squares = values[0].value * val;
+ ASSERT_EQ(data.type, expected_data.type);
+ ASSERT_EQ(data.sum, expected_data.sum);
+ ASSERT_EQ(data.squares, expected_data.squares);
+ ASSERT_EQ(data.samples, curTick());
+
+ increaseTick();
+
+ // Reset storage, and make sure all data has been cleared
+ expected_data.sum = 0;
+ expected_data.squares = 0;
+ stor.reset(nullptr);
+ stor.prepare(&info, data);
+ ASSERT_EQ(data.type, expected_data.type);
+ ASSERT_EQ(data.sum, expected_data.sum);
+ ASSERT_EQ(data.squares, expected_data.squares);
+ ASSERT_EQ(data.samples, curTick());
+
+ increaseTick();
+
+ // Populate storage with more data
+ for (int i = 0; i < num_values; i++) {
+ stor.sample(values[i].value, values[i].numSamples);
+
+ val = values[i].value * values[i].numSamples;
+ expected_data.sum += val;
+ expected_data.squares += values[i].value * val;
+ }
+ stor.prepare(&info, data);
+ ASSERT_EQ(data.type, expected_data.type);
+ ASSERT_EQ(data.sum, expected_data.sum);
+ ASSERT_EQ(data.squares, expected_data.squares);
+ ASSERT_EQ(data.samples, curTick());
+}
+
+/** The size is always 1, no matter which functions have been called. */
+TEST(StatsAvgSampleStorTest, Size)
+{
+ Stats::AvgSampleStor stor(nullptr);
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+ Stats::DistData data;
+ Stats::DistParams params(Stats::Deviation);
+ MockInfo info(¶ms);
+
+ ASSERT_EQ(stor.size(), 1);
+ stor.sample(val, num_samples);
+ ASSERT_EQ(stor.size(), 1);
+ stor.prepare(&info, data);
+ ASSERT_EQ(stor.size(), 1);
+ stor.reset(nullptr);
+ ASSERT_EQ(stor.size(), 1);
+ stor.zero();
+ ASSERT_EQ(stor.size(), 1);
+}
+
+/**
+ * Test whether zero is correctly set as the reset value. The test order is
+ * to check if it is initially zero on creation, then it is made non zero,
+ * and finally reset to zero.
+ */
+TEST(StatsSparseHistStorTest, ZeroReset)
+{
+ Stats::SparseHistStor stor(nullptr);
+ Stats::Counter val = 10;
+ Stats::Counter num_samples = 5;
+
+ ASSERT_TRUE(stor.zero());
+
+ stor.reset(nullptr);
+ stor.sample(val, num_samples);
+ ASSERT_FALSE(stor.zero());
+
+ stor.reset(nullptr);
+ ASSERT_TRUE(stor.zero());
+}
+
+/** Test setting and getting value from storage. */
+TEST(StatsSparseHistStorTest, SamplePrepare)
+{
+ Stats::SparseHistStor stor(nullptr);
+ ValueSamples values[] = {{10, 5}, {1234, 2}, {0xFFFFFFFF, 18}};
+ int num_values = sizeof(values) / sizeof(ValueSamples);
+ Stats::Counter total_samples;
+ Stats::SparseHistData data;
+
+ // Simple test with one value being sampled
+ stor.sample(values[0].value, values[0].numSamples);
+ stor.prepare(nullptr, data);
+ ASSERT_EQ(stor.size(), 1);
+ ASSERT_EQ(data.cmap.size(), 1);
+ ASSERT_EQ(data.cmap[values[0].value], values[0].numSamples);
+ ASSERT_EQ(data.samples, values[0].numSamples);
+
+ // Reset storage, and make sure all data has been cleared
+ stor.reset(nullptr);
+ stor.prepare(nullptr, data);
+ ASSERT_EQ(stor.size(), 0);
+ ASSERT_EQ(data.cmap.size(), 0);
+ ASSERT_EQ(data.samples, 0);
+
+ // Populate storage with more data
+ for (int i = 0; i < num_values; i++) {
+ stor.sample(values[i].value, values[i].numSamples);
+ }
+ stor.prepare(nullptr, data);
+ total_samples = 0;
+ ASSERT_EQ(stor.size(), num_values);
+ ASSERT_EQ(data.cmap.size(), num_values);
+ for (int i = 0; i < num_values; i++) {
+ ASSERT_EQ(data.cmap[values[i].value], values[i].numSamples);
+ total_samples += values[i].numSamples;
+ }
+ ASSERT_EQ(data.samples, total_samples);
+}