blob: 87a2369d54d2c2ec11c73ee59a5770e24e11a7c2 [file]
// Copyright 2006-2009 Google Inc.
//
// 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.
// ========================================================================
//
// Declares the interface to in-memory metrics capture
#ifndef OMAHA_STATSREPORT_METRICS_H__
#define OMAHA_STATSREPORT_METRICS_H__
#include <iterator>
#include "base/basictypes.h"
#include "omaha/common/highres_timer-win32.h"
#include "omaha/common/logging/logging.h"
/// Macros to declare & define named & typed metrics.
/// Put declarations in headers or in cpp files, where you need access
/// to the metrics. For each declared metric, there must be precisely
/// one definition in a compilation unit someplace.
/// A count metric should be used to report anything that monotonically
/// increases.
/// Examples:
/// # event count
/// how often does this condition hit, this function get called
/// # aggregate sums
/// how many bytes are written
#define DECLARE_METRIC_count(name) DECLARE_METRIC(CountMetric, name)
#define DEFINE_METRIC_count(name) DEFINE_METRIC(CountMetric, name)
/// Use timing metrics to report on the performance of important things.
/// A timing metric will report the count of occurrences, as well as the
/// average, min and max times.
/// Samples are measured in milliseconds if you use the TIME_SCOPE macro
/// or the HighResTimer class to collect samples.
#define DECLARE_METRIC_timing(name) DECLARE_METRIC(TimingMetric, name)
#define DEFINE_METRIC_timing(name) DEFINE_METRIC(TimingMetric, name)
/// Collects a sample from here to the end of the current scope, and
/// adds the sample to the timing metric supplied
#define TIME_SCOPE(timing) \
stats_report::TimingSample __xxsample__(timing)
/// Use integer metrics to report runtime values that fluctuate.
/// Examples:
/// # object count
/// How many objects of some type exist
/// # disk space or memory
/// How much disk space or memory is in use
#define DECLARE_METRIC_integer(name) DECLARE_METRIC(IntegerMetric, name)
#define DEFINE_METRIC_integer(name) DEFINE_METRIC(IntegerMetric, name)
/// Use boolean metrics to report the occurrence of important but rare events
/// or conditions. Note that a boolean metric is tri-state, so you typically
/// want to set it only in one direction, and typically to true.
/// Setting a boolean metric one way or another on a trigger event will report
/// the setting of the boolean immediately prior to reporting, which is
/// typically not what you want.
#define DECLARE_METRIC_bool(name) DECLARE_METRIC(BoolMetric, name)
#define DEFINE_METRIC_bool(name) DEFINE_METRIC(BoolMetric, name)
/// Implementation macros
#define DECLARE_METRIC(type, name) \
namespace omaha_client_statsreport { \
extern stats_report::type metric_##name; \
} \
using omaha_client_statsreport::metric_##name
#define DEFINE_METRIC(type, name) \
namespace omaha_client_statsreport { \
stats_report::type metric_##name(#name, \
&stats_report::g_global_metric_storage); \
} \
using omaha_client_statsreport::metric_##name
namespace stats_report {
enum MetricType {
// use zero for invalid, because global storage defaults to zero
kInvalidType = 0,
kCountType,
kTimingType,
kIntegerType,
kBoolType
};
// fwd.
struct MetricCollectionBase;
class MetricCollection;
class MetricBase;
class IntegerMetricBase;
class CountMetric;
class TimingMetric;
class IntegerMetric;
class BoolMetric;
/// Base class for all stats instances.
/// Stats instances are chained together against a MetricCollection to
/// allow enumerating stats.
///
/// MetricCollection is factored into a class to make it easier to unittest
/// the implementation.
class MetricBase {
public:
/// @name Downcasts
/// @{
CountMetric &AsCount();
TimingMetric &AsTiming();
IntegerMetric &AsInteger();
BoolMetric &AsBool();
const CountMetric &AsCount() const;
const TimingMetric &AsTiming() const;
const IntegerMetric &AsInteger() const;
const BoolMetric &AsBool() const;
/// @}
/// @name Accessors
/// @{
MetricType type() const { return type_; }
MetricBase *next() const { return next_; }
const char *name() const { return name_; }
/// @}
// TODO(omaha): does this need to be virtual?
virtual ~MetricBase() = 0;
protected:
class ObjectLock;
void Lock() const;
void Unlock() const;
/// Constructs a MetricBase and adds to the provided MetricCollection.
/// @note Metrics can only be constructed up to the point where the
/// MetricCollection is initialized, and there's no locking performed.
/// The assumption is that outside unit tests, Metrics will we declared
/// as static/global variables, and initialized at static initialization
/// time - and static initialization is single-threaded.
MetricBase(const char *name, MetricType type, MetricCollectionBase *coll);
/// Constructs a named typed MetricBase
MetricBase(const char *name, MetricType type);
/// Our name
char const *const name_;
/// type of this metric
MetricType const type_;
/// chains to next stat instance
MetricBase *const next_;
/// The collection we're created against
MetricCollectionBase *const coll_;
private:
DISALLOW_EVIL_CONSTRUCTORS(MetricBase);
};
/// Must be a POD
struct MetricCollectionBase {
bool initialized_;
MetricBase *first_;
};
/// Inherit from base, which is a POD and can be initialized at link time.
///
/// The global MetricCollection is aliased to a link-time initialized
/// instance of MetricCollectionBase, and must not extend the size of its
/// base class.
class MetricCollection: public MetricCollectionBase {
public:
MetricCollection() {
initialized_ = false;
first_ = NULL;
}
~MetricCollection() {
DCHECK(NULL == first_);
}
/// Initialize must be called after all metrics have been added to the
/// collection, but before enumerating it for e.g. aggregation or reporting.
/// The intent is that outside unit tests, there will only be the global
/// metrics collection, which will accrue all metrics defined with the
/// DEFINE_METRIC_* macros.
/// Typically you'd call Initialize very early in your main function, and
/// Uninitialize towards the end of main.
/// It is an error to Initialize() when the collection is initialized().
void Initialize();
/// Uninitialize must be called before removing (deleting or deconstructing)
/// metrics from the collection.
/// It is an error to Uninitialize() when the collection is !initialized().
void Uninitialize();
MetricBase *first() const { return first_; }
bool initialized() const { return initialized_; }
private:
using MetricCollectionBase::initialized_;
using MetricCollectionBase::first_;
DISALLOW_EVIL_CONSTRUCTORS(MetricCollection);
/// MetricBase is intimate with us
friend class MetricBase;
};
/// Implements a forward_iterator for MetricCollection.
class MetricIterator: public std::iterator<std::forward_iterator_tag,
MetricBase *> {
public:
MetricIterator() : curr_(NULL) {
}
MetricIterator(const MetricIterator &other) : curr_(other.curr_) {
}
MetricIterator(const MetricCollection &coll) : curr_(coll.first()) {
DCHECK(coll.initialized());
}
MetricBase *operator*() const {
return curr_;
}
MetricBase *operator->() const {
return curr_;
}
MetricIterator operator++() { // preincrement
if (curr_)
curr_ = curr_->next();
return (*this);
}
MetricIterator operator++(int) {// postincrement
MetricIterator ret = *this;
++*this;
return (ret);
}
private:
MetricBase *curr_;
};
inline bool operator == (const MetricIterator &a, const MetricIterator &b) {
return *a == *b;
}
inline bool operator != (const MetricIterator &a, const MetricIterator &b) {
return !operator == (a, b);
}
/// Globally defined counters are registered here
extern MetricCollectionBase g_global_metric_storage;
/// And more conveniently accessed through here
extern MetricCollection &g_global_metrics;
/// Base class for integer metrics
class IntegerMetricBase: public MetricBase {
public:
/// Sets the current value
void Set(uint64 value);
/// Retrieves the current value
uint64 value() const;
void operator ++ () { Increment(); }
void operator ++ (int) { Increment(); }
void operator += (uint64 addend) { Add(addend); }
protected:
IntegerMetricBase(const char *name,
MetricType type,
MetricCollectionBase *coll)
: MetricBase(name, type, coll), value_(0) {
}
IntegerMetricBase(const char *name, MetricType type, uint64 value)
: MetricBase(name, type), value_(value) {
}
void Increment();
void Decrement();
void Add(uint64 value);
void Subtract(uint64 value);
uint64 value_;
private:
DISALLOW_EVIL_CONSTRUCTORS(IntegerMetricBase);
};
/// A count metric is a cumulative counter of events.
class CountMetric: public IntegerMetricBase {
public:
CountMetric(const char *name, MetricCollectionBase *coll)
: IntegerMetricBase(name, kCountType, coll) {
}
CountMetric(const char *name, uint64 value)
: IntegerMetricBase(name, kCountType, value) {
}
/// Nulls the metric and returns the current values.
uint64 Reset();
private:
DISALLOW_EVIL_CONSTRUCTORS(CountMetric);
};
class TimingMetric: public MetricBase {
public:
struct TimingData {
uint32 count;
uint32 align; // allow access to the alignment gap between count and sum,
// makes it esier to unittest.
uint64 sum; // ms
uint64 minimum; // ms
uint64 maximum; // ms
};
TimingMetric(const char *name, MetricCollectionBase *coll)
: MetricBase(name, kTimingType, coll) {
Clear();
}
TimingMetric(const char *name, const TimingData &value)
: MetricBase(name, kTimingType), data_(value) {
}
uint32 count() const;
uint64 sum() const;
uint64 minimum() const;
uint64 maximum() const;
uint64 average() const;
/// Adds a single sample to the metric
/// @param time_ms time (in milliseconds) for this sample
void AddSample(uint64 time_ms);
/// Adds count samples to the metric
/// @note use this when capturing time over a variable number of items to
/// normalize e.g. download time per byte or KB. This records one sample
/// over count items, which is numerically more stable for the average
/// than dividing the captured time by the item count. As a side benefit
/// the timer will also record the item count.
/// @note if count == 0, no sample will be recorded
/// @param count number of samples to add
/// @param total_time_ms the total time consumed by all the "count" samples
void AddSamples(uint64 count, uint64 total_time_ms);
/// Nulls the metric and returns the current values.
TimingData Reset();
private:
DISALLOW_EVIL_CONSTRUCTORS(TimingMetric);
void Clear();
TimingData data_;
};
/// A convenience class to sample the time from construction to destruction
/// against a given timing metric.
class TimingSample {
public:
/// @param timing the metric the sample is to be tallied against
explicit TimingSample(TimingMetric &timing) : timing_(timing), count_(1) {
}
/// @param timing the metric the sample is to be tallied against
/// @param item_count count of items processed, used to divide the sampled
/// time so as to capture time per item, which is often a better measure
/// than the total time over a varying number of items.
TimingSample(TimingMetric &timing, uint32 item_count) : timing_(timing),
count_(item_count) {
}
~TimingSample() {
// We discard samples with a zero count
if(count_ == 1)
timing_.AddSample(timer_.GetElapsedMs());
else
timing_.AddSamples(count_, timer_.GetElapsedMs());
}
/// @name Accessors
/// @{
uint32 count() const { return count_; }
void set_count(uint32 count) { count_ = count; }
/// @}
private:
/// Collects the sample for us.
omaha::HighresTimer timer_;
/// The metric we tally against.
TimingMetric &timing_;
/// The item count we divide the captured time by
uint32 count_;
DISALLOW_EVIL_CONSTRUCTORS(TimingSample);
};
/// An integer metric is used to sample values that vary over time.
/// On aggregation the instantaneous value of the integer metric is captured.
class IntegerMetric: public IntegerMetricBase {
public:
IntegerMetric(const char *name, MetricCollectionBase *coll)
: IntegerMetricBase(name, kIntegerType, coll) {
}
IntegerMetric(const char *name, uint64 value)
: IntegerMetricBase(name, kIntegerType, value) {
}
void operator = (uint64 value) { Set(value); }
void operator -- () { Decrement(); }
void operator -- (int) { Decrement(); }
void operator -= (uint64 sub) { Subtract(sub); }
private:
DISALLOW_EVIL_CONSTRUCTORS(IntegerMetric);
};
/// A bool metric is tri-state, and can be:
/// - unset,
/// - true or
/// - false
/// to match other metrics, which are implicitly unset if they've not changed
/// from their initial value.
class BoolMetric: public MetricBase {
public:
/// Values we can take
enum TristateBoolValue {
kBoolUnset = -1,
kBoolFalse,
kBoolTrue,
};
BoolMetric(const char *name, MetricCollectionBase *coll)
: MetricBase(name, kBoolType, coll), value_(kBoolUnset) {
}
BoolMetric(const char *name, uint32 value)
: MetricBase(name, kBoolType) {
switch (value) {
case kBoolFalse:
case kBoolTrue:
value_ = static_cast<TristateBoolValue>(value);
break;
default:
DCHECK(false && "Unexpected tristate bool value on construction");
value_ = kBoolUnset;
}
}
/// Sets the flag to the provided value.
void Set(bool value);
void operator = (bool value) {
Set(value);
}
/// Nulls the metric and returns the current values.
TristateBoolValue Reset();
/// Returns the current value - not threadsafe
TristateBoolValue value() const { return value_; };
private:
DISALLOW_EVIL_CONSTRUCTORS(BoolMetric);
TristateBoolValue value_;
};
inline CountMetric &MetricBase::AsCount() {
DCHECK_EQ(kCountType, type());
return static_cast<CountMetric&>(*this);
}
inline TimingMetric &MetricBase::AsTiming() {
DCHECK_EQ(kTimingType, type());
return static_cast<TimingMetric&>(*this);
}
inline IntegerMetric &MetricBase::AsInteger() {
DCHECK_EQ(kIntegerType, type());
return static_cast<IntegerMetric&>(*this);
}
inline BoolMetric &MetricBase::AsBool() {
DCHECK_EQ(kBoolType, type());
return static_cast<BoolMetric&>(*this);
}
inline const CountMetric &MetricBase::AsCount() const {
DCHECK_EQ(kCountType, type());
return static_cast<const CountMetric&>(*this);
}
inline const TimingMetric &MetricBase::AsTiming() const {
DCHECK_EQ(kTimingType, type());
return static_cast<const TimingMetric&>(*this);
}
inline const IntegerMetric &MetricBase::AsInteger() const {
DCHECK_EQ(kIntegerType, type());
return static_cast<const IntegerMetric&>(*this);
}
inline const BoolMetric &MetricBase::AsBool() const {
DCHECK_EQ(kBoolType, type());
return static_cast<const BoolMetric&>(*this);
}
} // namespace stats_report
#endif // OMAHA_STATSREPORT_METRICS_H__