From c9e069a2f279cb4b51cce13a9db5fc7978c4938c Mon Sep 17 00:00:00 2001 From: "Daniel R. Carvalho" Date: Wed, 5 Feb 2020 22:12:38 +0100 Subject: [PATCH] base,tests: Create unit tests for Stats::Stor Create unit tests for the stats storage types. As a side effect storage-related classes have been moved to separate files. HistStor's grow_up, grow_out, and grow_convert have been made private and renamed to comply with gem5's naming convention and make grow_convert match its grow_up counterpart (growDown) which is more suitable for its expected behavior. The params declarations have been moved to be close to their storage class' constructor. HistStor has a explicit condition stating that there must be at least 2 buckets. Added documentation! Fixed grow_convert so that it yields consistent histograms. Previously buckets could not fully intersect, so doubling their bucket size would make them steal contents innaproprietly. For example, the neighbors [-6,-2[, [-2,2[, [2,6[, when doubled, become [-12,-4[, [-4,4[, [4,12[; however, since the individual values are not stored, it is impossible to know how to populate the middle bucket with its neighbor's partial contents. This fix forces the middle bucket of a storage to have its lower bound at 0, solving the partial intersection issue. Change-Id: Idb063e3dbda3cce3a8969e347660143162146eb9 Signed-off-by: Daniel R. Carvalho Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/25425 Reviewed-by: Jason Lowe-Power Maintainer: Jason Lowe-Power Tested-by: kokoro --- src/base/statistics.cc | 108 --- src/base/statistics.hh | 701 +---------------- src/base/stats/SConscript | 3 + src/base/stats/storage.cc | 228 ++++++ src/base/stats/storage.hh | 767 +++++++++++++++++++ src/base/stats/storage.test.cc | 1303 ++++++++++++++++++++++++++++++++ 6 files changed, 2302 insertions(+), 808 deletions(-) create mode 100644 src/base/stats/storage.cc create mode 100644 src/base/stats/storage.hh create mode 100644 src/base/stats/storage.test.cc diff --git a/src/base/statistics.cc b/src/base/statistics.cc index 80c71abf7..122f54be3 100644 --- a/src/base/statistics.cc +++ b/src/base/statistics.cc @@ -131,114 +131,6 @@ InfoAccess::info() const } } -StorageParams::~StorageParams() -{ -} - -void -HistStor::grow_out() -{ - int size = cvec.size(); - int zero = size / 2; // round down! - int top_half = zero + (size - zero + 1) / 2; // round up! - int bottom_half = (size - zero) / 2; // round down! - - // grow down - int low_pair = zero - 1; - for (int i = zero - 1; i >= bottom_half; i--) { - cvec[i] = cvec[low_pair]; - if (low_pair - 1 >= 0) - cvec[i] += cvec[low_pair - 1]; - low_pair -= 2; - } - assert(low_pair == 0 || low_pair == -1 || low_pair == -2); - - for (int i = bottom_half - 1; i >= 0; i--) - cvec[i] = Counter(); - - // grow up - int high_pair = zero; - for (int i = zero; i < top_half; i++) { - cvec[i] = cvec[high_pair]; - if (high_pair + 1 < size) - cvec[i] += cvec[high_pair + 1]; - high_pair += 2; - } - assert(high_pair == size || high_pair == size + 1); - - for (int i = top_half; i < size; i++) - cvec[i] = Counter(); - - max_bucket *= 2; - min_bucket *= 2; - bucket_size *= 2; -} - -void -HistStor::grow_convert() -{ - int size = cvec.size(); - int half = (size + 1) / 2; // round up! - //bool even = (size & 1) == 0; - - int pair = size - 1; - for (int i = size - 1; i >= half; --i) { - cvec[i] = cvec[pair]; - if (pair - 1 >= 0) - cvec[i] += cvec[pair - 1]; - pair -= 2; - } - - for (int i = half - 1; i >= 0; i--) - cvec[i] = Counter(); - - min_bucket = -max_bucket;// - (even ? bucket_size : 0); - bucket_size *= 2; -} - -void -HistStor::grow_up() -{ - int size = cvec.size(); - int half = (size + 1) / 2; // round up! - - int pair = 0; - for (int i = 0; i < half; i++) { - cvec[i] = cvec[pair]; - if (pair + 1 < size) - cvec[i] += cvec[pair + 1]; - pair += 2; - } - assert(pair == size || pair == size + 1); - - for (int i = half; i < size; i++) - cvec[i] = Counter(); - - max_bucket *= 2; - bucket_size *= 2; -} - -void -HistStor::add(HistStor *hs) -{ - int b_size = hs->size(); - assert(size() == b_size); - assert(min_bucket == hs->min_bucket); - - sum += hs->sum; - logs += hs->logs; - squares += hs->squares; - samples += hs->samples; - - while (bucket_size > hs->bucket_size) - hs->grow_up(); - while (bucket_size < hs->bucket_size) - grow_up(); - - for (uint32_t i = 0; i < b_size; i++) - cvec[i] += hs->cvec[i]; -} - Formula::Formula(Group *parent, const char *name, const char *desc) : DataWrapVec(parent, name, desc) diff --git a/src/base/statistics.hh b/src/base/statistics.hh index 20544c2a1..1c3d53df6 100644 --- a/src/base/statistics.hh +++ b/src/base/statistics.hh @@ -75,14 +75,13 @@ #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 { @@ -172,11 +171,6 @@ class Vector2dInfoProxy : public InfoProxy Result total() const { return this->s.total(); } }; -struct StorageParams -{ - virtual ~StorageParams(); -}; - class InfoAccess { private: @@ -482,167 +476,6 @@ class DataWrapVec2d : public DataWrapVec // ////////////////////////////////////////////////////////////////////// -/** - * 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. @@ -1406,462 +1239,6 @@ class Vector2dBase : public DataWrapVec2d // 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(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(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(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(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(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(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(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(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 @@ -1953,7 +1330,6 @@ class DistBase : public DataWrap * Add the argument distribution to the this distribution. */ void add(DistBase &d) { data()->add(d.data()); } - }; template @@ -2928,81 +2304,6 @@ class SparseHistBase : public DataWrap } }; -/** - * 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 { public: diff --git a/src/base/stats/SConscript b/src/base/stats/SConscript index 953999c13..69ac27532 100644 --- a/src/base/stats/SConscript +++ b/src/base/stats/SConscript @@ -30,6 +30,7 @@ Import('*') Source('group.cc') Source('info.cc') +Source('storage.cc') Source('text.cc') if env['USE_HDF5']: @@ -38,3 +39,5 @@ if env['USE_HDF5']: else: Source('hdf5.cc') +GTest('storage.test', 'storage.test.cc', '../debug.cc', '../str.cc', 'info.cc', + 'storage.cc', '../../sim/cur_tick.cc') diff --git a/src/base/stats/storage.cc b/src/base/stats/storage.cc new file mode 100644 index 000000000..02e871649 --- /dev/null +++ b/src/base/stats/storage.cc @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2021 Daniel R. Carvalho + * Copyright (c) 2019 Arm Limited + * All rights reserved. + * + * The license below extends only to copyright in the software and shall + * not be construed as granting a license to any other intellectual + * property including but not limited to intellectual property relating + * to a hardware implementation of the functionality of the software + * licensed hereunder. You may use the software subject to the license + * terms below provided that you ensure that this notice is replicated + * unmodified and in its entirety in all distributions of the software, + * modified or unmodified, in source code or in binary form. + * + * 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. + */ + +#include "base/stats/storage.hh" + +#include + +namespace Stats { + +void +DistStor::sample(Counter val, int number) +{ + assert(bucket_size > 0); + 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; +} + +void +HistStor::growOut() +{ + int size = cvec.size(); + int zero = size / 2; // round down! + int top_half = zero + (size - zero + 1) / 2; // round up! + int bottom_half = (size - zero) / 2; // round down! + + // grow down + int low_pair = zero - 1; + for (int i = zero - 1; i >= bottom_half; i--) { + cvec[i] = cvec[low_pair]; + if (low_pair - 1 >= 0) + cvec[i] += cvec[low_pair - 1]; + low_pair -= 2; + } + assert(low_pair == 0 || low_pair == -1 || low_pair == -2); + + for (int i = bottom_half - 1; i >= 0; i--) + cvec[i] = Counter(); + + // grow up + int high_pair = zero; + for (int i = zero; i < top_half; i++) { + cvec[i] = cvec[high_pair]; + if (high_pair + 1 < size) + cvec[i] += cvec[high_pair + 1]; + high_pair += 2; + } + assert(high_pair == size || high_pair == size + 1); + + for (int i = top_half; i < size; i++) + cvec[i] = Counter(); + + max_bucket *= 2; + min_bucket *= 2; + bucket_size *= 2; +} + +void +HistStor::growDown() +{ + const int size = cvec.size(); + const int zero = size / 2; // round down! + const bool even = ((size - 1) % 2) == 0; + + // Make sure that zero becomes the lower bound of the middle bucket. On + // an even number of buckets the last bucket does not change its lower + // bound, therefore it does not need to absorb any other bucket + int pair = size - 1; + if (even) { + pair--; + } + for (int i = pair; i >= zero; --i) { + cvec[i] = cvec[pair]; + if (pair - 1 >= 0) + cvec[i] += cvec[pair - 1]; + pair -= 2; + } + + for (int i = zero - 1; i >= 0; i--) + cvec[i] = Counter(); + + // Double the range by using the negative of the lower bound of the last + // bucket as the new lower bound of the first bucket + min_bucket = -max_bucket; + + // A special case must be handled when there is an odd number of + // buckets so that zero is kept as the lower bound of the middle bucket + if (!even) { + min_bucket -= bucket_size; + max_bucket -= bucket_size; + } + + // Only update the bucket size once the range has been updated + bucket_size *= 2; +} + +void +HistStor::growUp() +{ + int size = cvec.size(); + int half = (size + 1) / 2; // round up! + + int pair = 0; + for (int i = 0; i < half; i++) { + cvec[i] = cvec[pair]; + if (pair + 1 < size) + cvec[i] += cvec[pair + 1]; + pair += 2; + } + assert(pair == size || pair == size + 1); + + for (int i = half; i < size; i++) + cvec[i] = Counter(); + + max_bucket *= 2; + bucket_size *= 2; +} + +void +HistStor::sample(Counter val, int number) +{ + assert(min_bucket < max_bucket); + if (val < min_bucket) { + if (min_bucket == 0) + growDown(); + + while (val < min_bucket) + growOut(); + } else if (val >= max_bucket + bucket_size) { + if (min_bucket == 0) { + while (val >= max_bucket + bucket_size) + growUp(); + } else { + while (val >= max_bucket + bucket_size) + growOut(); + } + } + + assert(bucket_size > 0); + 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 += std::log(val) * number; + samples += number; +} + +void +HistStor::add(HistStor *hs) +{ + int b_size = hs->size(); + assert(size() == b_size); + assert(min_bucket == hs->min_bucket); + + sum += hs->sum; + logs += hs->logs; + squares += hs->squares; + samples += hs->samples; + + while (bucket_size > hs->bucket_size) + hs->growUp(); + while (bucket_size < hs->bucket_size) + growUp(); + + for (uint32_t i = 0; i < b_size; i++) + cvec[i] += hs->cvec[i]; +} + +} // namespace Stats diff --git a/src/base/stats/storage.hh b/src/base/stats/storage.hh new file mode 100644 index 000000000..66565bbd9 --- /dev/null +++ b/src/base/stats/storage.hh @@ -0,0 +1,767 @@ +/* + * 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 + +#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(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(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(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(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(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(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(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(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__ diff --git a/src/base/stats/storage.test.cc b/src/base/stats/storage.test.cc new file mode 100644 index 000000000..717c881a8 --- /dev/null +++ b/src/base/stats/storage.test.cc @@ -0,0 +1,1303 @@ +/* + * 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 +#include + +#include + +#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); +} -- 2.30.2