| /* |
| * 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 <cmath> |
| |
| #include "base/cast.hh" |
| #include "base/compiler.hh" |
| #include "base/logging.hh" |
| #include "base/stats/types.hh" |
| #include "sim/cur_tick.hh" |
| |
| namespace gem5 |
| { |
| |
| namespace statistics |
| { |
| |
| 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(const StorageParams* const storage_params) |
| : 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(const StorageParams* const storage_params) { } |
| |
| /** |
| * Reset stat value to default |
| */ |
| void reset(const StorageParams* const storage_params) { 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(const StorageParams* const storage_params) |
| : 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(const StorageParams* const storage_params) |
| { |
| total += current * (curTick() - last); |
| last = curTick(); |
| } |
| |
| /** |
| * Reset stat value to default |
| */ |
| void |
| reset(const StorageParams* const storage_params) |
| { |
| 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(Counter _min, Counter _max, Counter _bucket_size) |
| : DistParams(Dist), min(_min), max(_max), bucket_size(_bucket_size), |
| buckets(0) |
| { |
| fatal_if(bucket_size <= 0, |
| "Bucket size (%f) must be greater than zero", bucket_size); |
| warn_if(std::floor((max - min + 1.0) / bucket_size) != |
| std::ceil((max - min + 1.0) / bucket_size), |
| "Bucket size (%f) does not divide range [%f:%f] into equal-" \ |
| "sized buckets. Rounding up.", bucket_size, min + 1.0, max); |
| |
| buckets = std::ceil((max - min + 1.0) / bucket_size); |
| } |
| }; |
| |
| DistStor(const StorageParams* const storage_params) |
| : cvec(safe_cast<const Params *>(storage_params)->buckets) |
| { |
| reset(storage_params); |
| } |
| |
| /** |
| * 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(const StorageParams* const storage_params, DistData &data) |
| { |
| const Params *params = safe_cast<const Params *>(storage_params); |
| |
| 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(const StorageParams* const storage_params) |
| { |
| const Params *params = safe_cast<const Params *>(storage_params); |
| 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(size_type _buckets) |
| : DistParams(Hist) |
| { |
| fatal_if(_buckets < 2, |
| "There must be at least two buckets in a histogram"); |
| buckets = _buckets; |
| } |
| }; |
| |
| HistStor(const StorageParams* const storage_params) |
| : cvec(safe_cast<const Params *>(storage_params)->buckets) |
| { |
| reset(storage_params); |
| } |
| |
| /** |
| * 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(const StorageParams* const storage_params, DistData &data) |
| { |
| const Params *params = safe_cast<const Params *>(storage_params); |
| |
| 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(const StorageParams* const storage_params) |
| { |
| const Params *params = safe_cast<const Params *>(storage_params); |
| 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(const StorageParams* const storage_params) |
| : 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(const StorageParams* const storage_params, DistData &data) |
| { |
| const Params *params = safe_cast<const Params *>(storage_params); |
| |
| assert(params->type == Deviation); |
| data.type = params->type; |
| data.sum = sum; |
| data.squares = squares; |
| data.samples = samples; |
| } |
| |
| /** |
| * Reset stat value to default |
| */ |
| void |
| reset(const StorageParams* const storage_params) |
| { |
| 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(const StorageParams* const storage_params) |
| : 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(const StorageParams* const storage_params, DistData &data) |
| { |
| const Params *params = safe_cast<const Params *>(storage_params); |
| |
| assert(params->type == Deviation); |
| data.type = params->type; |
| data.sum = sum; |
| data.squares = squares; |
| data.samples = curTick(); |
| } |
| |
| /** |
| * Reset stat value to default |
| */ |
| void |
| reset(const StorageParams* const storage_params) |
| { |
| 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(const StorageParams* const storage_params) |
| { |
| reset(storage_params); |
| } |
| |
| /** |
| * 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(const StorageParams* const storage_params, 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(const StorageParams* const storage_params) |
| { |
| cmap.clear(); |
| samples = 0; |
| } |
| }; |
| |
| } // namespace statistics |
| } // namespace gem5 |
| |
| #endif // __BASE_STATS_STORAGE_HH__ |