blob: cf22e1008009d7ab73d871cdeb8ecb0009ba8478 [file] [log] [blame]
/*
* 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
{
GEM5_DEPRECATED_NAMESPACE(Stats, statistics);
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__