blob: 5dcd97e1909d99ffc4f8e3fe2ec6f201dd8e4590 [file] [log] [blame]
/*
* Copyright 2010 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.
*/
// Author: jmarantz@google.com (Joshua Marantz)
#ifndef PAGESPEED_KERNEL_BASE_STATISTICS_H_
#define PAGESPEED_KERNEL_BASE_STATISTICS_H_
#include <map>
#include "base/logging.h"
#include "pagespeed/kernel/base/abstract_mutex.h"
#include "pagespeed/kernel/base/basictypes.h"
#include "pagespeed/kernel/base/scoped_ptr.h"
#include "pagespeed/kernel/base/string.h"
#include "pagespeed/kernel/base/string_util.h"
#include "pagespeed/kernel/base/thread_annotations.h"
namespace net_instaweb {
class MessageHandler;
class Statistics;
class StatisticsLogger;
class Writer;
// Variables can normally only be increased, not decreased. However, for
// testing, They can also be Cleared.
//
// TODO(jmarantz): consider renaming this to Counter or maybe UpCounter.
class Variable {
public:
virtual ~Variable();
virtual int64 Get() const = 0;
// Return some name representing the variable, provided that the specific
// implementation has some sensible way of doing so.
virtual StringPiece GetName() const = 0;
// Adds 'delta' to the variable's value, returning the result.
int64 Add(int64 non_negative_delta) {
DCHECK_LE(0, non_negative_delta);
return AddHelper(non_negative_delta);
}
virtual void Clear() = 0;
protected:
// This is virtual so that subclasses can add platform-specific atomicity.
virtual int64 AddHelper(int64 delta) = 0;
};
// UpDownCounters are variables that can also be decreased (e.g. Add
// of a negative number) or Set to an arbitrary value.
//
// TODO(jmarantz): Make this not inherit from Variable, which will simplify the
// 'CheckNotNegative' and its ifndefs, but will require us to do more accurate
// type bookkeeping in tests, etc.
//
// TODO(jmarantz): consider renaming Variable->Counter, UpDownCounter->Variable.
class UpDownCounter {
public:
virtual ~UpDownCounter();
virtual int64 Get() const = 0;
// Return some name representing the variable, provided that the specific
// implementation has some sensible way of doing so.
virtual StringPiece GetName() const = 0;
// Sets the specified value, returning the previous value. This can be
// used to by two competing threads/processes to determine which thread
// modified the value first. The default implementation is non-atomic,
// but implementations can override to provide an atomic version.
//
// Non-atomic implementations may result in multiple concurrent updates
// each returning the old value. In an atomic implementation, only one
// concurrent update will return the old value.
virtual int64 SetReturningPreviousValue(int64 value);
virtual void Set(int64 value) = 0;
void Clear() { Set(0); }
int64 Add(int64 delta) { return AddHelper(delta); }
protected:
// This is virtual so that subclasses can add platform-specific atomicity.
virtual int64 AddHelper(int64 delta) = 0;
};
// Scalar value protected by a mutex. Mutex must fully protect access
// to underlying scalar. For example, in mod_pagespeed and
// ngx_pagespeed, variables are stored in shared memory and accessible
// from any process on a machine, so the mutex must provide protection
// across separate processes.
//
// StatisticsLogger depends upon these mutexes being cross-process so that
// several processes using the same file system don't clobber each others logs.
//
// TODO(jmarantz): this could be done using templates rather than using virtual
// methods, as MutexedScalar does not implement an abstract interface.
class MutexedScalar {
public:
virtual ~MutexedScalar();
// Subclasses should not define these methods, instead define the *LockHeld()
// methods below.
int64 Get() const;
void Set(int64 value);
int64 SetReturningPreviousValue(int64 value);
int64 AddHelper(int64 delta);
protected:
friend class StatisticsLogger;
virtual AbstractMutex* mutex() const = 0;
// Get/Setters that may only be called if you already hold the mutex.
virtual int64 GetLockHeld() const = 0;
virtual int64 SetReturningPreviousValueLockHeld(int64 value) = 0;
// These are implemented based on GetLockHeld() and
// SetReturningPreviousLockHeld().
void SetLockHeld(int64 value);
int64 AddLockHeld(int64 delta);
};
class Histogram {
public:
virtual ~Histogram();
// Record a value in its bucket.
virtual void Add(double value) = 0;
// Throw away all data.
virtual void Clear() = 0;
// True if the histogram is empty.
bool Empty() {
ScopedMutex hold(lock());
return CountInternal() == 0;
}
// Write Histogram Data to the writer.
// Default implementation does not include histogram graph, but only raw
// histogram data table. It looks like:
// ________________________________________
// | TITLE String |
// | Avg: StdDev: Median: 90%: 95%: 99% |
// | Raw Histogram Data: |
// | [0,1] 1 25% 25% ||||| |
// | [2,3] 1 25% 50% ||||| |
// | [4,5] 2 50% 100% |||||||||| |
// |_______________________________________|
virtual void Render(int index, Writer* writer, MessageHandler* handler);
// Returns number of buckets the histogram actually has.
virtual int NumBuckets() = 0;
// Allow histogram have negative values.
virtual void EnableNegativeBuckets() = 0;
// Set the minimum value allowed in histogram.
virtual void SetMinValue(double value) = 0;
// Set the value upper-bound of a histogram,
// the value range in histogram is [MinValue, MaxValue) or
// [-MaxValue, MaxValue) if enabled negative buckets.
virtual void SetMaxValue(double value) = 0;
// Set the suggested number of buckets for the histogram. The implementation
// may chose to use a somewhat different number.
virtual void SetSuggestedNumBuckets(int i) = 0;
// Returns average of the values added.
double Average() {
ScopedMutex hold(lock());
return AverageInternal();
}
// Return estimated value that is greater than perc% of all data.
// e.g. Percentile(20) returns the value which is greater than
// 20% of data.
double Percentile(const double perc) {
ScopedMutex hold(lock());
return PercentileInternal(perc);
}
double StandardDeviation() {
ScopedMutex hold(lock());
return StandardDeviationInternal();
}
double Count() {
ScopedMutex hold(lock());
return CountInternal();
}
double Maximum() {
ScopedMutex hold(lock());
return MaximumInternal();
}
double Minimum() {
ScopedMutex hold(lock());
return MinimumInternal();
}
double Median() {
return Percentile(50);
}
// Formats the histogram statistics as an HTML table row. This
// is intended for use in Statistics::RenderHistograms.
//
// The <tr> element id is given id=hist_row_%d where %d is from the index.
// Included in the row an input radio button which is initiated in state
// 'selected' for index==0.
GoogleString HtmlTableRow(const GoogleString& title, int index);
// Lower bound of a bucket. If index == NumBuckets() + 1, returns the
// upper bound of the histogram. DCHECK if index is in the range of
// [0, NumBuckets()+1].
virtual double BucketStart(int index) = 0;
// Upper bound of a bucket.
virtual double BucketLimit(int index) {
return BucketStart(index + 1);
}
// Value of a bucket.
virtual double BucketCount(int index) = 0;
protected:
Histogram() {}
// Note that these *Internal interfaces require the mutex to be held.
virtual double AverageInternal() = 0;
virtual double PercentileInternal(const double perc) = 0;
virtual double StandardDeviationInternal() = 0;
virtual double CountInternal() = 0;
virtual double MaximumInternal() = 0;
virtual double MinimumInternal() = 0;
virtual AbstractMutex* lock() = 0;
// Helper function of Render(), write entries of histogram raw data table.
// Each entry includes bucket range, bucket count, percentage,
// cumulative percentage, bar. It looks like:
// [0,1] 1 5% 5% ||||
// [2,3] 2 10% 15% ||||||||
// Precondition: mutex held.
void WriteRawHistogramData(Writer* writer, MessageHandler* handler);
private:
DISALLOW_COPY_AND_ASSIGN(Histogram);
};
// Trivial implementation. But Count() returns a meaningful value.
class CountHistogram : public Histogram {
public:
// Takes ownership of mutex.
explicit CountHistogram(AbstractMutex* mutex);
virtual ~CountHistogram();
virtual void Add(double value) {
ScopedMutex hold(lock());
++count_;
}
virtual void Clear() {
ScopedMutex hold(lock());
count_ = 0;
}
virtual int NumBuckets() { return 0; }
virtual void EnableNegativeBuckets() { }
virtual void SetMinValue(double value) { }
virtual void SetMaxValue(double value) { }
virtual void SetSuggestedNumBuckets(int i) { }
virtual GoogleString GetName() const { return ""; }
protected:
virtual AbstractMutex* lock() LOCK_RETURNED(mutex_) { return mutex_.get(); }
virtual double AverageInternal() { return 0.0; }
virtual double PercentileInternal(const double perc) { return 0.0; }
virtual double StandardDeviationInternal() { return 0.0; }
virtual double CountInternal() EXCLUSIVE_LOCKS_REQUIRED(lock()) {
return count_;
}
virtual double MaximumInternal() { return 0.0; }
virtual double MinimumInternal() { return 0.0; }
virtual double BucketStart(int index) { return 0.0; }
virtual double BucketCount(int index) { return 0.0; }
private:
scoped_ptr<AbstractMutex> mutex_;
int count_ GUARDED_BY(mutex_);
DISALLOW_COPY_AND_ASSIGN(CountHistogram);
};
// TimedVariable is a statistic class returns the amount added in the
// last interval, which could be last 10 seconds, last minute
// last one hour and total.
class TimedVariable {
public:
// The intervals for which we keep stats.
enum Levels { TENSEC, MINUTE, HOUR, START };
virtual ~TimedVariable();
// Update the stat value. delta is in milliseconds.
virtual void IncBy(int64 delta) = 0;
// Get the amount added over the last time interval
// specified by "level".
virtual int64 Get(int level) = 0;
// Throw away all data.
virtual void Clear() = 0;
};
// TimedVariable implementation that only updates a basic UpDownCounter.
class FakeTimedVariable : public TimedVariable {
public:
FakeTimedVariable(StringPiece name, Statistics* stats);
virtual ~FakeTimedVariable();
// Update the stat value. delta is in milliseconds.
virtual void IncBy(int64 delta) {
var_->Add(delta);
}
// Get the amount added over the last time interval
// specified by "level".
virtual int64 Get(int level) {
// This is a default implementation. Variable can only return the
// total value. This should be override in subclass if we want the
// values for different levels.
if (level == START) {
return var_->Get();
}
return 0;
}
// Throw away all data.
virtual void Clear() {
return var_->Clear();
}
protected:
Variable* var_;
};
// Base class for implementations of monitoring statistics.
class Statistics {
public:
Statistics() {}
virtual ~Statistics();
// Add a new variable, or returns an existing one of that name.
// The UpDownCounter* is owned by the Statistics class -- it should
// not be deleted by the caller.
virtual UpDownCounter* AddUpDownCounter(const StringPiece& name) = 0;
// Like AddVariable, but asks the implementation to scope the variable to the
// entire process, even if statistics are generally partitioned by domains or
// the like. Default implementation simply forwards to AddVariable.
virtual UpDownCounter* AddGlobalUpDownCounter(const StringPiece& name);
// Find a variable from a name, returning NULL if not found.
virtual UpDownCounter* FindUpDownCounter(const StringPiece& name) const = 0;
// Find a variable from a name, aborting if not found.
UpDownCounter* GetUpDownCounter(const StringPiece& name) const {
UpDownCounter* var = FindUpDownCounter(name);
CHECK(var != NULL) << "UpDownCounter not found: " << name;
return var;
}
// Add a new variable, or returns an existing one of that name.
// The Variable* is owned by the Statistics class -- it should
// not be deleted by the caller.
virtual Variable* AddVariable(const StringPiece& name) = 0;
// Find a variable from a name, returning NULL if not found.
virtual Variable* FindVariable(const StringPiece& name) const = 0;
// Find a variable from a name, aborting if not found.
Variable* GetVariable(const StringPiece& name) const {
Variable* var = FindVariable(name);
CHECK(var != NULL) << "Variable not found: " << name;
return var;
}
// Add a new histogram, or returns an existing one of that name.
// The Histogram* is owned by the Statistics class -- it should not
// be deleted by the caller.
virtual Histogram* AddHistogram(const StringPiece& name) = 0;
// Find a histogram from a name, returning NULL if not found.
virtual Histogram* FindHistogram(const StringPiece& name) const = 0;
// Find a histogram from a name, aborting if not found.
Histogram* GetHistogram(const StringPiece& name) const {
Histogram* hist = FindHistogram(name);
CHECK(hist != NULL) << "Histogram not found: " << name;
return hist;
}
// Add a new TimedVariable, or returns an existing one of that name.
// The TimedVariable* is owned by the Statistics class -- it should
// not be deleted by the caller. Each stat belongs to a group, such as
// "Statistics", "Disk Statistics", etc.
virtual TimedVariable* AddTimedVariable(
const StringPiece& name, const StringPiece& group) = 0;
// Find a TimedVariable from a name, returning NULL if not found.
virtual TimedVariable* FindTimedVariable(
const StringPiece& name) const = 0;
// Find a TimedVariable from a name, aborting if not found.
TimedVariable* GetTimedVariable(
const StringPiece& name) const {
TimedVariable* stat = FindTimedVariable(name);
CHECK(stat != NULL) << "TimedVariable not found: " << name;
return stat;
}
// Return the names of all the histograms for render.
virtual const StringVector& HistogramNames() = 0;
// Return the map of groupnames and names of all timedvariables for render.
virtual const std::map<GoogleString, StringVector>& TimedVariableMap() = 0;
// Dump the variable-values to a writer.
virtual void Dump(Writer* writer, MessageHandler* handler) = 0;
// Dump the variable-values in JSON format to a writer.
virtual void DumpJson(Writer* writer, MessageHandler* message_handler) = 0;
virtual void RenderTimedVariables(Writer* writer,
MessageHandler* handler);
// Write all the histograms in this Statistic object to a writer.
virtual void RenderHistograms(Writer* writer, MessageHandler* handler);
// Set all variables to 0.
// Throw away all data in histograms and stats.
virtual void Clear() = 0;
// This is implemented as NULL here because most Statistics don't
// need it. In the context in which it is needed we only have access to a
// Statistics*, rather than the specific subclass, hence its being here.
// Return the StatisticsLogger associated with this Statistics.
virtual StatisticsLogger* console_logger() { return NULL; }
// Testing helper method to look up a statistics numeric value by name.
// Please do not use this in production code. This finds the current
// value whether it is stored in a Variable, UpDownCounter, or TimedVariable.
//
// If the statistics is not found, the program check-fails.
int64 LookupValue(StringPiece stat_name);
private:
DISALLOW_COPY_AND_ASSIGN(Statistics);
};
} // namespace net_instaweb
#endif // PAGESPEED_KERNEL_BASE_STATISTICS_H_