- Separate out the packages' files' documentation into a separate documentation file per package.

- Provide better examples in the examples subdirectory.
- Make the comments consistent in terms of using multi-line format for future-proofing.
- Extract major constants out.
This commit is contained in:
Matt T. Proud 2012-05-24 20:02:44 +02:00
parent 5ea9b1a0b5
commit e0b92aec7a
36 changed files with 721 additions and 384 deletions

View File

@ -17,42 +17,47 @@ to be made, but this task has been deferred for now.
# Continuous Integration # Continuous Integration
[![Build Status](https://secure.travis-ci.org/matttproud/golang_instrumentation.png?branch=master)](http://travis-ci.org/matttproud/golang_instrumentation) [![Build Status](https://secure.travis-ci.org/matttproud/golang_instrumentation.png?branch=master)](http://travis-ci.org/matttproud/golang_instrumentation)
# Metrics # Documentation
Please read the [generated documentation](http://go.pkgdoc.org/launchpad.net/gocheck)
for the project's documentation from source code.
# Basic Overview
## Metrics
A metric is a measurement mechanism. A metric is a measurement mechanism.
## Gauge ### Gauge
A Gauge is a metric that exposes merely an instantaneous value or some snapshot A Gauge is a metric that exposes merely an instantaneous value or some snapshot
thereof. thereof.
## Histogram ### Histogram
A Histogram is a metric that captures events or samples into buckets. It A Histogram is a metric that captures events or samples into buckets. It
exposes its values via percentile estimations. exposes its values via percentile estimations.
### Buckets #### Buckets
A Bucket is a generic container that collects samples and their values. It A Bucket is a generic container that collects samples and their values. It
prescribes no behavior on its own aside from merely accepting a value, prescribes no behavior on its own aside from merely accepting a value,
leaving it up to the concrete implementation to what to do with the injected leaving it up to the concrete implementation to what to do with the injected
values. values.
#### Accumulating Bucket ##### Accumulating Bucket
An Accumulating Bucket is a bucket that appends the new sample to a timestamped An Accumulating Bucket is a bucket that appends the new sample to a timestamped
priority queue such that the eldest values are evicted according to a given priority queue such that the eldest values are evicted according to a given
policy. policy.
#### Eviction Policies ##### Eviction Policies
Once an Accumulating Bucket reaches capacity, its eviction policy is invoked. Once an Accumulating Bucket reaches capacity, its eviction policy is invoked.
This reaps the oldest N objects subject to certain behavior. This reaps the oldest N objects subject to certain behavior.
##### Remove Oldest ###### Remove Oldest
This merely removes the oldest N items without performing some aggregation This merely removes the oldest N items without performing some aggregation
replacement operation on them. replacement operation on them.
##### Aggregate Oldest ###### Aggregate Oldest
This removes the oldest N items while performing some summary aggregation This removes the oldest N items while performing some summary aggregation
operation thereupon, which is then appended to the list in the former values' operation thereupon, which is then appended to the list in the former values'
place. place.
#### Tallying Bucket ##### Tallying Bucket
A Tallying Bucket differs from an Accumulating Bucket in that it never stores A Tallying Bucket differs from an Accumulating Bucket in that it never stores
any of the values emitted into it but rather exposes a simplied summary any of the values emitted into it but rather exposes a simplied summary
representation thereof. For instance, if a values therein is requested, representation thereof. For instance, if a values therein is requested,

54
documentation.go Normal file
View File

@ -0,0 +1,54 @@
/*
Copyright (c) 2012, Matt T. Proud
All rights reserved.
Use of this source code is governed by a BSD-style license that can be found in
the LICENSE file.
*/
/*
registry.go provides a container for centralized exposition of metrics to
their prospective consumers.
registry.Register("human_readable_metric_name", metric)
Please try to observe the following rules when naming metrics:
- Use underbars "_" to separate words.
- Have the metric name start from generality and work toward specificity
toward the end. For example, when working with multiple caching subsystems,
consider using the following structure "cache" + "user_credentials"
"cache_user_credentials" and "cache" + "value_transformations"
"cache_value_transformations".
- Have whatever is being measured follow the system and subsystem names cited
supra. For instance, with "insertions", "deletions", "evictions",
"replacements" of the above cache, they should be named as
"cache_user_credentials_insertions" and "cache_user_credentials_deletions" and
"cache_user_credentials_deletions" and "cache_user_credentials_evictions".
- If what is being measured has a standardized unit around it, consider
providing a unit for it.
- Consider adding an additional suffix that designates what the value represents
such as a "total" or "size"---e.g., "cache_user_credentials_size_kb" or
"cache_user_credentials_insertions_total".
- Give heed to how future-proof the names are. Things may depend on these
names; and as your service evolves, the calculated values may take on
different meanings, which can be difficult to reflect if deployed code depends
on antique names.
Further considerations:
- The Registry's exposition mechanism is not backed by authorization and
authentication. This is something that will need to be addressed for
production services that are directly exposed to the outside world.
- Engage in as little in-process processing of values as possible. The job
of processing and aggregation of these values belongs in a separate
post-processing job. The same goes for archiving. I will need to evaluate
hooks into something like OpenTSBD.
*/
package registry

View File

@ -1,16 +1,19 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// main.go provides a simple example of how to use this instrumentation Use of this source code is governed by a BSD-style
// framework in the context of having something that emits values into license that can be found in the LICENSE file.
// its collectors. */
//
// The emitted values correspond to uniform, normal, and exponential
// distributions.
/*
main.go provides a simple example of how to use this instrumentation
framework in the context of having something that emits values into
its collectors.
The emitted values correspond to uniform, normal, and exponential
distributions.
*/
package main package main
import ( import (
@ -43,12 +46,12 @@ func main() {
zed_rpc_calls := &metrics.GaugeMetric{} zed_rpc_calls := &metrics.GaugeMetric{}
metrics := registry.NewRegistry() metrics := registry.NewRegistry()
metrics.Register("foo_rpc_latency_ms_histogram", foo_rpc_latency) metrics.Register("rpc_latency_foo_microseconds", foo_rpc_latency)
metrics.Register("foo_rpc_call_count", foo_rpc_calls) metrics.Register("rpc_calls_foo_total", foo_rpc_calls)
metrics.Register("bar_rpc_latency_ms_histogram", bar_rpc_latency) metrics.Register("rpc_latency_bar_microseconds", bar_rpc_latency)
metrics.Register("bar_rpc_call_count", bar_rpc_calls) metrics.Register("rpc_calls_bar_total", bar_rpc_calls)
metrics.Register("zed_rpc_latency_ms_histogram", zed_rpc_latency) metrics.Register("rpc_latency_zed_microseconds", zed_rpc_latency)
metrics.Register("zed_rpc_call_count", zed_rpc_calls) metrics.Register("rpc_calls_zed_total", zed_rpc_calls)
go func() { go func() {
for { for {

View File

@ -1,12 +1,15 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// main.go provides a simple skeletal example of how this instrumentation Use of this source code is governed by a BSD-style
// framework is registered and invoked. license that can be found in the LICENSE file.
*/
/*
main.go provides a simple skeletal example of how this instrumentation
framework is registered and invoked.
*/
package main package main
import ( import (

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// distributions.go provides basic distribution-generating functions that are Use of this source code is governed by a BSD-style
// used primarily in testing contexts. license that can be found in the LICENSE file.
*/
package maths package maths
@ -13,7 +12,9 @@ import (
"math" "math"
) )
// Go's standard library does not offer a factorial function. /*
Go's standard library does not offer a factorial function.
*/
func Factorial(of int) int64 { func Factorial(of int) int64 {
if of <= 0 { if of <= 0 {
return 1 return 1
@ -28,9 +29,11 @@ func Factorial(of int) int64 {
return result return result
} }
// Create calculate the value of a probability density for a given binomial /*
// statistic, where k is the target count of true cases, n is the number of Create calculate the value of a probability density for a given binomial
// subjects, and p is the probability. statistic, where k is the target count of true cases, n is the number of
subjects, and p is the probability.
*/
func BinomialPDF(k, n int, p float64) float64 { func BinomialPDF(k, n int, p float64) float64 {
binomialCoefficient := float64(Factorial(n)) / float64(Factorial(k)*Factorial(n-k)) binomialCoefficient := float64(Factorial(n)) / float64(Factorial(k)*Factorial(n-k))
intermediate := math.Pow(p, float64(k)) * math.Pow(1-p, float64(n-k)) intermediate := math.Pow(p, float64(k)) * math.Pow(1-p, float64(n-k))

26
maths/documentation.go Normal file
View File

@ -0,0 +1,26 @@
/*
Copyright (c) 2012, Matt T. Proud
All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
/*
The maths package provides a number of mathematical-related helpers:
distributions.go provides basic distribution-generating functions that are
used primarily in testing contexts.
helpers_for_testing.go provides a testing assistents for this package and its
dependents.
maths_test.go provides a test suite for all tests in the maths package
hierarchy. It employs the gocheck framework for test scaffolding.
statistics.go provides basic summary statistics functions for the purpose of
metrics aggregation.
statistics_test.go provides a test complement for the statistics.go module.
*/
package maths

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// helpers_for_testing.go provides a testing assistents for this package and its Use of this source code is governed by a BSD-style
// dependents. license that can be found in the LICENSE file.
*/
package maths package maths
@ -19,8 +18,10 @@ type isNaNChecker struct {
*CheckerInfo *CheckerInfo
} }
// This piece provides a simple tester for the gocheck testing library to /*
// ascertain if a value is not-a-number. This piece provides a simple tester for the gocheck testing library to
ascertain if a value is not-a-number.
*/
var IsNaN Checker = &isNaNChecker{ var IsNaN Checker = &isNaNChecker{
&CheckerInfo{Name: "IsNaN", Params: []string{"value"}}, &CheckerInfo{Name: "IsNaN", Params: []string{"value"}},
} }

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// maths_test.go provides a test suite for all tests in the maths package Use of this source code is governed by a BSD-style
// hierarchy. It employs the gocheck framework for test scaffolding. license that can be found in the LICENSE file.
*/
package maths package maths

View File

@ -1,14 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// statistics.go provides basic summary statistics functions for the purpose of Use of this source code is governed by a BSD-style
// metrics aggregation. license that can be found in the LICENSE file.
*/
// TODO(mtp): Split this out into a summary statistics file once moving/rolling
// averages are calculated.
package maths package maths
@ -17,8 +13,15 @@ import (
"sort" "sort"
) )
// ReductionMethod provides a method for reducing metrics into a given scalar /*
// value. TODO(mtp): Split this out into a summary statistics file once moving/rolling
averages are calculated.
*/
/*
ReductionMethod provides a method for reducing metrics into a given scalar
value.
*/
type ReductionMethod func([]float64) float64 type ReductionMethod func([]float64) float64
var Average ReductionMethod = func(input []float64) float64 { var Average ReductionMethod = func(input []float64) float64 {
@ -37,7 +40,9 @@ var Average ReductionMethod = func(input []float64) float64 {
return sum / count return sum / count
} }
// Extract the first modal value. /*
Extract the first modal value.
*/
var FirstMode ReductionMethod = func(input []float64) float64 { var FirstMode ReductionMethod = func(input []float64) float64 {
valuesToFrequency := map[float64]int64{} valuesToFrequency := map[float64]int64{}
var largestTally int64 = math.MinInt64 var largestTally int64 = math.MinInt64
@ -58,7 +63,9 @@ var FirstMode ReductionMethod = func(input []float64) float64 {
return largestTallyValue return largestTallyValue
} }
// Calculate the percentile by choosing the nearest neighboring value. /*
Calculate the percentile by choosing the nearest neighboring value.
*/
func NearestRank(input []float64, percentile float64) float64 { func NearestRank(input []float64, percentile float64) float64 {
inputSize := len(input) inputSize := len(input)
@ -81,7 +88,10 @@ func NearestRank(input []float64, percentile float64) float64 {
return copiedInput[preliminaryIndex] return copiedInput[preliminaryIndex]
} }
func NearestRankReducer(percentile float64) func(input []float64) float64 { /*
Generate a ReductionMethod based off of extracting a given percentile value.
*/
func NearestRankReducer(percentile float64) ReductionMethod {
return func(input []float64) float64 { return func(input []float64) float64 {
return NearestRank(input, percentile) return NearestRank(input, percentile)
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// statistics_test.go provides a test complement for the statistics.go module. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package maths package maths

View File

@ -1,12 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// accumulating_bucket.go provides a histogram bucket type that accumulates Use of this source code is governed by a BSD-style
// elements until a given capacity and enacts a given eviction policy upon license that can be found in the LICENSE file.
// such a condition. */
package metrics package metrics
@ -29,9 +27,11 @@ type AccumulatingBucket struct {
evictionPolicy EvictionPolicy evictionPolicy EvictionPolicy
} }
// AccumulatingBucketBuilder is a convenience method for generating a /*
// BucketBuilder that produces AccumatingBucket entries with a certain AccumulatingBucketBuilder is a convenience method for generating a
// behavior set. BucketBuilder that produces AccumatingBucket entries with a certain
behavior set.
*/
func AccumulatingBucketBuilder(evictionPolicy EvictionPolicy, maximumSize int) BucketBuilder { func AccumulatingBucketBuilder(evictionPolicy EvictionPolicy, maximumSize int) BucketBuilder {
return func() Bucket { return func() Bucket {
return &AccumulatingBucket{ return &AccumulatingBucket{
@ -42,8 +42,10 @@ func AccumulatingBucketBuilder(evictionPolicy EvictionPolicy, maximumSize int) B
} }
} }
// Add a value to the bucket. Depending on whether the bucket is full, it may /*
// trigger an eviction of older items. Add a value to the bucket. Depending on whether the bucket is full, it may
trigger an eviction of older items.
*/
func (b *AccumulatingBucket) Add(value float64) { func (b *AccumulatingBucket) Add(value float64) {
b.mutex.Lock() b.mutex.Lock()
defer b.mutex.Unlock() defer b.mutex.Unlock()
@ -98,9 +100,11 @@ func (b *AccumulatingBucket) ValueForIndex(index int) float64 {
sort.Float64s(sortedElements) sort.Float64s(sortedElements)
// N.B.(mtp): Interfacing components should not need to comprehend what /*
// eviction and storage container strategies used; therefore, N.B.(mtp): Interfacing components should not need to comprehend what
// we adjust this silently. eviction and storage container strategies used; therefore,
we adjust this silently.
*/
targetIndex := int(float64(elementCount-1) * (float64(index) / float64(b.observations))) targetIndex := int(float64(elementCount-1) * (float64(index) / float64(b.observations)))
return sortedElements[targetIndex] return sortedElements[targetIndex]

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// accumulating_bucket_test.go provides a test complement for the Use of this source code is governed by a BSD-style
// accumulating_bucket_go module. license that can be found in the LICENSE file.
*/
package metrics package metrics
@ -124,17 +123,23 @@ func (s *S) TestAccumulatingBucketValueForIndex(c *C) {
c.Assert(b.ValueForIndex(i), maths.IsNaN) c.Assert(b.ValueForIndex(i), maths.IsNaN)
} }
// The bucket has only observed one item and contains now one item. /*
The bucket has only observed one item and contains now one item.
*/
b.Add(1.0) b.Add(1.0)
c.Check(b.ValueForIndex(0), Equals, 1.0) c.Check(b.ValueForIndex(0), Equals, 1.0)
// Let's sanity check what occurs if presumably an eviction happened and /*
// we requested an index larger than what is contained. Let's sanity check what occurs if presumably an eviction happened and
we requested an index larger than what is contained.
*/
c.Check(b.ValueForIndex(1), Equals, 1.0) c.Check(b.ValueForIndex(1), Equals, 1.0)
for i := 2.0; i <= 100; i += 1 { for i := 2.0; i <= 100; i += 1 {
b.Add(i) b.Add(i)
// TODO(mtp): This is a sin. Provide a mechanism for deterministic testing. /*
TODO(mtp): This is a sin. Provide a mechanism for deterministic testing.
*/
time.Sleep(1 * time.Millisecond) time.Sleep(1 * time.Millisecond)
} }
@ -144,13 +149,17 @@ func (s *S) TestAccumulatingBucketValueForIndex(c *C) {
for i := 101.0; i <= 150; i += 1 { for i := 101.0; i <= 150; i += 1 {
b.Add(i) b.Add(i)
// TODO(mtp): This is a sin. Provide a mechanism for deterministic testing. /*
TODO(mtp): This is a sin. Provide a mechanism for deterministic testing.
*/
time.Sleep(1 * time.Millisecond) time.Sleep(1 * time.Millisecond)
} }
// The bucket's capacity has been exceeded by inputs at this point; /*
// consequently, we search for a given element by percentage offset The bucket's capacity has been exceeded by inputs at this point;
// therein. consequently, we search for a given element by percentage offset
therein.
*/
c.Check(b.ValueForIndex(0), Equals, 51.0) c.Check(b.ValueForIndex(0), Equals, 51.0)
c.Check(b.ValueForIndex(50), Equals, 84.0) c.Check(b.ValueForIndex(50), Equals, 84.0)
c.Check(b.ValueForIndex(99), Equals, 116.0) c.Check(b.ValueForIndex(99), Equals, 116.0)

View File

@ -1,17 +0,0 @@
// Copyright (c) 2012, Matt T. Proud
// All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// base.go provides fundamental interface expectations for the various metrics.
package metrics
// A Metric is something that can be exposed via the registry framework.
type Metric interface {
// Produce a human-consumable representation of the metric.
Humanize() string
// Produce a JSON-consumable representation of the metric.
Marshallable() map[string]interface{}
}

View File

@ -1,32 +1,47 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// bucket.go provides fundamental interface expectations for various bucket Use of this source code is governed by a BSD-style
// types. license that can be found in the LICENSE file.
*/
/*
bucket.go provides fundamental interface expectations for various bucket
types.
*/
package metrics package metrics
// The Histogram class and associated types build buckets on their own. /*
The Histogram class and associated types build buckets on their own.
*/
type BucketBuilder func() Bucket type BucketBuilder func() Bucket
// This defines the base Bucket type. The exact behaviors of the bucket are /*
// at the whim of the implementor. This defines the base Bucket type. The exact behaviors of the bucket are
// at the whim of the implementor.
// A Bucket is used as a container by Histogram as a collection for its
// accumulated samples. A Bucket is used as a container by Histogram as a collection for its
accumulated samples.
*/
type Bucket interface { type Bucket interface {
// Add a value to the bucket. /*
Add a value to the bucket.
*/
Add(value float64) Add(value float64)
// Provide a humanized representation hereof. /*
Provide a humanized representation hereof.
*/
Humanize() string Humanize() string
// Provide a count of observations throughout the bucket's lifetime. /*
Provide a count of observations throughout the bucket's lifetime.
*/
Observations() int Observations() int
// Provide the value from the given in-memory value cache or an estimate /*
// thereof for the given index. The consumer of the bucket's data makes Provide the value from the given in-memory value cache or an estimate
// no assumptions about the underlying storage mechanisms that the bucket thereof for the given index. The consumer of the bucket's data makes
// employs. no assumptions about the underlying storage mechanisms that the bucket
employs.
*/
ValueForIndex(index int) float64 ValueForIndex(index int) float64
} }

22
metrics/constants.go Normal file
View File

@ -0,0 +1,22 @@
/*
Copyright (c) 2012, Matt T. Proud
All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
/*
constants.go provides package-level constants for metrics.
*/
package metrics
const (
valueKey = "value"
gaugeTypeValue = "gauge"
typeKey = "type"
histogramTypeValue = "histogram"
floatFormat = 'f'
floatPrecision = 6
floatBitCount = 64
)

51
metrics/documentation.go Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright (c) 2012, Matt T. Proud
All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
/*
The metrics package provides general descriptors for the concept of exportable
metrics.
accumulating_bucket.go provides a histogram bucket type that accumulates
elements until a given capacity and enacts a given eviction policy upon
such a condition.
accumulating_bucket_test.go provides a test complement for the
accumulating_bucket_go module.
eviction.go provides several histogram bucket eviction strategies.
eviction_test.go provides a test complement for the eviction.go module.
gauge.go provides a scalar metric that one can monitor. It is useful for
certain cases, such as instantaneous temperature.
gauge_test.go provides a test complement for the gauge.go module.
histogram.go provides a basic histogram metric, which can accumulate scalar
event values or samples. The underlying histogram implementation is designed
to be performant in that it accepts tolerable inaccuracies.
histogram_test.go provides a test complement for the histogram.go module.
metric.go provides fundamental interface expectations for the various metrics.
metrics_test.go provides a test suite for all tests in the metrics package
hierarchy. It employs the gocheck framework for test scaffolding.
tallying_bucket.go provides a histogram bucket type that aggregates tallies
of events that fall into its ranges versus a summary of the values
themselves.
tallying_bucket_test.go provides a test complement for the
tallying_bucket.go module.
timer.go provides a scalar metric that times how long a given event takes.
timer_test.go provides a test complement for the timer.go module.
*/
package metrics

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// eviction.go provides several histogram bucket eviction strategies. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package metrics package metrics
@ -15,12 +15,16 @@ import (
"time" "time"
) )
// EvictionPolicy implements some sort of garbage collection methodology for /*
// an underlying heap.Interface. This is presently only used for EvictionPolicy implements some sort of garbage collection methodology for
// AccumulatingBucket. an underlying heap.Interface. This is presently only used for
AccumulatingBucket.
*/
type EvictionPolicy func(h heap.Interface) type EvictionPolicy func(h heap.Interface)
// As the name implies, this evicts the oldest x objects from the heap. /*
As the name implies, this evicts the oldest x objects from the heap.
*/
func EvictOldest(count int) EvictionPolicy { func EvictOldest(count int) EvictionPolicy {
return func(h heap.Interface) { return func(h heap.Interface) {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
@ -29,10 +33,10 @@ func EvictOldest(count int) EvictionPolicy {
} }
} }
// This factory produces an EvictionPolicy that applies some standardized /*
// reduction methodology on the to-be-terminated values. This factory produces an EvictionPolicy that applies some standardized
// reduction methodology on the to-be-terminated values.
// TODO(mtp): Parameterize the priority generation since these tools are useful. */
func EvictAndReplaceWith(count int, reducer maths.ReductionMethod) EvictionPolicy { func EvictAndReplaceWith(count int, reducer maths.ReductionMethod) EvictionPolicy {
return func(h heap.Interface) { return func(h heap.Interface) {
oldValues := make([]float64, count) oldValues := make([]float64, count)
@ -44,7 +48,10 @@ func EvictAndReplaceWith(count int, reducer maths.ReductionMethod) EvictionPolic
reduced := reducer(oldValues) reduced := reducer(oldValues)
heap.Push(h, &utility.Item{ heap.Push(h, &utility.Item{
Value: reduced, Value: reduced,
/*
TODO(mtp): Parameterize the priority generation since these tools are useful.
*/
Priority: -1 * time.Now().UnixNano(), Priority: -1 * time.Now().UnixNano(),
}) })
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// eviction_test.go provides a test complement for the eviction.go module. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package metrics package metrics

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// gauge.go provides a scalar metric that one can monitor. It is useful for Use of this source code is governed by a BSD-style
// certain cases, such as instantaneous temperature. license that can be found in the LICENSE file.
*/
package metrics package metrics
@ -14,10 +13,12 @@ import (
"sync" "sync"
) )
// A gauge metric merely provides an instantaneous representation of a scalar /*
// value or an accumulation. For instance, if one wants to expose the current A gauge metric merely provides an instantaneous representation of a scalar
// temperature or the hitherto bandwidth used, this would be the metric for such value or an accumulation. For instance, if one wants to expose the current
// circumstances. temperature or the hitherto bandwidth used, this would be the metric for such
circumstances.
*/
type GaugeMetric struct { type GaugeMetric struct {
value float64 value float64
mutex sync.RWMutex mutex sync.RWMutex
@ -80,8 +81,8 @@ func (metric *GaugeMetric) Marshallable() map[string]interface{} {
v := make(map[string]interface{}, 2) v := make(map[string]interface{}, 2)
v["value"] = metric.value v[valueKey] = metric.value
v["type"] = "gauge" v[typeKey] = gaugeTypeValue
return v return v
} }

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// gauge_test.go provides a test complement for the gauge.go module. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package metrics package metrics

View File

@ -1,12 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// histogram.go provides a basic histogram metric, which can accumulate scalar Use of this source code is governed by a BSD-style
// event values or samples. The underlying histogram implementation is designed license that can be found in the LICENSE file.
// to be performant in that it accepts tolerable inaccuracies. */
package metrics package metrics
@ -17,9 +15,11 @@ import (
"strconv" "strconv"
) )
// This generates count-buckets of equal size distributed along the open /*
// interval of lower to upper. For instance, {lower=0, upper=10, count=5} This generates count-buckets of equal size distributed along the open
// yields the following: [0, 2, 4, 6, 8]. interval of lower to upper. For instance, {lower=0, upper=10, count=5}
yields the following: [0, 2, 4, 6, 8].
*/
func EquallySizedBucketsFor(lower, upper float64, count int) []float64 { func EquallySizedBucketsFor(lower, upper float64, count int) []float64 {
buckets := make([]float64, count) buckets := make([]float64, count)
@ -33,8 +33,10 @@ func EquallySizedBucketsFor(lower, upper float64, count int) []float64 {
return buckets return buckets
} }
// This generates log2-sized buckets spanning from lower to upper inclusively /*
// as well as values beyond it. This generates log2-sized buckets spanning from lower to upper inclusively
as well as values beyond it.
*/
func LogarithmicSizedBucketsFor(lower, upper float64) []float64 { func LogarithmicSizedBucketsFor(lower, upper float64) []float64 {
bucketCount := int(math.Ceil(math.Log2(upper))) bucketCount := int(math.Ceil(math.Log2(upper)))
@ -47,36 +49,46 @@ func LogarithmicSizedBucketsFor(lower, upper float64) []float64 {
return buckets return buckets
} }
// A HistogramSpecification defines how a Histogram is to be built. /*
A HistogramSpecification defines how a Histogram is to be built.
*/
type HistogramSpecification struct { type HistogramSpecification struct {
Starts []float64 Starts []float64
BucketMaker BucketBuilder BucketMaker BucketBuilder
ReportablePercentiles []float64 ReportablePercentiles []float64
} }
// The histogram is an accumulator for samples. It merely routes into which /*
// to bucket to capture an event and provides a percentile calculation The histogram is an accumulator for samples. It merely routes into which
// mechanism. to bucket to capture an event and provides a percentile calculation
// mechanism.
// Histogram makes do without locking by employing the law of large numbers
// to presume a convergence toward a given bucket distribution. Locking Histogram makes do without locking by employing the law of large numbers
// may be implemented in the buckets themselves, though. to presume a convergence toward a given bucket distribution. Locking
may be implemented in the buckets themselves, though.
*/
type Histogram struct { type Histogram struct {
// This represents the open interval's start at which values shall be added to /*
// the bucket. The interval continues until the beginning of the next bucket This represents the open interval's start at which values shall be added to
// exclusive or positive infinity. the bucket. The interval continues until the beginning of the next bucket
// exclusive or positive infinity.
// N.B.
// - bucketStarts should be sorted in ascending order; N.B.
// - len(bucketStarts) must be equivalent to len(buckets); - bucketStarts should be sorted in ascending order;
// - The index of a given bucketStarts' element is presumed to match - len(bucketStarts) must be equivalent to len(buckets);
// correspond to the appropriate element in buckets. - The index of a given bucketStarts' element is presumed to match
correspond to the appropriate element in buckets.
*/
bucketStarts []float64 bucketStarts []float64
// These are the buckets that capture samples as they are emitted to the /*
// histogram. Please consult the reference interface and its implements for These are the buckets that capture samples as they are emitted to the
// further details about behavior expectations. histogram. Please consult the reference interface and its implements for
further details about behavior expectations.
*/
buckets []Bucket buckets []Bucket
// These are the percentile values that will be reported on marshalling. /*
These are the percentile values that will be reported on marshalling.
*/
reportablePercentiles []float64 reportablePercentiles []float64
} }
@ -108,7 +120,9 @@ func (h *Histogram) Humanize() string {
return string(stringBuffer.Bytes()) return string(stringBuffer.Bytes())
} }
// Determine the number of previous observations up to a given index. /*
Determine the number of previous observations up to a given index.
*/
func previousCumulativeObservations(cumulativeObservations []int, bucketIndex int) int { func previousCumulativeObservations(cumulativeObservations []int, bucketIndex int) int {
if bucketIndex == 0 { if bucketIndex == 0 {
return 0 return 0
@ -117,12 +131,16 @@ func previousCumulativeObservations(cumulativeObservations []int, bucketIndex in
return cumulativeObservations[bucketIndex-1] return cumulativeObservations[bucketIndex-1]
} }
// Determine the index for an element given a percentage of length. /*
Determine the index for an element given a percentage of length.
*/
func prospectiveIndexForPercentile(percentile float64, totalObservations int) int { func prospectiveIndexForPercentile(percentile float64, totalObservations int) int {
return int(percentile * float64(totalObservations-1)) return int(percentile * float64(totalObservations-1))
} }
// Determine the next bucket element when interim bucket intervals may be empty. /*
Determine the next bucket element when interim bucket intervals may be empty.
*/
func (h *Histogram) nextNonEmptyBucketElement(currentIndex, bucketCount int, observationsByBucket []int) (*Bucket, int) { func (h *Histogram) nextNonEmptyBucketElement(currentIndex, bucketCount int, observationsByBucket []int) (*Bucket, int) {
for i := currentIndex; i < bucketCount; i++ { for i := currentIndex; i < bucketCount; i++ {
if observationsByBucket[i] == 0 { if observationsByBucket[i] == 0 {
@ -135,18 +153,24 @@ func (h *Histogram) nextNonEmptyBucketElement(currentIndex, bucketCount int, obs
panic("Illegal Condition: There were no remaining buckets to provide a value.") panic("Illegal Condition: There were no remaining buckets to provide a value.")
} }
// Find what bucket and element index contains a given percentile value. /*
// If a percentile is requested that results in a corresponding index that is no Find what bucket and element index contains a given percentile value.
// longer contained by the bucket, the index of the last item is returned. This If a percentile is requested that results in a corresponding index that is no
// may occur if the underlying bucket catalogs values and employs an eviction longer contained by the bucket, the index of the last item is returned. This
// strategy. may occur if the underlying bucket catalogs values and employs an eviction
strategy.
*/
func (h *Histogram) bucketForPercentile(percentile float64) (*Bucket, int) { func (h *Histogram) bucketForPercentile(percentile float64) (*Bucket, int) {
bucketCount := len(h.buckets) bucketCount := len(h.buckets)
// This captures the quantity of samples in a given bucket's range. /*
This captures the quantity of samples in a given bucket's range.
*/
observationsByBucket := make([]int, bucketCount) observationsByBucket := make([]int, bucketCount)
// This captures the cumulative quantity of observations from all preceding /*
// buckets up and to the end of this bucket. This captures the cumulative quantity of observations from all preceding
buckets up and to the end of this bucket.
*/
cumulativeObservationsByBucket := make([]int, bucketCount) cumulativeObservationsByBucket := make([]int, bucketCount)
var totalObservations int = 0 var totalObservations int = 0
@ -158,9 +182,11 @@ func (h *Histogram) bucketForPercentile(percentile float64) (*Bucket, int) {
cumulativeObservationsByBucket[i] = totalObservations cumulativeObservationsByBucket[i] = totalObservations
} }
// This captures the index offset where the given percentile value would be /*
// were all submitted samples stored and never down-/re-sampled nor deleted This captures the index offset where the given percentile value would be
// and housed in a singular array. were all submitted samples stored and never down-/re-sampled nor deleted
and housed in a singular array.
*/
prospectiveIndex := prospectiveIndexForPercentile(percentile, totalObservations) prospectiveIndex := prospectiveIndexForPercentile(percentile, totalObservations)
for i, cumulativeObservation := range cumulativeObservationsByBucket { for i, cumulativeObservation := range cumulativeObservationsByBucket {
@ -168,15 +194,21 @@ func (h *Histogram) bucketForPercentile(percentile float64) (*Bucket, int) {
continue continue
} }
// Find the bucket that contains the given index. /*
Find the bucket that contains the given index.
*/
if cumulativeObservation >= prospectiveIndex { if cumulativeObservation >= prospectiveIndex {
var subIndex int var subIndex int
// This calculates the index within the current bucket where the given /*
// percentile may be found. This calculates the index within the current bucket where the given
percentile may be found.
*/
subIndex = prospectiveIndex - previousCumulativeObservations(cumulativeObservationsByBucket, i) subIndex = prospectiveIndex - previousCumulativeObservations(cumulativeObservationsByBucket, i)
// Sometimes the index may be the last item, in which case we need to /*
// take this into account. Sometimes the index may be the last item, in which case we need to
take this into account.
*/
if observationsByBucket[i] == subIndex { if observationsByBucket[i] == subIndex {
return h.nextNonEmptyBucketElement(i+1, bucketCount, observationsByBucket) return h.nextNonEmptyBucketElement(i+1, bucketCount, observationsByBucket)
} }
@ -188,34 +220,42 @@ func (h *Histogram) bucketForPercentile(percentile float64) (*Bucket, int) {
return &h.buckets[0], 0 return &h.buckets[0], 0
} }
// Return the histogram's estimate of the value for a given percentile of /*
// collected samples. The requested percentile is expected to be a real Return the histogram's estimate of the value for a given percentile of
// value within (0, 1.0]. collected samples. The requested percentile is expected to be a real
value within (0, 1.0].
*/
func (h *Histogram) Percentile(percentile float64) float64 { func (h *Histogram) Percentile(percentile float64) float64 {
bucket, index := h.bucketForPercentile(percentile) bucket, index := h.bucketForPercentile(percentile)
return (*bucket).ValueForIndex(index) return (*bucket).ValueForIndex(index)
} }
func formatFloat(value float64) string {
return strconv.FormatFloat(value, floatFormat, floatPrecision, floatBitCount)
}
func (h *Histogram) Marshallable() map[string]interface{} { func (h *Histogram) Marshallable() map[string]interface{} {
numberOfPercentiles := len(h.reportablePercentiles) numberOfPercentiles := len(h.reportablePercentiles)
result := make(map[string]interface{}, 2) result := make(map[string]interface{}, 2)
result["type"] = "histogram" result[typeKey] = histogramTypeValue
value := make(map[string]interface{}, numberOfPercentiles) value := make(map[string]interface{}, numberOfPercentiles)
for _, percentile := range h.reportablePercentiles { for _, percentile := range h.reportablePercentiles {
percentileString := strconv.FormatFloat(percentile, 'f', 6, 64) percentileString := formatFloat(percentile)
value[percentileString] = strconv.FormatFloat(h.Percentile(percentile), 'f', 6, 64) value[percentileString] = formatFloat(h.Percentile(percentile))
} }
result["value"] = value result[valueKey] = value
return result return result
} }
// Produce a histogram from a given specification. /*
Produce a histogram from a given specification.
*/
func CreateHistogram(specification *HistogramSpecification) *Histogram { func CreateHistogram(specification *HistogramSpecification) *Histogram {
bucketCount := len(specification.Starts) bucketCount := len(specification.Starts)

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// histogram_test.go provides a test complement for the histogram.go module. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package metrics package metrics

23
metrics/metric.go Normal file
View File

@ -0,0 +1,23 @@
/*
Copyright (c) 2012, Matt T. Proud
All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package metrics
/*
A Metric is something that can be exposed via the registry framework.
*/
type Metric interface {
/*
Produce a human-consumable representation of the metric.
*/
Humanize() string
/*
Produce a JSON-consumable representation of the metric.
*/
Marshallable() map[string]interface{}
}

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// metrics_test.go provides a test suite for all tests in the metrics package Use of this source code is governed by a BSD-style
// hierarchy. It employs the gocheck framework for test scaffolding. license that can be found in the LICENSE file.
*/
package metrics package metrics

View File

@ -1,12 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// tallying_bucket.go provides a histogram bucket type that aggregates tallies Use of this source code is governed by a BSD-style
// of events that fall into its ranges versus a summary of the values license that can be found in the LICENSE file.
// themselves. */
package metrics package metrics
@ -22,13 +20,17 @@ const (
upperThird = 2.0 * lowerThird upperThird = 2.0 * lowerThird
) )
// A TallyingIndexEstimator is responsible for estimating the value of index for /*
// a given TallyingBucket, even though a TallyingBucket does not possess a A TallyingIndexEstimator is responsible for estimating the value of index for
// collection of samples. There are a few strategies listed below for how a given TallyingBucket, even though a TallyingBucket does not possess a
// this value should be approximated. collection of samples. There are a few strategies listed below for how
this value should be approximated.
*/
type TallyingIndexEstimator func(minimum, maximum float64, index, observations int) float64 type TallyingIndexEstimator func(minimum, maximum float64, index, observations int) float64
// Provide a filter for handling empty buckets. /*
Provide a filter for handling empty buckets.
*/
func emptyFilter(e TallyingIndexEstimator) TallyingIndexEstimator { func emptyFilter(e TallyingIndexEstimator) TallyingIndexEstimator {
return func(minimum, maximum float64, index, observations int) float64 { return func(minimum, maximum float64, index, observations int) float64 {
if observations == 0 { if observations == 0 {
@ -39,23 +41,31 @@ func emptyFilter(e TallyingIndexEstimator) TallyingIndexEstimator {
} }
} }
// Report the smallest observed value in the bucket. /*
Report the smallest observed value in the bucket.
*/
var Minimum TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 { var Minimum TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 {
return minimum return minimum
}) })
// Report the largest observed value in the bucket. /*
Report the largest observed value in the bucket.
*/
var Maximum TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 { var Maximum TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 {
return maximum return maximum
}) })
// Report the average of the extrema. /*
Report the average of the extrema.
*/
var Average TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 { var Average TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, _, observations int) float64 {
return maths.Average([]float64{minimum, maximum}) return maths.Average([]float64{minimum, maximum})
}) })
// Report the minimum value of the index is in the lower-third of observations, /*
// the average if in the middle-third, and the maximum if in the largest third. Report the minimum value of the index is in the lower-third of observations,
the average if in the middle-third, and the maximum if in the largest third.
*/
var Uniform TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, index, observations int) float64 { var Uniform TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64, index, observations int) float64 {
if observations == 1 { if observations == 1 {
return minimum return minimum
@ -72,9 +82,11 @@ var Uniform TallyingIndexEstimator = emptyFilter(func(minimum, maximum float64,
return maths.Average([]float64{minimum, maximum}) return maths.Average([]float64{minimum, maximum})
}) })
// A TallyingBucket is a Bucket that tallies when an object is added to it. /*
// Upon insertion, an object is compared against collected extrema and noted A TallyingBucket is a Bucket that tallies when an object is added to it.
// as a new minimum or maximum if appropriate. Upon insertion, an object is compared against collected extrema and noted
as a new minimum or maximum if appropriate.
*/
type TallyingBucket struct { type TallyingBucket struct {
observations int observations int
smallestObserved float64 smallestObserved float64
@ -119,7 +131,9 @@ func (b *TallyingBucket) ValueForIndex(index int) float64 {
return b.estimator(b.smallestObserved, b.largestObserved, index, b.observations) return b.estimator(b.smallestObserved, b.largestObserved, index, b.observations)
} }
// Produce a TallyingBucket with sane defaults. /*
Produce a TallyingBucket with sane defaults.
*/
func DefaultTallyingBucket() TallyingBucket { func DefaultTallyingBucket() TallyingBucket {
return TallyingBucket{ return TallyingBucket{
smallestObserved: math.MaxFloat64, smallestObserved: math.MaxFloat64,
@ -136,7 +150,9 @@ func CustomTallyingBucket(estimator TallyingIndexEstimator) TallyingBucket {
} }
} }
// This is used strictly for testing. /*
This is used strictly for testing.
*/
func TallyingBucketBuilder() Bucket { func TallyingBucketBuilder() Bucket {
b := DefaultTallyingBucket() b := DefaultTallyingBucket()
return &b return &b

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// tallying_bucket_test.go provides a test complement for the Use of this source code is governed by a BSD-style
// tallying_bucket.go module. license that can be found in the LICENSE file.
*/
package metrics package metrics

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// timer.go provides a scalar metric that times how long a given event takes. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package metrics package metrics
@ -12,20 +12,33 @@ import (
"time" "time"
) )
// This callback is called upon the completion of the timer—i.e., when it stops. /*
This callback is called upon the completion of the timeri.e., when it stops.
*/
type CompletionCallback func(duration time.Duration) type CompletionCallback func(duration time.Duration)
// This is meant to capture a function that a StopWatch can call for purposes /*
// of instrumentation. This is meant to capture a function that a StopWatch can call for purposes
of instrumentation.
*/
type InstrumentableCall func() type InstrumentableCall func()
/*
StopWatch is the structure that captures instrumentation for durations.
N.B.(mtp): A major limitation hereof is that the StopWatch protocol cannot
retain instrumentation if a panic percolates within the context that is
being measured.
*/
type StopWatch struct { type StopWatch struct {
startTime time.Time startTime time.Time
endTime time.Time endTime time.Time
onCompletion CompletionCallback onCompletion CompletionCallback
} }
// Return a new StopWatch that is ready for instrumentation. /*
Return a new StopWatch that is ready for instrumentation.
*/
func Start(onCompletion CompletionCallback) *StopWatch { func Start(onCompletion CompletionCallback) *StopWatch {
return &StopWatch{ return &StopWatch{
startTime: time.Now(), startTime: time.Now(),
@ -33,8 +46,10 @@ func Start(onCompletion CompletionCallback) *StopWatch {
} }
} }
// Stop the StopWatch returning the elapsed duration of its lifetime while /*
// firing an optional CompletionCallback in the background. Stop the StopWatch returning the elapsed duration of its lifetime while
firing an optional CompletionCallback in the background.
*/
func (s *StopWatch) Stop() time.Duration { func (s *StopWatch) Stop() time.Duration {
s.endTime = time.Now() s.endTime = time.Now()
duration := s.endTime.Sub(s.startTime) duration := s.endTime.Sub(s.startTime)
@ -46,8 +61,10 @@ func (s *StopWatch) Stop() time.Duration {
return duration return duration
} }
// Provide a quick way of instrumenting a InstrumentableCall and emitting its /*
// duration. Provide a quick way of instrumenting a InstrumentableCall and emitting its
duration.
*/
func InstrumentCall(instrumentable InstrumentableCall, onCompletion CompletionCallback) time.Duration { func InstrumentCall(instrumentable InstrumentableCall, onCompletion CompletionCallback) time.Duration {
s := Start(onCompletion) s := Start(onCompletion)
instrumentable() instrumentable()

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// timer_test.go provides a test complement for the timer.go module. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package metrics package metrics

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// registry.go provides a container for centralized exposition of metrics to Use of this source code is governed by a BSD-style license that can be found in
// their prospective consumers. the LICENSE file.
*/
package registry package registry
@ -20,9 +19,18 @@ import (
"time" "time"
) )
// Boilerplate metrics about the metrics reporting subservice. These are only const (
// exposed if the DefaultRegistry's exporter is hooked into the HTTP request jsonContentType = "application/json"
// handler. contentType = "Content-Type"
jsonSuffix = ".json"
)
/*
Boilerplate metrics about the metrics reporting subservice. These are only
exposed if the DefaultRegistry's exporter is hooked into the HTTP request
handler.
*/
var requestCount *metrics.GaugeMetric = &metrics.GaugeMetric{} var requestCount *metrics.GaugeMetric = &metrics.GaugeMetric{}
var requestLatencyLogarithmicBuckets []float64 = metrics.LogarithmicSizedBucketsFor(0, 1000) var requestLatencyLogarithmicBuckets []float64 = metrics.LogarithmicSizedBucketsFor(0, 1000)
var requestLatencyEqualBuckets []float64 = metrics.EquallySizedBucketsFor(0, 1000, 10) var requestLatencyEqualBuckets []float64 = metrics.EquallySizedBucketsFor(0, 1000, 10)
@ -47,8 +55,10 @@ var requestLatencyEqualTallying *metrics.Histogram = metrics.CreateHistogram(&me
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99}, ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
}) })
// This callback accumulates the microsecond duration of the reporting /*
// framework's overhead such that it can be reported. This callback accumulates the microsecond duration of the reporting framework's
overhead such that it can be reported.
*/
var requestLatencyAccumulator metrics.CompletionCallback = func(duration time.Duration) { var requestLatencyAccumulator metrics.CompletionCallback = func(duration time.Duration) {
microseconds := float64(int64(duration) / 1E3) microseconds := float64(int64(duration) / 1E3)
@ -58,33 +68,43 @@ var requestLatencyAccumulator metrics.CompletionCallback = func(duration time.Du
requestLatencyEqualTallying.Add(microseconds) requestLatencyEqualTallying.Add(microseconds)
} }
// Registry is, as the name implies, a registrar where metrics are listed. /*
// Registry is, as the name implies, a registrar where metrics are listed.
// In most situations, using DefaultRegistry is sufficient versus creating
// one's own. In most situations, using DefaultRegistry is sufficient versus creating one's
own.
*/
type Registry struct { type Registry struct {
mutex sync.RWMutex mutex sync.RWMutex
NameToMetric map[string]metrics.Metric NameToMetric map[string]metrics.Metric
} }
// This builds a new metric registry. It is not needed in the majority of /*
// cases. This builds a new metric registry. It is not needed in the majority of
cases.
*/
func NewRegistry() *Registry { func NewRegistry() *Registry {
return &Registry{ return &Registry{
NameToMetric: make(map[string]metrics.Metric), NameToMetric: make(map[string]metrics.Metric),
} }
} }
// This is the default registry with which Metric objects are associated. It /*
// is primarily a read-only object after server instantiation. This is the default registry with which Metric objects are associated. It
is primarily a read-only object after server instantiation.
*/
var DefaultRegistry = NewRegistry() var DefaultRegistry = NewRegistry()
// Associate a Metric with the DefaultRegistry. /*
Associate a Metric with the DefaultRegistry.
*/
func Register(name string, metric metrics.Metric) { func Register(name string, metric metrics.Metric) {
DefaultRegistry.Register(name, metric) DefaultRegistry.Register(name, metric)
} }
// Register a metric with a given name. Name should be globally unique. /*
Register a metric with a given name. Name should be globally unique.
*/
func (r *Registry) Register(name string, metric metrics.Metric) { func (r *Registry) Register(name string, metric metrics.Metric) {
r.mutex.Lock() r.mutex.Lock()
defer r.mutex.Unlock() defer r.mutex.Unlock()
@ -97,17 +117,19 @@ func (r *Registry) Register(name string, metric metrics.Metric) {
} }
} }
// Create a http.HandlerFunc that is tied to r Registry such that requests /*
// against it generate a representation of the housed metrics. Create a http.HandlerFunc that is tied to r Registry such that requests
against it generate a representation of the housed metrics.
*/
func (registry *Registry) YieldExporter() http.HandlerFunc { func (registry *Registry) YieldExporter() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
var instrumentable metrics.InstrumentableCall = func() { var instrumentable metrics.InstrumentableCall = func() {
requestCount.Increment() requestCount.Increment()
url := r.URL url := r.URL
if strings.HasSuffix(url.Path, ".json") { if strings.HasSuffix(url.Path, jsonSuffix) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Header().Set("Content-Type", "application/json") w.Header().Set(contentType, jsonContentType)
composite := make(map[string]interface{}, len(registry.NameToMetric)) composite := make(map[string]interface{}, len(registry.NameToMetric))
for name, metric := range registry.NameToMetric { for name, metric := range registry.NameToMetric {
composite[name] = metric.Marshallable() composite[name] = metric.Marshallable()
@ -126,9 +148,9 @@ func (registry *Registry) YieldExporter() http.HandlerFunc {
} }
func init() { func init() {
DefaultRegistry.Register("requests_total", requestCount) DefaultRegistry.Register("requests_metrics_total", requestCount)
DefaultRegistry.Register("request_latency_logarithmic_accumulating_microseconds", requestLatencyLogarithmicAccumulating) DefaultRegistry.Register("requests_metrics_latency_logarithmic_accumulating_microseconds", requestLatencyLogarithmicAccumulating)
DefaultRegistry.Register("request_latency_equal_accumulating_microseconds", requestLatencyEqualAccumulating) DefaultRegistry.Register("requests_metrics_latency_equal_accumulating_microseconds", requestLatencyEqualAccumulating)
DefaultRegistry.Register("request_latency_logarithmic_tallying_microseconds", requestLatencyLogarithmicTallying) DefaultRegistry.Register("requests_metrics_latency_logarithmic_tallying_microseconds", requestLatencyLogarithmicTallying)
DefaultRegistry.Register("request_latency_equal_tallying_microseconds", requestLatencyEqualTallying) DefaultRegistry.Register("request_metrics_latency_equal_tallying_microseconds", requestLatencyEqualTallying)
} }

29
utility/documentation.go Normal file
View File

@ -0,0 +1,29 @@
/*
Copyright (c) 2012, Matt T. Proud
All rights reserved.
Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
/*
The utility package provides general purpose helpers to assist with this
library.
optional.go provides a mechanism for safely getting a set value or falling
back to defaults a la a Haskell and Scala Maybe or Guava Optional.
optional_test.go provides a test complement for the optional.go module.
priority_queue.go provides a simple priority queue.
priority_queue_test.go provides a test complement for the priority_queue.go
module.
test_helper.go provides a testing assistents for this package and its
dependents.
utility_test.go provides a test suite for all tests in the utility package
hierarchy. It employs the gocheck framework for test scaffolding.
*/
package documentation

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// optional.go provides a mechanism for safely getting a set value or falling Use of this source code is governed by a BSD-style
// back to defaults. license that can be found in the LICENSE file.
*/
package utility package utility

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// optional_test.go provides a test complement for the optional.go module. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package utility package utility

View File

@ -1,10 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// priority_queue.go provides a simple priority queue. Use of this source code is governed by a BSD-style
license that can be found in the LICENSE file.
*/
package utility package utility

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// priority_queue_test.go provides a test complement for the priority_queue.go Use of this source code is governed by a BSD-style
// module. license that can be found in the LICENSE file.
*/
package utility package utility

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// test_helper.go provides a testing assistents for this package and its Use of this source code is governed by a BSD-style
// dependents. license that can be found in the LICENSE file.
*/
package utility package utility

View File

@ -1,11 +1,10 @@
// Copyright (c) 2012, Matt T. Proud /*
// All rights reserved. Copyright (c) 2012, Matt T. Proud
// All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// utility_test.go provides a test suite for all tests in the utility package Use of this source code is governed by a BSD-style
// hierarchy. It employs the gocheck framework for test scaffolding. license that can be found in the LICENSE file.
*/
package utility package utility