135 lines
5.1 KiB
Go
135 lines
5.1 KiB
Go
// Copyright (c) 2012, Matt T. Proud
|
|
// All rights reserved.
|
|
//
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// registry.go provides a container for centralized exposition of metrics to
|
|
// their prospective consumers.
|
|
|
|
package registry
|
|
|
|
import (
|
|
"encoding/json"
|
|
"github.com/matttproud/golang_instrumentation/maths"
|
|
"github.com/matttproud/golang_instrumentation/metrics"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// Boilerplate metrics about the metrics reporting subservice. These are only
|
|
// exposed if the DefaultRegistry's exporter is hooked into the HTTP request
|
|
// handler.
|
|
var requestCount *metrics.GaugeMetric = &metrics.GaugeMetric{}
|
|
var requestLatencyLogarithmicBuckets []float64 = metrics.LogarithmicSizedBucketsFor(0, 1000)
|
|
var requestLatencyEqualBuckets []float64 = metrics.EquallySizedBucketsFor(0, 1000, 10)
|
|
var requestLatencyLogarithmicAccumulating *metrics.Histogram = metrics.CreateHistogram(&metrics.HistogramSpecification{
|
|
Starts: requestLatencyLogarithmicBuckets,
|
|
BucketMaker: metrics.AccumulatingBucketBuilder(metrics.EvictAndReplaceWith(50, maths.Average), 1000),
|
|
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
|
|
})
|
|
var 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},
|
|
})
|
|
var requestLatencyLogarithmicTallying *metrics.Histogram = metrics.CreateHistogram(&metrics.HistogramSpecification{
|
|
Starts: requestLatencyLogarithmicBuckets,
|
|
BucketMaker: metrics.TallyingBucketBuilder,
|
|
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
|
|
})
|
|
var requestLatencyEqualTallying *metrics.Histogram = metrics.CreateHistogram(&metrics.HistogramSpecification{
|
|
Starts: requestLatencyEqualBuckets,
|
|
BucketMaker: metrics.TallyingBucketBuilder,
|
|
ReportablePercentiles: []float64{0.01, 0.05, 0.5, 0.9, 0.99},
|
|
})
|
|
|
|
// This callback accumulates the microsecond duration of the reporting
|
|
// framework's overhead such that it can be reported.
|
|
var requestLatencyAccumulator metrics.CompletionCallback = func(duration time.Duration) {
|
|
microseconds := float64(int64(duration) / 1E3)
|
|
|
|
requestLatencyLogarithmicAccumulating.Add(microseconds)
|
|
requestLatencyEqualAccumulating.Add(microseconds)
|
|
requestLatencyLogarithmicTallying.Add(microseconds)
|
|
requestLatencyEqualTallying.Add(microseconds)
|
|
}
|
|
|
|
// Registry is, as the name implies, a registrar where metrics are listed.
|
|
//
|
|
// In most situations, using DefaultRegistry is sufficient versus creating
|
|
// one's own.
|
|
type Registry struct {
|
|
mutex sync.RWMutex
|
|
NameToMetric map[string]metrics.Metric
|
|
}
|
|
|
|
// This builds a new metric registry. It is not needed in the majority of
|
|
// cases.
|
|
func NewRegistry() *Registry {
|
|
return &Registry{
|
|
NameToMetric: make(map[string]metrics.Metric),
|
|
}
|
|
}
|
|
|
|
// This is the default registry with which Metric objects are associated. It
|
|
// is primarily a read-only object after server instantiation.
|
|
var DefaultRegistry = NewRegistry()
|
|
|
|
// Associate a Metric with the DefaultRegistry.
|
|
func Register(name string, metric metrics.Metric) {
|
|
DefaultRegistry.Register(name, metric)
|
|
}
|
|
|
|
// Register a metric with a given name. Name should be globally unique.
|
|
func (r *Registry) Register(name string, metric metrics.Metric) {
|
|
r.mutex.Lock()
|
|
defer r.mutex.Unlock()
|
|
|
|
if _, present := r.NameToMetric[name]; !present {
|
|
r.NameToMetric[name] = metric
|
|
log.Printf("Registered %s.\n", name)
|
|
} else {
|
|
log.Printf("Attempted to register duplicate %s metric.\n", name)
|
|
}
|
|
}
|
|
|
|
// Create a http.HandlerFunc that is tied to r Registry such that requests
|
|
// against it generate a representation of the housed metrics.
|
|
func (registry *Registry) YieldExporter() http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var instrumentable metrics.InstrumentableCall = func() {
|
|
requestCount.Increment()
|
|
url := r.URL
|
|
|
|
if strings.HasSuffix(url.Path, ".json") {
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
composite := make(map[string]interface{}, len(registry.NameToMetric))
|
|
for name, metric := range registry.NameToMetric {
|
|
composite[name] = metric.Marshallable()
|
|
}
|
|
|
|
data, _ := json.Marshal(composite)
|
|
|
|
w.Write(data)
|
|
} else {
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}
|
|
|
|
}
|
|
metrics.InstrumentCall(instrumentable, requestLatencyAccumulator)
|
|
}
|
|
}
|
|
|
|
func init() {
|
|
DefaultRegistry.Register("requests_total", requestCount)
|
|
DefaultRegistry.Register("request_latency_logarithmic_accumulating_microseconds", requestLatencyLogarithmicAccumulating)
|
|
DefaultRegistry.Register("request_latency_equal_accumulating_microseconds", requestLatencyEqualAccumulating)
|
|
DefaultRegistry.Register("request_latency_logarithmic_tallying_microseconds", requestLatencyLogarithmicTallying)
|
|
DefaultRegistry.Register("request_latency_equal_tallying_microseconds", requestLatencyEqualTallying)
|
|
}
|