blob: 69dec89f81705488b645bbdea92c4c6ec499237e [file] [log] [blame]
// Copyright 2017 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_C_VAL_COLLECTION_ENTRY_STATS_H_
#define COBALT_BASE_C_VAL_COLLECTION_ENTRY_STATS_H_
#include <algorithm>
#include <cmath>
#include <memory>
#include <numeric>
#include <sstream>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "cobalt/base/c_val.h"
namespace base {
namespace CValDetail {
// This struct contains the data generated by flushes of
// CValCollectionEntryStats for use with callbacks.
template <typename EntryType>
struct CValCollectionEntryStatsFlushResults {
typedef base::Callback<void(const CValCollectionEntryStatsFlushResults&)>
OnFlushCallback;
CValCollectionEntryStatsFlushResults(size_t sample_count, EntryType average,
EntryType minimum, EntryType maximum,
EntryType standard_deviation,
EntryType percentile_25th,
EntryType percentile_50th,
EntryType percentile_75th,
EntryType percentile_95th)
: sample_count(sample_count),
average(average),
minimum(minimum),
maximum(maximum),
standard_deviation(standard_deviation),
percentile_25th(percentile_25th),
percentile_50th(percentile_50th),
percentile_75th(percentile_75th),
percentile_95th(percentile_95th) {}
size_t sample_count;
EntryType average;
EntryType minimum;
EntryType maximum;
EntryType standard_deviation;
EntryType percentile_25th;
EntryType percentile_50th;
EntryType percentile_75th;
EntryType percentile_95th;
};
// This class tracks a collection of entries, which it retains in memory. When
// either the max size of the collection is reached or Flush() is manually
// called, the count, average, minimum, maximum, 25th, 50th, 75th and 95th
// percentiles, and standard deviation of the collection are recorded with
// CVals, and the tracking resets in preparation for the next collection of
// entries.
// NOTE1: By default there is no max size and the collection will continue to
// grow indefinitely until Flush() is called.
// NOTE2: This class keeps all of the entries in memory until flush is called so
// that percentiles can be determined. In cases where the number of
// entries is extremely large, |CValTimeIntervalEntryStats| is more
// appropriate, as it does its tracking without keeping entries in memory
// (at the cost of not being able to provide percentiles);
// NOTE3: This class provides the ability to record all entries within a single
// string CVal when |enable_entry_list_c_val| is set to true in the
// constructor. By default, this CVal is not used.
template <typename EntryType, typename Visibility = CValDebug>
class CValCollectionEntryStatsImpl {
public:
static const size_t kNoMaxSize = 0;
typedef CValCollectionEntryStatsFlushResults<EntryType> FlushResults;
typedef typename FlushResults::OnFlushCallback OnFlushCallback;
CValCollectionEntryStatsImpl(
const std::string& name, size_t max_size = kNoMaxSize,
bool enable_entry_list_c_val = false,
const OnFlushCallback& on_flush = OnFlushCallback());
// Add an entry to the collection. This may trigger a Flush() if adding the
// entry causes the max size to be reached.
void AddEntry(const EntryType& value);
// Manually flush the collection's entries. This updates the stat cvals and
// clears the entries.
void Flush();
private:
typedef std::vector<EntryType> CollectionType;
static EntryType CalculatePercentile(const CollectionType& sorted_collection,
int percentile);
static double CalculateStandardDeviation(const CollectionType& collection,
double mean);
// Constructs a string representing the entries within the collection and
// populates |entry_list_| with it if the entry list CVal was enabled during
// construction.
void PopulateEntryList();
// The maximum size of the collection before Flush() is automatically called.
const size_t max_size_;
// Callback to call whenever the values are flushed.
const OnFlushCallback on_flush_;
// The current collection of entries. These will be used to generate the cval
// stats during the next call of Flush().
CollectionType collection_;
// CVals of the stats for the previous collection.
base::CVal<size_t, Visibility> count_;
base::CVal<EntryType, Visibility> average_;
base::CVal<EntryType, Visibility> minimum_;
base::CVal<EntryType, Visibility> maximum_;
base::CVal<EntryType, Visibility> percentile_25th_;
base::CVal<EntryType, Visibility> percentile_50th_;
base::CVal<EntryType, Visibility> percentile_75th_;
base::CVal<EntryType, Visibility> percentile_95th_;
base::CVal<EntryType, Visibility> standard_deviation_;
// |entry_list_| is only non-NULL when it is enabled.
std::unique_ptr<base::CVal<std::string, Visibility> > entry_list_;
};
template <typename EntryType, typename Visibility>
CValCollectionEntryStatsImpl<EntryType, Visibility>::
CValCollectionEntryStatsImpl(
const std::string& name, size_t max_size /*=kNoMaxSize*/,
bool enable_entry_list_c_val /*=false*/,
const OnFlushCallback& on_flush /*=OnFlushCallback()*/)
: max_size_(max_size),
on_flush_(on_flush),
count_(base::StringPrintf("%s.Cnt", name.c_str()), 0, "Total entries."),
average_(base::StringPrintf("%s.Avg", name.c_str()), EntryType(),
"Average value."),
minimum_(base::StringPrintf("%s.Min", name.c_str()), EntryType(),
"Minimum value."),
maximum_(base::StringPrintf("%s.Max", name.c_str()), EntryType(),
"Maximum value."),
percentile_25th_(base::StringPrintf("%s.Pct.25th", name.c_str()),
EntryType(), "25th percentile value."),
percentile_50th_(base::StringPrintf("%s.Pct.50th", name.c_str()),
EntryType(), "50th percentile value."),
percentile_75th_(base::StringPrintf("%s.Pct.75th", name.c_str()),
EntryType(), "75th percentile value."),
percentile_95th_(base::StringPrintf("%s.Pct.95th", name.c_str()),
EntryType(), "95th percentile value."),
standard_deviation_(base::StringPrintf("%s.Std", name.c_str()),
EntryType(), "Standard deviation of values.") {
if (enable_entry_list_c_val) {
entry_list_.reset(new base::CVal<std::string, Visibility>(
base::StringPrintf("%s.EntryList", name.c_str()), "[]",
"The list of entries in the collection."));
}
}
template <typename EntryType, typename Visibility>
void CValCollectionEntryStatsImpl<EntryType, Visibility>::AddEntry(
const EntryType& value) {
collection_.push_back(value);
if (collection_.size() == max_size_) {
Flush();
}
}
template <typename EntryType, typename Visibility>
void CValCollectionEntryStatsImpl<EntryType, Visibility>::Flush() {
if (collection_.size() == 0) {
return;
}
// Populate the entry list cval before the collection is sorted.
PopulateEntryList();
// Sort the collection. This allows min, max, and percentiles to be easily
// determined.
std::sort(collection_.begin(), collection_.end());
// Calculate the mean of the collection.
EntryType sum = std::accumulate(collection_.begin(), collection_.end(),
CValDetail::FromDouble<EntryType>(0));
double mean = CValDetail::ToDouble<EntryType>(sum) / collection_.size();
// Update the collection stat cvals.
size_t count = collection_.size();
EntryType average = CValDetail::FromDouble<EntryType>(mean);
EntryType minimum = collection_.front();
EntryType maximum = collection_.back();
EntryType percentile_25th = CalculatePercentile(collection_, 25);
EntryType percentile_50th = CalculatePercentile(collection_, 50);
EntryType percentile_75th = CalculatePercentile(collection_, 75);
EntryType percentile_95th = CalculatePercentile(collection_, 95);
EntryType standard_deviation = CValDetail::FromDouble<EntryType>(
CalculateStandardDeviation(collection_, mean));
// Flush the computed values out to the CVals.
count_ = count;
average_ = average;
minimum_ = minimum;
maximum_ = maximum;
percentile_25th_ = percentile_25th;
percentile_50th_ = percentile_50th;
percentile_75th_ = percentile_75th;
percentile_95th_ = percentile_95th;
standard_deviation_ = standard_deviation;
collection_.clear();
// Callback to any listeners with the flush values.
if (!on_flush_.is_null()) {
on_flush_.Run(FlushResults(
count, average, minimum, maximum, standard_deviation, percentile_25th,
percentile_50th, percentile_75th, percentile_95th));
}
}
template <typename EntryType, typename Visibility>
EntryType
CValCollectionEntryStatsImpl<EntryType, Visibility>::CalculatePercentile(
const CollectionType& sorted_collection, int percentile) {
DCHECK_GT(sorted_collection.size(), 0);
DCHECK(percentile >= 0 && percentile <= 100);
// Determine the position of the percentile within the collection.
double percentile_position =
(sorted_collection.size() - 1) * static_cast<double>(percentile) * 0.01;
// Split out the integral and fractional parts of the percentile position.
double percentile_integral_position, percentile_fractional_position;
percentile_fractional_position =
std::modf(percentile_position, &percentile_integral_position);
int percentile_first_index = static_cast<int>(percentile_integral_position);
// If |percentile_first_index| maps to the last entry, then there is no
// second entry and there's nothing to interpolate; simply use the last entry.
if (sorted_collection.size() == percentile_first_index + 1) {
return sorted_collection.back();
}
// Interpolate between the two entries that the percentile falls between.
double first_data_point = CValDetail::ToDouble<EntryType>(
sorted_collection[percentile_first_index]) *
(1.0 - percentile_fractional_position);
double second_data_point =
CValDetail::ToDouble<EntryType>(
sorted_collection[percentile_first_index + 1]) *
percentile_fractional_position;
return CValDetail::FromDouble<EntryType>(first_data_point +
second_data_point);
}
template <typename EntryType, typename Visibility>
double
CValCollectionEntryStatsImpl<EntryType, Visibility>::CalculateStandardDeviation(
const CollectionType& collection, double mean) {
if (collection.size() <= 1) {
return 0;
}
double dif_squared_sum = 0;
for (size_t i = 0; i < collection.size(); ++i) {
double dif = CValDetail::ToDouble<EntryType>(collection[i]) - mean;
dif_squared_sum += dif * dif;
}
double variance =
dif_squared_sum / static_cast<double>(collection.size() - 1);
return std::sqrt(variance);
}
template <typename EntryType, typename Visibility>
void CValCollectionEntryStatsImpl<EntryType, Visibility>::PopulateEntryList() {
// If the entry list was not enabled, then |entry_list_| will be NULL and
// there is nothing to do. Simply return.
if (!entry_list_) {
return;
}
// Construct a string containing a list representation of the collection
// entries and set the entry list CVal to it.
std::ostringstream oss;
oss << "[";
for (size_t i = 0; i < collection_.size(); ++i) {
if (i > 0) {
oss << ", ";
}
oss << CValDetail::ValToString<EntryType>(collection_[i]);
}
oss << "]";
*entry_list_ = oss.str();
}
#if !defined(ENABLE_DEBUG_C_VAL)
// This is a stub class that disables CValCollectionEntryStats when
// ENABLE_DEBUG_C_VAL is not defined.
template <typename EntryType>
class CValCollectionEntryStatsStub {
public:
typedef CValCollectionEntryStatsFlushResults<EntryType> FlushResults;
typedef typename FlushResults::OnFlushCallback OnFlushCallback;
explicit CValCollectionEntryStatsStub(const std::string& name) {}
CValCollectionEntryStatsStub(
const std::string& name, size_t max_size, bool enable_entry_list_c_val,
const OnFlushCallback& on_flush = OnFlushCallback()) {}
void AddEntry(const EntryType& value) {}
void Flush() {}
};
#endif // ENABLE_DEBUG_C_VAL
} // namespace CValDetail
template <typename EntryType, typename Visibility = CValDebug>
class CValCollectionEntryStats {};
template <typename EntryType>
#if defined(ENABLE_DEBUG_C_VAL)
// When ENABLE_DEBUG_C_VAL is defined, CVals with Visibility set to CValDebug
// are tracked through the CVal system.
class CValCollectionEntryStats<EntryType, CValDebug>
: public CValDetail::CValCollectionEntryStatsImpl<EntryType, CValDebug> {
typedef CValDetail::CValCollectionEntryStatsImpl<EntryType, CValDebug>
CValParent;
#else // ENABLE_DEBUG_C_VAL
// When ENABLE_DEBUG_C_VAL is not defined, CVals with Visibility set to
// CValDebug are not tracked though the CVal system and
// CValCollectionEntryStats can be stubbed out.
class CValCollectionEntryStats<EntryType, CValDebug>
: public CValDetail::CValCollectionEntryStatsStub<EntryType> {
typedef CValDetail::CValCollectionEntryStatsStub<EntryType> CValParent;
#endif // ENABLE_DEBUG_C_VAL
public:
typedef typename CValParent::OnFlushCallback OnFlushCallback;
typedef typename CValParent::FlushResults FlushResults;
explicit CValCollectionEntryStats(const std::string& name)
: CValParent(name) {}
CValCollectionEntryStats(const std::string& name, size_t max_size,
bool enable_entry_list_c_val,
const OnFlushCallback& on_flush = OnFlushCallback())
: CValParent(name, max_size, enable_entry_list_c_val, on_flush) {}
};
// CVals with visibility set to CValPublic are always tracked though the CVal
// system, regardless of the ENABLE_DEBUG_C_VAL state.
template <typename EntryType>
class CValCollectionEntryStats<EntryType, CValPublic>
: public CValDetail::CValCollectionEntryStatsImpl<EntryType, CValPublic> {
typedef CValDetail::CValCollectionEntryStatsImpl<EntryType, CValPublic>
CValParent;
public:
typedef typename CValParent::OnFlushCallback OnFlushCallback;
typedef typename CValParent::FlushResults FlushResults;
explicit CValCollectionEntryStats(const std::string& name)
: CValParent(name) {}
CValCollectionEntryStats(const std::string& name, size_t max_size,
bool enable_entry_list_c_val,
const OnFlushCallback& on_flush = OnFlushCallback())
: CValParent(name, max_size, enable_entry_list_c_val, on_flush) {}
};
} // namespace base
#endif // COBALT_BASE_C_VAL_COLLECTION_ENTRY_STATS_H_