Moved to google.golang.org/genproto/googleapis/api/annotations
Fixes #52
This commit is contained in:
97
vendor/github.com/go-kit/kit/metrics/README.md
generated
vendored
Normal file
97
vendor/github.com/go-kit/kit/metrics/README.md
generated
vendored
Normal file
@@ -0,0 +1,97 @@
|
||||
# package metrics
|
||||
|
||||
`package metrics` provides a set of uniform interfaces for service instrumentation.
|
||||
It has
|
||||
[counters](http://prometheus.io/docs/concepts/metric_types/#counter),
|
||||
[gauges](http://prometheus.io/docs/concepts/metric_types/#gauge), and
|
||||
[histograms](http://prometheus.io/docs/concepts/metric_types/#histogram),
|
||||
and provides adapters to popular metrics packages, like
|
||||
[expvar](https://golang.org/pkg/expvar),
|
||||
[StatsD](https://github.com/etsy/statsd), and
|
||||
[Prometheus](https://prometheus.io).
|
||||
|
||||
## Rationale
|
||||
|
||||
Code instrumentation is absolutely essential to achieve
|
||||
[observability](https://speakerdeck.com/mattheath/observability-in-micro-service-architectures)
|
||||
into a distributed system.
|
||||
Metrics and instrumentation tools have coalesced around a few well-defined idioms.
|
||||
`package metrics` provides a common, minimal interface those idioms for service authors.
|
||||
|
||||
## Usage
|
||||
|
||||
A simple counter, exported via expvar.
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/expvar"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var myCount metrics.Counter
|
||||
myCount = expvar.NewCounter("my_count")
|
||||
myCount.Add(1)
|
||||
}
|
||||
```
|
||||
|
||||
A histogram for request duration,
|
||||
exported via a Prometheus summary with dynamically-computed quantiles.
|
||||
|
||||
```go
|
||||
import (
|
||||
"time"
|
||||
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/prometheus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var dur metrics.Histogram = prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: "myservice",
|
||||
Subsystem: "api",
|
||||
Name: "request_duration_seconds",
|
||||
Help: "Total time spent serving requests.",
|
||||
}, []string{})
|
||||
// ...
|
||||
}
|
||||
|
||||
func handleRequest(dur metrics.Histogram) {
|
||||
defer func(begin time.Time) { dur.Observe(time.Since(begin).Seconds()) }(time.Now())
|
||||
// handle request
|
||||
}
|
||||
```
|
||||
|
||||
A gauge for the number of goroutines currently running, exported via StatsD.
|
||||
|
||||
```go
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/statsd"
|
||||
)
|
||||
|
||||
func main() {
|
||||
statsd := statsd.New("foo_svc.", log.NewNopLogger())
|
||||
report := time.NewTicker(5 * time.Second)
|
||||
defer report.Stop()
|
||||
go statsd.SendLoop(report.C, "tcp", "statsd.internal:8125")
|
||||
goroutines := statsd.NewGauge("goroutine_count")
|
||||
go exportGoroutines(goroutines)
|
||||
// ...
|
||||
}
|
||||
|
||||
func exportGoroutines(g metrics.Gauge) {
|
||||
for range time.Tick(time.Second) {
|
||||
g.Set(float64(runtime.NumGoroutine()))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For more information, see [the package documentation](https://godoc.org/github.com/go-kit/kit/metrics).
|
40
vendor/github.com/go-kit/kit/metrics/discard/discard.go
generated
vendored
Normal file
40
vendor/github.com/go-kit/kit/metrics/discard/discard.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Package discard provides a no-op metrics backend.
|
||||
package discard
|
||||
|
||||
import "github.com/go-kit/kit/metrics"
|
||||
|
||||
type counter struct{}
|
||||
|
||||
// NewCounter returns a new no-op counter.
|
||||
func NewCounter() metrics.Counter { return counter{} }
|
||||
|
||||
// With implements Counter.
|
||||
func (c counter) With(labelValues ...string) metrics.Counter { return c }
|
||||
|
||||
// Add implements Counter.
|
||||
func (c counter) Add(delta float64) {}
|
||||
|
||||
type gauge struct{}
|
||||
|
||||
// NewGauge returns a new no-op gauge.
|
||||
func NewGauge() metrics.Gauge { return gauge{} }
|
||||
|
||||
// With implements Gauge.
|
||||
func (g gauge) With(labelValues ...string) metrics.Gauge { return g }
|
||||
|
||||
// Set implements Gauge.
|
||||
func (g gauge) Set(value float64) {}
|
||||
|
||||
// Add implements metrics.Gauge.
|
||||
func (g gauge) Add(delta float64) {}
|
||||
|
||||
type histogram struct{}
|
||||
|
||||
// NewHistogram returns a new no-op histogram.
|
||||
func NewHistogram() metrics.Histogram { return histogram{} }
|
||||
|
||||
// With implements Histogram.
|
||||
func (h histogram) With(labelValues ...string) metrics.Histogram { return h }
|
||||
|
||||
// Observe implements histogram.
|
||||
func (h histogram) Observe(value float64) {}
|
96
vendor/github.com/go-kit/kit/metrics/doc.go
generated
vendored
Normal file
96
vendor/github.com/go-kit/kit/metrics/doc.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
// Package metrics provides a framework for application instrumentation. It's
|
||||
// primarily designed to help you get started with good and robust
|
||||
// instrumentation, and to help you migrate from a less-capable system like
|
||||
// Graphite to a more-capable system like Prometheus. If your organization has
|
||||
// already standardized on an instrumentation system like Prometheus, and has no
|
||||
// plans to change, it may make sense to use that system's instrumentation
|
||||
// library directly.
|
||||
//
|
||||
// This package provides three core metric abstractions (Counter, Gauge, and
|
||||
// Histogram) and implementations for almost all common instrumentation
|
||||
// backends. Each metric has an observation method (Add, Set, or Observe,
|
||||
// respectively) used to record values, and a With method to "scope" the
|
||||
// observation by various parameters. For example, you might have a Histogram to
|
||||
// record request durations, parameterized by the method that's being called.
|
||||
//
|
||||
// var requestDuration metrics.Histogram
|
||||
// // ...
|
||||
// requestDuration.With("method", "MyMethod").Observe(time.Since(begin))
|
||||
//
|
||||
// This allows a single high-level metrics object (requestDuration) to work with
|
||||
// many code paths somewhat dynamically. The concept of With is fully supported
|
||||
// in some backends like Prometheus, and not supported in other backends like
|
||||
// Graphite. So, With may be a no-op, depending on the concrete implementation
|
||||
// you choose. Please check the implementation to know for sure. For
|
||||
// implementations that don't provide With, it's necessary to fully parameterize
|
||||
// each metric in the metric name, e.g.
|
||||
//
|
||||
// // Statsd
|
||||
// c := statsd.NewCounter("request_duration_MyMethod_200")
|
||||
// c.Add(1)
|
||||
//
|
||||
// // Prometheus
|
||||
// c := prometheus.NewCounter(stdprometheus.CounterOpts{
|
||||
// Name: "request_duration",
|
||||
// ...
|
||||
// }, []string{"method", "status_code"})
|
||||
// c.With("method", "MyMethod", "status_code", strconv.Itoa(code)).Add(1)
|
||||
//
|
||||
// Usage
|
||||
//
|
||||
// Metrics are dependencies, and should be passed to the components that need
|
||||
// them in the same way you'd construct and pass a database handle, or reference
|
||||
// to another component. Metrics should *not* be created in the global scope.
|
||||
// Instead, instantiate metrics in your func main, using whichever concrete
|
||||
// implementation is appropriate for your organization.
|
||||
//
|
||||
// latency := prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
// Namespace: "myteam",
|
||||
// Subsystem: "foosvc",
|
||||
// Name: "request_latency_seconds",
|
||||
// Help: "Incoming request latency in seconds."
|
||||
// }, []string{"method", "status_code"})
|
||||
//
|
||||
// Write your components to take the metrics they will use as parameters to
|
||||
// their constructors. Use the interface types, not the concrete types. That is,
|
||||
//
|
||||
// // NewAPI takes metrics.Histogram, not *prometheus.Summary
|
||||
// func NewAPI(s Store, logger log.Logger, latency metrics.Histogram) *API {
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// func (a *API) ServeFoo(w http.ResponseWriter, r *http.Request) {
|
||||
// begin := time.Now()
|
||||
// // ...
|
||||
// a.latency.Observe(time.Since(begin).Seconds())
|
||||
// }
|
||||
//
|
||||
// Finally, pass the metrics as dependencies when building your object graph.
|
||||
// This should happen in func main, not in the global scope.
|
||||
//
|
||||
// api := NewAPI(store, logger, latency)
|
||||
// http.ListenAndServe("/", api)
|
||||
//
|
||||
// Note that metrics are "write-only" interfaces.
|
||||
//
|
||||
// Implementation details
|
||||
//
|
||||
// All metrics are safe for concurrent use. Considerable design influence has
|
||||
// been taken from https://github.com/codahale/metrics and
|
||||
// https://prometheus.io.
|
||||
//
|
||||
// Each telemetry system has different semantics for label values, push vs.
|
||||
// pull, support for histograms, etc. These properties influence the design of
|
||||
// their respective packages. This table attempts to summarize the key points of
|
||||
// distinction.
|
||||
//
|
||||
// SYSTEM DIM COUNTERS GAUGES HISTOGRAMS
|
||||
// dogstatsd n batch, push-aggregate batch, push-aggregate native, batch, push-each
|
||||
// statsd 1 batch, push-aggregate batch, push-aggregate native, batch, push-each
|
||||
// graphite 1 batch, push-aggregate batch, push-aggregate synthetic, batch, push-aggregate
|
||||
// expvar 1 atomic atomic synthetic, batch, in-place expose
|
||||
// influx n custom custom custom
|
||||
// prometheus n native native native
|
||||
// pcp 1 native native native
|
||||
//
|
||||
package metrics
|
314
vendor/github.com/go-kit/kit/metrics/dogstatsd/dogstatsd.go
generated
vendored
Normal file
314
vendor/github.com/go-kit/kit/metrics/dogstatsd/dogstatsd.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
// Package dogstatsd provides a DogStatsD backend for package metrics. It's very
|
||||
// similar to StatsD, but supports arbitrary tags per-metric, which map to Go
|
||||
// kit's label values. So, while label values are no-ops in StatsD, they are
|
||||
// supported here. For more details, see the documentation at
|
||||
// http://docs.datadoghq.com/guides/dogstatsd/.
|
||||
//
|
||||
// This package batches observations and emits them on some schedule to the
|
||||
// remote server. This is useful even if you connect to your DogStatsD server
|
||||
// over UDP. Emitting one network packet per observation can quickly overwhelm
|
||||
// even the fastest internal network.
|
||||
package dogstatsd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/internal/lv"
|
||||
"github.com/go-kit/kit/metrics/internal/ratemap"
|
||||
"github.com/go-kit/kit/util/conn"
|
||||
)
|
||||
|
||||
// Dogstatsd receives metrics observations and forwards them to a DogStatsD
|
||||
// server. Create a Dogstatsd object, use it to create metrics, and pass those
|
||||
// metrics as dependencies to the components that will use them.
|
||||
//
|
||||
// All metrics are buffered until WriteTo is called. Counters and gauges are
|
||||
// aggregated into a single observation per timeseries per write. Timings and
|
||||
// histograms are buffered but not aggregated.
|
||||
//
|
||||
// To regularly report metrics to an io.Writer, use the WriteLoop helper method.
|
||||
// To send to a DogStatsD server, use the SendLoop helper method.
|
||||
type Dogstatsd struct {
|
||||
prefix string
|
||||
rates *ratemap.RateMap
|
||||
counters *lv.Space
|
||||
gauges *lv.Space
|
||||
timings *lv.Space
|
||||
histograms *lv.Space
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// New returns a Dogstatsd object that may be used to create metrics. Prefix is
|
||||
// applied to all created metrics. Callers must ensure that regular calls to
|
||||
// WriteTo are performed, either manually or with one of the helper methods.
|
||||
func New(prefix string, logger log.Logger) *Dogstatsd {
|
||||
return &Dogstatsd{
|
||||
prefix: prefix,
|
||||
rates: ratemap.New(),
|
||||
counters: lv.NewSpace(),
|
||||
gauges: lv.NewSpace(),
|
||||
timings: lv.NewSpace(),
|
||||
histograms: lv.NewSpace(),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter returns a counter, sending observations to this Dogstatsd object.
|
||||
func (d *Dogstatsd) NewCounter(name string, sampleRate float64) *Counter {
|
||||
d.rates.Set(d.prefix+name, sampleRate)
|
||||
return &Counter{
|
||||
name: d.prefix + name,
|
||||
obs: d.counters.Observe,
|
||||
}
|
||||
}
|
||||
|
||||
// NewGauge returns a gauge, sending observations to this Dogstatsd object.
|
||||
func (d *Dogstatsd) NewGauge(name string) *Gauge {
|
||||
return &Gauge{
|
||||
name: d.prefix + name,
|
||||
obs: d.gauges.Observe,
|
||||
add: d.gauges.Add,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTiming returns a histogram whose observations are interpreted as
|
||||
// millisecond durations, and are forwarded to this Dogstatsd object.
|
||||
func (d *Dogstatsd) NewTiming(name string, sampleRate float64) *Timing {
|
||||
d.rates.Set(d.prefix+name, sampleRate)
|
||||
return &Timing{
|
||||
name: d.prefix + name,
|
||||
obs: d.timings.Observe,
|
||||
}
|
||||
}
|
||||
|
||||
// NewHistogram returns a histogram whose observations are of an unspecified
|
||||
// unit, and are forwarded to this Dogstatsd object.
|
||||
func (d *Dogstatsd) NewHistogram(name string, sampleRate float64) *Histogram {
|
||||
d.rates.Set(d.prefix+name, sampleRate)
|
||||
return &Histogram{
|
||||
name: d.prefix + name,
|
||||
obs: d.histograms.Observe,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteLoop is a helper method that invokes WriteTo to the passed writer every
|
||||
// time the passed channel fires. This method blocks until the channel is
|
||||
// closed, so clients probably want to run it in its own goroutine. For typical
|
||||
// usage, create a time.Ticker and pass its C channel to this method.
|
||||
func (d *Dogstatsd) WriteLoop(c <-chan time.Time, w io.Writer) {
|
||||
for range c {
|
||||
if _, err := d.WriteTo(w); err != nil {
|
||||
d.logger.Log("during", "WriteTo", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendLoop is a helper method that wraps WriteLoop, passing a managed
|
||||
// connection to the network and address. Like WriteLoop, this method blocks
|
||||
// until the channel is closed, so clients probably want to start it in its own
|
||||
// goroutine. For typical usage, create a time.Ticker and pass its C channel to
|
||||
// this method.
|
||||
func (d *Dogstatsd) SendLoop(c <-chan time.Time, network, address string) {
|
||||
d.WriteLoop(c, conn.NewDefaultManager(network, address, d.logger))
|
||||
}
|
||||
|
||||
// WriteTo flushes the buffered content of the metrics to the writer, in
|
||||
// DogStatsD format. WriteTo abides best-effort semantics, so observations are
|
||||
// lost if there is a problem with the write. Clients should be sure to call
|
||||
// WriteTo regularly, ideally through the WriteLoop or SendLoop helper methods.
|
||||
func (d *Dogstatsd) WriteTo(w io.Writer) (count int64, err error) {
|
||||
var n int
|
||||
|
||||
d.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
|
||||
n, err = fmt.Fprintf(w, "%s:%f|c%s%s\n", name, sum(values), sampling(d.rates.Get(name)), tagValues(lvs))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
count += int64(n)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
d.gauges.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
|
||||
n, err = fmt.Fprintf(w, "%s:%f|g%s\n", name, last(values), tagValues(lvs))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
count += int64(n)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
d.timings.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
|
||||
sampleRate := d.rates.Get(name)
|
||||
for _, value := range values {
|
||||
n, err = fmt.Fprintf(w, "%s:%f|ms%s%s\n", name, value, sampling(sampleRate), tagValues(lvs))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
count += int64(n)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
d.histograms.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
|
||||
sampleRate := d.rates.Get(name)
|
||||
for _, value := range values {
|
||||
n, err = fmt.Fprintf(w, "%s:%f|h%s%s\n", name, value, sampling(sampleRate), tagValues(lvs))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
count += int64(n)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
func sum(a []float64) float64 {
|
||||
var v float64
|
||||
for _, f := range a {
|
||||
v += f
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func last(a []float64) float64 {
|
||||
return a[len(a)-1]
|
||||
}
|
||||
|
||||
func sampling(r float64) string {
|
||||
var sv string
|
||||
if r < 1.0 {
|
||||
sv = fmt.Sprintf("|@%f", r)
|
||||
}
|
||||
return sv
|
||||
}
|
||||
|
||||
func tagValues(labelValues []string) string {
|
||||
if len(labelValues) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(labelValues)%2 != 0 {
|
||||
panic("tagValues received a labelValues with an odd number of strings")
|
||||
}
|
||||
pairs := make([]string, 0, len(labelValues)/2)
|
||||
for i := 0; i < len(labelValues); i += 2 {
|
||||
pairs = append(pairs, labelValues[i]+":"+labelValues[i+1])
|
||||
}
|
||||
return "|#" + strings.Join(pairs, ",")
|
||||
}
|
||||
|
||||
type observeFunc func(name string, lvs lv.LabelValues, value float64)
|
||||
|
||||
// Counter is a DogStatsD counter. Observations are forwarded to a Dogstatsd
|
||||
// object, and aggregated (summed) per timeseries.
|
||||
type Counter struct {
|
||||
name string
|
||||
lvs lv.LabelValues
|
||||
obs observeFunc
|
||||
}
|
||||
|
||||
// With implements metrics.Counter.
|
||||
func (c *Counter) With(labelValues ...string) metrics.Counter {
|
||||
return &Counter{
|
||||
name: c.name,
|
||||
lvs: c.lvs.With(labelValues...),
|
||||
obs: c.obs,
|
||||
}
|
||||
}
|
||||
|
||||
// Add implements metrics.Counter.
|
||||
func (c *Counter) Add(delta float64) {
|
||||
c.obs(c.name, c.lvs, delta)
|
||||
}
|
||||
|
||||
// Gauge is a DogStatsD gauge. Observations are forwarded to a Dogstatsd
|
||||
// object, and aggregated (the last observation selected) per timeseries.
|
||||
type Gauge struct {
|
||||
name string
|
||||
lvs lv.LabelValues
|
||||
obs observeFunc
|
||||
add observeFunc
|
||||
}
|
||||
|
||||
// With implements metrics.Gauge.
|
||||
func (g *Gauge) With(labelValues ...string) metrics.Gauge {
|
||||
return &Gauge{
|
||||
name: g.name,
|
||||
lvs: g.lvs.With(labelValues...),
|
||||
obs: g.obs,
|
||||
add: g.add,
|
||||
}
|
||||
}
|
||||
|
||||
// Set implements metrics.Gauge.
|
||||
func (g *Gauge) Set(value float64) {
|
||||
g.obs(g.name, g.lvs, value)
|
||||
}
|
||||
|
||||
// Add implements metrics.Gauge.
|
||||
func (g *Gauge) Add(delta float64) {
|
||||
g.add(g.name, g.lvs, delta)
|
||||
}
|
||||
|
||||
// Timing is a DogStatsD timing, or metrics.Histogram. Observations are
|
||||
// forwarded to a Dogstatsd object, and collected (but not aggregated) per
|
||||
// timeseries.
|
||||
type Timing struct {
|
||||
name string
|
||||
lvs lv.LabelValues
|
||||
obs observeFunc
|
||||
}
|
||||
|
||||
// With implements metrics.Timing.
|
||||
func (t *Timing) With(labelValues ...string) metrics.Histogram {
|
||||
return &Timing{
|
||||
name: t.name,
|
||||
lvs: t.lvs.With(labelValues...),
|
||||
obs: t.obs,
|
||||
}
|
||||
}
|
||||
|
||||
// Observe implements metrics.Histogram. Value is interpreted as milliseconds.
|
||||
func (t *Timing) Observe(value float64) {
|
||||
t.obs(t.name, t.lvs, value)
|
||||
}
|
||||
|
||||
// Histogram is a DogStatsD histrogram. Observations are forwarded to a
|
||||
// Dogstatsd object, and collected (but not aggregated) per timeseries.
|
||||
type Histogram struct {
|
||||
name string
|
||||
lvs lv.LabelValues
|
||||
obs observeFunc
|
||||
}
|
||||
|
||||
// With implements metrics.Histogram.
|
||||
func (h *Histogram) With(labelValues ...string) metrics.Histogram {
|
||||
return &Histogram{
|
||||
name: h.name,
|
||||
lvs: h.lvs.With(labelValues...),
|
||||
obs: h.obs,
|
||||
}
|
||||
}
|
||||
|
||||
// Observe implements metrics.Histogram.
|
||||
func (h *Histogram) Observe(value float64) {
|
||||
h.obs(h.name, h.lvs, value)
|
||||
}
|
90
vendor/github.com/go-kit/kit/metrics/dogstatsd/dogstatsd_test.go
generated
vendored
Normal file
90
vendor/github.com/go-kit/kit/metrics/dogstatsd/dogstatsd_test.go
generated
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
package dogstatsd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics/teststat"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
prefix, name := "abc.", "def"
|
||||
label, value := "label", "value"
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|c\|#` + label + `:` + value + `$`
|
||||
d := New(prefix, log.NewNopLogger())
|
||||
counter := d.NewCounter(name, 1.0).With(label, value)
|
||||
valuef := teststat.SumLines(d, regex)
|
||||
if err := teststat.TestCounter(counter, valuef); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterSampled(t *testing.T) {
|
||||
// This will involve multiplying the observed sum by the inverse of the
|
||||
// sample rate and checking against the expected value within some
|
||||
// tolerance.
|
||||
t.Skip("TODO")
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
prefix, name := "ghi.", "jkl"
|
||||
label, value := "xyz", "abc"
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|g\|#` + label + `:` + value + `$`
|
||||
d := New(prefix, log.NewNopLogger())
|
||||
gauge := d.NewGauge(name).With(label, value)
|
||||
valuef := teststat.LastLine(d, regex)
|
||||
if err := teststat.TestGauge(gauge, valuef); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// DogStatsD histograms just emit all observations. So, we collect them into
|
||||
// a generic histogram, and run the statistics test on that.
|
||||
|
||||
func TestHistogram(t *testing.T) {
|
||||
prefix, name := "dogstatsd.", "histogram_test"
|
||||
label, value := "abc", "def"
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|h\|#` + label + `:` + value + `$`
|
||||
d := New(prefix, log.NewNopLogger())
|
||||
histogram := d.NewHistogram(name, 1.0).With(label, value)
|
||||
quantiles := teststat.Quantiles(d, regex, 50) // no |@0.X
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogramSampled(t *testing.T) {
|
||||
prefix, name := "dogstatsd.", "sampled_histogram_test"
|
||||
label, value := "foo", "bar"
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|h\|@0\.01[0]*\|#` + label + `:` + value + `$`
|
||||
d := New(prefix, log.NewNopLogger())
|
||||
histogram := d.NewHistogram(name, 0.01).With(label, value)
|
||||
quantiles := teststat.Quantiles(d, regex, 50)
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.02); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTiming(t *testing.T) {
|
||||
prefix, name := "dogstatsd.", "timing_test"
|
||||
label, value := "wiggle", "bottom"
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|ms\|#` + label + `:` + value + `$`
|
||||
d := New(prefix, log.NewNopLogger())
|
||||
histogram := d.NewTiming(name, 1.0).With(label, value)
|
||||
quantiles := teststat.Quantiles(d, regex, 50) // no |@0.X
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimingSampled(t *testing.T) {
|
||||
prefix, name := "dogstatsd.", "sampled_timing_test"
|
||||
label, value := "internal", "external"
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|ms\|@0.03[0]*\|#` + label + `:` + value + `$`
|
||||
d := New(prefix, log.NewNopLogger())
|
||||
histogram := d.NewTiming(name, 0.03).With(label, value)
|
||||
quantiles := teststat.Quantiles(d, regex, 50)
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.02); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
94
vendor/github.com/go-kit/kit/metrics/expvar/expvar.go
generated
vendored
Normal file
94
vendor/github.com/go-kit/kit/metrics/expvar/expvar.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// Package expvar provides expvar backends for metrics.
|
||||
// Label values are not supported.
|
||||
package expvar
|
||||
|
||||
import (
|
||||
"expvar"
|
||||
"sync"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/generic"
|
||||
)
|
||||
|
||||
// Counter implements the counter metric with an expvar float.
|
||||
// Label values are not supported.
|
||||
type Counter struct {
|
||||
f *expvar.Float
|
||||
}
|
||||
|
||||
// NewCounter creates an expvar Float with the given name, and returns an object
|
||||
// that implements the Counter interface.
|
||||
func NewCounter(name string) *Counter {
|
||||
return &Counter{
|
||||
f: expvar.NewFloat(name),
|
||||
}
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (c *Counter) With(labelValues ...string) metrics.Counter { return c }
|
||||
|
||||
// Add implements Counter.
|
||||
func (c *Counter) Add(delta float64) { c.f.Add(delta) }
|
||||
|
||||
// Gauge implements the gauge metric with an expvar float.
|
||||
// Label values are not supported.
|
||||
type Gauge struct {
|
||||
f *expvar.Float
|
||||
}
|
||||
|
||||
// NewGauge creates an expvar Float with the given name, and returns an object
|
||||
// that implements the Gauge interface.
|
||||
func NewGauge(name string) *Gauge {
|
||||
return &Gauge{
|
||||
f: expvar.NewFloat(name),
|
||||
}
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (g *Gauge) With(labelValues ...string) metrics.Gauge { return g }
|
||||
|
||||
// Set implements Gauge.
|
||||
func (g *Gauge) Set(value float64) { g.f.Set(value) }
|
||||
|
||||
// Add implements metrics.Gauge.
|
||||
func (g *Gauge) Add(delta float64) { g.f.Add(delta) }
|
||||
|
||||
// Histogram implements the histogram metric with a combination of the generic
|
||||
// Histogram object and several expvar Floats, one for each of the 50th, 90th,
|
||||
// 95th, and 99th quantiles of observed values, with the quantile attached to
|
||||
// the name as a suffix. Label values are not supported.
|
||||
type Histogram struct {
|
||||
mtx sync.Mutex
|
||||
h *generic.Histogram
|
||||
p50 *expvar.Float
|
||||
p90 *expvar.Float
|
||||
p95 *expvar.Float
|
||||
p99 *expvar.Float
|
||||
}
|
||||
|
||||
// NewHistogram returns a Histogram object with the given name and number of
|
||||
// buckets in the underlying histogram object. 50 is a good default number of
|
||||
// buckets.
|
||||
func NewHistogram(name string, buckets int) *Histogram {
|
||||
return &Histogram{
|
||||
h: generic.NewHistogram(name, buckets),
|
||||
p50: expvar.NewFloat(name + ".p50"),
|
||||
p90: expvar.NewFloat(name + ".p90"),
|
||||
p95: expvar.NewFloat(name + ".p95"),
|
||||
p99: expvar.NewFloat(name + ".p99"),
|
||||
}
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (h *Histogram) With(labelValues ...string) metrics.Histogram { return h }
|
||||
|
||||
// Observe implements Histogram.
|
||||
func (h *Histogram) Observe(value float64) {
|
||||
h.mtx.Lock()
|
||||
defer h.mtx.Unlock()
|
||||
h.h.Observe(value)
|
||||
h.p50.Set(h.h.Quantile(0.50))
|
||||
h.p90.Set(h.h.Quantile(0.90))
|
||||
h.p95.Set(h.h.Quantile(0.95))
|
||||
h.p99.Set(h.h.Quantile(0.99))
|
||||
}
|
38
vendor/github.com/go-kit/kit/metrics/expvar/expvar_test.go
generated
vendored
Normal file
38
vendor/github.com/go-kit/kit/metrics/expvar/expvar_test.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
package expvar
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/metrics/teststat"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
counter := NewCounter("expvar_counter").With("label values", "not supported").(*Counter)
|
||||
value := func() float64 { f, _ := strconv.ParseFloat(counter.f.String(), 64); return f }
|
||||
if err := teststat.TestCounter(counter, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
gauge := NewGauge("expvar_gauge").With("label values", "not supported").(*Gauge)
|
||||
value := func() float64 { f, _ := strconv.ParseFloat(gauge.f.String(), 64); return f }
|
||||
if err := teststat.TestGauge(gauge, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogram(t *testing.T) {
|
||||
histogram := NewHistogram("expvar_histogram", 50).With("label values", "not supported").(*Histogram)
|
||||
quantiles := func() (float64, float64, float64, float64) {
|
||||
p50, _ := strconv.ParseFloat(histogram.p50.String(), 64)
|
||||
p90, _ := strconv.ParseFloat(histogram.p90.String(), 64)
|
||||
p95, _ := strconv.ParseFloat(histogram.p95.String(), 64)
|
||||
p99, _ := strconv.ParseFloat(histogram.p99.String(), 64)
|
||||
return p50, p90, p95, p99
|
||||
}
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
247
vendor/github.com/go-kit/kit/metrics/generic/generic.go
generated
vendored
Normal file
247
vendor/github.com/go-kit/kit/metrics/generic/generic.go
generated
vendored
Normal file
@@ -0,0 +1,247 @@
|
||||
// Package generic implements generic versions of each of the metric types. They
|
||||
// can be embedded by other implementations, and converted to specific formats
|
||||
// as necessary.
|
||||
package generic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/VividCortex/gohistogram"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/internal/lv"
|
||||
)
|
||||
|
||||
// Counter is an in-memory implementation of a Counter.
|
||||
type Counter struct {
|
||||
Name string
|
||||
lvs lv.LabelValues
|
||||
bits uint64
|
||||
}
|
||||
|
||||
// NewCounter returns a new, usable Counter.
|
||||
func NewCounter(name string) *Counter {
|
||||
return &Counter{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// With implements Counter.
|
||||
func (c *Counter) With(labelValues ...string) metrics.Counter {
|
||||
return &Counter{
|
||||
Name: c.Name,
|
||||
bits: atomic.LoadUint64(&c.bits),
|
||||
lvs: c.lvs.With(labelValues...),
|
||||
}
|
||||
}
|
||||
|
||||
// Add implements Counter.
|
||||
func (c *Counter) Add(delta float64) {
|
||||
for {
|
||||
var (
|
||||
old = atomic.LoadUint64(&c.bits)
|
||||
newf = math.Float64frombits(old) + delta
|
||||
new = math.Float64bits(newf)
|
||||
)
|
||||
if atomic.CompareAndSwapUint64(&c.bits, old, new) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Value returns the current value of the counter.
|
||||
func (c *Counter) Value() float64 {
|
||||
return math.Float64frombits(atomic.LoadUint64(&c.bits))
|
||||
}
|
||||
|
||||
// ValueReset returns the current value of the counter, and resets it to zero.
|
||||
// This is useful for metrics backends whose counter aggregations expect deltas,
|
||||
// like Graphite.
|
||||
func (c *Counter) ValueReset() float64 {
|
||||
for {
|
||||
var (
|
||||
old = atomic.LoadUint64(&c.bits)
|
||||
newf = 0.0
|
||||
new = math.Float64bits(newf)
|
||||
)
|
||||
if atomic.CompareAndSwapUint64(&c.bits, old, new) {
|
||||
return math.Float64frombits(old)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LabelValues returns the set of label values attached to the counter.
|
||||
func (c *Counter) LabelValues() []string {
|
||||
return c.lvs
|
||||
}
|
||||
|
||||
// Gauge is an in-memory implementation of a Gauge.
|
||||
type Gauge struct {
|
||||
Name string
|
||||
lvs lv.LabelValues
|
||||
bits uint64
|
||||
}
|
||||
|
||||
// NewGauge returns a new, usable Gauge.
|
||||
func NewGauge(name string) *Gauge {
|
||||
return &Gauge{
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// With implements Gauge.
|
||||
func (g *Gauge) With(labelValues ...string) metrics.Gauge {
|
||||
return &Gauge{
|
||||
Name: g.Name,
|
||||
bits: atomic.LoadUint64(&g.bits),
|
||||
lvs: g.lvs.With(labelValues...),
|
||||
}
|
||||
}
|
||||
|
||||
// Set implements Gauge.
|
||||
func (g *Gauge) Set(value float64) {
|
||||
atomic.StoreUint64(&g.bits, math.Float64bits(value))
|
||||
}
|
||||
|
||||
// Add implements metrics.Gauge.
|
||||
func (g *Gauge) Add(delta float64) {
|
||||
for {
|
||||
var (
|
||||
old = atomic.LoadUint64(&g.bits)
|
||||
newf = math.Float64frombits(old) + delta
|
||||
new = math.Float64bits(newf)
|
||||
)
|
||||
if atomic.CompareAndSwapUint64(&g.bits, old, new) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Value returns the current value of the gauge.
|
||||
func (g *Gauge) Value() float64 {
|
||||
return math.Float64frombits(atomic.LoadUint64(&g.bits))
|
||||
}
|
||||
|
||||
// LabelValues returns the set of label values attached to the gauge.
|
||||
func (g *Gauge) LabelValues() []string {
|
||||
return g.lvs
|
||||
}
|
||||
|
||||
// Histogram is an in-memory implementation of a streaming histogram, based on
|
||||
// VividCortex/gohistogram. It dynamically computes quantiles, so it's not
|
||||
// suitable for aggregation.
|
||||
type Histogram struct {
|
||||
Name string
|
||||
lvs lv.LabelValues
|
||||
h *safeHistogram
|
||||
}
|
||||
|
||||
// NewHistogram returns a numeric histogram based on VividCortex/gohistogram. A
|
||||
// good default value for buckets is 50.
|
||||
func NewHistogram(name string, buckets int) *Histogram {
|
||||
return &Histogram{
|
||||
Name: name,
|
||||
h: &safeHistogram{Histogram: gohistogram.NewHistogram(buckets)},
|
||||
}
|
||||
}
|
||||
|
||||
// With implements Histogram.
|
||||
func (h *Histogram) With(labelValues ...string) metrics.Histogram {
|
||||
return &Histogram{
|
||||
Name: h.Name,
|
||||
lvs: h.lvs.With(labelValues...),
|
||||
h: h.h,
|
||||
}
|
||||
}
|
||||
|
||||
// Observe implements Histogram.
|
||||
func (h *Histogram) Observe(value float64) {
|
||||
h.h.Lock()
|
||||
defer h.h.Unlock()
|
||||
h.h.Add(value)
|
||||
}
|
||||
|
||||
// Quantile returns the value of the quantile q, 0.0 < q < 1.0.
|
||||
func (h *Histogram) Quantile(q float64) float64 {
|
||||
h.h.RLock()
|
||||
defer h.h.RUnlock()
|
||||
return h.h.Quantile(q)
|
||||
}
|
||||
|
||||
// LabelValues returns the set of label values attached to the histogram.
|
||||
func (h *Histogram) LabelValues() []string {
|
||||
return h.lvs
|
||||
}
|
||||
|
||||
// Print writes a string representation of the histogram to the passed writer.
|
||||
// Useful for printing to a terminal.
|
||||
func (h *Histogram) Print(w io.Writer) {
|
||||
h.h.RLock()
|
||||
defer h.h.RUnlock()
|
||||
fmt.Fprintf(w, h.h.String())
|
||||
}
|
||||
|
||||
// safeHistogram exists as gohistogram.Histogram is not goroutine-safe.
|
||||
type safeHistogram struct {
|
||||
sync.RWMutex
|
||||
gohistogram.Histogram
|
||||
}
|
||||
|
||||
// Bucket is a range in a histogram which aggregates observations.
|
||||
type Bucket struct {
|
||||
From, To, Count int64
|
||||
}
|
||||
|
||||
// Quantile is a pair of a quantile (0..100) and its observed maximum value.
|
||||
type Quantile struct {
|
||||
Quantile int // 0..100
|
||||
Value int64
|
||||
}
|
||||
|
||||
// SimpleHistogram is an in-memory implementation of a Histogram. It only tracks
|
||||
// an approximate moving average, so is likely too naïve for many use cases.
|
||||
type SimpleHistogram struct {
|
||||
mtx sync.RWMutex
|
||||
lvs lv.LabelValues
|
||||
avg float64
|
||||
n uint64
|
||||
}
|
||||
|
||||
// NewSimpleHistogram returns a SimpleHistogram, ready for observations.
|
||||
func NewSimpleHistogram() *SimpleHistogram {
|
||||
return &SimpleHistogram{}
|
||||
}
|
||||
|
||||
// With implements Histogram.
|
||||
func (h *SimpleHistogram) With(labelValues ...string) metrics.Histogram {
|
||||
return &SimpleHistogram{
|
||||
lvs: h.lvs.With(labelValues...),
|
||||
avg: h.avg,
|
||||
n: h.n,
|
||||
}
|
||||
}
|
||||
|
||||
// Observe implements Histogram.
|
||||
func (h *SimpleHistogram) Observe(value float64) {
|
||||
h.mtx.Lock()
|
||||
defer h.mtx.Unlock()
|
||||
h.n++
|
||||
h.avg -= h.avg / float64(h.n)
|
||||
h.avg += value / float64(h.n)
|
||||
}
|
||||
|
||||
// ApproximateMovingAverage returns the approximate moving average of observations.
|
||||
func (h *SimpleHistogram) ApproximateMovingAverage() float64 {
|
||||
h.mtx.RLock()
|
||||
defer h.mtx.RUnlock()
|
||||
return h.avg
|
||||
}
|
||||
|
||||
// LabelValues returns the set of label values attached to the histogram.
|
||||
func (h *SimpleHistogram) LabelValues() []string {
|
||||
return h.lvs
|
||||
}
|
109
vendor/github.com/go-kit/kit/metrics/generic/generic_test.go
generated
vendored
Normal file
109
vendor/github.com/go-kit/kit/metrics/generic/generic_test.go
generated
vendored
Normal file
@@ -0,0 +1,109 @@
|
||||
package generic_test
|
||||
|
||||
// This is package generic_test in order to get around an import cycle: this
|
||||
// package imports teststat to do its testing, but package teststat imports
|
||||
// generic to use its Histogram in the Quantiles helper function.
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/metrics/generic"
|
||||
"github.com/go-kit/kit/metrics/teststat"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
name := "my_counter"
|
||||
counter := generic.NewCounter(name).With("label", "counter").(*generic.Counter)
|
||||
if want, have := name, counter.Name; want != have {
|
||||
t.Errorf("Name: want %q, have %q", want, have)
|
||||
}
|
||||
value := func() float64 { return counter.Value() }
|
||||
if err := teststat.TestCounter(counter, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValueReset(t *testing.T) {
|
||||
counter := generic.NewCounter("test_value_reset")
|
||||
counter.Add(123)
|
||||
counter.Add(456)
|
||||
counter.Add(789)
|
||||
if want, have := float64(123+456+789), counter.ValueReset(); want != have {
|
||||
t.Errorf("want %f, have %f", want, have)
|
||||
}
|
||||
if want, have := float64(0), counter.Value(); want != have {
|
||||
t.Errorf("want %f, have %f", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
name := "my_gauge"
|
||||
gauge := generic.NewGauge(name).With("label", "gauge").(*generic.Gauge)
|
||||
if want, have := name, gauge.Name; want != have {
|
||||
t.Errorf("Name: want %q, have %q", want, have)
|
||||
}
|
||||
value := func() float64 { return gauge.Value() }
|
||||
if err := teststat.TestGauge(gauge, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogram(t *testing.T) {
|
||||
name := "my_histogram"
|
||||
histogram := generic.NewHistogram(name, 50).With("label", "histogram").(*generic.Histogram)
|
||||
if want, have := name, histogram.Name; want != have {
|
||||
t.Errorf("Name: want %q, have %q", want, have)
|
||||
}
|
||||
quantiles := func() (float64, float64, float64, float64) {
|
||||
return histogram.Quantile(0.50), histogram.Quantile(0.90), histogram.Quantile(0.95), histogram.Quantile(0.99)
|
||||
}
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue424(t *testing.T) {
|
||||
var (
|
||||
histogram = generic.NewHistogram("dont_panic", 50)
|
||||
concurrency = 100
|
||||
operations = 1000
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
|
||||
wg.Add(concurrency)
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < operations; j++ {
|
||||
histogram.Observe(float64(j))
|
||||
histogram.Observe(histogram.Quantile(0.5))
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSimpleHistogram(t *testing.T) {
|
||||
histogram := generic.NewSimpleHistogram().With("label", "simple_histogram").(*generic.SimpleHistogram)
|
||||
var (
|
||||
sum int
|
||||
count = 1234 // not too big
|
||||
)
|
||||
for i := 0; i < count; i++ {
|
||||
value := rand.Intn(1000)
|
||||
sum += value
|
||||
histogram.Observe(float64(value))
|
||||
}
|
||||
|
||||
var (
|
||||
want = float64(sum) / float64(count)
|
||||
have = histogram.ApproximateMovingAverage()
|
||||
tolerance = 0.001 // real real slim
|
||||
)
|
||||
if math.Abs(want-have)/want > tolerance {
|
||||
t.Errorf("want %f, have %f", want, have)
|
||||
}
|
||||
}
|
203
vendor/github.com/go-kit/kit/metrics/graphite/graphite.go
generated
vendored
Normal file
203
vendor/github.com/go-kit/kit/metrics/graphite/graphite.go
generated
vendored
Normal file
@@ -0,0 +1,203 @@
|
||||
// Package graphite provides a Graphite backend for metrics. Metrics are batched
|
||||
// and emitted in the plaintext protocol. For more information, see
|
||||
// http://graphite.readthedocs.io/en/latest/feeding-carbon.html#the-plaintext-protocol
|
||||
//
|
||||
// Graphite does not have a native understanding of metric parameterization, so
|
||||
// label values not supported. Use distinct metrics for each unique combination
|
||||
// of label values.
|
||||
package graphite
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/generic"
|
||||
"github.com/go-kit/kit/util/conn"
|
||||
)
|
||||
|
||||
// Graphite receives metrics observations and forwards them to a Graphite server.
|
||||
// Create a Graphite object, use it to create metrics, and pass those metrics as
|
||||
// dependencies to the components that will use them.
|
||||
//
|
||||
// All metrics are buffered until WriteTo is called. Counters and gauges are
|
||||
// aggregated into a single observation per timeseries per write. Histograms are
|
||||
// exploded into per-quantile gauges and reported once per write.
|
||||
//
|
||||
// To regularly report metrics to an io.Writer, use the WriteLoop helper method.
|
||||
// To send to a Graphite server, use the SendLoop helper method.
|
||||
type Graphite struct {
|
||||
mtx sync.RWMutex
|
||||
prefix string
|
||||
counters map[string]*Counter
|
||||
gauges map[string]*Gauge
|
||||
histograms map[string]*Histogram
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// New returns a Graphite object that may be used to create metrics. Prefix is
|
||||
// applied to all created metrics. Callers must ensure that regular calls to
|
||||
// WriteTo are performed, either manually or with one of the helper methods.
|
||||
func New(prefix string, logger log.Logger) *Graphite {
|
||||
return &Graphite{
|
||||
prefix: prefix,
|
||||
counters: map[string]*Counter{},
|
||||
gauges: map[string]*Gauge{},
|
||||
histograms: map[string]*Histogram{},
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter returns a counter. Observations are aggregated and emitted once
|
||||
// per write invocation.
|
||||
func (g *Graphite) NewCounter(name string) *Counter {
|
||||
c := NewCounter(g.prefix + name)
|
||||
g.mtx.Lock()
|
||||
g.counters[g.prefix+name] = c
|
||||
g.mtx.Unlock()
|
||||
return c
|
||||
}
|
||||
|
||||
// NewGauge returns a gauge. Observations are aggregated and emitted once per
|
||||
// write invocation.
|
||||
func (g *Graphite) NewGauge(name string) *Gauge {
|
||||
ga := NewGauge(g.prefix + name)
|
||||
g.mtx.Lock()
|
||||
g.gauges[g.prefix+name] = ga
|
||||
g.mtx.Unlock()
|
||||
return ga
|
||||
}
|
||||
|
||||
// NewHistogram returns a histogram. Observations are aggregated and emitted as
|
||||
// per-quantile gauges, once per write invocation. 50 is a good default value
|
||||
// for buckets.
|
||||
func (g *Graphite) NewHistogram(name string, buckets int) *Histogram {
|
||||
h := NewHistogram(g.prefix+name, buckets)
|
||||
g.mtx.Lock()
|
||||
g.histograms[g.prefix+name] = h
|
||||
g.mtx.Unlock()
|
||||
return h
|
||||
}
|
||||
|
||||
// WriteLoop is a helper method that invokes WriteTo to the passed writer every
|
||||
// time the passed channel fires. This method blocks until the channel is
|
||||
// closed, so clients probably want to run it in its own goroutine. For typical
|
||||
// usage, create a time.Ticker and pass its C channel to this method.
|
||||
func (g *Graphite) WriteLoop(c <-chan time.Time, w io.Writer) {
|
||||
for range c {
|
||||
if _, err := g.WriteTo(w); err != nil {
|
||||
g.logger.Log("during", "WriteTo", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendLoop is a helper method that wraps WriteLoop, passing a managed
|
||||
// connection to the network and address. Like WriteLoop, this method blocks
|
||||
// until the channel is closed, so clients probably want to start it in its own
|
||||
// goroutine. For typical usage, create a time.Ticker and pass its C channel to
|
||||
// this method.
|
||||
func (g *Graphite) SendLoop(c <-chan time.Time, network, address string) {
|
||||
g.WriteLoop(c, conn.NewDefaultManager(network, address, g.logger))
|
||||
}
|
||||
|
||||
// WriteTo flushes the buffered content of the metrics to the writer, in
|
||||
// Graphite plaintext format. WriteTo abides best-effort semantics, so
|
||||
// observations are lost if there is a problem with the write. Clients should be
|
||||
// sure to call WriteTo regularly, ideally through the WriteLoop or SendLoop
|
||||
// helper methods.
|
||||
func (g *Graphite) WriteTo(w io.Writer) (count int64, err error) {
|
||||
g.mtx.RLock()
|
||||
defer g.mtx.RUnlock()
|
||||
now := time.Now().Unix()
|
||||
|
||||
for name, c := range g.counters {
|
||||
n, err := fmt.Fprintf(w, "%s %f %d\n", name, c.c.ValueReset(), now)
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
count += int64(n)
|
||||
}
|
||||
|
||||
for name, ga := range g.gauges {
|
||||
n, err := fmt.Fprintf(w, "%s %f %d\n", name, ga.g.Value(), now)
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
count += int64(n)
|
||||
}
|
||||
|
||||
for name, h := range g.histograms {
|
||||
for _, p := range []struct {
|
||||
s string
|
||||
f float64
|
||||
}{
|
||||
{"50", 0.50},
|
||||
{"90", 0.90},
|
||||
{"95", 0.95},
|
||||
{"99", 0.99},
|
||||
} {
|
||||
n, err := fmt.Fprintf(w, "%s.p%s %f %d\n", name, p.s, h.h.Quantile(p.f), now)
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
count += int64(n)
|
||||
}
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
// Counter is a Graphite counter metric.
|
||||
type Counter struct {
|
||||
c *generic.Counter
|
||||
}
|
||||
|
||||
// NewCounter returns a new usable counter metric.
|
||||
func NewCounter(name string) *Counter {
|
||||
return &Counter{generic.NewCounter(name)}
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (c *Counter) With(...string) metrics.Counter { return c }
|
||||
|
||||
// Add implements counter.
|
||||
func (c *Counter) Add(delta float64) { c.c.Add(delta) }
|
||||
|
||||
// Gauge is a Graphite gauge metric.
|
||||
type Gauge struct {
|
||||
g *generic.Gauge
|
||||
}
|
||||
|
||||
// NewGauge returns a new usable Gauge metric.
|
||||
func NewGauge(name string) *Gauge {
|
||||
return &Gauge{generic.NewGauge(name)}
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (g *Gauge) With(...string) metrics.Gauge { return g }
|
||||
|
||||
// Set implements gauge.
|
||||
func (g *Gauge) Set(value float64) { g.g.Set(value) }
|
||||
|
||||
// Add implements metrics.Gauge.
|
||||
func (g *Gauge) Add(delta float64) { g.g.Add(delta) }
|
||||
|
||||
// Histogram is a Graphite histogram metric. Observations are bucketed into
|
||||
// per-quantile gauges.
|
||||
type Histogram struct {
|
||||
h *generic.Histogram
|
||||
}
|
||||
|
||||
// NewHistogram returns a new usable Histogram metric.
|
||||
func NewHistogram(name string, buckets int) *Histogram {
|
||||
return &Histogram{generic.NewHistogram(name, buckets)}
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (h *Histogram) With(...string) metrics.Histogram { return h }
|
||||
|
||||
// Observe implements histogram.
|
||||
func (h *Histogram) Observe(value float64) { h.h.Observe(value) }
|
63
vendor/github.com/go-kit/kit/metrics/graphite/graphite_test.go
generated
vendored
Normal file
63
vendor/github.com/go-kit/kit/metrics/graphite/graphite_test.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package graphite
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics/teststat"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
prefix, name := "abc.", "def"
|
||||
label, value := "label", "value" // ignored for Graphite
|
||||
regex := `^` + prefix + name + ` ([0-9\.]+) [0-9]+$`
|
||||
g := New(prefix, log.NewNopLogger())
|
||||
counter := g.NewCounter(name).With(label, value)
|
||||
valuef := teststat.SumLines(g, regex)
|
||||
if err := teststat.TestCounter(counter, valuef); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
prefix, name := "ghi.", "jkl"
|
||||
label, value := "xyz", "abc" // ignored for Graphite
|
||||
regex := `^` + prefix + name + ` ([0-9\.]+) [0-9]+$`
|
||||
g := New(prefix, log.NewNopLogger())
|
||||
gauge := g.NewGauge(name).With(label, value)
|
||||
valuef := teststat.LastLine(g, regex)
|
||||
if err := teststat.TestGauge(gauge, valuef); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogram(t *testing.T) {
|
||||
// The histogram test is actually like 4 gauge tests.
|
||||
prefix, name := "graphite.", "histogram_test"
|
||||
label, value := "abc", "def" // ignored for Graphite
|
||||
re50 := regexp.MustCompile(prefix + name + `.p50 ([0-9\.]+) [0-9]+`)
|
||||
re90 := regexp.MustCompile(prefix + name + `.p90 ([0-9\.]+) [0-9]+`)
|
||||
re95 := regexp.MustCompile(prefix + name + `.p95 ([0-9\.]+) [0-9]+`)
|
||||
re99 := regexp.MustCompile(prefix + name + `.p99 ([0-9\.]+) [0-9]+`)
|
||||
g := New(prefix, log.NewNopLogger())
|
||||
histogram := g.NewHistogram(name, 50).With(label, value)
|
||||
quantiles := func() (float64, float64, float64, float64) {
|
||||
var buf bytes.Buffer
|
||||
g.WriteTo(&buf)
|
||||
match50 := re50.FindStringSubmatch(buf.String())
|
||||
p50, _ := strconv.ParseFloat(match50[1], 64)
|
||||
match90 := re90.FindStringSubmatch(buf.String())
|
||||
p90, _ := strconv.ParseFloat(match90[1], 64)
|
||||
match95 := re95.FindStringSubmatch(buf.String())
|
||||
p95, _ := strconv.ParseFloat(match95[1], 64)
|
||||
match99 := re99.FindStringSubmatch(buf.String())
|
||||
p99, _ := strconv.ParseFloat(match99[1], 64)
|
||||
return p50, p90, p95, p99
|
||||
}
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
108
vendor/github.com/go-kit/kit/metrics/influx/example_test.go
generated
vendored
Normal file
108
vendor/github.com/go-kit/kit/metrics/influx/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,108 @@
|
||||
package influx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
influxdb "github.com/influxdata/influxdb/client/v2"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
)
|
||||
|
||||
func ExampleCounter() {
|
||||
in := New(map[string]string{"a": "b"}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
|
||||
counter := in.NewCounter("influx_counter")
|
||||
counter.Add(10)
|
||||
counter.With("error", "true").Add(1)
|
||||
counter.With("error", "false").Add(2)
|
||||
counter.Add(50)
|
||||
|
||||
client := &bufWriter{}
|
||||
in.WriteTo(client)
|
||||
|
||||
expectedLines := []string{
|
||||
`(influx_counter,a=b count=60) [0-9]{19}`,
|
||||
`(influx_counter,a=b,error=true count=1) [0-9]{19}`,
|
||||
`(influx_counter,a=b,error=false count=2) [0-9]{19}`,
|
||||
}
|
||||
|
||||
if err := extractAndPrintMessage(expectedLines, client.buf.String()); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// influx_counter,a=b count=60
|
||||
// influx_counter,a=b,error=true count=1
|
||||
// influx_counter,a=b,error=false count=2
|
||||
}
|
||||
|
||||
func ExampleGauge() {
|
||||
in := New(map[string]string{"a": "b"}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
|
||||
gauge := in.NewGauge("influx_gauge")
|
||||
gauge.Set(10)
|
||||
gauge.With("error", "true").Set(2)
|
||||
gauge.With("error", "true").Set(1)
|
||||
gauge.With("error", "false").Set(2)
|
||||
gauge.Set(50)
|
||||
gauge.With("test", "true").Set(1)
|
||||
gauge.With("test", "true").Add(1)
|
||||
|
||||
client := &bufWriter{}
|
||||
in.WriteTo(client)
|
||||
|
||||
expectedLines := []string{
|
||||
`(influx_gauge,a=b,test=true value=2) [0-9]{19}`,
|
||||
`(influx_gauge,a=b value=50) [0-9]{19}`,
|
||||
`(influx_gauge,a=b,error=true value=1) [0-9]{19}`,
|
||||
`(influx_gauge,a=b,error=false value=2) [0-9]{19}`,
|
||||
}
|
||||
|
||||
if err := extractAndPrintMessage(expectedLines, client.buf.String()); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// influx_gauge,a=b,test=true value=2
|
||||
// influx_gauge,a=b value=50
|
||||
// influx_gauge,a=b,error=true value=1
|
||||
// influx_gauge,a=b,error=false value=2
|
||||
}
|
||||
|
||||
func ExampleHistogram() {
|
||||
in := New(map[string]string{"foo": "alpha"}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
|
||||
histogram := in.NewHistogram("influx_histogram")
|
||||
histogram.Observe(float64(10))
|
||||
histogram.With("error", "true").Observe(float64(1))
|
||||
histogram.With("error", "false").Observe(float64(2))
|
||||
histogram.Observe(float64(50))
|
||||
|
||||
client := &bufWriter{}
|
||||
in.WriteTo(client)
|
||||
|
||||
expectedLines := []string{
|
||||
`(influx_histogram,foo=alpha p50=10,p90=50,p95=50,p99=50) [0-9]{19}`,
|
||||
`(influx_histogram,error=true,foo=alpha p50=1,p90=1,p95=1,p99=1) [0-9]{19}`,
|
||||
`(influx_histogram,error=false,foo=alpha p50=2,p90=2,p95=2,p99=2) [0-9]{19}`,
|
||||
}
|
||||
|
||||
if err := extractAndPrintMessage(expectedLines, client.buf.String()); err != nil {
|
||||
fmt.Println(err.Error())
|
||||
}
|
||||
|
||||
// Output:
|
||||
// influx_histogram,foo=alpha p50=10,p90=50,p95=50,p99=50
|
||||
// influx_histogram,error=true,foo=alpha p50=1,p90=1,p95=1,p99=1
|
||||
// influx_histogram,error=false,foo=alpha p50=2,p90=2,p95=2,p99=2
|
||||
}
|
||||
|
||||
func extractAndPrintMessage(expected []string, msg string) error {
|
||||
for _, pattern := range expected {
|
||||
re := regexp.MustCompile(pattern)
|
||||
match := re.FindStringSubmatch(msg)
|
||||
if len(match) != 2 {
|
||||
return fmt.Errorf("Pattern not found! {%s} [%s]: %v\n", pattern, msg, match)
|
||||
}
|
||||
fmt.Println(match[1])
|
||||
}
|
||||
return nil
|
||||
}
|
267
vendor/github.com/go-kit/kit/metrics/influx/influx.go
generated
vendored
Normal file
267
vendor/github.com/go-kit/kit/metrics/influx/influx.go
generated
vendored
Normal file
@@ -0,0 +1,267 @@
|
||||
// Package influx provides an InfluxDB implementation for metrics. The model is
|
||||
// similar to other push-based instrumentation systems. Observations are
|
||||
// aggregated locally and emitted to the Influx server on regular intervals.
|
||||
package influx
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
influxdb "github.com/influxdata/influxdb/client/v2"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/generic"
|
||||
"github.com/go-kit/kit/metrics/internal/lv"
|
||||
)
|
||||
|
||||
// Influx is a store for metrics that will be emitted to an Influx database.
|
||||
//
|
||||
// Influx is a general purpose time-series database, and has no native concepts
|
||||
// of counters, gauges, or histograms. Counters are modeled as a timeseries with
|
||||
// one data point per flush, with a "count" field that reflects all adds since
|
||||
// the last flush. Gauges are modeled as a timeseries with one data point per
|
||||
// flush, with a "value" field that reflects the current state of the gauge.
|
||||
// Histograms are modeled as a timeseries with one data point per combination of tags,
|
||||
// with a set of quantile fields that reflects the p50, p90, p95 & p99.
|
||||
//
|
||||
// Influx tags are attached to the Influx object, can be given to each
|
||||
// metric at construction and can be updated anytime via With function. Influx fields
|
||||
// are mapped to Go kit label values directly by this collector. Actual metric
|
||||
// values are provided as fields with specific names depending on the metric.
|
||||
//
|
||||
// All observations are collected in memory locally, and flushed on demand.
|
||||
type Influx struct {
|
||||
counters *lv.Space
|
||||
gauges *lv.Space
|
||||
histograms *lv.Space
|
||||
tags map[string]string
|
||||
conf influxdb.BatchPointsConfig
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// New returns an Influx, ready to create metrics and collect observations. Tags
|
||||
// are applied to all metrics created from this object. The BatchPointsConfig is
|
||||
// used during flushing.
|
||||
func New(tags map[string]string, conf influxdb.BatchPointsConfig, logger log.Logger) *Influx {
|
||||
return &Influx{
|
||||
counters: lv.NewSpace(),
|
||||
gauges: lv.NewSpace(),
|
||||
histograms: lv.NewSpace(),
|
||||
tags: tags,
|
||||
conf: conf,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter returns an Influx counter.
|
||||
func (in *Influx) NewCounter(name string) *Counter {
|
||||
return &Counter{
|
||||
name: name,
|
||||
obs: in.counters.Observe,
|
||||
}
|
||||
}
|
||||
|
||||
// NewGauge returns an Influx gauge.
|
||||
func (in *Influx) NewGauge(name string) *Gauge {
|
||||
return &Gauge{
|
||||
name: name,
|
||||
obs: in.gauges.Observe,
|
||||
add: in.gauges.Add,
|
||||
}
|
||||
}
|
||||
|
||||
// NewHistogram returns an Influx histogram.
|
||||
func (in *Influx) NewHistogram(name string) *Histogram {
|
||||
return &Histogram{
|
||||
name: name,
|
||||
obs: in.histograms.Observe,
|
||||
}
|
||||
}
|
||||
|
||||
// BatchPointsWriter captures a subset of the influxdb.Client methods necessary
|
||||
// for emitting metrics observations.
|
||||
type BatchPointsWriter interface {
|
||||
Write(influxdb.BatchPoints) error
|
||||
}
|
||||
|
||||
// WriteLoop is a helper method that invokes WriteTo to the passed writer every
|
||||
// time the passed channel fires. This method blocks until the channel is
|
||||
// closed, so clients probably want to run it in its own goroutine. For typical
|
||||
// usage, create a time.Ticker and pass its C channel to this method.
|
||||
func (in *Influx) WriteLoop(c <-chan time.Time, w BatchPointsWriter) {
|
||||
for range c {
|
||||
if err := in.WriteTo(w); err != nil {
|
||||
in.logger.Log("during", "WriteTo", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteTo flushes the buffered content of the metrics to the writer, in an
|
||||
// Influx BatchPoints format. WriteTo abides best-effort semantics, so
|
||||
// observations are lost if there is a problem with the write. Clients should be
|
||||
// sure to call WriteTo regularly, ideally through the WriteLoop helper method.
|
||||
func (in *Influx) WriteTo(w BatchPointsWriter) (err error) {
|
||||
bp, err := influxdb.NewBatchPoints(in.conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
|
||||
in.counters.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
|
||||
tags := mergeTags(in.tags, lvs)
|
||||
var p *influxdb.Point
|
||||
fields := map[string]interface{}{"count": sum(values)}
|
||||
p, err = influxdb.NewPoint(name, tags, fields, now)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bp.AddPoint(p)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in.gauges.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
|
||||
tags := mergeTags(in.tags, lvs)
|
||||
var p *influxdb.Point
|
||||
fields := map[string]interface{}{"value": last(values)}
|
||||
p, err = influxdb.NewPoint(name, tags, fields, now)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bp.AddPoint(p)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
in.histograms.Reset().Walk(func(name string, lvs lv.LabelValues, values []float64) bool {
|
||||
histogram := generic.NewHistogram(name, 50)
|
||||
tags := mergeTags(in.tags, lvs)
|
||||
var p *influxdb.Point
|
||||
for _, v := range values {
|
||||
histogram.Observe(v)
|
||||
}
|
||||
fields := map[string]interface{}{
|
||||
"p50": histogram.Quantile(0.50),
|
||||
"p90": histogram.Quantile(0.90),
|
||||
"p95": histogram.Quantile(0.95),
|
||||
"p99": histogram.Quantile(0.99),
|
||||
}
|
||||
p, err = influxdb.NewPoint(name, tags, fields, now)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
bp.AddPoint(p)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Write(bp)
|
||||
}
|
||||
|
||||
func mergeTags(tags map[string]string, labelValues []string) map[string]string {
|
||||
if len(labelValues)%2 != 0 {
|
||||
panic("mergeTags received a labelValues with an odd number of strings")
|
||||
}
|
||||
ret := make(map[string]string, len(tags)+len(labelValues)/2)
|
||||
for k, v := range tags {
|
||||
ret[k] = v
|
||||
}
|
||||
for i := 0; i < len(labelValues); i += 2 {
|
||||
ret[labelValues[i]] = labelValues[i+1]
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func sum(a []float64) float64 {
|
||||
var v float64
|
||||
for _, f := range a {
|
||||
v += f
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func last(a []float64) float64 {
|
||||
return a[len(a)-1]
|
||||
}
|
||||
|
||||
type observeFunc func(name string, lvs lv.LabelValues, value float64)
|
||||
|
||||
// Counter is an Influx counter. Observations are forwarded to an Influx
|
||||
// object, and aggregated (summed) per timeseries.
|
||||
type Counter struct {
|
||||
name string
|
||||
lvs lv.LabelValues
|
||||
obs observeFunc
|
||||
}
|
||||
|
||||
// With implements metrics.Counter.
|
||||
func (c *Counter) With(labelValues ...string) metrics.Counter {
|
||||
return &Counter{
|
||||
name: c.name,
|
||||
lvs: c.lvs.With(labelValues...),
|
||||
obs: c.obs,
|
||||
}
|
||||
}
|
||||
|
||||
// Add implements metrics.Counter.
|
||||
func (c *Counter) Add(delta float64) {
|
||||
c.obs(c.name, c.lvs, delta)
|
||||
}
|
||||
|
||||
// Gauge is an Influx gauge. Observations are forwarded to a Dogstatsd
|
||||
// object, and aggregated (the last observation selected) per timeseries.
|
||||
type Gauge struct {
|
||||
name string
|
||||
lvs lv.LabelValues
|
||||
obs observeFunc
|
||||
add observeFunc
|
||||
}
|
||||
|
||||
// With implements metrics.Gauge.
|
||||
func (g *Gauge) With(labelValues ...string) metrics.Gauge {
|
||||
return &Gauge{
|
||||
name: g.name,
|
||||
lvs: g.lvs.With(labelValues...),
|
||||
obs: g.obs,
|
||||
add: g.add,
|
||||
}
|
||||
}
|
||||
|
||||
// Set implements metrics.Gauge.
|
||||
func (g *Gauge) Set(value float64) {
|
||||
g.obs(g.name, g.lvs, value)
|
||||
}
|
||||
|
||||
// Add implements metrics.Gauge.
|
||||
func (g *Gauge) Add(delta float64) {
|
||||
g.add(g.name, g.lvs, delta)
|
||||
}
|
||||
|
||||
// Histogram is an Influx histrogram. Observations are aggregated into a
|
||||
// generic.Histogram and emitted as per-quantile gauges to the Influx server.
|
||||
type Histogram struct {
|
||||
name string
|
||||
lvs lv.LabelValues
|
||||
obs observeFunc
|
||||
}
|
||||
|
||||
// With implements metrics.Histogram.
|
||||
func (h *Histogram) With(labelValues ...string) metrics.Histogram {
|
||||
return &Histogram{
|
||||
name: h.name,
|
||||
lvs: h.lvs.With(labelValues...),
|
||||
obs: h.obs,
|
||||
}
|
||||
}
|
||||
|
||||
// Observe implements metrics.Histogram.
|
||||
func (h *Histogram) Observe(value float64) {
|
||||
h.obs(h.name, h.lvs, value)
|
||||
}
|
125
vendor/github.com/go-kit/kit/metrics/influx/influx_test.go
generated
vendored
Normal file
125
vendor/github.com/go-kit/kit/metrics/influx/influx_test.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
package influx
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
influxdb "github.com/influxdata/influxdb/client/v2"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics/teststat"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
in := New(map[string]string{"a": "b"}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
|
||||
re := regexp.MustCompile(`influx_counter,a=b count=([0-9\.]+) [0-9]+`) // reverse-engineered :\
|
||||
counter := in.NewCounter("influx_counter")
|
||||
value := func() float64 {
|
||||
client := &bufWriter{}
|
||||
in.WriteTo(client)
|
||||
match := re.FindStringSubmatch(client.buf.String())
|
||||
f, _ := strconv.ParseFloat(match[1], 64)
|
||||
return f
|
||||
}
|
||||
if err := teststat.TestCounter(counter, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
in := New(map[string]string{"foo": "alpha"}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
|
||||
re := regexp.MustCompile(`influx_gauge,foo=alpha value=([0-9\.]+) [0-9]+`)
|
||||
gauge := in.NewGauge("influx_gauge")
|
||||
value := func() float64 {
|
||||
client := &bufWriter{}
|
||||
in.WriteTo(client)
|
||||
match := re.FindStringSubmatch(client.buf.String())
|
||||
f, _ := strconv.ParseFloat(match[1], 64)
|
||||
return f
|
||||
}
|
||||
if err := teststat.TestGauge(gauge, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogram(t *testing.T) {
|
||||
in := New(map[string]string{"foo": "alpha"}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
|
||||
re := regexp.MustCompile(`influx_histogram,bar=beta,foo=alpha p50=([0-9\.]+),p90=([0-9\.]+),p95=([0-9\.]+),p99=([0-9\.]+) [0-9]+`)
|
||||
histogram := in.NewHistogram("influx_histogram").With("bar", "beta")
|
||||
quantiles := func() (float64, float64, float64, float64) {
|
||||
w := &bufWriter{}
|
||||
in.WriteTo(w)
|
||||
match := re.FindStringSubmatch(w.buf.String())
|
||||
if len(match) != 5 {
|
||||
t.Errorf("These are not the quantiles you're looking for: %v\n", match)
|
||||
}
|
||||
var result [4]float64
|
||||
for i, q := range match[1:] {
|
||||
result[i], _ = strconv.ParseFloat(q, 64)
|
||||
}
|
||||
return result[0], result[1], result[2], result[3]
|
||||
}
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogramLabels(t *testing.T) {
|
||||
in := New(map[string]string{}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
|
||||
h := in.NewHistogram("foo")
|
||||
h.Observe(123)
|
||||
h.With("abc", "xyz").Observe(456)
|
||||
w := &bufWriter{}
|
||||
if err := in.WriteTo(w); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want, have := 2, len(strings.Split(strings.TrimSpace(w.buf.String()), "\n")); want != have {
|
||||
t.Errorf("want %d, have %d", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue404(t *testing.T) {
|
||||
in := New(map[string]string{}, influxdb.BatchPointsConfig{}, log.NewNopLogger())
|
||||
|
||||
counterOne := in.NewCounter("influx_counter_one").With("a", "b")
|
||||
counterOne.Add(123)
|
||||
|
||||
counterTwo := in.NewCounter("influx_counter_two").With("c", "d")
|
||||
counterTwo.Add(456)
|
||||
|
||||
w := &bufWriter{}
|
||||
in.WriteTo(w)
|
||||
|
||||
lines := strings.Split(strings.TrimSpace(w.buf.String()), "\n")
|
||||
if want, have := 2, len(lines); want != have {
|
||||
t.Fatalf("want %d, have %d", want, have)
|
||||
}
|
||||
for _, line := range lines {
|
||||
if strings.HasPrefix(line, "influx_counter_one") {
|
||||
if !strings.HasPrefix(line, "influx_counter_one,a=b count=123 ") {
|
||||
t.Errorf("invalid influx_counter_one: %s", line)
|
||||
}
|
||||
} else if strings.HasPrefix(line, "influx_counter_two") {
|
||||
if !strings.HasPrefix(line, "influx_counter_two,c=d count=456 ") {
|
||||
t.Errorf("invalid influx_counter_two: %s", line)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("unexpected line: %s", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type bufWriter struct {
|
||||
buf bytes.Buffer
|
||||
}
|
||||
|
||||
func (w *bufWriter) Write(bp influxdb.BatchPoints) error {
|
||||
for _, p := range bp.Points() {
|
||||
fmt.Fprintf(&w.buf, p.String()+"\n")
|
||||
}
|
||||
return nil
|
||||
}
|
14
vendor/github.com/go-kit/kit/metrics/internal/lv/labelvalues.go
generated
vendored
Normal file
14
vendor/github.com/go-kit/kit/metrics/internal/lv/labelvalues.go
generated
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
package lv
|
||||
|
||||
// LabelValues is a type alias that provides validation on its With method.
|
||||
// Metrics may include it as a member to help them satisfy With semantics and
|
||||
// save some code duplication.
|
||||
type LabelValues []string
|
||||
|
||||
// With validates the input, and returns a new aggregate labelValues.
|
||||
func (lvs LabelValues) With(labelValues ...string) LabelValues {
|
||||
if len(labelValues)%2 != 0 {
|
||||
labelValues = append(labelValues, "unknown")
|
||||
}
|
||||
return append(lvs, labelValues...)
|
||||
}
|
22
vendor/github.com/go-kit/kit/metrics/internal/lv/labelvalues_test.go
generated
vendored
Normal file
22
vendor/github.com/go-kit/kit/metrics/internal/lv/labelvalues_test.go
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
package lv
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWith(t *testing.T) {
|
||||
var a LabelValues
|
||||
b := a.With("a", "1")
|
||||
c := a.With("b", "2", "c", "3")
|
||||
|
||||
if want, have := "", strings.Join(a, ""); want != have {
|
||||
t.Errorf("With appears to mutate the original LabelValues: want %q, have %q", want, have)
|
||||
}
|
||||
if want, have := "a1", strings.Join(b, ""); want != have {
|
||||
t.Errorf("With does not appear to return the right thing: want %q, have %q", want, have)
|
||||
}
|
||||
if want, have := "b2c3", strings.Join(c, ""); want != have {
|
||||
t.Errorf("With does not appear to return the right thing: want %q, have %q", want, have)
|
||||
}
|
||||
}
|
145
vendor/github.com/go-kit/kit/metrics/internal/lv/space.go
generated
vendored
Normal file
145
vendor/github.com/go-kit/kit/metrics/internal/lv/space.go
generated
vendored
Normal file
@@ -0,0 +1,145 @@
|
||||
package lv
|
||||
|
||||
import "sync"
|
||||
|
||||
// NewSpace returns an N-dimensional vector space.
|
||||
func NewSpace() *Space {
|
||||
return &Space{}
|
||||
}
|
||||
|
||||
// Space represents an N-dimensional vector space. Each name and unique label
|
||||
// value pair establishes a new dimension and point within that dimension. Order
|
||||
// matters, i.e. [a=1 b=2] identifies a different timeseries than [b=2 a=1].
|
||||
type Space struct {
|
||||
mtx sync.RWMutex
|
||||
nodes map[string]*node
|
||||
}
|
||||
|
||||
// Observe locates the time series identified by the name and label values in
|
||||
// the vector space, and appends the value to the list of observations.
|
||||
func (s *Space) Observe(name string, lvs LabelValues, value float64) {
|
||||
s.nodeFor(name).observe(lvs, value)
|
||||
}
|
||||
|
||||
// Add locates the time series identified by the name and label values in
|
||||
// the vector space, and appends the delta to the last value in the list of
|
||||
// observations.
|
||||
func (s *Space) Add(name string, lvs LabelValues, delta float64) {
|
||||
s.nodeFor(name).add(lvs, delta)
|
||||
}
|
||||
|
||||
// Walk traverses the vector space and invokes fn for each non-empty time series
|
||||
// which is encountered. Return false to abort the traversal.
|
||||
func (s *Space) Walk(fn func(name string, lvs LabelValues, observations []float64) bool) {
|
||||
s.mtx.RLock()
|
||||
defer s.mtx.RUnlock()
|
||||
for name, node := range s.nodes {
|
||||
f := func(lvs LabelValues, observations []float64) bool { return fn(name, lvs, observations) }
|
||||
if !node.walk(LabelValues{}, f) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset empties the current space and returns a new Space with the old
|
||||
// contents. Reset a Space to get an immutable copy suitable for walking.
|
||||
func (s *Space) Reset() *Space {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
n := NewSpace()
|
||||
n.nodes, s.nodes = s.nodes, n.nodes
|
||||
return n
|
||||
}
|
||||
|
||||
func (s *Space) nodeFor(name string) *node {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
if s.nodes == nil {
|
||||
s.nodes = map[string]*node{}
|
||||
}
|
||||
n, ok := s.nodes[name]
|
||||
if !ok {
|
||||
n = &node{}
|
||||
s.nodes[name] = n
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// node exists at a specific point in the N-dimensional vector space of all
|
||||
// possible label values. The node collects observations and has child nodes
|
||||
// with greater specificity.
|
||||
type node struct {
|
||||
mtx sync.RWMutex
|
||||
observations []float64
|
||||
children map[pair]*node
|
||||
}
|
||||
|
||||
type pair struct{ label, value string }
|
||||
|
||||
func (n *node) observe(lvs LabelValues, value float64) {
|
||||
n.mtx.Lock()
|
||||
defer n.mtx.Unlock()
|
||||
if len(lvs) == 0 {
|
||||
n.observations = append(n.observations, value)
|
||||
return
|
||||
}
|
||||
if len(lvs) < 2 {
|
||||
panic("too few LabelValues; programmer error!")
|
||||
}
|
||||
head, tail := pair{lvs[0], lvs[1]}, lvs[2:]
|
||||
if n.children == nil {
|
||||
n.children = map[pair]*node{}
|
||||
}
|
||||
child, ok := n.children[head]
|
||||
if !ok {
|
||||
child = &node{}
|
||||
n.children[head] = child
|
||||
}
|
||||
child.observe(tail, value)
|
||||
}
|
||||
|
||||
func (n *node) add(lvs LabelValues, delta float64) {
|
||||
n.mtx.Lock()
|
||||
defer n.mtx.Unlock()
|
||||
if len(lvs) == 0 {
|
||||
var value float64
|
||||
if len(n.observations) > 0 {
|
||||
value = last(n.observations) + delta
|
||||
} else {
|
||||
value = delta
|
||||
}
|
||||
n.observations = append(n.observations, value)
|
||||
return
|
||||
}
|
||||
if len(lvs) < 2 {
|
||||
panic("too few LabelValues; programmer error!")
|
||||
}
|
||||
head, tail := pair{lvs[0], lvs[1]}, lvs[2:]
|
||||
if n.children == nil {
|
||||
n.children = map[pair]*node{}
|
||||
}
|
||||
child, ok := n.children[head]
|
||||
if !ok {
|
||||
child = &node{}
|
||||
n.children[head] = child
|
||||
}
|
||||
child.add(tail, delta)
|
||||
}
|
||||
|
||||
func (n *node) walk(lvs LabelValues, fn func(LabelValues, []float64) bool) bool {
|
||||
n.mtx.RLock()
|
||||
defer n.mtx.RUnlock()
|
||||
if len(n.observations) > 0 && !fn(lvs, n.observations) {
|
||||
return false
|
||||
}
|
||||
for p, child := range n.children {
|
||||
if !child.walk(append(lvs, p.label, p.value), fn) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func last(a []float64) float64 {
|
||||
return a[len(a)-1]
|
||||
}
|
86
vendor/github.com/go-kit/kit/metrics/internal/lv/space_test.go
generated
vendored
Normal file
86
vendor/github.com/go-kit/kit/metrics/internal/lv/space_test.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
package lv
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSpaceWalkAbort(t *testing.T) {
|
||||
s := NewSpace()
|
||||
s.Observe("a", LabelValues{"a", "b"}, 1)
|
||||
s.Observe("a", LabelValues{"c", "d"}, 2)
|
||||
s.Observe("a", LabelValues{"e", "f"}, 4)
|
||||
s.Observe("a", LabelValues{"g", "h"}, 8)
|
||||
s.Observe("b", LabelValues{"a", "b"}, 16)
|
||||
s.Observe("b", LabelValues{"c", "d"}, 32)
|
||||
s.Observe("b", LabelValues{"e", "f"}, 64)
|
||||
s.Observe("b", LabelValues{"g", "h"}, 128)
|
||||
|
||||
var count int
|
||||
s.Walk(func(name string, lvs LabelValues, obs []float64) bool {
|
||||
count++
|
||||
return false
|
||||
})
|
||||
if want, have := 1, count; want != have {
|
||||
t.Errorf("want %d, have %d", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpaceWalkSums(t *testing.T) {
|
||||
s := NewSpace()
|
||||
s.Observe("metric_one", LabelValues{}, 1)
|
||||
s.Observe("metric_one", LabelValues{}, 2)
|
||||
s.Observe("metric_one", LabelValues{"a", "1", "b", "2"}, 4)
|
||||
s.Observe("metric_one", LabelValues{"a", "1", "b", "2"}, 8)
|
||||
s.Observe("metric_one", LabelValues{}, 16)
|
||||
s.Observe("metric_one", LabelValues{"a", "1", "b", "3"}, 32)
|
||||
s.Observe("metric_two", LabelValues{}, 64)
|
||||
s.Observe("metric_two", LabelValues{}, 128)
|
||||
s.Observe("metric_two", LabelValues{"a", "1", "b", "2"}, 256)
|
||||
|
||||
have := map[string]float64{}
|
||||
s.Walk(func(name string, lvs LabelValues, obs []float64) bool {
|
||||
//t.Logf("%s %v => %v", name, lvs, obs)
|
||||
have[name+" ["+strings.Join(lvs, "")+"]"] += sum(obs)
|
||||
return true
|
||||
})
|
||||
|
||||
want := map[string]float64{
|
||||
"metric_one []": 1 + 2 + 16,
|
||||
"metric_one [a1b2]": 4 + 8,
|
||||
"metric_one [a1b3]": 32,
|
||||
"metric_two []": 64 + 128,
|
||||
"metric_two [a1b2]": 256,
|
||||
}
|
||||
for keystr, wantsum := range want {
|
||||
if havesum := have[keystr]; wantsum != havesum {
|
||||
t.Errorf("%q: want %.1f, have %.1f", keystr, wantsum, havesum)
|
||||
}
|
||||
delete(want, keystr)
|
||||
delete(have, keystr)
|
||||
}
|
||||
for keystr, havesum := range have {
|
||||
t.Errorf("%q: unexpected observations recorded: %.1f", keystr, havesum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpaceWalkSkipsEmptyDimensions(t *testing.T) {
|
||||
s := NewSpace()
|
||||
s.Observe("foo", LabelValues{"bar", "1", "baz", "2"}, 123)
|
||||
|
||||
var count int
|
||||
s.Walk(func(name string, lvs LabelValues, obs []float64) bool {
|
||||
count++
|
||||
return true
|
||||
})
|
||||
if want, have := 1, count; want != have {
|
||||
t.Errorf("want %d, have %d", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func sum(a []float64) (v float64) {
|
||||
for _, f := range a {
|
||||
v += f
|
||||
}
|
||||
return
|
||||
}
|
40
vendor/github.com/go-kit/kit/metrics/internal/ratemap/ratemap.go
generated
vendored
Normal file
40
vendor/github.com/go-kit/kit/metrics/internal/ratemap/ratemap.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Package ratemap implements a goroutine-safe map of string to float64. It can
|
||||
// be embedded in implementations whose metrics support fixed sample rates, so
|
||||
// that an additional parameter doesn't have to be tracked through the e.g.
|
||||
// lv.Space object.
|
||||
package ratemap
|
||||
|
||||
import "sync"
|
||||
|
||||
// RateMap is a simple goroutine-safe map of string to float64.
|
||||
type RateMap struct {
|
||||
mtx sync.RWMutex
|
||||
m map[string]float64
|
||||
}
|
||||
|
||||
// New returns a new RateMap.
|
||||
func New() *RateMap {
|
||||
return &RateMap{
|
||||
m: map[string]float64{},
|
||||
}
|
||||
}
|
||||
|
||||
// Set writes the given name/rate pair to the map.
|
||||
// Set is safe for concurrent access by multiple goroutines.
|
||||
func (m *RateMap) Set(name string, rate float64) {
|
||||
m.mtx.Lock()
|
||||
defer m.mtx.Unlock()
|
||||
m.m[name] = rate
|
||||
}
|
||||
|
||||
// Get retrieves the rate for the given name, or 1.0 if none is set.
|
||||
// Get is safe for concurrent access by multiple goroutines.
|
||||
func (m *RateMap) Get(name string) float64 {
|
||||
m.mtx.RLock()
|
||||
defer m.mtx.RUnlock()
|
||||
f, ok := m.m[name]
|
||||
if !ok {
|
||||
f = 1.0
|
||||
}
|
||||
return f
|
||||
}
|
25
vendor/github.com/go-kit/kit/metrics/metrics.go
generated
vendored
Normal file
25
vendor/github.com/go-kit/kit/metrics/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
package metrics
|
||||
|
||||
// Counter describes a metric that accumulates values monotonically.
|
||||
// An example of a counter is the number of received HTTP requests.
|
||||
type Counter interface {
|
||||
With(labelValues ...string) Counter
|
||||
Add(delta float64)
|
||||
}
|
||||
|
||||
// Gauge describes a metric that takes specific values over time.
|
||||
// An example of a gauge is the current depth of a job queue.
|
||||
type Gauge interface {
|
||||
With(labelValues ...string) Gauge
|
||||
Set(value float64)
|
||||
Add(delta float64)
|
||||
}
|
||||
|
||||
// Histogram describes a metric that takes repeated observations of the same
|
||||
// kind of thing, and produces a statistical summary of those observations,
|
||||
// typically expressed as quantiles or buckets. An example of a histogram is
|
||||
// HTTP request latencies.
|
||||
type Histogram interface {
|
||||
With(labelValues ...string) Histogram
|
||||
Observe(value float64)
|
||||
}
|
86
vendor/github.com/go-kit/kit/metrics/multi/multi.go
generated
vendored
Normal file
86
vendor/github.com/go-kit/kit/metrics/multi/multi.go
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// Package multi provides adapters that send observations to multiple metrics
|
||||
// simultaneously. This is useful if your service needs to emit to multiple
|
||||
// instrumentation systems at the same time, for example if your organization is
|
||||
// transitioning from one system to another.
|
||||
package multi
|
||||
|
||||
import "github.com/go-kit/kit/metrics"
|
||||
|
||||
// Counter collects multiple individual counters and treats them as a unit.
|
||||
type Counter []metrics.Counter
|
||||
|
||||
// NewCounter returns a multi-counter, wrapping the passed counters.
|
||||
func NewCounter(c ...metrics.Counter) Counter {
|
||||
return Counter(c)
|
||||
}
|
||||
|
||||
// Add implements counter.
|
||||
func (c Counter) Add(delta float64) {
|
||||
for _, counter := range c {
|
||||
counter.Add(delta)
|
||||
}
|
||||
}
|
||||
|
||||
// With implements counter.
|
||||
func (c Counter) With(labelValues ...string) metrics.Counter {
|
||||
next := make(Counter, len(c))
|
||||
for i := range c {
|
||||
next[i] = c[i].With(labelValues...)
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// Gauge collects multiple individual gauges and treats them as a unit.
|
||||
type Gauge []metrics.Gauge
|
||||
|
||||
// NewGauge returns a multi-gauge, wrapping the passed gauges.
|
||||
func NewGauge(g ...metrics.Gauge) Gauge {
|
||||
return Gauge(g)
|
||||
}
|
||||
|
||||
// Set implements Gauge.
|
||||
func (g Gauge) Set(value float64) {
|
||||
for _, gauge := range g {
|
||||
gauge.Set(value)
|
||||
}
|
||||
}
|
||||
|
||||
// With implements gauge.
|
||||
func (g Gauge) With(labelValues ...string) metrics.Gauge {
|
||||
next := make(Gauge, len(g))
|
||||
for i := range g {
|
||||
next[i] = g[i].With(labelValues...)
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// Add implements metrics.Gauge.
|
||||
func (g Gauge) Add(delta float64) {
|
||||
for _, gauge := range g {
|
||||
gauge.Add(delta)
|
||||
}
|
||||
}
|
||||
|
||||
// Histogram collects multiple individual histograms and treats them as a unit.
|
||||
type Histogram []metrics.Histogram
|
||||
|
||||
// NewHistogram returns a multi-histogram, wrapping the passed histograms.
|
||||
func NewHistogram(h ...metrics.Histogram) Histogram {
|
||||
return Histogram(h)
|
||||
}
|
||||
|
||||
// Observe implements Histogram.
|
||||
func (h Histogram) Observe(value float64) {
|
||||
for _, histogram := range h {
|
||||
histogram.Observe(value)
|
||||
}
|
||||
}
|
||||
|
||||
// With implements histogram.
|
||||
func (h Histogram) With(labelValues ...string) metrics.Histogram {
|
||||
next := make(Histogram, len(h))
|
||||
for i := range h {
|
||||
next[i] = h[i].With(labelValues...)
|
||||
}
|
||||
return next
|
||||
}
|
96
vendor/github.com/go-kit/kit/metrics/multi/multi_test.go
generated
vendored
Normal file
96
vendor/github.com/go-kit/kit/metrics/multi/multi_test.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
package multi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
func TestMultiCounter(t *testing.T) {
|
||||
c1 := &mockCounter{}
|
||||
c2 := &mockCounter{}
|
||||
c3 := &mockCounter{}
|
||||
mc := NewCounter(c1, c2, c3)
|
||||
|
||||
mc.Add(123)
|
||||
mc.Add(456)
|
||||
|
||||
want := "[123 456]"
|
||||
for i, m := range []fmt.Stringer{c1, c2, c3} {
|
||||
if have := m.String(); want != have {
|
||||
t.Errorf("c%d: want %q, have %q", i+1, want, have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiGauge(t *testing.T) {
|
||||
g1 := &mockGauge{}
|
||||
g2 := &mockGauge{}
|
||||
g3 := &mockGauge{}
|
||||
mg := NewGauge(g1, g2, g3)
|
||||
|
||||
mg.Set(9)
|
||||
mg.Set(8)
|
||||
mg.Set(7)
|
||||
mg.Add(3)
|
||||
|
||||
want := "[9 8 7 10]"
|
||||
for i, m := range []fmt.Stringer{g1, g2, g3} {
|
||||
if have := m.String(); want != have {
|
||||
t.Errorf("g%d: want %q, have %q", i+1, want, have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMultiHistogram(t *testing.T) {
|
||||
h1 := &mockHistogram{}
|
||||
h2 := &mockHistogram{}
|
||||
h3 := &mockHistogram{}
|
||||
mh := NewHistogram(h1, h2, h3)
|
||||
|
||||
mh.Observe(1)
|
||||
mh.Observe(2)
|
||||
mh.Observe(4)
|
||||
mh.Observe(8)
|
||||
|
||||
want := "[1 2 4 8]"
|
||||
for i, m := range []fmt.Stringer{h1, h2, h3} {
|
||||
if have := m.String(); want != have {
|
||||
t.Errorf("g%d: want %q, have %q", i+1, want, have)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type mockCounter struct {
|
||||
obs []float64
|
||||
}
|
||||
|
||||
func (c *mockCounter) Add(delta float64) { c.obs = append(c.obs, delta) }
|
||||
func (c *mockCounter) With(...string) metrics.Counter { return c }
|
||||
func (c *mockCounter) String() string { return fmt.Sprintf("%v", c.obs) }
|
||||
|
||||
type mockGauge struct {
|
||||
obs []float64
|
||||
}
|
||||
|
||||
func (g *mockGauge) Set(value float64) { g.obs = append(g.obs, value) }
|
||||
func (g *mockGauge) With(...string) metrics.Gauge { return g }
|
||||
func (g *mockGauge) String() string { return fmt.Sprintf("%v", g.obs) }
|
||||
func (g *mockGauge) Add(delta float64) {
|
||||
var value float64
|
||||
if len(g.obs) > 0 {
|
||||
value = g.obs[len(g.obs)-1] + delta
|
||||
} else {
|
||||
value = delta
|
||||
}
|
||||
g.obs = append(g.obs, value)
|
||||
}
|
||||
|
||||
type mockHistogram struct {
|
||||
obs []float64
|
||||
}
|
||||
|
||||
func (h *mockHistogram) Observe(value float64) { h.obs = append(h.obs, value) }
|
||||
func (h *mockHistogram) With(...string) metrics.Histogram { return h }
|
||||
func (h *mockHistogram) String() string { return fmt.Sprintf("%v", h.obs) }
|
125
vendor/github.com/go-kit/kit/metrics/pcp/pcp.go
generated
vendored
Normal file
125
vendor/github.com/go-kit/kit/metrics/pcp/pcp.go
generated
vendored
Normal file
@@ -0,0 +1,125 @@
|
||||
package pcp
|
||||
|
||||
import (
|
||||
"github.com/performancecopilot/speed"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
// Reporter encapsulates a speed client.
|
||||
type Reporter struct {
|
||||
c *speed.PCPClient
|
||||
}
|
||||
|
||||
// NewReporter creates a new Reporter instance. The first parameter is the
|
||||
// application name and is used to create the speed client. Hence it should be a
|
||||
// valid speed parameter name and should not contain spaces or the path
|
||||
// separator for your operating system.
|
||||
func NewReporter(appname string) (*Reporter, error) {
|
||||
c, err := speed.NewPCPClient(appname)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Reporter{c}, nil
|
||||
}
|
||||
|
||||
// Start starts the underlying speed client so it can start reporting registered
|
||||
// metrics to your PCP installation.
|
||||
func (r *Reporter) Start() { r.c.MustStart() }
|
||||
|
||||
// Stop stops the underlying speed client so it can stop reporting registered
|
||||
// metrics to your PCP installation.
|
||||
func (r *Reporter) Stop() { r.c.MustStop() }
|
||||
|
||||
// Counter implements metrics.Counter via a single dimensional speed.Counter.
|
||||
type Counter struct {
|
||||
c speed.Counter
|
||||
}
|
||||
|
||||
// NewCounter creates a new Counter. This requires a name parameter and can
|
||||
// optionally take a couple of description strings, that are used to create the
|
||||
// underlying speed.Counter and are reported by PCP.
|
||||
func (r *Reporter) NewCounter(name string, desc ...string) (*Counter, error) {
|
||||
c, err := speed.NewPCPCounter(0, name, desc...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.c.MustRegister(c)
|
||||
return &Counter{c}, nil
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (c *Counter) With(labelValues ...string) metrics.Counter { return c }
|
||||
|
||||
// Add increments Counter. speed.Counters only take int64, so delta is converted
|
||||
// to int64 before observation.
|
||||
func (c *Counter) Add(delta float64) { c.c.Inc(int64(delta)) }
|
||||
|
||||
// Gauge implements metrics.Gauge via a single dimensional speed.Gauge.
|
||||
type Gauge struct {
|
||||
g speed.Gauge
|
||||
}
|
||||
|
||||
// NewGauge creates a new Gauge. This requires a name parameter and can
|
||||
// optionally take a couple of description strings, that are used to create the
|
||||
// underlying speed.Gauge and are reported by PCP.
|
||||
func (r *Reporter) NewGauge(name string, desc ...string) (*Gauge, error) {
|
||||
g, err := speed.NewPCPGauge(0, name, desc...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.c.MustRegister(g)
|
||||
return &Gauge{g}, nil
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (g *Gauge) With(labelValues ...string) metrics.Gauge { return g }
|
||||
|
||||
// Set sets the value of the gauge.
|
||||
func (g *Gauge) Set(value float64) { g.g.Set(value) }
|
||||
|
||||
// Add adds a value to the gauge.
|
||||
func (g *Gauge) Add(delta float64) { g.g.Inc(delta) }
|
||||
|
||||
// Histogram wraps a speed Histogram.
|
||||
type Histogram struct {
|
||||
h speed.Histogram
|
||||
}
|
||||
|
||||
// NewHistogram creates a new Histogram. The minimum observeable value is 0. The
|
||||
// maximum observeable value is 3600000000 (3.6e9).
|
||||
//
|
||||
// The required parameters are a metric name, the minimum and maximum observable
|
||||
// values, and a metric unit for the units of the observed values.
|
||||
//
|
||||
// Optionally, it can also take a couple of description strings.
|
||||
func (r *Reporter) NewHistogram(name string, min, max int64, unit speed.MetricUnit, desc ...string) (*Histogram, error) {
|
||||
h, err := speed.NewPCPHistogram(name, min, max, 5, unit, desc...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r.c.MustRegister(h)
|
||||
return &Histogram{h}, nil
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (h *Histogram) With(labelValues ...string) metrics.Histogram { return h }
|
||||
|
||||
// Observe observes a value.
|
||||
//
|
||||
// This converts float64 value to int64 before observation, as the Histogram in
|
||||
// speed is backed using codahale/hdrhistogram, which only observes int64
|
||||
// values. Additionally, the value is interpreted in the metric unit used to
|
||||
// construct the histogram.
|
||||
func (h *Histogram) Observe(value float64) { h.h.MustRecord(int64(value)) }
|
||||
|
||||
// Mean returns the mean of the values observed so far by the Histogram.
|
||||
func (h *Histogram) Mean() float64 { return h.h.Mean() }
|
||||
|
||||
// Percentile returns a percentile value for the given percentile
|
||||
// between 0 and 100 for all values observed by the histogram.
|
||||
func (h *Histogram) Percentile(p float64) int64 { return h.h.Percentile(p) }
|
72
vendor/github.com/go-kit/kit/metrics/pcp/pcp_test.go
generated
vendored
Normal file
72
vendor/github.com/go-kit/kit/metrics/pcp/pcp_test.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package pcp
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/performancecopilot/speed"
|
||||
|
||||
"github.com/go-kit/kit/metrics/teststat"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
r, err := NewReporter("test_counter")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
counter, err := r.NewCounter("speed_counter")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
counter = counter.With("label values", "not supported").(*Counter)
|
||||
|
||||
value := func() float64 { f := counter.c.Val(); return float64(f) }
|
||||
if err := teststat.TestCounter(counter, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
r, err := NewReporter("test_gauge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gauge, err := r.NewGauge("speed_gauge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gauge = gauge.With("label values", "not supported").(*Gauge)
|
||||
|
||||
value := func() float64 { f := gauge.g.Val(); return f }
|
||||
if err := teststat.TestGauge(gauge, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogram(t *testing.T) {
|
||||
r, err := NewReporter("test_histogram")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
histogram, err := r.NewHistogram("speed_histogram", 0, 3600000000, speed.OneUnit)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
histogram = histogram.With("label values", "not supported").(*Histogram)
|
||||
|
||||
quantiles := func() (float64, float64, float64, float64) {
|
||||
p50 := float64(histogram.Percentile(50))
|
||||
p90 := float64(histogram.Percentile(90))
|
||||
p95 := float64(histogram.Percentile(95))
|
||||
p99 := float64(histogram.Percentile(99))
|
||||
return p50, p90, p95, p99
|
||||
}
|
||||
if err := teststat.TestHistogram(histogram, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
165
vendor/github.com/go-kit/kit/metrics/prometheus/prometheus.go
generated
vendored
Normal file
165
vendor/github.com/go-kit/kit/metrics/prometheus/prometheus.go
generated
vendored
Normal file
@@ -0,0 +1,165 @@
|
||||
// Package prometheus provides Prometheus implementations for metrics.
|
||||
// Individual metrics are mapped to their Prometheus counterparts, and
|
||||
// (depending on the constructor used) may be automatically registered in the
|
||||
// global Prometheus metrics registry.
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/internal/lv"
|
||||
)
|
||||
|
||||
// Counter implements Counter, via a Prometheus CounterVec.
|
||||
type Counter struct {
|
||||
cv *prometheus.CounterVec
|
||||
lvs lv.LabelValues
|
||||
}
|
||||
|
||||
// NewCounterFrom constructs and registers a Prometheus CounterVec,
|
||||
// and returns a usable Counter object.
|
||||
func NewCounterFrom(opts prometheus.CounterOpts, labelNames []string) *Counter {
|
||||
cv := prometheus.NewCounterVec(opts, labelNames)
|
||||
prometheus.MustRegister(cv)
|
||||
return NewCounter(cv)
|
||||
}
|
||||
|
||||
// NewCounter wraps the CounterVec and returns a usable Counter object.
|
||||
func NewCounter(cv *prometheus.CounterVec) *Counter {
|
||||
return &Counter{
|
||||
cv: cv,
|
||||
}
|
||||
}
|
||||
|
||||
// With implements Counter.
|
||||
func (c *Counter) With(labelValues ...string) metrics.Counter {
|
||||
return &Counter{
|
||||
cv: c.cv,
|
||||
lvs: c.lvs.With(labelValues...),
|
||||
}
|
||||
}
|
||||
|
||||
// Add implements Counter.
|
||||
func (c *Counter) Add(delta float64) {
|
||||
c.cv.With(makeLabels(c.lvs...)).Add(delta)
|
||||
}
|
||||
|
||||
// Gauge implements Gauge, via a Prometheus GaugeVec.
|
||||
type Gauge struct {
|
||||
gv *prometheus.GaugeVec
|
||||
lvs lv.LabelValues
|
||||
}
|
||||
|
||||
// NewGaugeFrom construts and registers a Prometheus GaugeVec,
|
||||
// and returns a usable Gauge object.
|
||||
func NewGaugeFrom(opts prometheus.GaugeOpts, labelNames []string) *Gauge {
|
||||
gv := prometheus.NewGaugeVec(opts, labelNames)
|
||||
prometheus.MustRegister(gv)
|
||||
return NewGauge(gv)
|
||||
}
|
||||
|
||||
// NewGauge wraps the GaugeVec and returns a usable Gauge object.
|
||||
func NewGauge(gv *prometheus.GaugeVec) *Gauge {
|
||||
return &Gauge{
|
||||
gv: gv,
|
||||
}
|
||||
}
|
||||
|
||||
// With implements Gauge.
|
||||
func (g *Gauge) With(labelValues ...string) metrics.Gauge {
|
||||
return &Gauge{
|
||||
gv: g.gv,
|
||||
lvs: g.lvs.With(labelValues...),
|
||||
}
|
||||
}
|
||||
|
||||
// Set implements Gauge.
|
||||
func (g *Gauge) Set(value float64) {
|
||||
g.gv.With(makeLabels(g.lvs...)).Set(value)
|
||||
}
|
||||
|
||||
// Add is supported by Prometheus GaugeVecs.
|
||||
func (g *Gauge) Add(delta float64) {
|
||||
g.gv.With(makeLabels(g.lvs...)).Add(delta)
|
||||
}
|
||||
|
||||
// Summary implements Histogram, via a Prometheus SummaryVec. The difference
|
||||
// between a Summary and a Histogram is that Summaries don't require predefined
|
||||
// quantile buckets, but cannot be statistically aggregated.
|
||||
type Summary struct {
|
||||
sv *prometheus.SummaryVec
|
||||
lvs lv.LabelValues
|
||||
}
|
||||
|
||||
// NewSummaryFrom constructs and registers a Prometheus SummaryVec,
|
||||
// and returns a usable Summary object.
|
||||
func NewSummaryFrom(opts prometheus.SummaryOpts, labelNames []string) *Summary {
|
||||
sv := prometheus.NewSummaryVec(opts, labelNames)
|
||||
prometheus.MustRegister(sv)
|
||||
return NewSummary(sv)
|
||||
}
|
||||
|
||||
// NewSummary wraps the SummaryVec and returns a usable Summary object.
|
||||
func NewSummary(sv *prometheus.SummaryVec) *Summary {
|
||||
return &Summary{
|
||||
sv: sv,
|
||||
}
|
||||
}
|
||||
|
||||
// With implements Histogram.
|
||||
func (s *Summary) With(labelValues ...string) metrics.Histogram {
|
||||
return &Summary{
|
||||
sv: s.sv,
|
||||
lvs: s.lvs.With(labelValues...),
|
||||
}
|
||||
}
|
||||
|
||||
// Observe implements Histogram.
|
||||
func (s *Summary) Observe(value float64) {
|
||||
s.sv.With(makeLabels(s.lvs...)).Observe(value)
|
||||
}
|
||||
|
||||
// Histogram implements Histogram via a Prometheus HistogramVec. The difference
|
||||
// between a Histogram and a Summary is that Histograms require predefined
|
||||
// quantile buckets, and can be statistically aggregated.
|
||||
type Histogram struct {
|
||||
hv *prometheus.HistogramVec
|
||||
lvs lv.LabelValues
|
||||
}
|
||||
|
||||
// NewHistogramFrom constructs and registers a Prometheus HistogramVec,
|
||||
// and returns a usable Histogram object.
|
||||
func NewHistogramFrom(opts prometheus.HistogramOpts, labelNames []string) *Histogram {
|
||||
hv := prometheus.NewHistogramVec(opts, labelNames)
|
||||
prometheus.MustRegister(hv)
|
||||
return NewHistogram(hv)
|
||||
}
|
||||
|
||||
// NewHistogram wraps the HistogramVec and returns a usable Histogram object.
|
||||
func NewHistogram(hv *prometheus.HistogramVec) *Histogram {
|
||||
return &Histogram{
|
||||
hv: hv,
|
||||
}
|
||||
}
|
||||
|
||||
// With implements Histogram.
|
||||
func (h *Histogram) With(labelValues ...string) metrics.Histogram {
|
||||
return &Histogram{
|
||||
hv: h.hv,
|
||||
lvs: h.lvs.With(labelValues...),
|
||||
}
|
||||
}
|
||||
|
||||
// Observe implements Histogram.
|
||||
func (h *Histogram) Observe(value float64) {
|
||||
h.hv.With(makeLabels(h.lvs...)).Observe(value)
|
||||
}
|
||||
|
||||
func makeLabels(labelValues ...string) prometheus.Labels {
|
||||
labels := prometheus.Labels{}
|
||||
for i := 0; i < len(labelValues); i += 2 {
|
||||
labels[labelValues[i]] = labelValues[i+1]
|
||||
}
|
||||
return labels
|
||||
}
|
214
vendor/github.com/go-kit/kit/metrics/prometheus/prometheus_test.go
generated
vendored
Normal file
214
vendor/github.com/go-kit/kit/metrics/prometheus/prometheus_test.go
generated
vendored
Normal file
@@ -0,0 +1,214 @@
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/go-kit/kit/metrics/teststat"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
|
||||
defer s.Close()
|
||||
|
||||
scrape := func() string {
|
||||
resp, _ := http.Get(s.URL)
|
||||
buf, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
namespace, subsystem, name := "ns", "ss", "foo"
|
||||
re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{alpha="alpha-value",beta="beta-value"} ([0-9\.]+)`)
|
||||
|
||||
counter := NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: name,
|
||||
Help: "This is the help string.",
|
||||
}, []string{"alpha", "beta"}).With("beta", "beta-value", "alpha", "alpha-value") // order shouldn't matter
|
||||
|
||||
value := func() float64 {
|
||||
matches := re.FindStringSubmatch(scrape())
|
||||
f, _ := strconv.ParseFloat(matches[1], 64)
|
||||
return f
|
||||
}
|
||||
|
||||
if err := teststat.TestCounter(counter, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
|
||||
defer s.Close()
|
||||
|
||||
scrape := func() string {
|
||||
resp, _ := http.Get(s.URL)
|
||||
buf, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
namespace, subsystem, name := "aaa", "bbb", "ccc"
|
||||
re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{foo="bar"} ([0-9\.]+)`)
|
||||
|
||||
gauge := NewGaugeFrom(stdprometheus.GaugeOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: name,
|
||||
Help: "This is a different help string.",
|
||||
}, []string{"foo"}).With("foo", "bar")
|
||||
|
||||
value := func() float64 {
|
||||
matches := re.FindStringSubmatch(scrape())
|
||||
f, _ := strconv.ParseFloat(matches[1], 64)
|
||||
return f
|
||||
}
|
||||
|
||||
if err := teststat.TestGauge(gauge, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSummary(t *testing.T) {
|
||||
s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
|
||||
defer s.Close()
|
||||
|
||||
scrape := func() string {
|
||||
resp, _ := http.Get(s.URL)
|
||||
buf, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
namespace, subsystem, name := "test", "prometheus", "summary"
|
||||
re50 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.5"} ([0-9\.]+)`)
|
||||
re90 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.9"} ([0-9\.]+)`)
|
||||
re99 := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `{a="a",b="b",quantile="0.99"} ([0-9\.]+)`)
|
||||
|
||||
summary := NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: name,
|
||||
Help: "This is the help string for the summary.",
|
||||
}, []string{"a", "b"}).With("b", "b").With("a", "a")
|
||||
|
||||
quantiles := func() (float64, float64, float64, float64) {
|
||||
buf := scrape()
|
||||
match50 := re50.FindStringSubmatch(buf)
|
||||
p50, _ := strconv.ParseFloat(match50[1], 64)
|
||||
match90 := re90.FindStringSubmatch(buf)
|
||||
p90, _ := strconv.ParseFloat(match90[1], 64)
|
||||
match99 := re99.FindStringSubmatch(buf)
|
||||
p99, _ := strconv.ParseFloat(match99[1], 64)
|
||||
p95 := p90 + ((p99 - p90) / 2) // Prometheus, y u no p95??? :< #yolo
|
||||
return p50, p90, p95, p99
|
||||
}
|
||||
|
||||
if err := teststat.TestHistogram(summary, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHistogram(t *testing.T) {
|
||||
// Prometheus reports histograms as a count of observations that fell into
|
||||
// each predefined bucket, with the bucket value representing a global upper
|
||||
// limit. That is, the count monotonically increases over the buckets. This
|
||||
// requires a different strategy to test.
|
||||
|
||||
s := httptest.NewServer(stdprometheus.UninstrumentedHandler())
|
||||
defer s.Close()
|
||||
|
||||
scrape := func() string {
|
||||
resp, _ := http.Get(s.URL)
|
||||
buf, _ := ioutil.ReadAll(resp.Body)
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
namespace, subsystem, name := "test", "prometheus", "histogram"
|
||||
re := regexp.MustCompile(namespace + `_` + subsystem + `_` + name + `_bucket{x="1",le="([0-9]+|\+Inf)"} ([0-9\.]+)`)
|
||||
|
||||
numStdev := 3
|
||||
bucketMin := (teststat.Mean - (numStdev * teststat.Stdev))
|
||||
bucketMax := (teststat.Mean + (numStdev * teststat.Stdev))
|
||||
if bucketMin < 0 {
|
||||
bucketMin = 0
|
||||
}
|
||||
bucketCount := 10
|
||||
bucketDelta := (bucketMax - bucketMin) / bucketCount
|
||||
buckets := []float64{}
|
||||
for i := bucketMin; i <= bucketMax; i += bucketDelta {
|
||||
buckets = append(buckets, float64(i))
|
||||
}
|
||||
|
||||
histogram := NewHistogramFrom(stdprometheus.HistogramOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: name,
|
||||
Help: "This is the help string for the histogram.",
|
||||
Buckets: buckets,
|
||||
}, []string{"x"}).With("x", "1")
|
||||
|
||||
// Can't TestHistogram, because Prometheus Histograms don't dynamically
|
||||
// compute quantiles. Instead, they fill up buckets. So, let's populate the
|
||||
// histogram kind of manually.
|
||||
teststat.PopulateNormalHistogram(histogram, rand.Int())
|
||||
|
||||
// Then, we use ExpectedObservationsLessThan to validate.
|
||||
for _, line := range strings.Split(scrape(), "\n") {
|
||||
match := re.FindStringSubmatch(line)
|
||||
if match == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
bucket, _ := strconv.ParseInt(match[1], 10, 64)
|
||||
have, _ := strconv.ParseInt(match[2], 10, 64)
|
||||
|
||||
want := teststat.ExpectedObservationsLessThan(bucket)
|
||||
if match[1] == "+Inf" {
|
||||
want = int64(teststat.Count) // special case
|
||||
}
|
||||
|
||||
// Unfortunately, we observe experimentally that Prometheus is quite
|
||||
// imprecise at the extremes. I'm setting a very high tolerance for now.
|
||||
// It would be great to dig in and figure out whether that's a problem
|
||||
// with my Expected calculation, or in Prometheus.
|
||||
tolerance := 0.25
|
||||
if delta := math.Abs(float64(want) - float64(have)); (delta / float64(want)) > tolerance {
|
||||
t.Errorf("Bucket %d: want %d, have %d (%.1f%%)", bucket, want, have, (100.0 * delta / float64(want)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInconsistentLabelCardinality(t *testing.T) {
|
||||
defer func() {
|
||||
x := recover()
|
||||
if x == nil {
|
||||
t.Fatal("expected panic, got none")
|
||||
}
|
||||
err, ok := x.(error)
|
||||
if !ok {
|
||||
t.Fatalf("expected error, got %s", reflect.TypeOf(x))
|
||||
}
|
||||
if want, have := "inconsistent label cardinality", err.Error(); want != have {
|
||||
t.Fatalf("want %q, have %q", want, have)
|
||||
}
|
||||
}()
|
||||
|
||||
NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: "test",
|
||||
Subsystem: "inconsistent_label_cardinality",
|
||||
Name: "foobar",
|
||||
Help: "This is the help string for the metric.",
|
||||
}, []string{"a", "b"}).With(
|
||||
"a", "1", "b", "2", "c", "KABOOM!",
|
||||
).Add(123)
|
||||
}
|
24
vendor/github.com/go-kit/kit/metrics/provider/discard.go
generated
vendored
Normal file
24
vendor/github.com/go-kit/kit/metrics/provider/discard.go
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/discard"
|
||||
)
|
||||
|
||||
type discardProvider struct{}
|
||||
|
||||
// NewDiscardProvider returns a provider that produces no-op metrics via the
|
||||
// discarding backend.
|
||||
func NewDiscardProvider() Provider { return discardProvider{} }
|
||||
|
||||
// NewCounter implements Provider.
|
||||
func (discardProvider) NewCounter(string) metrics.Counter { return discard.NewCounter() }
|
||||
|
||||
// NewGauge implements Provider.
|
||||
func (discardProvider) NewGauge(string) metrics.Gauge { return discard.NewGauge() }
|
||||
|
||||
// NewHistogram implements Provider.
|
||||
func (discardProvider) NewHistogram(string, int) metrics.Histogram { return discard.NewHistogram() }
|
||||
|
||||
// Stop implements Provider.
|
||||
func (discardProvider) Stop() {}
|
43
vendor/github.com/go-kit/kit/metrics/provider/dogstatsd.go
generated
vendored
Normal file
43
vendor/github.com/go-kit/kit/metrics/provider/dogstatsd.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/dogstatsd"
|
||||
)
|
||||
|
||||
type dogstatsdProvider struct {
|
||||
d *dogstatsd.Dogstatsd
|
||||
stop func()
|
||||
}
|
||||
|
||||
// NewDogstatsdProvider wraps the given Dogstatsd object and stop func and
|
||||
// returns a Provider that produces Dogstatsd metrics. A typical stop function
|
||||
// would be ticker.Stop from the ticker passed to the SendLoop helper method.
|
||||
func NewDogstatsdProvider(d *dogstatsd.Dogstatsd, stop func()) Provider {
|
||||
return &dogstatsdProvider{
|
||||
d: d,
|
||||
stop: stop,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter implements Provider, returning a new Dogstatsd Counter with a
|
||||
// sample rate of 1.0.
|
||||
func (p *dogstatsdProvider) NewCounter(name string) metrics.Counter {
|
||||
return p.d.NewCounter(name, 1.0)
|
||||
}
|
||||
|
||||
// NewGauge implements Provider.
|
||||
func (p *dogstatsdProvider) NewGauge(name string) metrics.Gauge {
|
||||
return p.d.NewGauge(name)
|
||||
}
|
||||
|
||||
// NewHistogram implements Provider, returning a new Dogstatsd Histogram (note:
|
||||
// not a Timing) with a sample rate of 1.0. The buckets argument is ignored.
|
||||
func (p *dogstatsdProvider) NewHistogram(name string, _ int) metrics.Histogram {
|
||||
return p.d.NewHistogram(name, 1.0)
|
||||
}
|
||||
|
||||
// Stop implements Provider, invoking the stop function passed at construction.
|
||||
func (p *dogstatsdProvider) Stop() {
|
||||
p.stop()
|
||||
}
|
31
vendor/github.com/go-kit/kit/metrics/provider/expvar.go
generated
vendored
Normal file
31
vendor/github.com/go-kit/kit/metrics/provider/expvar.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/expvar"
|
||||
)
|
||||
|
||||
type expvarProvider struct{}
|
||||
|
||||
// NewExpvarProvider returns a Provider that produces expvar metrics.
|
||||
func NewExpvarProvider() Provider {
|
||||
return expvarProvider{}
|
||||
}
|
||||
|
||||
// NewCounter implements Provider.
|
||||
func (p expvarProvider) NewCounter(name string) metrics.Counter {
|
||||
return expvar.NewCounter(name)
|
||||
}
|
||||
|
||||
// NewGauge implements Provider.
|
||||
func (p expvarProvider) NewGauge(name string) metrics.Gauge {
|
||||
return expvar.NewGauge(name)
|
||||
}
|
||||
|
||||
// NewHistogram implements Provider.
|
||||
func (p expvarProvider) NewHistogram(name string, buckets int) metrics.Histogram {
|
||||
return expvar.NewHistogram(name, buckets)
|
||||
}
|
||||
|
||||
// Stop implements Provider, but is a no-op.
|
||||
func (p expvarProvider) Stop() {}
|
41
vendor/github.com/go-kit/kit/metrics/provider/graphite.go
generated
vendored
Normal file
41
vendor/github.com/go-kit/kit/metrics/provider/graphite.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/graphite"
|
||||
)
|
||||
|
||||
type graphiteProvider struct {
|
||||
g *graphite.Graphite
|
||||
stop func()
|
||||
}
|
||||
|
||||
// NewGraphiteProvider wraps the given Graphite object and stop func and returns
|
||||
// a Provider that produces Graphite metrics. A typical stop function would be
|
||||
// ticker.Stop from the ticker passed to the SendLoop helper method.
|
||||
func NewGraphiteProvider(g *graphite.Graphite, stop func()) Provider {
|
||||
return &graphiteProvider{
|
||||
g: g,
|
||||
stop: stop,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter implements Provider.
|
||||
func (p *graphiteProvider) NewCounter(name string) metrics.Counter {
|
||||
return p.g.NewCounter(name)
|
||||
}
|
||||
|
||||
// NewGauge implements Provider.
|
||||
func (p *graphiteProvider) NewGauge(name string) metrics.Gauge {
|
||||
return p.g.NewGauge(name)
|
||||
}
|
||||
|
||||
// NewHistogram implements Provider.
|
||||
func (p *graphiteProvider) NewHistogram(name string, buckets int) metrics.Histogram {
|
||||
return p.g.NewHistogram(name, buckets)
|
||||
}
|
||||
|
||||
// Stop implements Provider, invoking the stop function passed at construction.
|
||||
func (p *graphiteProvider) Stop() {
|
||||
p.stop()
|
||||
}
|
40
vendor/github.com/go-kit/kit/metrics/provider/influx.go
generated
vendored
Normal file
40
vendor/github.com/go-kit/kit/metrics/provider/influx.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/influx"
|
||||
)
|
||||
|
||||
type influxProvider struct {
|
||||
in *influx.Influx
|
||||
stop func()
|
||||
}
|
||||
|
||||
// NewInfluxProvider takes the given Influx object and stop func, and returns
|
||||
// a Provider that produces Influx metrics.
|
||||
func NewInfluxProvider(in *influx.Influx, stop func()) Provider {
|
||||
return &influxProvider{
|
||||
in: in,
|
||||
stop: stop,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter implements Provider. Per-metric tags are not supported.
|
||||
func (p *influxProvider) NewCounter(name string) metrics.Counter {
|
||||
return p.in.NewCounter(name)
|
||||
}
|
||||
|
||||
// NewGauge implements Provider. Per-metric tags are not supported.
|
||||
func (p *influxProvider) NewGauge(name string) metrics.Gauge {
|
||||
return p.in.NewGauge(name)
|
||||
}
|
||||
|
||||
// NewHistogram implements Provider. Per-metric tags are not supported.
|
||||
func (p *influxProvider) NewHistogram(name string, buckets int) metrics.Histogram {
|
||||
return p.in.NewHistogram(name)
|
||||
}
|
||||
|
||||
// Stop implements Provider, invoking the stop function passed at construction.
|
||||
func (p *influxProvider) Stop() {
|
||||
p.stop()
|
||||
}
|
63
vendor/github.com/go-kit/kit/metrics/provider/prometheus.go
generated
vendored
Normal file
63
vendor/github.com/go-kit/kit/metrics/provider/prometheus.go
generated
vendored
Normal file
@@ -0,0 +1,63 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
stdprometheus "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/prometheus"
|
||||
)
|
||||
|
||||
type prometheusProvider struct {
|
||||
namespace string
|
||||
subsystem string
|
||||
}
|
||||
|
||||
// NewPrometheusProvider returns a Provider that produces Prometheus metrics.
|
||||
// Namespace and subsystem are applied to all produced metrics.
|
||||
func NewPrometheusProvider(namespace, subsystem string) Provider {
|
||||
return &prometheusProvider{
|
||||
namespace: namespace,
|
||||
subsystem: subsystem,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter implements Provider via prometheus.NewCounterFrom, i.e. the
|
||||
// counter is registered. The metric's namespace and subsystem are taken from
|
||||
// the Provider. Help is set to the name of the metric, and no const label names
|
||||
// are set.
|
||||
func (p *prometheusProvider) NewCounter(name string) metrics.Counter {
|
||||
return prometheus.NewCounterFrom(stdprometheus.CounterOpts{
|
||||
Namespace: p.namespace,
|
||||
Subsystem: p.subsystem,
|
||||
Name: name,
|
||||
Help: name,
|
||||
}, []string{})
|
||||
}
|
||||
|
||||
// NewGauge implements Provider via prometheus.NewGaugeFrom, i.e. the gauge is
|
||||
// registered. The metric's namespace and subsystem are taken from the Provider.
|
||||
// Help is set to the name of the metric, and no const label names are set.
|
||||
func (p *prometheusProvider) NewGauge(name string) metrics.Gauge {
|
||||
return prometheus.NewGaugeFrom(stdprometheus.GaugeOpts{
|
||||
Namespace: p.namespace,
|
||||
Subsystem: p.subsystem,
|
||||
Name: name,
|
||||
Help: name,
|
||||
}, []string{})
|
||||
}
|
||||
|
||||
// NewGauge implements Provider via prometheus.NewSummaryFrom, i.e. the summary
|
||||
// is registered. The metric's namespace and subsystem are taken from the
|
||||
// Provider. Help is set to the name of the metric, and no const label names are
|
||||
// set. Buckets are ignored.
|
||||
func (p *prometheusProvider) NewHistogram(name string, _ int) metrics.Histogram {
|
||||
return prometheus.NewSummaryFrom(stdprometheus.SummaryOpts{
|
||||
Namespace: p.namespace,
|
||||
Subsystem: p.subsystem,
|
||||
Name: name,
|
||||
Help: name,
|
||||
}, []string{})
|
||||
}
|
||||
|
||||
// Stop implements Provider, but is a no-op.
|
||||
func (p *prometheusProvider) Stop() {}
|
42
vendor/github.com/go-kit/kit/metrics/provider/provider.go
generated
vendored
Normal file
42
vendor/github.com/go-kit/kit/metrics/provider/provider.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Package provider provides a factory-like abstraction for metrics backends.
|
||||
// This package is provided specifically for the needs of the NY Times framework
|
||||
// Gizmo. Most normal Go kit users shouldn't need to use it.
|
||||
//
|
||||
// Normally, if your microservice needs to support different metrics backends,
|
||||
// you can simply do different construction based on a flag. For example,
|
||||
//
|
||||
// var latency metrics.Histogram
|
||||
// var requests metrics.Counter
|
||||
// switch *metricsBackend {
|
||||
// case "prometheus":
|
||||
// latency = prometheus.NewSummaryVec(...)
|
||||
// requests = prometheus.NewCounterVec(...)
|
||||
// case "statsd":
|
||||
// s := statsd.New(...)
|
||||
// t := time.NewTicker(5*time.Second)
|
||||
// go s.SendLoop(t.C, "tcp", "statsd.local:8125")
|
||||
// latency = s.NewHistogram(...)
|
||||
// requests = s.NewCounter(...)
|
||||
// default:
|
||||
// log.Fatal("unsupported metrics backend %q", *metricsBackend)
|
||||
// }
|
||||
//
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
// Provider abstracts over constructors and lifecycle management functions for
|
||||
// each supported metrics backend. It should only be used by those who need to
|
||||
// swap out implementations dynamically.
|
||||
//
|
||||
// This is primarily useful for intermediating frameworks, and is likely
|
||||
// unnecessary for most Go kit services. See the package-level doc comment for
|
||||
// more typical usage instructions.
|
||||
type Provider interface {
|
||||
NewCounter(name string) metrics.Counter
|
||||
NewGauge(name string) metrics.Gauge
|
||||
NewHistogram(name string, buckets int) metrics.Histogram
|
||||
Stop()
|
||||
}
|
43
vendor/github.com/go-kit/kit/metrics/provider/statsd.go
generated
vendored
Normal file
43
vendor/github.com/go-kit/kit/metrics/provider/statsd.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/statsd"
|
||||
)
|
||||
|
||||
type statsdProvider struct {
|
||||
s *statsd.Statsd
|
||||
stop func()
|
||||
}
|
||||
|
||||
// NewStatsdProvider wraps the given Statsd object and stop func and returns a
|
||||
// Provider that produces Statsd metrics. A typical stop function would be
|
||||
// ticker.Stop from the ticker passed to the SendLoop helper method.
|
||||
func NewStatsdProvider(s *statsd.Statsd, stop func()) Provider {
|
||||
return &statsdProvider{
|
||||
s: s,
|
||||
stop: stop,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter implements Provider.
|
||||
func (p *statsdProvider) NewCounter(name string) metrics.Counter {
|
||||
return p.s.NewCounter(name, 1.0)
|
||||
}
|
||||
|
||||
// NewGauge implements Provider.
|
||||
func (p *statsdProvider) NewGauge(name string) metrics.Gauge {
|
||||
return p.s.NewGauge(name)
|
||||
}
|
||||
|
||||
// NewHistogram implements Provider, returning a StatsD Timing that accepts
|
||||
// observations in milliseconds. The sample rate is fixed at 1.0. The bucket
|
||||
// parameter is ignored.
|
||||
func (p *statsdProvider) NewHistogram(name string, _ int) metrics.Histogram {
|
||||
return p.s.NewTiming(name, 1.0)
|
||||
}
|
||||
|
||||
// Stop implements Provider, invoking the stop function passed at construction.
|
||||
func (p *statsdProvider) Stop() {
|
||||
p.stop()
|
||||
}
|
239
vendor/github.com/go-kit/kit/metrics/statsd/statsd.go
generated
vendored
Normal file
239
vendor/github.com/go-kit/kit/metrics/statsd/statsd.go
generated
vendored
Normal file
@@ -0,0 +1,239 @@
|
||||
// Package statsd provides a StatsD backend for package metrics. StatsD has no
|
||||
// concept of arbitrary key-value tagging, so label values are not supported,
|
||||
// and With is a no-op on all metrics.
|
||||
//
|
||||
// This package batches observations and emits them on some schedule to the
|
||||
// remote server. This is useful even if you connect to your StatsD server over
|
||||
// UDP. Emitting one network packet per observation can quickly overwhelm even
|
||||
// the fastest internal network.
|
||||
package statsd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/internal/lv"
|
||||
"github.com/go-kit/kit/metrics/internal/ratemap"
|
||||
"github.com/go-kit/kit/util/conn"
|
||||
)
|
||||
|
||||
// Statsd receives metrics observations and forwards them to a StatsD server.
|
||||
// Create a Statsd object, use it to create metrics, and pass those metrics as
|
||||
// dependencies to the components that will use them.
|
||||
//
|
||||
// All metrics are buffered until WriteTo is called. Counters and gauges are
|
||||
// aggregated into a single observation per timeseries per write. Timings are
|
||||
// buffered but not aggregated.
|
||||
//
|
||||
// To regularly report metrics to an io.Writer, use the WriteLoop helper method.
|
||||
// To send to a StatsD server, use the SendLoop helper method.
|
||||
type Statsd struct {
|
||||
prefix string
|
||||
rates *ratemap.RateMap
|
||||
|
||||
// The observations are collected in an N-dimensional vector space, even
|
||||
// though they only take advantage of a single dimension (name). This is an
|
||||
// implementation detail born purely from convenience. It would be more
|
||||
// accurate to collect them in a map[string][]float64, but we already have
|
||||
// this nice data structure and helper methods.
|
||||
counters *lv.Space
|
||||
gauges *lv.Space
|
||||
timings *lv.Space
|
||||
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
// New returns a Statsd object that may be used to create metrics. Prefix is
|
||||
// applied to all created metrics. Callers must ensure that regular calls to
|
||||
// WriteTo are performed, either manually or with one of the helper methods.
|
||||
func New(prefix string, logger log.Logger) *Statsd {
|
||||
return &Statsd{
|
||||
prefix: prefix,
|
||||
rates: ratemap.New(),
|
||||
counters: lv.NewSpace(),
|
||||
gauges: lv.NewSpace(),
|
||||
timings: lv.NewSpace(),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// NewCounter returns a counter, sending observations to this Statsd object.
|
||||
func (s *Statsd) NewCounter(name string, sampleRate float64) *Counter {
|
||||
s.rates.Set(s.prefix+name, sampleRate)
|
||||
return &Counter{
|
||||
name: s.prefix + name,
|
||||
obs: s.counters.Observe,
|
||||
}
|
||||
}
|
||||
|
||||
// NewGauge returns a gauge, sending observations to this Statsd object.
|
||||
func (s *Statsd) NewGauge(name string) *Gauge {
|
||||
return &Gauge{
|
||||
name: s.prefix + name,
|
||||
obs: s.gauges.Observe,
|
||||
add: s.gauges.Add,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTiming returns a histogram whose observations are interpreted as
|
||||
// millisecond durations, and are forwarded to this Statsd object.
|
||||
func (s *Statsd) NewTiming(name string, sampleRate float64) *Timing {
|
||||
s.rates.Set(s.prefix+name, sampleRate)
|
||||
return &Timing{
|
||||
name: s.prefix + name,
|
||||
obs: s.timings.Observe,
|
||||
}
|
||||
}
|
||||
|
||||
// WriteLoop is a helper method that invokes WriteTo to the passed writer every
|
||||
// time the passed channel fires. This method blocks until the channel is
|
||||
// closed, so clients probably want to run it in its own goroutine. For typical
|
||||
// usage, create a time.Ticker and pass its C channel to this method.
|
||||
func (s *Statsd) WriteLoop(c <-chan time.Time, w io.Writer) {
|
||||
for range c {
|
||||
if _, err := s.WriteTo(w); err != nil {
|
||||
s.logger.Log("during", "WriteTo", "err", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SendLoop is a helper method that wraps WriteLoop, passing a managed
|
||||
// connection to the network and address. Like WriteLoop, this method blocks
|
||||
// until the channel is closed, so clients probably want to start it in its own
|
||||
// goroutine. For typical usage, create a time.Ticker and pass its C channel to
|
||||
// this method.
|
||||
func (s *Statsd) SendLoop(c <-chan time.Time, network, address string) {
|
||||
s.WriteLoop(c, conn.NewDefaultManager(network, address, s.logger))
|
||||
}
|
||||
|
||||
// WriteTo flushes the buffered content of the metrics to the writer, in
|
||||
// StatsD format. WriteTo abides best-effort semantics, so observations are
|
||||
// lost if there is a problem with the write. Clients should be sure to call
|
||||
// WriteTo regularly, ideally through the WriteLoop or SendLoop helper methods.
|
||||
func (s *Statsd) WriteTo(w io.Writer) (count int64, err error) {
|
||||
var n int
|
||||
|
||||
s.counters.Reset().Walk(func(name string, _ lv.LabelValues, values []float64) bool {
|
||||
n, err = fmt.Fprintf(w, "%s:%f|c%s\n", name, sum(values), sampling(s.rates.Get(name)))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
count += int64(n)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
s.gauges.Reset().Walk(func(name string, _ lv.LabelValues, values []float64) bool {
|
||||
n, err = fmt.Fprintf(w, "%s:%f|g\n", name, last(values))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
count += int64(n)
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
s.timings.Reset().Walk(func(name string, _ lv.LabelValues, values []float64) bool {
|
||||
sampleRate := s.rates.Get(name)
|
||||
for _, value := range values {
|
||||
n, err = fmt.Fprintf(w, "%s:%f|ms%s\n", name, value, sampling(sampleRate))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
count += int64(n)
|
||||
}
|
||||
return true
|
||||
})
|
||||
if err != nil {
|
||||
return count, err
|
||||
}
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
func sum(a []float64) float64 {
|
||||
var v float64
|
||||
for _, f := range a {
|
||||
v += f
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func last(a []float64) float64 {
|
||||
return a[len(a)-1]
|
||||
}
|
||||
|
||||
func sampling(r float64) string {
|
||||
var sv string
|
||||
if r < 1.0 {
|
||||
sv = fmt.Sprintf("|@%f", r)
|
||||
}
|
||||
return sv
|
||||
}
|
||||
|
||||
type observeFunc func(name string, lvs lv.LabelValues, value float64)
|
||||
|
||||
// Counter is a StatsD counter. Observations are forwarded to a Statsd object,
|
||||
// and aggregated (summed) per timeseries.
|
||||
type Counter struct {
|
||||
name string
|
||||
obs observeFunc
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (c *Counter) With(...string) metrics.Counter {
|
||||
return c
|
||||
}
|
||||
|
||||
// Add implements metrics.Counter.
|
||||
func (c *Counter) Add(delta float64) {
|
||||
c.obs(c.name, lv.LabelValues{}, delta)
|
||||
}
|
||||
|
||||
// Gauge is a StatsD gauge. Observations are forwarded to a Statsd object, and
|
||||
// aggregated (the last observation selected) per timeseries.
|
||||
type Gauge struct {
|
||||
name string
|
||||
obs observeFunc
|
||||
add observeFunc
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (g *Gauge) With(...string) metrics.Gauge {
|
||||
return g
|
||||
}
|
||||
|
||||
// Set implements metrics.Gauge.
|
||||
func (g *Gauge) Set(value float64) {
|
||||
g.obs(g.name, lv.LabelValues{}, value)
|
||||
}
|
||||
|
||||
// Add implements metrics.Gauge.
|
||||
func (g *Gauge) Add(delta float64) {
|
||||
g.add(g.name, lv.LabelValues{}, delta)
|
||||
}
|
||||
|
||||
// Timing is a StatsD timing, or metrics.Histogram. Observations are
|
||||
// forwarded to a Statsd object, and collected (but not aggregated) per
|
||||
// timeseries.
|
||||
type Timing struct {
|
||||
name string
|
||||
obs observeFunc
|
||||
}
|
||||
|
||||
// With is a no-op.
|
||||
func (t *Timing) With(...string) metrics.Histogram {
|
||||
return t
|
||||
}
|
||||
|
||||
// Observe implements metrics.Histogram. Value is interpreted as milliseconds.
|
||||
func (t *Timing) Observe(value float64) {
|
||||
t.obs(t.name, lv.LabelValues{}, value)
|
||||
}
|
66
vendor/github.com/go-kit/kit/metrics/statsd/statsd_test.go
generated
vendored
Normal file
66
vendor/github.com/go-kit/kit/metrics/statsd/statsd_test.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package statsd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-kit/kit/log"
|
||||
"github.com/go-kit/kit/metrics/teststat"
|
||||
)
|
||||
|
||||
func TestCounter(t *testing.T) {
|
||||
prefix, name := "abc.", "def"
|
||||
label, value := "label", "value" // ignored
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|c$`
|
||||
s := New(prefix, log.NewNopLogger())
|
||||
counter := s.NewCounter(name, 1.0).With(label, value)
|
||||
valuef := teststat.SumLines(s, regex)
|
||||
if err := teststat.TestCounter(counter, valuef); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCounterSampled(t *testing.T) {
|
||||
// This will involve multiplying the observed sum by the inverse of the
|
||||
// sample rate and checking against the expected value within some
|
||||
// tolerance.
|
||||
t.Skip("TODO")
|
||||
}
|
||||
|
||||
func TestGauge(t *testing.T) {
|
||||
prefix, name := "ghi.", "jkl"
|
||||
label, value := "xyz", "abc" // ignored
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|g$`
|
||||
s := New(prefix, log.NewNopLogger())
|
||||
gauge := s.NewGauge(name).With(label, value)
|
||||
valuef := teststat.LastLine(s, regex)
|
||||
if err := teststat.TestGauge(gauge, valuef); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// StatsD timings just emit all observations. So, we collect them into a generic
|
||||
// histogram, and run the statistics test on that.
|
||||
|
||||
func TestTiming(t *testing.T) {
|
||||
prefix, name := "statsd.", "timing_test"
|
||||
label, value := "abc", "def" // ignored
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|ms$`
|
||||
s := New(prefix, log.NewNopLogger())
|
||||
timing := s.NewTiming(name, 1.0).With(label, value)
|
||||
quantiles := teststat.Quantiles(s, regex, 50) // no |@0.X
|
||||
if err := teststat.TestHistogram(timing, quantiles, 0.01); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimingSampled(t *testing.T) {
|
||||
prefix, name := "statsd.", "sampled_timing_test"
|
||||
label, value := "foo", "bar" // ignored
|
||||
regex := `^` + prefix + name + `:([0-9\.]+)\|ms\|@0\.01[0]*$`
|
||||
s := New(prefix, log.NewNopLogger())
|
||||
timing := s.NewTiming(name, 0.01).With(label, value)
|
||||
quantiles := teststat.Quantiles(s, regex, 50)
|
||||
if err := teststat.TestHistogram(timing, quantiles, 0.02); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
65
vendor/github.com/go-kit/kit/metrics/teststat/buffers.go
generated
vendored
Normal file
65
vendor/github.com/go-kit/kit/metrics/teststat/buffers.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package teststat
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-kit/kit/metrics/generic"
|
||||
)
|
||||
|
||||
// SumLines expects a regex whose first capture group can be parsed as a
|
||||
// float64. It will dump the WriterTo and parse each line, expecting to find a
|
||||
// match. It returns the sum of all captured floats.
|
||||
func SumLines(w io.WriterTo, regex string) func() float64 {
|
||||
return func() float64 {
|
||||
sum, _ := stats(w, regex, nil)
|
||||
return sum
|
||||
}
|
||||
}
|
||||
|
||||
// LastLine expects a regex whose first capture group can be parsed as a
|
||||
// float64. It will dump the WriterTo and parse each line, expecting to find a
|
||||
// match. It returns the final captured float.
|
||||
func LastLine(w io.WriterTo, regex string) func() float64 {
|
||||
return func() float64 {
|
||||
_, final := stats(w, regex, nil)
|
||||
return final
|
||||
}
|
||||
}
|
||||
|
||||
// Quantiles expects a regex whose first capture group can be parsed as a
|
||||
// float64. It will dump the WriterTo and parse each line, expecting to find a
|
||||
// match. It observes all captured floats into a generic.Histogram with the
|
||||
// given number of buckets, and returns the 50th, 90th, 95th, and 99th quantiles
|
||||
// from that histogram.
|
||||
func Quantiles(w io.WriterTo, regex string, buckets int) func() (float64, float64, float64, float64) {
|
||||
return func() (float64, float64, float64, float64) {
|
||||
h := generic.NewHistogram("quantile-test", buckets)
|
||||
stats(w, regex, h)
|
||||
return h.Quantile(0.50), h.Quantile(0.90), h.Quantile(0.95), h.Quantile(0.99)
|
||||
}
|
||||
}
|
||||
|
||||
func stats(w io.WriterTo, regex string, h *generic.Histogram) (sum, final float64) {
|
||||
re := regexp.MustCompile(regex)
|
||||
buf := &bytes.Buffer{}
|
||||
w.WriteTo(buf)
|
||||
//fmt.Fprintf(os.Stderr, "%s\n", buf.String())
|
||||
s := bufio.NewScanner(buf)
|
||||
for s.Scan() {
|
||||
match := re.FindStringSubmatch(s.Text())
|
||||
f, err := strconv.ParseFloat(match[1], 64)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
sum += f
|
||||
final = f
|
||||
if h != nil {
|
||||
h.Observe(f)
|
||||
}
|
||||
}
|
||||
return sum, final
|
||||
}
|
72
vendor/github.com/go-kit/kit/metrics/teststat/populate.go
generated
vendored
Normal file
72
vendor/github.com/go-kit/kit/metrics/teststat/populate.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
package teststat
|
||||
|
||||
import (
|
||||
"math"
|
||||
"math/rand"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
// PopulateNormalHistogram makes a series of normal random observations into the
|
||||
// histogram. The number of observations is determined by Count. The randomness
|
||||
// is determined by Mean, Stdev, and the seed parameter.
|
||||
//
|
||||
// This is a low-level function, exported only for metrics that don't perform
|
||||
// dynamic quantile computation, like a Prometheus Histogram (c.f. Summary). In
|
||||
// most cases, you don't need to use this function, and can use TestHistogram
|
||||
// instead.
|
||||
func PopulateNormalHistogram(h metrics.Histogram, seed int) {
|
||||
r := rand.New(rand.NewSource(int64(seed)))
|
||||
for i := 0; i < Count; i++ {
|
||||
sample := r.NormFloat64()*float64(Stdev) + float64(Mean)
|
||||
if sample < 0 {
|
||||
sample = 0
|
||||
}
|
||||
h.Observe(sample)
|
||||
}
|
||||
}
|
||||
|
||||
func normalQuantiles() (p50, p90, p95, p99 float64) {
|
||||
return nvq(50), nvq(90), nvq(95), nvq(99)
|
||||
}
|
||||
|
||||
func nvq(quantile int) float64 {
|
||||
// https://en.wikipedia.org/wiki/Normal_distribution#Quantile_function
|
||||
return float64(Mean) + float64(Stdev)*math.Sqrt2*erfinv(2*(float64(quantile)/100)-1)
|
||||
}
|
||||
|
||||
func erfinv(y float64) float64 {
|
||||
// https://stackoverflow.com/questions/5971830/need-code-for-inverse-error-function
|
||||
if y < -1.0 || y > 1.0 {
|
||||
panic("invalid input")
|
||||
}
|
||||
|
||||
var (
|
||||
a = [4]float64{0.886226899, -1.645349621, 0.914624893, -0.140543331}
|
||||
b = [4]float64{-2.118377725, 1.442710462, -0.329097515, 0.012229801}
|
||||
c = [4]float64{-1.970840454, -1.624906493, 3.429567803, 1.641345311}
|
||||
d = [2]float64{3.543889200, 1.637067800}
|
||||
)
|
||||
|
||||
const y0 = 0.7
|
||||
var x, z float64
|
||||
|
||||
if math.Abs(y) == 1.0 {
|
||||
x = -y * math.Log(0.0)
|
||||
} else if y < -y0 {
|
||||
z = math.Sqrt(-math.Log((1.0 + y) / 2.0))
|
||||
x = -(((c[3]*z+c[2])*z+c[1])*z + c[0]) / ((d[1]*z+d[0])*z + 1.0)
|
||||
} else {
|
||||
if y < y0 {
|
||||
z = y * y
|
||||
x = y * (((a[3]*z+a[2])*z+a[1])*z + a[0]) / ((((b[3]*z+b[3])*z+b[1])*z+b[0])*z + 1.0)
|
||||
} else {
|
||||
z = math.Sqrt(-math.Log((1.0 - y) / 2.0))
|
||||
x = (((c[3]*z+c[2])*z+c[1])*z + c[0]) / ((d[1]*z+d[0])*z + 1.0)
|
||||
}
|
||||
x = x - (math.Erf(x)-y)/(2.0/math.SqrtPi*math.Exp(-x*x))
|
||||
x = x - (math.Erf(x)-y)/(2.0/math.SqrtPi*math.Exp(-x*x))
|
||||
}
|
||||
|
||||
return x
|
||||
}
|
114
vendor/github.com/go-kit/kit/metrics/teststat/teststat.go
generated
vendored
Normal file
114
vendor/github.com/go-kit/kit/metrics/teststat/teststat.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// Package teststat provides helpers for testing metrics backends.
|
||||
package teststat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
)
|
||||
|
||||
// TestCounter puts some deltas through the counter, and then calls the value
|
||||
// func to check that the counter has the correct final value.
|
||||
func TestCounter(counter metrics.Counter, value func() float64) error {
|
||||
a := rand.Perm(100)
|
||||
n := rand.Intn(len(a))
|
||||
|
||||
var want float64
|
||||
for i := 0; i < n; i++ {
|
||||
f := float64(a[i])
|
||||
counter.Add(f)
|
||||
want += f
|
||||
}
|
||||
|
||||
if have := value(); want != have {
|
||||
return fmt.Errorf("want %f, have %f", want, have)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestGauge puts some values through the gauge, and then calls the value func
|
||||
// to check that the gauge has the correct final value.
|
||||
func TestGauge(gauge metrics.Gauge, value func() float64) error {
|
||||
a := rand.Perm(100)
|
||||
n := rand.Intn(len(a))
|
||||
|
||||
var want float64
|
||||
for i := 0; i < n; i++ {
|
||||
f := float64(a[i])
|
||||
gauge.Set(f)
|
||||
want = f
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
f := float64(a[i])
|
||||
gauge.Add(f)
|
||||
want += f
|
||||
}
|
||||
|
||||
if have := value(); want != have {
|
||||
return fmt.Errorf("want %f, have %f", want, have)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestHistogram puts some observations through the histogram, and then calls
|
||||
// the quantiles func to checks that the histogram has computed the correct
|
||||
// quantiles within some tolerance
|
||||
func TestHistogram(histogram metrics.Histogram, quantiles func() (p50, p90, p95, p99 float64), tolerance float64) error {
|
||||
PopulateNormalHistogram(histogram, rand.Int())
|
||||
|
||||
want50, want90, want95, want99 := normalQuantiles()
|
||||
have50, have90, have95, have99 := quantiles()
|
||||
|
||||
var errs []string
|
||||
if want, have := want50, have50; !cmp(want, have, tolerance) {
|
||||
errs = append(errs, fmt.Sprintf("p50: want %f, have %f", want, have))
|
||||
}
|
||||
if want, have := want90, have90; !cmp(want, have, tolerance) {
|
||||
errs = append(errs, fmt.Sprintf("p90: want %f, have %f", want, have))
|
||||
}
|
||||
if want, have := want95, have95; !cmp(want, have, tolerance) {
|
||||
errs = append(errs, fmt.Sprintf("p95: want %f, have %f", want, have))
|
||||
}
|
||||
if want, have := want99, have99; !cmp(want, have, tolerance) {
|
||||
errs = append(errs, fmt.Sprintf("p99: want %f, have %f", want, have))
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return errors.New(strings.Join(errs, "; "))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
// Count is the number of observations.
|
||||
Count = 12345
|
||||
|
||||
// Mean is the center of the normal distribution of observations.
|
||||
Mean = 500
|
||||
|
||||
// Stdev of the normal distribution of observations.
|
||||
Stdev = 25
|
||||
)
|
||||
|
||||
// ExpectedObservationsLessThan returns the number of observations that should
|
||||
// have a value less than or equal to the given value, given a normal
|
||||
// distribution of observations described by Count, Mean, and Stdev.
|
||||
func ExpectedObservationsLessThan(bucket int64) int64 {
|
||||
// https://code.google.com/p/gostat/source/browse/stat/normal.go
|
||||
cdf := ((1.0 / 2.0) * (1 + math.Erf((float64(bucket)-float64(Mean))/(float64(Stdev)*math.Sqrt2))))
|
||||
return int64(cdf * float64(Count))
|
||||
}
|
||||
|
||||
func cmp(want, have, tol float64) bool {
|
||||
if (math.Abs(want-have) / want) > tol {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
28
vendor/github.com/go-kit/kit/metrics/timer.go
generated
vendored
Normal file
28
vendor/github.com/go-kit/kit/metrics/timer.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
package metrics
|
||||
|
||||
import "time"
|
||||
|
||||
// Timer acts as a stopwatch, sending observations to a wrapped histogram.
|
||||
// It's a bit of helpful syntax sugar for h.Observe(time.Since(x)).
|
||||
type Timer struct {
|
||||
h Histogram
|
||||
t time.Time
|
||||
}
|
||||
|
||||
// NewTimer wraps the given histogram and records the current time.
|
||||
func NewTimer(h Histogram) *Timer {
|
||||
return &Timer{
|
||||
h: h,
|
||||
t: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// ObserveDuration captures the number of seconds since the timer was
|
||||
// constructed, and forwards that observation to the histogram.
|
||||
func (t *Timer) ObserveDuration() {
|
||||
d := time.Since(t.t).Seconds()
|
||||
if d < 0 {
|
||||
d = 0
|
||||
}
|
||||
t.h.Observe(d)
|
||||
}
|
33
vendor/github.com/go-kit/kit/metrics/timer_test.go
generated
vendored
Normal file
33
vendor/github.com/go-kit/kit/metrics/timer_test.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
package metrics_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/go-kit/kit/metrics"
|
||||
"github.com/go-kit/kit/metrics/generic"
|
||||
)
|
||||
|
||||
func TestTimerFast(t *testing.T) {
|
||||
h := generic.NewSimpleHistogram()
|
||||
metrics.NewTimer(h).ObserveDuration()
|
||||
|
||||
tolerance := 0.050
|
||||
if want, have := 0.000, h.ApproximateMovingAverage(); math.Abs(want-have) > tolerance {
|
||||
t.Errorf("want %.3f, have %.3f", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimerSlow(t *testing.T) {
|
||||
h := generic.NewSimpleHistogram()
|
||||
timer := metrics.NewTimer(h)
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
timer.ObserveDuration()
|
||||
|
||||
tolerance := 0.050
|
||||
if want, have := 0.250, h.ApproximateMovingAverage(); math.Abs(want-have) > tolerance {
|
||||
t.Errorf("want %.3f, have %.3f", want, have)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user