forked from mirror/client_golang
Merge pull request #13 from matttproud/feature/labels/base-exposed
Update client to reflect new API needs.
This commit is contained in:
commit
5a29c27491
15
README.md
15
README.md
|
@ -1,3 +1,18 @@
|
||||||
|
# Major Notes
|
||||||
|
The project's documentation is *not up-to-date due to rapidly-changing
|
||||||
|
requirements* that have quieted down in the interim, but the overall API should
|
||||||
|
be stable for several months even if things change under the hood.
|
||||||
|
|
||||||
|
An update to reflect the current state is pending. Key changes for current
|
||||||
|
users:
|
||||||
|
|
||||||
|
1. The code has been qualified in production environments.
|
||||||
|
2. Label-oriented metric exposition and registration, including docstrings.
|
||||||
|
3. Deprecation of gocheck in favor of native table-driven tests.
|
||||||
|
4. The best way to get a handle on this is to look at the examples.
|
||||||
|
|
||||||
|
Barring that, the antique documentation is below:
|
||||||
|
|
||||||
# Overview
|
# Overview
|
||||||
This [Go](http://golang.org) package is an extraction of a piece of
|
This [Go](http://golang.org) package is an extraction of a piece of
|
||||||
instrumentation code I whipped-up for a personal project that a friend of mine
|
instrumentation code I whipped-up for a personal project that a friend of mine
|
||||||
|
|
28
constants.go
28
constants.go
|
@ -1,14 +1,26 @@
|
||||||
/*
|
// Copyright (c) 2013, Matt T. Proud
|
||||||
Copyright (c) 2013, Matt T. Proud
|
// All rights reserved.
|
||||||
All rights reserved.
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be found in
|
||||||
Use of this source code is governed by a BSD-style license that can be found in
|
// the LICENSE file.
|
||||||
the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// NilLabels is a nil set of labels merely for end-user convenience.
|
// NilLabels is a nil set of labels merely for end-user convenience.
|
||||||
NilLabels map[string]string
|
NilLabels map[string]string = nil
|
||||||
|
|
||||||
|
// A prefix to be used to namespace instrumentation flags from others.
|
||||||
|
FlagNamespace = "telemetry."
|
||||||
|
|
||||||
|
// The format of the exported data. This will match this library's version,
|
||||||
|
// which subscribes to the Semantic Versioning scheme.
|
||||||
|
APIVersion = "0.0.1"
|
||||||
|
|
||||||
|
ProtocolVersionHeader = "X-Prometheus-API-Version"
|
||||||
|
|
||||||
|
baseLabelsKey = "baseLabels"
|
||||||
|
docstringKey = "docstring"
|
||||||
|
metricKey = "metric"
|
||||||
|
nameLabel = "name"
|
||||||
)
|
)
|
||||||
|
|
|
@ -37,46 +37,29 @@ func init() {
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
foo_rpc_latency := metrics.CreateHistogram(&metrics.HistogramSpecification{
|
rpc_latency := metrics.NewHistogram(&metrics.HistogramSpecification{
|
||||||
Starts: metrics.EquallySizedBucketsFor(0, 200, 4),
|
Starts: metrics.EquallySizedBucketsFor(0, 200, 4),
|
||||||
BucketMaker: metrics.AccumulatingBucketBuilder(metrics.EvictAndReplaceWith(10, maths.Average), 50),
|
BucketBuilder: metrics.AccumulatingBucketBuilder(metrics.EvictAndReplaceWith(10, maths.Average), 50),
|
||||||
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.90, 0.99},
|
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.90, 0.99},
|
||||||
})
|
})
|
||||||
foo_rpc_calls := &metrics.CounterMetric{}
|
|
||||||
bar_rpc_latency := metrics.CreateHistogram(&metrics.HistogramSpecification{
|
rpc_calls := metrics.NewCounter()
|
||||||
Starts: metrics.EquallySizedBucketsFor(0, 200, 4),
|
|
||||||
BucketMaker: metrics.AccumulatingBucketBuilder(metrics.EvictAndReplaceWith(10, maths.Average), 50),
|
|
||||||
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.90, 0.99},
|
|
||||||
})
|
|
||||||
bar_rpc_calls := &metrics.CounterMetric{}
|
|
||||||
zed_rpc_latency := metrics.CreateHistogram(&metrics.HistogramSpecification{
|
|
||||||
Starts: metrics.EquallySizedBucketsFor(0, 200, 4),
|
|
||||||
BucketMaker: metrics.AccumulatingBucketBuilder(metrics.EvictAndReplaceWith(10, maths.Average), 50),
|
|
||||||
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.90, 0.99},
|
|
||||||
})
|
|
||||||
zed_rpc_calls := &metrics.CounterMetric{}
|
|
||||||
|
|
||||||
metrics := registry.NewRegistry()
|
metrics := registry.NewRegistry()
|
||||||
|
|
||||||
nilBaseLabels := make(map[string]string)
|
metrics.Register("rpc_latency_microseconds", "RPC latency.", registry.NilLabels, rpc_latency)
|
||||||
|
metrics.Register("rpc_calls_total", "RPC calls.", registry.NilLabels, rpc_calls)
|
||||||
metrics.Register("rpc_latency_foo_microseconds", "RPC latency for foo service.", nilBaseLabels, foo_rpc_latency)
|
|
||||||
metrics.Register("rpc_calls_foo_total", "RPC calls for foo service.", nilBaseLabels, foo_rpc_calls)
|
|
||||||
metrics.Register("rpc_latency_bar_microseconds", "RPC latency for bar service.", nilBaseLabels, bar_rpc_latency)
|
|
||||||
metrics.Register("rpc_calls_bar_total", "RPC calls for bar service.", nilBaseLabels, bar_rpc_calls)
|
|
||||||
metrics.Register("rpc_latency_zed_microseconds", "RPC latency for zed service.", nilBaseLabels, zed_rpc_latency)
|
|
||||||
metrics.Register("rpc_calls_zed_total", "RPC calls for zed service.", nilBaseLabels, zed_rpc_calls)
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
foo_rpc_latency.Add(rand.Float64() * 200)
|
rpc_latency.Add(map[string]string{"service": "foo"}, rand.Float64()*200)
|
||||||
foo_rpc_calls.Increment()
|
rpc_calls.Increment(map[string]string{"service": "foo"})
|
||||||
|
|
||||||
bar_rpc_latency.Add((rand.NormFloat64() * 10.0) + 100.0)
|
rpc_latency.Add(map[string]string{"service": "bar"}, (rand.NormFloat64()*10.0)+100.0)
|
||||||
bar_rpc_calls.Increment()
|
rpc_calls.Increment(map[string]string{"service": "bar"})
|
||||||
|
|
||||||
zed_rpc_latency.Add(rand.ExpFloat64())
|
rpc_latency.Add(map[string]string{"service": "zed"}, rand.ExpFloat64())
|
||||||
zed_rpc_calls.Increment()
|
rpc_calls.Increment(map[string]string{"service": "zed"})
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,11 +20,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type AccumulatingBucket struct {
|
type AccumulatingBucket struct {
|
||||||
observations int
|
|
||||||
elements utility.PriorityQueue
|
elements utility.PriorityQueue
|
||||||
|
evictionPolicy EvictionPolicy
|
||||||
maximumSize int
|
maximumSize int
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
evictionPolicy EvictionPolicy
|
observations int
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -35,9 +35,9 @@ 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{
|
||||||
maximumSize: maximumSize,
|
|
||||||
evictionPolicy: evictionPolicy,
|
|
||||||
elements: make(utility.PriorityQueue, 0, maximumSize),
|
elements: make(utility.PriorityQueue, 0, maximumSize),
|
||||||
|
evictionPolicy: evictionPolicy,
|
||||||
|
maximumSize: maximumSize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,8 +54,8 @@ func (b *AccumulatingBucket) Add(value float64) {
|
||||||
size := len(b.elements)
|
size := len(b.elements)
|
||||||
|
|
||||||
v := utility.Item{
|
v := utility.Item{
|
||||||
Value: value,
|
|
||||||
Priority: -1 * time.Now().UnixNano(),
|
Priority: -1 * time.Now().UnixNano(),
|
||||||
|
Value: value,
|
||||||
}
|
}
|
||||||
|
|
||||||
if size == b.maximumSize {
|
if size == b.maximumSize {
|
||||||
|
@ -69,7 +69,7 @@ func (b *AccumulatingBucket) String() string {
|
||||||
b.mutex.RLock()
|
b.mutex.RLock()
|
||||||
defer b.mutex.RUnlock()
|
defer b.mutex.RUnlock()
|
||||||
|
|
||||||
buffer := new(bytes.Buffer)
|
buffer := &bytes.Buffer{}
|
||||||
|
|
||||||
fmt.Fprintf(buffer, "[AccumulatingBucket with %d elements and %d capacity] { ", len(b.elements), b.maximumSize)
|
fmt.Fprintf(buffer, "[AccumulatingBucket with %d elements and %d capacity] { ", len(b.elements), b.maximumSize)
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ func (b *AccumulatingBucket) String() string {
|
||||||
|
|
||||||
fmt.Fprintf(buffer, "}")
|
fmt.Fprintf(buffer, "}")
|
||||||
|
|
||||||
return string(buffer.Bytes())
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *AccumulatingBucket) ValueForIndex(index int) float64 {
|
func (b *AccumulatingBucket) ValueForIndex(index int) float64 {
|
||||||
|
@ -116,3 +116,14 @@ func (b *AccumulatingBucket) Observations() int {
|
||||||
|
|
||||||
return b.observations
|
return b.observations
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *AccumulatingBucket) Reset() {
|
||||||
|
b.mutex.Lock()
|
||||||
|
defer b.mutex.RUnlock()
|
||||||
|
|
||||||
|
for i := 0; i < b.elements.Len(); i++ {
|
||||||
|
b.elements.Pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
b.observations = 0
|
||||||
|
}
|
||||||
|
|
|
@ -29,14 +29,16 @@ 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.
|
|
||||||
*/
|
|
||||||
String() 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
|
||||||
|
// Reset is responsible for resetting this bucket back to a pristine state.
|
||||||
|
Reset()
|
||||||
|
/*
|
||||||
|
Provide a humanized representation hereof.
|
||||||
|
*/
|
||||||
|
String() string
|
||||||
/*
|
/*
|
||||||
Provide the value from the given in-memory value cache or an estimate
|
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
|
thereof for the given index. The consumer of the bucket's data makes
|
||||||
|
|
|
@ -12,12 +12,13 @@ constants.go provides package-level constants for metrics.
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
const (
|
const (
|
||||||
valueKey = "value"
|
|
||||||
gaugeTypeValue = "gauge"
|
|
||||||
counterTypeValue = "counter"
|
counterTypeValue = "counter"
|
||||||
typeKey = "type"
|
floatBitCount = 64
|
||||||
histogramTypeValue = "histogram"
|
|
||||||
floatFormat = 'f'
|
floatFormat = 'f'
|
||||||
floatPrecision = 6
|
floatPrecision = 6
|
||||||
floatBitCount = 64
|
gaugeTypeValue = "gauge"
|
||||||
|
histogramTypeValue = "histogram"
|
||||||
|
typeKey = "type"
|
||||||
|
valueKey = "value"
|
||||||
|
labelsKey = "labels"
|
||||||
)
|
)
|
||||||
|
|
|
@ -10,77 +10,145 @@ package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/matttproud/golang_instrumentation/utility"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CounterMetric struct {
|
// TODO(matt): Refactor to de-duplicate behaviors.
|
||||||
value float64
|
|
||||||
mutex sync.RWMutex
|
type Counter interface {
|
||||||
|
AsMarshallable() map[string]interface{}
|
||||||
|
Decrement(labels map[string]string) float64
|
||||||
|
DecrementBy(labels map[string]string, value float64) float64
|
||||||
|
Increment(labels map[string]string) float64
|
||||||
|
IncrementBy(labels map[string]string, value float64) float64
|
||||||
|
ResetAll()
|
||||||
|
Set(labels map[string]string, value float64) float64
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *CounterMetric) Set(value float64) float64 {
|
type counterValue struct {
|
||||||
|
labels map[string]string
|
||||||
|
value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCounter() Counter {
|
||||||
|
return &counter{
|
||||||
|
values: map[string]*counterValue{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type counter struct {
|
||||||
|
mutex sync.RWMutex
|
||||||
|
values map[string]*counterValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (metric *counter) Set(labels map[string]string, value float64) float64 {
|
||||||
metric.mutex.Lock()
|
metric.mutex.Lock()
|
||||||
defer metric.mutex.Unlock()
|
defer metric.mutex.Unlock()
|
||||||
|
|
||||||
metric.value = value
|
if labels == nil {
|
||||||
|
labels = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
return metric.value
|
signature := utility.LabelsToSignature(labels)
|
||||||
|
if original, ok := metric.values[signature]; ok {
|
||||||
|
original.value = value
|
||||||
|
} else {
|
||||||
|
metric.values[signature] = &counterValue{
|
||||||
|
labels: labels,
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *CounterMetric) Reset() {
|
func (metric *counter) ResetAll() {
|
||||||
metric.Set(0)
|
metric.mutex.Lock()
|
||||||
|
defer metric.mutex.Unlock()
|
||||||
|
|
||||||
|
for key, value := range metric.values {
|
||||||
|
for label := range value.labels {
|
||||||
|
delete(value.labels, label)
|
||||||
|
}
|
||||||
|
delete(metric.values, key)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *CounterMetric) String() string {
|
func (metric *counter) String() string {
|
||||||
formatString := "[CounterMetric; value=%f]"
|
formatString := "[Counter %s]"
|
||||||
|
|
||||||
metric.mutex.RLock()
|
metric.mutex.RLock()
|
||||||
defer metric.mutex.RUnlock()
|
defer metric.mutex.RUnlock()
|
||||||
|
|
||||||
return fmt.Sprintf(formatString, metric.value)
|
return fmt.Sprintf(formatString, metric.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *CounterMetric) IncrementBy(value float64) float64 {
|
func (metric *counter) IncrementBy(labels map[string]string, value float64) float64 {
|
||||||
metric.mutex.Lock()
|
metric.mutex.Lock()
|
||||||
defer metric.mutex.Unlock()
|
defer metric.mutex.Unlock()
|
||||||
|
|
||||||
metric.value += value
|
if labels == nil {
|
||||||
|
labels = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
return metric.value
|
signature := utility.LabelsToSignature(labels)
|
||||||
|
if original, ok := metric.values[signature]; ok {
|
||||||
|
original.value += value
|
||||||
|
} else {
|
||||||
|
metric.values[signature] = &counterValue{
|
||||||
|
labels: labels,
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *CounterMetric) Increment() float64 {
|
func (metric *counter) Increment(labels map[string]string) float64 {
|
||||||
return metric.IncrementBy(1)
|
return metric.IncrementBy(labels, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *CounterMetric) DecrementBy(value float64) float64 {
|
func (metric *counter) DecrementBy(labels map[string]string, value float64) float64 {
|
||||||
metric.mutex.Lock()
|
metric.mutex.Lock()
|
||||||
defer metric.mutex.Unlock()
|
defer metric.mutex.Unlock()
|
||||||
|
|
||||||
metric.value -= value
|
if labels == nil {
|
||||||
|
labels = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
return metric.value
|
signature := utility.LabelsToSignature(labels)
|
||||||
|
if original, ok := metric.values[signature]; ok {
|
||||||
|
original.value -= value
|
||||||
|
} else {
|
||||||
|
metric.values[signature] = &counterValue{
|
||||||
|
labels: labels,
|
||||||
|
value: -1 * value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *CounterMetric) Decrement() float64 {
|
func (metric *counter) Decrement(labels map[string]string) float64 {
|
||||||
return metric.DecrementBy(1)
|
return metric.DecrementBy(labels, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *CounterMetric) Get() float64 {
|
func (metric *counter) AsMarshallable() map[string]interface{} {
|
||||||
metric.mutex.RLock()
|
metric.mutex.RLock()
|
||||||
defer metric.mutex.RUnlock()
|
defer metric.mutex.RUnlock()
|
||||||
|
|
||||||
return metric.value
|
values := make([]map[string]interface{}, 0, len(metric.values))
|
||||||
}
|
for _, value := range metric.values {
|
||||||
|
values = append(values, map[string]interface{}{
|
||||||
func (metric *CounterMetric) Marshallable() map[string]interface{} {
|
labelsKey: value.labels,
|
||||||
metric.mutex.RLock()
|
valueKey: value.value,
|
||||||
defer metric.mutex.RUnlock()
|
})
|
||||||
|
}
|
||||||
v := make(map[string]interface{}, 2)
|
|
||||||
|
return map[string]interface{}{
|
||||||
v[valueKey] = metric.value
|
valueKey: values,
|
||||||
v[typeKey] = counterTypeValue
|
typeKey: counterTypeValue,
|
||||||
|
}
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,90 +9,215 @@ license that can be found in the LICENSE file.
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/matttproud/gocheck"
|
"encoding/json"
|
||||||
|
"github.com/matttproud/golang_instrumentation/utility/test"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *S) TestCounterCreate(c *C) {
|
func testCounter(t test.Tester) {
|
||||||
m := CounterMetric{value: 1.0}
|
type input struct {
|
||||||
|
steps []func(g Counter)
|
||||||
|
}
|
||||||
|
type output struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
c.Assert(m, Not(IsNil))
|
var scenarios = []struct {
|
||||||
|
in input
|
||||||
|
out output
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(nil, 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{},\"value\":1}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{}, 2)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{},\"value\":2}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{}, 3)
|
||||||
|
},
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{}, 5)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{},\"value\":5}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{"handler": "/foo"}, 13)
|
||||||
|
},
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{"handler": "/bar"}, 17)
|
||||||
|
},
|
||||||
|
func(g Counter) {
|
||||||
|
g.ResetAll()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{"handler": "/foo"}, 19)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":19}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{"handler": "/foo"}, 23)
|
||||||
|
},
|
||||||
|
func(g Counter) {
|
||||||
|
g.Increment(map[string]string{"handler": "/foo"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":24}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Increment(map[string]string{"handler": "/foo"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":1}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Decrement(map[string]string{"handler": "/foo"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":-1}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{"handler": "/foo"}, 29)
|
||||||
|
},
|
||||||
|
func(g Counter) {
|
||||||
|
g.Decrement(map[string]string{"handler": "/foo"})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":28}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{"handler": "/foo"}, 31)
|
||||||
|
},
|
||||||
|
func(g Counter) {
|
||||||
|
g.IncrementBy(map[string]string{"handler": "/foo"}, 5)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":36}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Counter){
|
||||||
|
func(g Counter) {
|
||||||
|
g.Set(map[string]string{"handler": "/foo"}, 37)
|
||||||
|
},
|
||||||
|
func(g Counter) {
|
||||||
|
g.DecrementBy(map[string]string{"handler": "/foo"}, 10)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"counter\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":27}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, scenario := range scenarios {
|
||||||
|
counter := NewCounter()
|
||||||
|
|
||||||
|
for _, step := range scenario.in.steps {
|
||||||
|
step(counter)
|
||||||
|
}
|
||||||
|
|
||||||
|
marshallable := counter.AsMarshallable()
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(marshallable)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d. could not marshal into JSON %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
asString := string(bytes)
|
||||||
|
|
||||||
|
if scenario.out.value != asString {
|
||||||
|
t.Errorf("%d. expected %q, got %q", i, scenario.out.value, asString)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) TestCounterGet(c *C) {
|
func TestCounter(t *testing.T) {
|
||||||
m := CounterMetric{value: 42.23}
|
testCounter(t)
|
||||||
|
|
||||||
c.Check(m.Get(), Equals, 42.23)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) TestCounterSet(c *C) {
|
func BenchmarkCounter(b *testing.B) {
|
||||||
m := CounterMetric{value: 42.23}
|
for i := 0; i < b.N; i++ {
|
||||||
m.Set(40.4)
|
testCounter(b)
|
||||||
|
}
|
||||||
c.Check(m.Get(), Equals, 40.4)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCounterReset(c *C) {
|
|
||||||
m := CounterMetric{value: 42.23}
|
|
||||||
m.Reset()
|
|
||||||
|
|
||||||
c.Check(m.Get(), Equals, 0.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCounterIncrementBy(c *C) {
|
|
||||||
m := CounterMetric{value: 1.0}
|
|
||||||
|
|
||||||
m.IncrementBy(1.5)
|
|
||||||
|
|
||||||
c.Check(m.Get(), Equals, 2.5)
|
|
||||||
c.Check(m.String(), Equals, "[CounterMetric; value=2.500000]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCounterIncrement(c *C) {
|
|
||||||
m := CounterMetric{value: 1.0}
|
|
||||||
|
|
||||||
m.Increment()
|
|
||||||
|
|
||||||
c.Check(m.Get(), Equals, 2.0)
|
|
||||||
c.Check(m.String(), Equals, "[CounterMetric; value=2.000000]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCounterDecrementBy(c *C) {
|
|
||||||
m := CounterMetric{value: 1.0}
|
|
||||||
|
|
||||||
m.DecrementBy(1.0)
|
|
||||||
|
|
||||||
c.Check(m.Get(), Equals, 0.0)
|
|
||||||
c.Check(m.String(), Equals, "[CounterMetric; value=0.000000]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCounterDecrement(c *C) {
|
|
||||||
m := CounterMetric{value: 1.0}
|
|
||||||
|
|
||||||
m.Decrement()
|
|
||||||
|
|
||||||
c.Check(m.Get(), Equals, 0.0)
|
|
||||||
c.Check(m.String(), Equals, "[CounterMetric; value=0.000000]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCounterString(c *C) {
|
|
||||||
m := CounterMetric{value: 2.0}
|
|
||||||
c.Check(m.String(), Equals, "[CounterMetric; value=2.000000]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCounterMetricMarshallable(c *C) {
|
|
||||||
m := CounterMetric{value: 1.0}
|
|
||||||
|
|
||||||
returned := m.Marshallable()
|
|
||||||
|
|
||||||
c.Assert(returned, Not(IsNil))
|
|
||||||
|
|
||||||
c.Check(returned, HasLen, 2)
|
|
||||||
c.Check(returned["value"], Equals, 1.0)
|
|
||||||
c.Check(returned["type"], Equals, "counter")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCounterAsMetric(c *C) {
|
|
||||||
var metric Metric = &CounterMetric{value: 1.0}
|
|
||||||
|
|
||||||
c.Assert(metric, Not(IsNil))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,8 +22,8 @@ func (s *S) TestEvictOldest(c *C) {
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
var item utility.Item = utility.Item{
|
var item utility.Item = utility.Item{
|
||||||
Value: float64(i),
|
|
||||||
Priority: int64(i),
|
Priority: int64(i),
|
||||||
|
Value: float64(i),
|
||||||
}
|
}
|
||||||
|
|
||||||
heap.Push(&q, &item)
|
heap.Push(&q, &item)
|
||||||
|
@ -49,8 +49,8 @@ func (s *S) TestEvictAndReplaceWithAverage(c *C) {
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
var item utility.Item = utility.Item{
|
var item utility.Item = utility.Item{
|
||||||
Value: float64(i),
|
|
||||||
Priority: int64(i),
|
Priority: int64(i),
|
||||||
|
Value: float64(i),
|
||||||
}
|
}
|
||||||
|
|
||||||
heap.Push(&q, &item)
|
heap.Push(&q, &item)
|
||||||
|
@ -77,8 +77,8 @@ func (s *S) TestEvictAndReplaceWithMedian(c *C) {
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
var item utility.Item = utility.Item{
|
var item utility.Item = utility.Item{
|
||||||
Value: float64(i),
|
|
||||||
Priority: int64(i),
|
Priority: int64(i),
|
||||||
|
Value: float64(i),
|
||||||
}
|
}
|
||||||
|
|
||||||
heap.Push(&q, &item)
|
heap.Push(&q, &item)
|
||||||
|
@ -105,8 +105,8 @@ func (s *S) TestEvictAndReplaceWithFirstMode(c *C) {
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
heap.Push(&q, &utility.Item{
|
heap.Push(&q, &utility.Item{
|
||||||
Value: float64(i),
|
|
||||||
Priority: int64(i),
|
Priority: int64(i),
|
||||||
|
Value: float64(i),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,8 +131,8 @@ func (s *S) TestEvictAndReplaceWithMinimum(c *C) {
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
var item utility.Item = utility.Item{
|
var item utility.Item = utility.Item{
|
||||||
Value: float64(i),
|
|
||||||
Priority: int64(i),
|
Priority: int64(i),
|
||||||
|
Value: float64(i),
|
||||||
}
|
}
|
||||||
|
|
||||||
heap.Push(&q, &item)
|
heap.Push(&q, &item)
|
||||||
|
@ -159,8 +159,8 @@ func (s *S) TestEvictAndReplaceWithMaximum(c *C) {
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 10; i++ {
|
||||||
var item utility.Item = utility.Item{
|
var item utility.Item = utility.Item{
|
||||||
Value: float64(i),
|
|
||||||
Priority: int64(i),
|
Priority: int64(i),
|
||||||
|
Value: float64(i),
|
||||||
}
|
}
|
||||||
|
|
||||||
heap.Push(&q, &item)
|
heap.Push(&q, &item)
|
||||||
|
|
|
@ -10,6 +10,7 @@ package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/matttproud/golang_instrumentation/utility"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -19,44 +20,86 @@ value or an accumulation. For instance, if one wants to expose the current
|
||||||
temperature or the hitherto bandwidth used, this would be the metric for such
|
temperature or the hitherto bandwidth used, this would be the metric for such
|
||||||
circumstances.
|
circumstances.
|
||||||
*/
|
*/
|
||||||
type GaugeMetric struct {
|
type Gauge interface {
|
||||||
value float64
|
AsMarshallable() map[string]interface{}
|
||||||
mutex sync.RWMutex
|
ResetAll()
|
||||||
|
Set(labels map[string]string, value float64) float64
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *GaugeMetric) String() string {
|
type gaugeValue struct {
|
||||||
formatString := "[GaugeMetric; value=%f]"
|
labels map[string]string
|
||||||
|
value float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGauge() Gauge {
|
||||||
|
return &gauge{
|
||||||
|
values: map[string]*gaugeValue{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type gauge struct {
|
||||||
|
mutex sync.RWMutex
|
||||||
|
values map[string]*gaugeValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (metric *gauge) String() string {
|
||||||
|
formatString := "[Gauge %s]"
|
||||||
|
|
||||||
metric.mutex.RLock()
|
metric.mutex.RLock()
|
||||||
defer metric.mutex.RUnlock()
|
defer metric.mutex.RUnlock()
|
||||||
|
|
||||||
return fmt.Sprintf(formatString, metric.value)
|
return fmt.Sprintf(formatString, metric.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *GaugeMetric) Set(value float64) float64 {
|
func (metric *gauge) Set(labels map[string]string, value float64) float64 {
|
||||||
metric.mutex.Lock()
|
metric.mutex.Lock()
|
||||||
defer metric.mutex.Unlock()
|
defer metric.mutex.Unlock()
|
||||||
|
|
||||||
metric.value = value
|
if labels == nil {
|
||||||
|
labels = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
return metric.value
|
signature := utility.LabelsToSignature(labels)
|
||||||
|
|
||||||
|
if original, ok := metric.values[signature]; ok {
|
||||||
|
original.value = value
|
||||||
|
} else {
|
||||||
|
metric.values[signature] = &gaugeValue{
|
||||||
|
labels: labels,
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
func (metric *GaugeMetric) Get() float64 {
|
func (metric *gauge) ResetAll() {
|
||||||
|
metric.mutex.Lock()
|
||||||
|
defer metric.mutex.Unlock()
|
||||||
|
|
||||||
|
for key, value := range metric.values {
|
||||||
|
for label := range value.labels {
|
||||||
|
delete(value.labels, label)
|
||||||
|
}
|
||||||
|
delete(metric.values, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (metric *gauge) AsMarshallable() map[string]interface{} {
|
||||||
metric.mutex.RLock()
|
metric.mutex.RLock()
|
||||||
defer metric.mutex.RUnlock()
|
defer metric.mutex.RUnlock()
|
||||||
|
|
||||||
return metric.value
|
values := make([]map[string]interface{}, 0, len(metric.values))
|
||||||
}
|
for _, value := range metric.values {
|
||||||
|
values = append(values, map[string]interface{}{
|
||||||
func (metric *GaugeMetric) Marshallable() map[string]interface{} {
|
labelsKey: value.labels,
|
||||||
metric.mutex.RLock()
|
valueKey: value.value,
|
||||||
defer metric.mutex.RUnlock()
|
})
|
||||||
|
}
|
||||||
v := make(map[string]interface{}, 2)
|
|
||||||
|
return map[string]interface{}{
|
||||||
v[valueKey] = metric.value
|
typeKey: gaugeTypeValue,
|
||||||
v[typeKey] = gaugeTypeValue
|
valueKey: values,
|
||||||
|
}
|
||||||
return v
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,43 +9,131 @@ license that can be found in the LICENSE file.
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/matttproud/gocheck"
|
"encoding/json"
|
||||||
|
"github.com/matttproud/golang_instrumentation/utility/test"
|
||||||
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *S) TestGaugeCreate(c *C) {
|
func testGauge(t test.Tester) {
|
||||||
m := GaugeMetric{value: 1.0}
|
type input struct {
|
||||||
|
steps []func(g Gauge)
|
||||||
|
}
|
||||||
|
type output struct {
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
c.Assert(m, Not(IsNil))
|
var scenarios = []struct {
|
||||||
c.Check(m.Get(), Equals, 1.0)
|
in input
|
||||||
|
out output
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Gauge){},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"gauge\",\"value\":[]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Gauge){
|
||||||
|
func(g Gauge) {
|
||||||
|
g.Set(nil, 1)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"gauge\",\"value\":[{\"labels\":{},\"value\":1}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Gauge){
|
||||||
|
func(g Gauge) {
|
||||||
|
g.Set(map[string]string{}, 2)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"gauge\",\"value\":[{\"labels\":{},\"value\":2}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Gauge){
|
||||||
|
func(g Gauge) {
|
||||||
|
g.Set(map[string]string{}, 3)
|
||||||
|
},
|
||||||
|
func(g Gauge) {
|
||||||
|
g.Set(map[string]string{}, 5)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"gauge\",\"value\":[{\"labels\":{},\"value\":5}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Gauge){
|
||||||
|
func(g Gauge) {
|
||||||
|
g.Set(map[string]string{"handler": "/foo"}, 13)
|
||||||
|
},
|
||||||
|
func(g Gauge) {
|
||||||
|
g.Set(map[string]string{"handler": "/bar"}, 17)
|
||||||
|
},
|
||||||
|
func(g Gauge) {
|
||||||
|
g.ResetAll()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"gauge\",\"value\":[]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
steps: []func(g Gauge){
|
||||||
|
func(g Gauge) {
|
||||||
|
g.Set(map[string]string{"handler": "/foo"}, 19)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
value: "{\"type\":\"gauge\",\"value\":[{\"labels\":{\"handler\":\"/foo\"},\"value\":19}]}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, scenario := range scenarios {
|
||||||
|
gauge := NewGauge()
|
||||||
|
|
||||||
|
for _, step := range scenario.in.steps {
|
||||||
|
step(gauge)
|
||||||
|
}
|
||||||
|
|
||||||
|
marshallable := gauge.AsMarshallable()
|
||||||
|
|
||||||
|
bytes, err := json.Marshal(marshallable)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d. could not marshal into JSON %s", i, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
asString := string(bytes)
|
||||||
|
|
||||||
|
if scenario.out.value != asString {
|
||||||
|
t.Errorf("%d. expected %q, got %q", i, scenario.out.value, asString)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) TestGaugeString(c *C) {
|
func TestGauge(t *testing.T) {
|
||||||
m := GaugeMetric{value: 2.0}
|
testGauge(t)
|
||||||
c.Check(m.String(), Equals, "[GaugeMetric; value=2.000000]")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *S) TestGaugeSet(c *C) {
|
func BenchmarkGauge(b *testing.B) {
|
||||||
m := GaugeMetric{value: -1.0}
|
for i := 0; i < b.N; i++ {
|
||||||
|
testGauge(b)
|
||||||
m.Set(-99.0)
|
}
|
||||||
|
|
||||||
c.Check(m.Get(), Equals, -99.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGaugeMetricMarshallable(c *C) {
|
|
||||||
m := GaugeMetric{value: 1.0}
|
|
||||||
|
|
||||||
returned := m.Marshallable()
|
|
||||||
|
|
||||||
c.Assert(returned, Not(IsNil))
|
|
||||||
|
|
||||||
c.Check(returned, HasLen, 2)
|
|
||||||
c.Check(returned["value"], Equals, 1.0)
|
|
||||||
c.Check(returned["type"], Equals, "gauge")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestGaugeAsMetric(c *C) {
|
|
||||||
var metric Metric = &GaugeMetric{value: 1.0}
|
|
||||||
|
|
||||||
c.Assert(metric, Not(IsNil))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,10 @@ package metrics
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/matttproud/golang_instrumentation/utility"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -53,21 +55,25 @@ func LogarithmicSizedBucketsFor(lower, upper float64) []float64 {
|
||||||
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
|
BucketBuilder BucketBuilder
|
||||||
BucketMaker BucketBuilder
|
|
||||||
ReportablePercentiles []float64
|
ReportablePercentiles []float64
|
||||||
|
Starts []float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Histogram interface {
|
||||||
|
Add(labels map[string]string, value float64)
|
||||||
|
AsMarshallable() map[string]interface{}
|
||||||
|
ResetAll()
|
||||||
|
String() string
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
The histogram is an accumulator for samples. It merely routes into which
|
The histogram is an accumulator for samples. It merely routes into which
|
||||||
to bucket to capture an event and provides a percentile calculation
|
to bucket to capture an event and provides a percentile calculation
|
||||||
mechanism.
|
mechanism.
|
||||||
|
|
||||||
Histogram makes do without locking by employing the law of large numbers
|
|
||||||
to presume a convergence toward a given bucket distribution. Locking
|
|
||||||
may be implemented in the buckets themselves, though.
|
|
||||||
*/
|
*/
|
||||||
type Histogram struct {
|
type histogram struct {
|
||||||
|
bucketMaker BucketBuilder
|
||||||
/*
|
/*
|
||||||
This represents the open interval's start at which values shall be added to
|
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
|
the bucket. The interval continues until the beginning of the next bucket
|
||||||
|
@ -76,23 +82,52 @@ type Histogram struct {
|
||||||
N.B.
|
N.B.
|
||||||
- bucketStarts should be sorted in ascending order;
|
- bucketStarts should be sorted in ascending order;
|
||||||
- len(bucketStarts) must be equivalent to len(buckets);
|
- len(bucketStarts) must be equivalent to len(buckets);
|
||||||
- The index of a given bucketStarts' element is presumed to match
|
- The index of a given bucketStarts' element is presumed to
|
||||||
correspond to the appropriate element in buckets.
|
correspond to the appropriate element in buckets.
|
||||||
*/
|
*/
|
||||||
bucketStarts []float64
|
bucketStarts []float64
|
||||||
|
mutex sync.RWMutex
|
||||||
/*
|
/*
|
||||||
These are the buckets that capture samples as they are emitted to the
|
These are the buckets that capture samples as they are emitted to the
|
||||||
histogram. Please consult the reference interface and its implements for
|
histogram. Please consult the reference interface and its implements for
|
||||||
further details about behavior expectations.
|
further details about behavior expectations.
|
||||||
*/
|
*/
|
||||||
buckets []Bucket
|
values map[string]*histogramValue
|
||||||
/*
|
/*
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Histogram) Add(value float64) {
|
type histogramValue struct {
|
||||||
|
buckets []Bucket
|
||||||
|
labels map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *histogram) Add(labels map[string]string, value float64) {
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
if labels == nil {
|
||||||
|
labels = map[string]string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
signature := utility.LabelsToSignature(labels)
|
||||||
|
var histogram *histogramValue = nil
|
||||||
|
if original, ok := h.values[signature]; ok {
|
||||||
|
histogram = original
|
||||||
|
} else {
|
||||||
|
bucketCount := len(h.bucketStarts)
|
||||||
|
histogram = &histogramValue{
|
||||||
|
buckets: make([]Bucket, bucketCount),
|
||||||
|
labels: labels,
|
||||||
|
}
|
||||||
|
for i := 0; i < bucketCount; i++ {
|
||||||
|
histogram.buckets[i] = h.bucketMaker()
|
||||||
|
}
|
||||||
|
h.values[signature] = histogram
|
||||||
|
}
|
||||||
|
|
||||||
lastIndex := 0
|
lastIndex := 0
|
||||||
|
|
||||||
for i, bucketStart := range h.bucketStarts {
|
for i, bucketStart := range h.bucketStarts {
|
||||||
|
@ -103,21 +138,27 @@ func (h *Histogram) Add(value float64) {
|
||||||
lastIndex = i
|
lastIndex = i
|
||||||
}
|
}
|
||||||
|
|
||||||
h.buckets[lastIndex].Add(value)
|
histogram.buckets[lastIndex].Add(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Histogram) String() string {
|
func (h *histogram) String() string {
|
||||||
stringBuffer := bytes.NewBufferString("")
|
h.mutex.RLock()
|
||||||
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
|
stringBuffer := &bytes.Buffer{}
|
||||||
stringBuffer.WriteString("[Histogram { ")
|
stringBuffer.WriteString("[Histogram { ")
|
||||||
|
|
||||||
for i, bucketStart := range h.bucketStarts {
|
for _, histogram := range h.values {
|
||||||
bucket := h.buckets[i]
|
fmt.Fprintf(stringBuffer, "Labels: %s ", histogram.labels)
|
||||||
stringBuffer.WriteString(fmt.Sprintf("[%f, inf) = %s, ", bucketStart, bucket.String()))
|
for i, bucketStart := range h.bucketStarts {
|
||||||
|
bucket := histogram.buckets[i]
|
||||||
|
fmt.Fprintf(stringBuffer, "[%f, inf) = %s, ", bucketStart, bucket)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stringBuffer.WriteString("}]")
|
stringBuffer.WriteString("}]")
|
||||||
|
|
||||||
return string(stringBuffer.Bytes())
|
return stringBuffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -141,13 +182,15 @@ func prospectiveIndexForPercentile(percentile float64, totalObservations int) in
|
||||||
/*
|
/*
|
||||||
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(signature string, 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 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
return &h.buckets[i], 0
|
histogram := h.values[signature]
|
||||||
|
|
||||||
|
return &histogram.buckets[i], 0
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("Illegal Condition: There were no remaining buckets to provide a value.")
|
panic("Illegal Condition: There were no remaining buckets to provide a value.")
|
||||||
|
@ -160,8 +203,8 @@ longer contained by the bucket, the index of the last item is returned. This
|
||||||
may occur if the underlying bucket catalogs values and employs an eviction
|
may occur if the underlying bucket catalogs values and employs an eviction
|
||||||
strategy.
|
strategy.
|
||||||
*/
|
*/
|
||||||
func (h *Histogram) bucketForPercentile(percentile float64) (*Bucket, int) {
|
func (h *histogram) bucketForPercentile(signature string, percentile float64) (*Bucket, int) {
|
||||||
bucketCount := len(h.buckets)
|
bucketCount := len(h.bucketStarts)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This captures the quantity of samples in a given bucket's range.
|
This captures the quantity of samples in a given bucket's range.
|
||||||
|
@ -173,9 +216,11 @@ func (h *Histogram) bucketForPercentile(percentile float64) (*Bucket, int) {
|
||||||
*/
|
*/
|
||||||
cumulativeObservationsByBucket := make([]int, bucketCount)
|
cumulativeObservationsByBucket := make([]int, bucketCount)
|
||||||
|
|
||||||
var totalObservations int = 0
|
totalObservations := 0
|
||||||
|
|
||||||
for i, bucket := range h.buckets {
|
histogram := h.values[signature]
|
||||||
|
|
||||||
|
for i, bucket := range histogram.buckets {
|
||||||
observations := bucket.Observations()
|
observations := bucket.Observations()
|
||||||
observationsByBucket[i] = observations
|
observationsByBucket[i] = observations
|
||||||
totalObservations += bucket.Observations()
|
totalObservations += bucket.Observations()
|
||||||
|
@ -210,14 +255,14 @@ func (h *Histogram) bucketForPercentile(percentile float64) (*Bucket, int) {
|
||||||
take this into account.
|
take this into account.
|
||||||
*/
|
*/
|
||||||
if observationsByBucket[i] == subIndex {
|
if observationsByBucket[i] == subIndex {
|
||||||
return h.nextNonEmptyBucketElement(i+1, bucketCount, observationsByBucket)
|
return h.nextNonEmptyBucketElement(signature, i+1, bucketCount, observationsByBucket)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &h.buckets[i], subIndex
|
return &histogram.buckets[i], subIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &h.buckets[0], 0
|
return &histogram.buckets[0], 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -225,8 +270,8 @@ Return the histogram's estimate of the value for a given percentile of
|
||||||
collected samples. The requested percentile is expected to be a real
|
collected samples. The requested percentile is expected to be a real
|
||||||
value within (0, 1.0].
|
value within (0, 1.0].
|
||||||
*/
|
*/
|
||||||
func (h *Histogram) Percentile(percentile float64) float64 {
|
func (h *histogram) percentile(signature string, percentile float64) float64 {
|
||||||
bucket, index := h.bucketForPercentile(percentile)
|
bucket, index := h.bucketForPercentile(signature, percentile)
|
||||||
|
|
||||||
return (*bucket).ValueForIndex(index)
|
return (*bucket).ValueForIndex(index)
|
||||||
}
|
}
|
||||||
|
@ -235,38 +280,53 @@ func formatFloat(value float64) string {
|
||||||
return strconv.FormatFloat(value, floatFormat, floatPrecision, floatBitCount)
|
return strconv.FormatFloat(value, floatFormat, floatPrecision, floatBitCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Histogram) Marshallable() map[string]interface{} {
|
func (h *histogram) AsMarshallable() map[string]interface{} {
|
||||||
numberOfPercentiles := len(h.reportablePercentiles)
|
h.mutex.RLock()
|
||||||
|
defer h.mutex.RUnlock()
|
||||||
|
|
||||||
result := make(map[string]interface{}, 2)
|
result := make(map[string]interface{}, 2)
|
||||||
|
|
||||||
result[typeKey] = histogramTypeValue
|
result[typeKey] = histogramTypeValue
|
||||||
|
values := make([]map[string]interface{}, 0, len(h.values))
|
||||||
|
|
||||||
value := make(map[string]interface{}, numberOfPercentiles)
|
for signature, value := range h.values {
|
||||||
|
metricContainer := map[string]interface{}{}
|
||||||
for _, percentile := range h.reportablePercentiles {
|
metricContainer[labelsKey] = value.labels
|
||||||
percentileString := formatFloat(percentile)
|
intermediate := map[string]interface{}{}
|
||||||
value[percentileString] = formatFloat(h.Percentile(percentile))
|
for _, percentile := range h.reportablePercentiles {
|
||||||
|
formatted := formatFloat(percentile)
|
||||||
|
intermediate[formatted] = h.percentile(signature, percentile)
|
||||||
|
}
|
||||||
|
metricContainer[valueKey] = intermediate
|
||||||
|
values = append(values, metricContainer)
|
||||||
}
|
}
|
||||||
|
|
||||||
result[valueKey] = value
|
result[valueKey] = values
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *histogram) ResetAll() {
|
||||||
|
h.mutex.Lock()
|
||||||
|
defer h.mutex.Unlock()
|
||||||
|
|
||||||
|
for signature, value := range h.values {
|
||||||
|
for _, bucket := range value.buckets {
|
||||||
|
bucket.Reset()
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(h.values, signature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Produce a histogram from a given specification.
|
Produce a histogram from a given specification.
|
||||||
*/
|
*/
|
||||||
func CreateHistogram(specification *HistogramSpecification) *Histogram {
|
func NewHistogram(specification *HistogramSpecification) Histogram {
|
||||||
bucketCount := len(specification.Starts)
|
metric := &histogram{
|
||||||
|
bucketMaker: specification.BucketBuilder,
|
||||||
metric := &Histogram{
|
|
||||||
bucketStarts: specification.Starts,
|
bucketStarts: specification.Starts,
|
||||||
buckets: make([]Bucket, bucketCount),
|
|
||||||
reportablePercentiles: specification.ReportablePercentiles,
|
reportablePercentiles: specification.ReportablePercentiles,
|
||||||
}
|
values: map[string]*histogramValue{},
|
||||||
|
|
||||||
for i := 0; i < bucketCount; i++ {
|
|
||||||
metric.buckets[i] = specification.BucketMaker()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return metric
|
return metric
|
||||||
|
|
|
@ -1,974 +1,9 @@
|
||||||
/*
|
// Copyright (c) 2013, Matt T. Proud
|
||||||
Copyright (c) 2012, Matt T. Proud
|
// All rights reserved.
|
||||||
All rights reserved.
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
Use of this source code is governed by a BSD-style
|
// license that can be found in the LICENSE file.
|
||||||
license that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
import (
|
// TODO(matt): Re-Add tests for this type.
|
||||||
. "github.com/matttproud/gocheck"
|
|
||||||
"github.com/matttproud/golang_instrumentation/maths"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *S) TestEquallySizedBucketsFor(c *C) {
|
|
||||||
h := EquallySizedBucketsFor(0, 10, 5)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
c.Check(h, HasLen, 5)
|
|
||||||
c.Check(h[0], Equals, 0.0)
|
|
||||||
c.Check(h[1], Equals, 2.0)
|
|
||||||
c.Check(h[2], Equals, 4.0)
|
|
||||||
c.Check(h[3], Equals, 6.0)
|
|
||||||
c.Check(h[4], Equals, 8.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestLogarithmicSizedBucketsFor(c *C) {
|
|
||||||
h := LogarithmicSizedBucketsFor(0, 2048)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
c.Check(h, HasLen, 11)
|
|
||||||
c.Check(h[0], Equals, 0.0)
|
|
||||||
c.Check(h[1], Equals, 2.0)
|
|
||||||
c.Check(h[2], Equals, 4.0)
|
|
||||||
c.Check(h[3], Equals, 8.0)
|
|
||||||
c.Check(h[4], Equals, 16.0)
|
|
||||||
c.Check(h[5], Equals, 32.0)
|
|
||||||
c.Check(h[6], Equals, 64.0)
|
|
||||||
c.Check(h[7], Equals, 128.0)
|
|
||||||
c.Check(h[8], Equals, 256.0)
|
|
||||||
c.Check(h[9], Equals, 512.0)
|
|
||||||
c.Check(h[10], Equals, 1024.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestCreateHistogram(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 10, 5),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
h := CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
c.Check(h.String(), Equals, "[Histogram { [0.000000, inf) = [TallyingBucket (Empty)], [2.000000, inf) = [TallyingBucket (Empty)], [4.000000, inf) = [TallyingBucket (Empty)], [6.000000, inf) = [TallyingBucket (Empty)], [8.000000, inf) = [TallyingBucket (Empty)], }]")
|
|
||||||
|
|
||||||
h.Add(1)
|
|
||||||
|
|
||||||
c.Check(h.String(), Equals, "[Histogram { [0.000000, inf) = [TallyingBucket (1.000000, 1.000000); 1 items], [2.000000, inf) = [TallyingBucket (Empty)], [4.000000, inf) = [TallyingBucket (Empty)], [6.000000, inf) = [TallyingBucket (Empty)], [8.000000, inf) = [TallyingBucket (Empty)], }]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestBucketForPercentile(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 100, 100),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
h := CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
var bucket *Bucket = nil
|
|
||||||
var subindex int = 0
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(1.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
for i := 2.0; i <= 100.0; i++ {
|
|
||||||
h.Add(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.05)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
for i := 0; i < 50; i++ {
|
|
||||||
h.Add(50)
|
|
||||||
h.Add(51)
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.50)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 50)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 51)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.51)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 51)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestBucketForPercentileSingleton(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 3, 3),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
var h *Histogram = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
var bucket *Bucket = nil
|
|
||||||
var subindex int = 0
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(0.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(1.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(2.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestBucketForPercentileDoubleInSingleBucket(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 3, 3),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
var h *Histogram = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
var bucket *Bucket = nil
|
|
||||||
var subindex int = 0
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(0.0)
|
|
||||||
h.Add(0.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(1.0)
|
|
||||||
h.Add(1.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(2.0)
|
|
||||||
h.Add(2.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestBucketForPercentileTripleInSingleBucket(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 3, 3),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
var h *Histogram = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
var bucket *Bucket = nil
|
|
||||||
var subindex int = 0
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(0.0)
|
|
||||||
h.Add(0.0)
|
|
||||||
h.Add(0.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 2)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
h.Add(1.0)
|
|
||||||
h.Add(1.0)
|
|
||||||
h.Add(1.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 2)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
h.Add(2.0)
|
|
||||||
h.Add(2.0)
|
|
||||||
h.Add(2.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 2)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestBucketForPercentileTwoEqualAdjacencies(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 3, 3),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
var h *Histogram = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
var bucket *Bucket = nil
|
|
||||||
var subindex int = 0
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(0.0)
|
|
||||||
h.Add(1.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
h.Add(1.0)
|
|
||||||
h.Add(2.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestBucketForPercentileTwoAdjacenciesUnequal(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 3, 3),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
var h *Histogram = CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
var bucket *Bucket = nil
|
|
||||||
var subindex int = 0
|
|
||||||
|
|
||||||
for i := 0.0; i < 1.0; i += 0.01 {
|
|
||||||
bucket, subindex := h.bucketForPercentile(i)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
h.Add(0.0)
|
|
||||||
h.Add(0.0)
|
|
||||||
h.Add(1.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
h.Add(0.0)
|
|
||||||
h.Add(1.0)
|
|
||||||
h.Add(1.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
h.Add(1.0)
|
|
||||||
h.Add(1.0)
|
|
||||||
h.Add(2.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
h = CreateHistogram(hs)
|
|
||||||
|
|
||||||
h.Add(1.0)
|
|
||||||
h.Add(2.0)
|
|
||||||
h.Add(2.0)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 1)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.67)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(2.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.5)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 2)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0 / 3.0)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestBucketForPercentileWithBinomialApproximation(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 5, 6),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(hs, Not(IsNil))
|
|
||||||
|
|
||||||
h := CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
n := 5
|
|
||||||
p := 0.5
|
|
||||||
|
|
||||||
for k := 0; k < 6; k++ {
|
|
||||||
limit := 1000000.0 * maths.BinomialPDF(k, n, p)
|
|
||||||
for j := 0.0; j < limit; j++ {
|
|
||||||
h.Add(float64(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var bucket *Bucket = nil
|
|
||||||
var subindex int = 0
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.0)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 31250)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.03125)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 31249)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 31250)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.1875)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 156249)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 156250)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.50)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 312499)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 312500)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.8125)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 312499)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 312500)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.96875)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 156249)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 156250)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 31249)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 31250)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestBucketForPercentileWithUniform(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 100, 100),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Assert(hs, Not(IsNil))
|
|
||||||
|
|
||||||
h := CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
for i := 0.0; i <= 99.0; i++ {
|
|
||||||
h.Add(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i <= 99; i++ {
|
|
||||||
c.Check(h.bucketStarts[i], Equals, float64(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i <= 100; i++ {
|
|
||||||
c.Check(h.buckets[i-1].Observations(), Equals, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
var bucket *Bucket = nil
|
|
||||||
var subindex int = 0
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(0.01)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
|
|
||||||
bucket, subindex = h.bucketForPercentile(1.0)
|
|
||||||
c.Assert(*bucket, Not(IsNil))
|
|
||||||
c.Check(subindex, Equals, 0)
|
|
||||||
c.Check((*bucket).Observations(), Equals, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestHistogramPercentileUniform(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 100, 100),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
h := CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
for i := 0.0; i <= 99.0; i++ {
|
|
||||||
h.Add(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Check(h.Percentile(0.01), Equals, 0.0)
|
|
||||||
c.Check(h.Percentile(0.49), Equals, 48.0)
|
|
||||||
c.Check(h.Percentile(0.50), Equals, 49.0)
|
|
||||||
c.Check(h.Percentile(0.51), Equals, 50.0)
|
|
||||||
c.Check(h.Percentile(1.0), Equals, 99.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestHistogramPercentileBinomialApproximation(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 5, 6),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
h := CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
n := 5
|
|
||||||
p := 0.5
|
|
||||||
|
|
||||||
for k := 0; k < 6; k++ {
|
|
||||||
limit := 1000000.0 * maths.BinomialPDF(k, n, p)
|
|
||||||
for j := 0.0; j < limit; j++ {
|
|
||||||
h.Add(float64(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Check(h.Percentile(0.0), Equals, 0.0)
|
|
||||||
c.Check(h.Percentile(0.03125), Equals, 0.0)
|
|
||||||
c.Check(h.Percentile(0.1875), Equals, 1.0)
|
|
||||||
c.Check(h.Percentile(0.5), Equals, 2.0)
|
|
||||||
c.Check(h.Percentile(0.8125), Equals, 3.0)
|
|
||||||
c.Check(h.Percentile(0.96875), Equals, 4.0)
|
|
||||||
c.Check(h.Percentile(1.0), Equals, 5.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestHistogramMarshallable(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 5, 6),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
ReportablePercentiles: []float64{0.03125, 0.1875, 0.5, 0.8125, 0.96875, 1.0},
|
|
||||||
}
|
|
||||||
|
|
||||||
h := CreateHistogram(hs)
|
|
||||||
|
|
||||||
c.Assert(h, Not(IsNil))
|
|
||||||
|
|
||||||
n := 5
|
|
||||||
p := 0.5
|
|
||||||
|
|
||||||
for k := 0; k < 6; k++ {
|
|
||||||
limit := 1000000.0 * maths.BinomialPDF(k, n, p)
|
|
||||||
for j := 0.0; j < limit; j++ {
|
|
||||||
h.Add(float64(k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m := h.Marshallable()
|
|
||||||
|
|
||||||
c.Assert(m, Not(IsNil))
|
|
||||||
c.Check(m, HasLen, 2)
|
|
||||||
c.Check(m["type"], Equals, "histogram")
|
|
||||||
|
|
||||||
var v map[string]interface{} = m["value"].(map[string]interface{})
|
|
||||||
|
|
||||||
c.Assert(v, Not(IsNil))
|
|
||||||
|
|
||||||
c.Check(v, HasLen, 6)
|
|
||||||
c.Check(v["0.031250"], Equals, "0.000000")
|
|
||||||
c.Check(v["0.187500"], Equals, "1.000000")
|
|
||||||
c.Check(v["0.500000"], Equals, "2.000000")
|
|
||||||
c.Check(v["0.812500"], Equals, "3.000000")
|
|
||||||
c.Check(v["0.968750"], Equals, "4.000000")
|
|
||||||
c.Check(v["1.000000"], Equals, "5.000000")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestHistogramAsMetric(c *C) {
|
|
||||||
hs := &HistogramSpecification{
|
|
||||||
Starts: EquallySizedBucketsFor(0, 5, 6),
|
|
||||||
BucketMaker: TallyingBucketBuilder,
|
|
||||||
ReportablePercentiles: []float64{0.0, 0.03125, 0.1875, 0.5, 0.8125, 0.96875, 1.0},
|
|
||||||
}
|
|
||||||
|
|
||||||
h := CreateHistogram(hs)
|
|
||||||
|
|
||||||
var metric Metric = h
|
|
||||||
|
|
||||||
c.Assert(metric, Not(IsNil))
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,23 +1,17 @@
|
||||||
/*
|
// Copyright (c) 2012, Matt T. Proud
|
||||||
Copyright (c) 2012, Matt T. Proud
|
// All rights reserved.
|
||||||
All rights reserved.
|
//
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
Use of this source code is governed by a BSD-style
|
// license that can be found in the LICENSE file.
|
||||||
license that can be found in the LICENSE file.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package metrics
|
package metrics
|
||||||
|
|
||||||
/*
|
// A Metric is something that can be exposed via the registry framework.
|
||||||
A Metric is something that can be exposed via the registry framework.
|
|
||||||
*/
|
|
||||||
type Metric interface {
|
type Metric interface {
|
||||||
/*
|
// Produce a JSON-consumable representation of the metric.
|
||||||
Produce a human-consumable representation of the metric.
|
AsMarshallable() map[string]interface{}
|
||||||
*/
|
// Reset the parent metrics and delete all child metrics.
|
||||||
|
ResetAll()
|
||||||
|
// Produce a human-consumable representation of the metric.
|
||||||
String() string
|
String() string
|
||||||
/*
|
|
||||||
Produce a JSON-consumable representation of the metric.
|
|
||||||
*/
|
|
||||||
Marshallable() map[string]interface{}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,11 +88,11 @@ Upon insertion, an object is compared against collected extrema and noted
|
||||||
as a new minimum or maximum if appropriate.
|
as a new minimum or maximum if appropriate.
|
||||||
*/
|
*/
|
||||||
type TallyingBucket struct {
|
type TallyingBucket struct {
|
||||||
observations int
|
estimator TallyingIndexEstimator
|
||||||
smallestObserved float64
|
|
||||||
largestObserved float64
|
largestObserved float64
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
estimator TallyingIndexEstimator
|
observations int
|
||||||
|
smallestObserved float64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *TallyingBucket) Add(value float64) {
|
func (b *TallyingBucket) Add(value float64) {
|
||||||
|
@ -131,22 +131,31 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *TallyingBucket) Reset() {
|
||||||
|
b.mutex.Lock()
|
||||||
|
defer b.mutex.Unlock()
|
||||||
|
|
||||||
|
b.largestObserved = math.SmallestNonzeroFloat64
|
||||||
|
b.observations = 0
|
||||||
|
b.smallestObserved = math.MaxFloat64
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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,
|
|
||||||
largestObserved: math.SmallestNonzeroFloat64,
|
|
||||||
estimator: Minimum,
|
estimator: Minimum,
|
||||||
|
largestObserved: math.SmallestNonzeroFloat64,
|
||||||
|
smallestObserved: math.MaxFloat64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CustomTallyingBucket(estimator TallyingIndexEstimator) TallyingBucket {
|
func CustomTallyingBucket(estimator TallyingIndexEstimator) TallyingBucket {
|
||||||
return TallyingBucket{
|
return TallyingBucket{
|
||||||
smallestObserved: math.MaxFloat64,
|
|
||||||
largestObserved: math.SmallestNonzeroFloat64,
|
|
||||||
estimator: estimator,
|
estimator: estimator,
|
||||||
|
largestObserved: math.SmallestNonzeroFloat64,
|
||||||
|
smallestObserved: math.MaxFloat64,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,19 +30,23 @@ N.B.(mtp): A major limitation hereof is that the StopWatch protocol cannot
|
||||||
retain instrumentation if a panic percolates within the context that is
|
retain instrumentation if a panic percolates within the context that is
|
||||||
being measured.
|
being measured.
|
||||||
*/
|
*/
|
||||||
type StopWatch struct {
|
type StopWatch interface {
|
||||||
startTime time.Time
|
Stop() time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type stopWatch struct {
|
||||||
endTime time.Time
|
endTime time.Time
|
||||||
onCompletion CompletionCallback
|
onCompletion CompletionCallback
|
||||||
|
startTime time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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(),
|
|
||||||
onCompletion: onCompletion,
|
onCompletion: onCompletion,
|
||||||
|
startTime: time.Now(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,7 +54,7 @@ func Start(onCompletion CompletionCallback) *StopWatch {
|
||||||
Stop the StopWatch returning the elapsed duration of its lifetime while
|
Stop the StopWatch returning the elapsed duration of its lifetime while
|
||||||
firing an optional CompletionCallback in the background.
|
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)
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *S) TestTimerStart(c *C) {
|
func (s *S) TestTimerStart(c *C) {
|
||||||
stopWatch := Start(nil)
|
stopWatch, ok := Start(nil).(*stopWatch)
|
||||||
|
if !ok {
|
||||||
|
c.Check(ok, Equals, true)
|
||||||
|
}
|
||||||
|
|
||||||
c.Assert(stopWatch, Not(IsNil))
|
c.Assert(stopWatch, Not(IsNil))
|
||||||
c.Assert(stopWatch.startTime, Not(IsNil))
|
c.Assert(stopWatch.startTime, Not(IsNil))
|
||||||
|
|
229
registry.go
229
registry.go
|
@ -9,21 +9,39 @@ the LICENSE file.
|
||||||
package registry
|
package registry
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"compress/gzip"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
"github.com/matttproud/golang_instrumentation/metrics"
|
"github.com/matttproud/golang_instrumentation/metrics"
|
||||||
|
"github.com/matttproud/golang_instrumentation/utility"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
authorization = "Authorization"
|
acceptEncodingHeader = "Accept-Encoding"
|
||||||
contentType = "Content-Type"
|
authorization = "Authorization"
|
||||||
jsonContentType = "application/json"
|
authorizationHeader = "WWW-Authenticate"
|
||||||
jsonSuffix = ".json"
|
authorizationHeaderValue = "Basic"
|
||||||
|
contentEncodingHeader = "Content-Encoding"
|
||||||
|
contentTypeHeader = "Content-Type"
|
||||||
|
gzipAcceptEncodingValue = "gzip"
|
||||||
|
gzipContentEncodingValue = "gzip"
|
||||||
|
jsonContentType = "application/json"
|
||||||
|
jsonSuffix = ".json"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
abortOnMisuse bool
|
||||||
|
debugRegistration bool
|
||||||
|
useAggressiveSanityChecks bool
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -31,12 +49,18 @@ This callback accumulates the microsecond duration of the reporting framework's
|
||||||
overhead such that it can be reported.
|
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(duration / time.Millisecond)
|
microseconds := float64(duration / time.Microsecond)
|
||||||
|
|
||||||
requestLatencyLogarithmicAccumulating.Add(microseconds)
|
requestLatency.Add(nil, microseconds)
|
||||||
requestLatencyEqualAccumulating.Add(microseconds)
|
}
|
||||||
requestLatencyLogarithmicTallying.Add(microseconds)
|
|
||||||
requestLatencyEqualTallying.Add(microseconds)
|
// container represents a top-level registered metric that encompasses its
|
||||||
|
// static metadata.
|
||||||
|
type container struct {
|
||||||
|
baseLabels map[string]string
|
||||||
|
docstring string
|
||||||
|
metric metrics.Metric
|
||||||
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -46,8 +70,8 @@ In most situations, using DefaultRegistry is sufficient versus creating one's
|
||||||
own.
|
own.
|
||||||
*/
|
*/
|
||||||
type Registry struct {
|
type Registry struct {
|
||||||
mutex sync.RWMutex
|
mutex sync.RWMutex
|
||||||
NameToMetric map[string]metrics.Metric
|
signatureContainers map[string]container
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -56,7 +80,7 @@ cases.
|
||||||
*/
|
*/
|
||||||
func NewRegistry() *Registry {
|
func NewRegistry() *Registry {
|
||||||
return &Registry{
|
return &Registry{
|
||||||
NameToMetric: make(map[string]metrics.Metric),
|
signatureContainers: make(map[string]container),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,25 +93,96 @@ var DefaultRegistry = NewRegistry()
|
||||||
/*
|
/*
|
||||||
Associate a Metric with the DefaultRegistry.
|
Associate a Metric with the DefaultRegistry.
|
||||||
*/
|
*/
|
||||||
func Register(name, unusedDocstring string, unusedBaseLabels map[string]string, metric metrics.Metric) {
|
func Register(name, docstring string, baseLabels map[string]string, metric metrics.Metric) error {
|
||||||
DefaultRegistry.Register(name, unusedDocstring, unusedBaseLabels, metric)
|
return DefaultRegistry.Register(name, docstring, baseLabels, metric)
|
||||||
|
}
|
||||||
|
|
||||||
|
// isValidCandidate returns true if the candidate is acceptable for use. In the
|
||||||
|
// event of any apparent incorrect use it will report the problem, invalidate
|
||||||
|
// the candidate, or outright abort.
|
||||||
|
func (r *Registry) isValidCandidate(name string, baseLabels map[string]string) (signature string, err error) {
|
||||||
|
if len(name) == 0 {
|
||||||
|
err = fmt.Errorf("unnamed metric named with baseLabels %s is invalid", baseLabels)
|
||||||
|
|
||||||
|
if abortOnMisuse {
|
||||||
|
panic(err)
|
||||||
|
} else if debugRegistration {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, contains := baseLabels[nameLabel]; contains {
|
||||||
|
err = fmt.Errorf("metric named %s with baseLabels %s contains reserved label name %s in baseLabels", name, baseLabels, nameLabel)
|
||||||
|
|
||||||
|
if abortOnMisuse {
|
||||||
|
panic(err)
|
||||||
|
} else if debugRegistration {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
baseLabels[nameLabel] = name
|
||||||
|
signature = utility.LabelsToSignature(baseLabels)
|
||||||
|
|
||||||
|
if _, contains := r.signatureContainers[signature]; contains {
|
||||||
|
err = fmt.Errorf("metric named %s with baseLabels %s is already registered", name, baseLabels)
|
||||||
|
if abortOnMisuse {
|
||||||
|
panic(err)
|
||||||
|
} else if debugRegistration {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if useAggressiveSanityChecks {
|
||||||
|
for _, container := range r.signatureContainers {
|
||||||
|
if container.name == name {
|
||||||
|
err = fmt.Errorf("metric named %s with baseLabels %s is already registered as %s and risks causing confusion", name, baseLabels, container.baseLabels)
|
||||||
|
if abortOnMisuse {
|
||||||
|
panic(err)
|
||||||
|
} else if debugRegistration {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
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, unusedDocstring string, unusedBaseLabels map[string]string, metric metrics.Metric) {
|
func (r *Registry) Register(name, docstring string, baseLabels map[string]string, metric metrics.Metric) (err error) {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
if _, present := r.NameToMetric[name]; !present {
|
if baseLabels == nil {
|
||||||
r.NameToMetric[name] = metric
|
baseLabels = map[string]string{}
|
||||||
log.Printf("Registered %s.\n", name)
|
|
||||||
} else {
|
|
||||||
log.Printf("Attempted to register duplicate %s metric.\n", name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signature, err := r.isValidCandidate(name, baseLabels)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
r.signatureContainers[signature] = container{
|
||||||
|
baseLabels: baseLabels,
|
||||||
|
docstring: docstring,
|
||||||
|
metric: metric,
|
||||||
|
name: name,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// YieldBasicAuthExporter creates a http.HandlerFunc that is protected by HTTP's
|
||||||
|
// basic authentication.
|
||||||
func (register *Registry) YieldBasicAuthExporter(username, password string) http.HandlerFunc {
|
func (register *Registry) YieldBasicAuthExporter(username, password string) http.HandlerFunc {
|
||||||
exporter := register.YieldExporter()
|
exporter := register.YieldExporter()
|
||||||
|
|
||||||
|
@ -108,12 +203,80 @@ func (register *Registry) YieldBasicAuthExporter(username, password string) http
|
||||||
if authenticated {
|
if authenticated {
|
||||||
exporter.ServeHTTP(w, r)
|
exporter.ServeHTTP(w, r)
|
||||||
} else {
|
} else {
|
||||||
w.Header().Add("WWW-Authenticate", "Basic")
|
w.Header().Add(authorizationHeader, authorizationHeaderValue)
|
||||||
http.Error(w, "access forbidden", 401)
|
http.Error(w, "access forbidden", 401)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (registry *Registry) dumpToWriter(writer io.Writer) (err error) {
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
dumpErrorCount.Increment(nil)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
numberOfMetrics := len(registry.signatureContainers)
|
||||||
|
keys := make([]string, 0, numberOfMetrics)
|
||||||
|
for key := range registry.signatureContainers {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
_, err = writer.Write([]byte("["))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
index := 0
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
container := registry.signatureContainers[key]
|
||||||
|
intermediate := map[string]interface{}{
|
||||||
|
baseLabelsKey: container.baseLabels,
|
||||||
|
docstringKey: container.docstring,
|
||||||
|
metricKey: container.metric.AsMarshallable(),
|
||||||
|
}
|
||||||
|
marshaled, err := json.Marshal(intermediate)
|
||||||
|
if err != nil {
|
||||||
|
marshalErrorCount.Increment(nil)
|
||||||
|
index++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if index > 0 && index < numberOfMetrics {
|
||||||
|
_, err = writer.Write([]byte(","))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = writer.Write(marshaled)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
index++
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = writer.Write([]byte("]"))
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// decorateWriter annotates the response writer to handle any other behaviors
|
||||||
|
// that might be beneficial to the client---e.g., GZIP encoding.
|
||||||
|
func decorateWriter(request *http.Request, writer http.ResponseWriter) io.Writer {
|
||||||
|
if !strings.Contains(request.Header.Get(acceptEncodingHeader), gzipAcceptEncodingValue) {
|
||||||
|
return writer
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Header().Set(contentEncodingHeader, gzipContentEncodingValue)
|
||||||
|
gziper := gzip.NewWriter(writer)
|
||||||
|
|
||||||
|
return gziper
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Create a http.HandlerFunc that is tied to r Registry such that requests
|
Create a http.HandlerFunc that is tied to r Registry such that requests
|
||||||
against it generate a representation of the housed metrics.
|
against it generate a representation of the housed metrics.
|
||||||
|
@ -121,19 +284,23 @@ 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(nil)
|
||||||
url := r.URL
|
url := r.URL
|
||||||
|
|
||||||
if strings.HasSuffix(url.Path, jsonSuffix) {
|
if strings.HasSuffix(url.Path, jsonSuffix) {
|
||||||
w.Header().Set(contentType, jsonContentType)
|
header := w.Header()
|
||||||
composite := make(map[string]interface{}, len(registry.NameToMetric))
|
header.Set(ProtocolVersionHeader, APIVersion)
|
||||||
for name, metric := range registry.NameToMetric {
|
header.Set(contentTypeHeader, jsonContentType)
|
||||||
composite[name] = metric.Marshallable()
|
|
||||||
|
writer := decorateWriter(r, w)
|
||||||
|
|
||||||
|
// TODO(matt): Migrate to ioutil.NopCloser.
|
||||||
|
if closer, ok := writer.(io.Closer); ok {
|
||||||
|
defer closer.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
data, _ := json.Marshal(composite)
|
registry.dumpToWriter(writer)
|
||||||
|
|
||||||
w.Write(data)
|
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
@ -142,3 +309,9 @@ func (registry *Registry) YieldExporter() http.HandlerFunc {
|
||||||
metrics.InstrumentCall(instrumentable, requestLatencyAccumulator)
|
metrics.InstrumentCall(instrumentable, requestLatencyAccumulator)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
flag.BoolVar(&abortOnMisuse, FlagNamespace+"abortonmisuse", false, "abort if a semantic misuse is encountered (bool).")
|
||||||
|
flag.BoolVar(&debugRegistration, FlagNamespace+"debugregistration", false, "display information about the metric registration process (bool).")
|
||||||
|
flag.BoolVar(&useAggressiveSanityChecks, FlagNamespace+"useaggressivesanitychecks", false, "perform expensive validation of metrics (bool).")
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,331 @@
|
||||||
|
// Copyright (c) 2013, 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 registry
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/matttproud/golang_instrumentation/metrics"
|
||||||
|
"github.com/matttproud/golang_instrumentation/utility/test"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testRegister(t test.Tester) {
|
||||||
|
var oldState = struct {
|
||||||
|
abortOnMisuse bool
|
||||||
|
debugRegistration bool
|
||||||
|
useAggressiveSanityChecks bool
|
||||||
|
}{
|
||||||
|
abortOnMisuse: abortOnMisuse,
|
||||||
|
debugRegistration: debugRegistration,
|
||||||
|
useAggressiveSanityChecks: useAggressiveSanityChecks,
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
abortOnMisuse = oldState.abortOnMisuse
|
||||||
|
debugRegistration = oldState.debugRegistration
|
||||||
|
useAggressiveSanityChecks = oldState.useAggressiveSanityChecks
|
||||||
|
}()
|
||||||
|
|
||||||
|
type input struct {
|
||||||
|
name string
|
||||||
|
baseLabels map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var scenarios = []struct {
|
||||||
|
inputs []input
|
||||||
|
outputs []bool
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
inputs: []input{
|
||||||
|
{
|
||||||
|
name: "my_name_without_labels",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: []bool{
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: []input{
|
||||||
|
{
|
||||||
|
name: "my_name_without_labels",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "another_name_without_labels",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: []bool{
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: []input{
|
||||||
|
{
|
||||||
|
name: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: []bool{
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: []input{
|
||||||
|
{
|
||||||
|
name: "valid_name",
|
||||||
|
baseLabels: map[string]string{"name": "illegal_duplicate_name"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: []bool{
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: []input{
|
||||||
|
{
|
||||||
|
name: "duplicate_names",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate_names",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: []bool{
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: []input{
|
||||||
|
{
|
||||||
|
name: "duplicate_names_with_identical_labels",
|
||||||
|
baseLabels: map[string]string{"label": "value"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate_names_with_identical_labels",
|
||||||
|
baseLabels: map[string]string{"label": "value"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: []bool{
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inputs: []input{
|
||||||
|
{
|
||||||
|
name: "duplicate_names_with_dissimilar_labels",
|
||||||
|
baseLabels: map[string]string{"label": "foo"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "duplicate_names_with_dissimilar_labels",
|
||||||
|
baseLabels: map[string]string{"label": "bar"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
outputs: []bool{
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, scenario := range scenarios {
|
||||||
|
if len(scenario.inputs) != len(scenario.outputs) {
|
||||||
|
t.Fatalf("%d. len(scenario.inputs) != len(scenario.outputs)")
|
||||||
|
}
|
||||||
|
|
||||||
|
abortOnMisuse = false
|
||||||
|
debugRegistration = false
|
||||||
|
useAggressiveSanityChecks = true
|
||||||
|
|
||||||
|
registry := NewRegistry()
|
||||||
|
|
||||||
|
for j, input := range scenario.inputs {
|
||||||
|
actual := registry.Register(input.name, "", input.baseLabels, nil)
|
||||||
|
if scenario.outputs[j] != (actual == nil) {
|
||||||
|
t.Errorf("%d.%d. expected %s, got %s", i, j, scenario.outputs[j], actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegister(t *testing.T) {
|
||||||
|
testRegister(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRegister(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testRegister(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type fakeResponseWriter struct {
|
||||||
|
header http.Header
|
||||||
|
body bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeResponseWriter) Header() http.Header {
|
||||||
|
return r.header
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeResponseWriter) Write(d []byte) (l int, err error) {
|
||||||
|
return r.body.Write(d)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *fakeResponseWriter) WriteHeader(c int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDecorateWriter(t test.Tester) {
|
||||||
|
type input struct {
|
||||||
|
headers map[string]string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type output struct {
|
||||||
|
headers map[string]string
|
||||||
|
body []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var scenarios = []struct {
|
||||||
|
in input
|
||||||
|
out output
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
headers: map[string]string{
|
||||||
|
"Accept-Encoding": "gzip,deflate,sdch",
|
||||||
|
},
|
||||||
|
body: []byte("Hi, mom!"),
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
headers: map[string]string{
|
||||||
|
"Content-Encoding": "gzip",
|
||||||
|
},
|
||||||
|
body: []byte("\x1f\x8b\b\x00\x00\tn\x88\x00\xff\xf2\xc8\xd4Q\xc8\xcd\xcfU\x04\x04\x00\x00\xff\xff9C&&\b\x00\x00\x00"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
headers: map[string]string{
|
||||||
|
"Accept-Encoding": "foo",
|
||||||
|
},
|
||||||
|
body: []byte("Hi, mom!"),
|
||||||
|
},
|
||||||
|
out: output{
|
||||||
|
headers: map[string]string{},
|
||||||
|
body: []byte("Hi, mom!"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, scenario := range scenarios {
|
||||||
|
request, _ := http.NewRequest("GET", "/", nil)
|
||||||
|
|
||||||
|
for key, value := range scenario.in.headers {
|
||||||
|
request.Header.Add(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
baseWriter := &fakeResponseWriter{
|
||||||
|
header: make(http.Header),
|
||||||
|
}
|
||||||
|
|
||||||
|
writer := decorateWriter(request, baseWriter)
|
||||||
|
|
||||||
|
for key, value := range scenario.out.headers {
|
||||||
|
if baseWriter.Header().Get(key) != value {
|
||||||
|
t.Errorf("%d. expected %s for header %s, got %s", i, value, key, baseWriter.Header().Get(key))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(scenario.in.body)
|
||||||
|
|
||||||
|
if closer, ok := writer.(io.Closer); ok {
|
||||||
|
closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(scenario.out.body, baseWriter.body.Bytes()) {
|
||||||
|
t.Errorf("%d. expected %s for body, got %s", i, scenario.out.body, baseWriter.body.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecorateWriter(t *testing.T) {
|
||||||
|
testDecorateWriter(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDecorateWriter(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testDecorateWriter(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDumpToWriter(t test.Tester) {
|
||||||
|
type input struct {
|
||||||
|
metrics map[string]metrics.Metric
|
||||||
|
}
|
||||||
|
|
||||||
|
var scenarios = []struct {
|
||||||
|
in input
|
||||||
|
out []byte
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
out: []byte("[]"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
metrics: map[string]metrics.Metric{
|
||||||
|
"foo": metrics.NewCounter(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: []byte("[{\"baseLabels\":{\"label_foo\":\"foo\",\"name\":\"foo\"},\"docstring\":\"metric foo\",\"metric\":{\"type\":\"counter\",\"value\":[]}}]"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: input{
|
||||||
|
metrics: map[string]metrics.Metric{
|
||||||
|
"foo": metrics.NewCounter(),
|
||||||
|
"bar": metrics.NewCounter(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
out: []byte("[{\"baseLabels\":{\"label_bar\":\"bar\",\"name\":\"bar\"},\"docstring\":\"metric bar\",\"metric\":{\"type\":\"counter\",\"value\":[]}},{\"baseLabels\":{\"label_foo\":\"foo\",\"name\":\"foo\"},\"docstring\":\"metric foo\",\"metric\":{\"type\":\"counter\",\"value\":[]}}]"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, scenario := range scenarios {
|
||||||
|
registry := NewRegistry()
|
||||||
|
|
||||||
|
for name, metric := range scenario.in.metrics {
|
||||||
|
err := registry.Register(name, fmt.Sprintf("metric %s", name), map[string]string{fmt.Sprintf("label_%s", name): name}, metric)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d. encountered error while registering metric %s", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual := &bytes.Buffer{}
|
||||||
|
|
||||||
|
err := registry.dumpToWriter(actual)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%d. encountered error while dumping %s", i, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(scenario.out, actual.Bytes()) {
|
||||||
|
t.Errorf("%d. expected %q for dumping, got %q", i, scenario.out, actual.Bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDumpToWriter(t *testing.T) {
|
||||||
|
testDumpToWriter(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDumpToWriter(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testDumpToWriter(b)
|
||||||
|
}
|
||||||
|
}
|
40
telemetry.go
40
telemetry.go
|
@ -20,43 +20,25 @@ exposed if the DefaultRegistry's exporter is hooked into the HTTP request
|
||||||
handler.
|
handler.
|
||||||
*/
|
*/
|
||||||
var (
|
var (
|
||||||
// TODO(matt): Refresh these names to support namespacing.
|
marshalErrorCount = metrics.NewCounter()
|
||||||
|
dumpErrorCount = metrics.NewCounter()
|
||||||
|
|
||||||
requestCount *metrics.CounterMetric = &metrics.CounterMetric{}
|
requestCount = metrics.NewCounter()
|
||||||
requestLatencyLogarithmicBuckets []float64 = metrics.LogarithmicSizedBucketsFor(0, 1000)
|
requestLatencyBuckets = metrics.LogarithmicSizedBucketsFor(0, 1000)
|
||||||
requestLatencyEqualBuckets []float64 = metrics.EquallySizedBucketsFor(0, 1000, 10)
|
requestLatency = metrics.NewHistogram(&metrics.HistogramSpecification{
|
||||||
requestLatencyLogarithmicAccumulating *metrics.Histogram = metrics.CreateHistogram(&metrics.HistogramSpecification{
|
Starts: requestLatencyBuckets,
|
||||||
Starts: requestLatencyLogarithmicBuckets,
|
BucketBuilder: metrics.AccumulatingBucketBuilder(metrics.EvictAndReplaceWith(50, maths.Average), 1000),
|
||||||
BucketMaker: metrics.AccumulatingBucketBuilder(metrics.EvictAndReplaceWith(50, maths.Average), 1000),
|
|
||||||
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
|
|
||||||
})
|
|
||||||
requestLatencyEqualAccumulating *metrics.Histogram = metrics.CreateHistogram(&metrics.HistogramSpecification{
|
|
||||||
Starts: requestLatencyEqualBuckets,
|
|
||||||
BucketMaker: metrics.AccumulatingBucketBuilder(metrics.EvictAndReplaceWith(50, maths.Average), 1000),
|
|
||||||
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
|
|
||||||
})
|
|
||||||
requestLatencyLogarithmicTallying *metrics.Histogram = metrics.CreateHistogram(&metrics.HistogramSpecification{
|
|
||||||
Starts: requestLatencyLogarithmicBuckets,
|
|
||||||
BucketMaker: metrics.TallyingBucketBuilder,
|
|
||||||
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
|
|
||||||
})
|
|
||||||
requestLatencyEqualTallying *metrics.Histogram = metrics.CreateHistogram(&metrics.HistogramSpecification{
|
|
||||||
Starts: requestLatencyEqualBuckets,
|
|
||||||
BucketMaker: metrics.TallyingBucketBuilder,
|
|
||||||
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
|
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
|
||||||
})
|
})
|
||||||
|
|
||||||
startTime *metrics.GaugeMetric = &metrics.GaugeMetric{}
|
startTime = metrics.NewGauge()
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
startTime.Set(float64(time.Now().Unix()))
|
startTime.Set(nil, float64(time.Now().Unix()))
|
||||||
|
|
||||||
DefaultRegistry.Register("requests_metrics_total", "A counter of the total requests made against the telemetry system.", NilLabels, requestCount)
|
DefaultRegistry.Register("telemetry_requests_metrics_total", "A counter of the total requests made against the telemetry system.", NilLabels, requestCount)
|
||||||
DefaultRegistry.Register("requests_metrics_latency_logarithmic_accumulating_microseconds", "A histogram of the response latency for requests made against the telemetry system.", NilLabels, requestLatencyLogarithmicAccumulating)
|
DefaultRegistry.Register("telemetry_requests_metrics_latency_microseconds", "A histogram of the response latency for requests made against the telemetry system.", NilLabels, requestLatency)
|
||||||
DefaultRegistry.Register("requests_metrics_latency_equal_accumulating_microseconds", "A histogram of the response latency for requests made against the telemetry system.", NilLabels, requestLatencyEqualAccumulating)
|
|
||||||
DefaultRegistry.Register("requests_metrics_latency_logarithmic_tallying_microseconds", "A histogram of the response latency for requests made against the telemetry system.", NilLabels, requestLatencyLogarithmicTallying)
|
|
||||||
DefaultRegistry.Register("request_metrics_latency_equal_tallying_microseconds", "A histogram of the response latency for requests made against the telemetry system.", NilLabels, requestLatencyEqualTallying)
|
|
||||||
|
|
||||||
DefaultRegistry.Register("instance_start_time_seconds", "The time at which the current instance started (UTC).", NilLabels, startTime)
|
DefaultRegistry.Register("instance_start_time_seconds", "The time at which the current instance started (UTC).", NilLabels, startTime)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,6 @@ license that can be found in the LICENSE file.
|
||||||
The utility package provides general purpose helpers to assist with this
|
The utility package provides general purpose helpers to assist with this
|
||||||
library.
|
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.go provides a simple priority queue.
|
||||||
|
|
||||||
priority_queue_test.go provides a test complement for the priority_queue.go
|
priority_queue_test.go provides a test complement for the priority_queue.go
|
||||||
|
|
|
@ -1,44 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package utility
|
|
||||||
|
|
||||||
type Optional struct {
|
|
||||||
value interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func EmptyOptional() *Optional {
|
|
||||||
emission := &Optional{value: nil}
|
|
||||||
|
|
||||||
return emission
|
|
||||||
}
|
|
||||||
|
|
||||||
func Of(value interface{}) *Optional {
|
|
||||||
emission := &Optional{value: value}
|
|
||||||
|
|
||||||
return emission
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Optional) IsSet() bool {
|
|
||||||
return o.value != nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Optional) Get() interface{} {
|
|
||||||
if o.value == nil {
|
|
||||||
panic("Expected a value to be set.")
|
|
||||||
}
|
|
||||||
|
|
||||||
return o.value
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Optional) Or(a interface{}) interface{} {
|
|
||||||
if o.IsSet() {
|
|
||||||
return o.Get()
|
|
||||||
}
|
|
||||||
return a
|
|
||||||
}
|
|
|
@ -1,30 +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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package utility
|
|
||||||
|
|
||||||
import (
|
|
||||||
. "github.com/matttproud/gocheck"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *S) TestEmptyOptional(c *C) {
|
|
||||||
var o *Optional = EmptyOptional()
|
|
||||||
|
|
||||||
c.Assert(o, Not(IsNil))
|
|
||||||
c.Check(o.IsSet(), Equals, false)
|
|
||||||
c.Assert("default", Equals, o.Or("default"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *S) TestOf(c *C) {
|
|
||||||
var o *Optional = Of(1)
|
|
||||||
|
|
||||||
c.Assert(o, Not(IsNil))
|
|
||||||
c.Check(o.IsSet(), Equals, true)
|
|
||||||
c.Check(o.Get(), Equals, 1)
|
|
||||||
c.Check(o.Or(2), Equals, 1)
|
|
||||||
}
|
|
|
@ -9,8 +9,8 @@ license that can be found in the LICENSE file.
|
||||||
package utility
|
package utility
|
||||||
|
|
||||||
type Item struct {
|
type Item struct {
|
||||||
Value interface{}
|
|
||||||
Priority int64
|
Priority int64
|
||||||
|
Value interface{}
|
||||||
index int
|
index int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
// Copyright (c) 2013, 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 utility
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"sort"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
delimiter = "|"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LabelsToSignature provides a way of building a unique signature
|
||||||
|
// (i.e., fingerprint) for a given label set sequence.
|
||||||
|
func LabelsToSignature(labels map[string]string) string {
|
||||||
|
// TODO(matt): This is a wart, and we'll want to validate that collisions
|
||||||
|
// do not occur in less-than-diligent environments.
|
||||||
|
cardinality := len(labels)
|
||||||
|
keys := make([]string, 0, cardinality)
|
||||||
|
|
||||||
|
for label := range labels {
|
||||||
|
keys = append(keys, label)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Strings(keys)
|
||||||
|
|
||||||
|
buffer := bytes.Buffer{}
|
||||||
|
|
||||||
|
for _, label := range keys {
|
||||||
|
buffer.WriteString(label)
|
||||||
|
buffer.WriteString(delimiter)
|
||||||
|
buffer.WriteString(labels[label])
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
// Copyright (c) 2013, 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 utility
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/matttproud/golang_instrumentation/utility/test"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testLabelsToSignature(t test.Tester) {
|
||||||
|
var scenarios = []struct {
|
||||||
|
in map[string]string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
in: map[string]string{},
|
||||||
|
out: "",
|
||||||
|
},
|
||||||
|
{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, scenario := range scenarios {
|
||||||
|
actual := LabelsToSignature(scenario.in)
|
||||||
|
|
||||||
|
if actual != scenario.out {
|
||||||
|
t.Errorf("%d. expected %s, got %s", i, scenario.out, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLabelToSignature(t *testing.T) {
|
||||||
|
testLabelsToSignature(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLabelToSignature(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
testLabelsToSignature(b)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
// Copyright (c) 2013, 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 test
|
||||||
|
|
||||||
|
type Tester interface {
|
||||||
|
Error(args ...interface{})
|
||||||
|
Errorf(format string, args ...interface{})
|
||||||
|
Fatal(args ...interface{})
|
||||||
|
Fatalf(format string, args ...interface{})
|
||||||
|
}
|
Loading…
Reference in New Issue