README.textile

Aggregate

By Joseph Ruscio

Aggregate is an intuitive ruby implementation of a statistics aggregator
including both default and configurable histogram support. It does this
without recording/storing any of the actual sample values, making it
suitable for tracking statistics across millions/billions of sample
without any impact on performance or memory footprint. Originally
inspired by the Aggregate support in SystemTap.

Getting Started

Aggregates are easy to instantiate, populate with sample data, and then
inspect for common aggregate statistics:

#After instantiation use the << operator to add a sample to the aggregate:
stats = Aggregate.new
loop do
# Take some action that generates a sample measurement
stats << sample
end
# The number of samples
stats.count
# The average
stats.mean
# Max sample value
stats.max
# Min sample value
stats.min
# The standard deviation
stats.std_dev

Histograms

Perhaps more importantly than the basic aggregate statistics detailed above
Aggregate also maintains a histogram of samples. For anything other than
normally distributed data are insufficient at best and often downright misleading
37Signals recently posted a terse but effective explanation of the importance of histograms.
Aggregates maintains its histogram internally as a set of “buckets”.
Each bucket represents a range of possible sample values. The set of all buckets
represents the range of “normal” sample values.

Binary Histograms

Without any configuration Aggregate instance maintains a binary histogram, where
each bucket represents a range twice as large as the preceding bucket i.e.
[1,1], [2,3], [4,5,6,7], [8,9,10,11,12,13,14,15]. The default binary histogram
provides for 128 buckets, theoretically covering the range [1, (2^127) – 1]
(See NOTES below for a discussion on the effects in practice of insufficient
precision.)

Binary histograms are useful when we have little idea about what the
sample distribution may look like as almost any positive value will
fall into some bucket. After using binary histograms to determine
the coarse-grained characteristics of your sample space you can
configure a linear histogram to examine it in closer detail.

Linear Histograms

Linear histograms are specified with the three values low, high, and width.
Low and high specify a range [low, high) of values included in the
histogram (all others are outliers). Width specifies the number of
values represented by each bucket and therefore the number of
buckets i.e. granularity of the histogram. The histogram range
(high – low) must be a multiple of width:

The example above creates a linear histogram that tracks the
response times from 0 ms to 2000 ms in buckets of width 50 ms. Hopefully
most of your samples fall in the first couple buckets!

Histogram Outliers

An Aggregate records any samples that fall outside the histogram range as
outliers:

# Number of samples that fall below the normal range
stats.outliers_low
# Number of samples that fall above the normal range
stats.outliers_high

Histogram Iterators

Once a histogram is populated Aggregate provides iterator support for
examining the contents of buckets. The iterators provide both the
number of samples in the bucket, as well as its range:

#Examine every bucket
@stats.each do |bucket, count|
end
#Examine only buckets containing samples
@stats.each_nonzero do |bucket, count|
end

Histogram Bar Chart

Finally Aggregate contains sophisticated pretty-printing support to generate
ASCII bar charts. For any given number of columns >= 80 (defaults to 80) and
sample distribution the to_s method properly sets a marker weight based on the
samples per bucket and aligns all output. Empty buckets are skipped to conserve
screen space.

Examples

NOTES

Ruby doesn’t have a log2 function built into Math, so we approximate with
log(x)/log(2). Theoretically log( 2^n – 1 )/ log(2) == n-1. Unfortunately due
to precision limitations, once n reaches a certain size (somewhere > 32)
this starts to return n. The larger the value of n, the more numbers i.e.
(2^n – 2), (2^n – 3), etc fall trap to this errors. Could probably look into
using something like BigDecimal, but for the current purposes of the binary
histogram i.e. a simple coarse-grained view the current implementation is
sufficient.