blob: b154573f4d15c53ecdea465f01937f49f232b304 [file] [log] [blame]
// Copyright 2022 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef COBALT_BASE_STATISTICS_H_
#define COBALT_BASE_STATISTICS_H_
#include <algorithm>
#include <functional>
#include <string>
#include <vector>
#include "cobalt/base/c_val.h"
#include "starboard/types.h"
namespace base {
namespace internal {
// The default function to convert a sample to its value by dividing.
inline int64_t DefaultSampleToValueFunc(int64_t dividend, int64_t divisor) {
if (divisor == 0) {
divisor = 1;
}
return dividend / divisor;
}
} // namespace internal
// Track the statistics of a series of generic samples reprensented as
// dividends and divisors in integer types. The value of a sample is calculated
// by `SampleToValueFunc` using its dividend and divisor.
//
// Example usages:
// 1. Set dividends to bytes in int and divisors to SbTime, to track the
// statistics of bandwidth.
// 2. Set the divisor to always be 1, to track the statistics of a count or a
// duration.
//
// Currently the class can produce the following statistics:
// 1. Average, tracked across all samples.
// 2. Median, tracked across the most recent |MaxSamples| samples.
// 3. Min, tracked across all samples.
// 4. Max, tracked across all samples.
//
// Notes:
// 1. The accumulated values are stored in `int64_t`, and it is the user's
// responsibility to avoid overflow.
// 2. The class isn't multi-thread safe, and it is the user's responsibility
// to synchronize the usage when necessary.
// 3. Both the dividend and divisor of the samples should ideally be positive,
// which is NOT enforced by the class.
#if defined(COBALT_BUILD_TYPE_GOLD)
template <typename DividendType, typename DivisorType, int MaxSamples,
int64_t (*SampleToValueFunc)(int64_t, int64_t) =
internal::DefaultSampleToValueFunc>
class Statistics {
public:
explicit Statistics(const std::string& metric_name) {}
void AddSample(DividendType dividend, DivisorType divisor) {}
int64_t accumulated_dividend() const { return 0; }
int64_t accumulated_divisor() const { return 1; }
int64_t average() const { return 0; }
int64_t min() const { return 0; }
int64_t max() const { return 0; }
int64_t GetMedian() const { return 0; }
};
#else // defined(COBALT_BUILD_TYPE_GOLD)
template <typename DividendType, typename DivisorType, size_t MaxSamples,
int64_t (*SampleToValueFunc)(int64_t, int64_t) =
internal::DefaultSampleToValueFunc>
class Statistics {
public:
explicit Statistics(const std::string& metric_name)
: last_sample_value_(metric_name + ".Latest", 0,
metric_name + " most recent value."),
median_sample_value_(metric_name + ".Median", 0,
metric_name + " median value.") {}
void AddSample(DividendType dividend, DivisorType divisor) {
static_assert(MaxSamples > 0, "MaxSamples has to be greater than 0.");
auto& current =
samples_[(first_sample_index_ + number_of_samples_) % MaxSamples];
if (number_of_samples_ == MaxSamples) {
first_sample_index_ = (first_sample_index_ + 1) % MaxSamples;
} else {
++number_of_samples_;
}
current.dividend = dividend;
current.divisor = divisor;
accumulated_dividend_ += dividend;
accumulated_divisor_ += divisor;
auto value = GetSampleValue(current);
if (first_sample_added_) {
min_ = std::min(min_, value);
max_ = std::max(max_, value);
} else {
min_ = max_ = value;
first_sample_added_ = true;
}
last_sample_value_ = value;
// TODO(b/258531018) this should be optimized in future.
median_sample_value_ = GetMedian();
}
int64_t accumulated_dividend() const { return accumulated_dividend_; }
int64_t accumulated_divisor() const { return accumulated_divisor_; }
int64_t average() const {
return SampleToValueFunc(accumulated_dividend_, accumulated_divisor_);
}
int64_t min() const { return min_; }
int64_t max() const { return max_; }
// When there are even number of samples in the object, it is implementation
// specific to pick any number close to the middle, or the median of the
// numbers close to the middle. Use {1, 2, 3, 4} as an example, the median
// can be 3, or 4, or 3.5.
int64_t GetMedian() const {
if (number_of_samples_ == 0) {
return 0;
}
std::vector<Sample> copy;
copy.reserve(number_of_samples_);
if (first_sample_index_ + number_of_samples_ <= MaxSamples) {
copy.assign(samples_ + first_sample_index_,
samples_ + first_sample_index_ + number_of_samples_);
} else {
auto samples_to_copy = number_of_samples_;
copy.assign(samples_ + first_sample_index_, samples_ + MaxSamples);
samples_to_copy -= MaxSamples - first_sample_index_;
copy.insert(copy.end(), samples_, samples_ + samples_to_copy);
}
std::nth_element(copy.begin(), copy.begin() + number_of_samples_ / 2,
copy.end(), [](const Sample& left, const Sample& right) {
return GetSampleValue(left) < GetSampleValue(right);
});
return GetSampleValue(copy[number_of_samples_ / 2]);
}
private:
Statistics(const Statistics&) = delete;
Statistics& operator=(const Statistics&) = delete;
struct Sample {
DividendType dividend = 0;
DivisorType divisor = 0;
};
static int64_t GetSampleValue(const Sample& sample) {
return SampleToValueFunc(static_cast<int64_t>(sample.dividend),
static_cast<int64_t>(sample.divisor));
}
bool first_sample_added_ = false;
int64_t accumulated_dividend_ = 0;
int64_t accumulated_divisor_ = 0;
int64_t min_ = 0;
int64_t max_ = 0;
Sample samples_[MaxSamples] = {}; // Ring buffer for samples.
size_t number_of_samples_ = 0; // Number of samples in |samples_|.
size_t first_sample_index_ = 0; // Index of the first sample in |samples_|.
base::CVal<int64_t> last_sample_value_;
base::CVal<int64_t> median_sample_value_;
};
#endif // defined(COBALT_BUILD_TYPE_GOLD)
} // namespace base
#endif // COBALT_BASE_STATISTICS_H_