diff --git a/metrics/expvar/expvar.go b/metrics/expvar/expvar.go index eba74c9..bab7a20 100644 --- a/metrics/expvar/expvar.go +++ b/metrics/expvar/expvar.go @@ -19,6 +19,7 @@ import ( "expvar" "fmt" + "strconv" "sync" "time" @@ -38,15 +39,15 @@ } func (c *counter) With(metrics.Field) metrics.Counter { return c } - -func (c *counter) Add(delta uint64) { c.v.Add(int64(delta)) } +func (c *counter) Add(delta uint64) { c.v.Add(int64(delta)) } type gauge struct { v *expvar.Float } -// NewGauge returns a new Gauge backed by an expvar with the given name. -// Fields are ignored. +// NewGauge returns a new Gauge backed by an expvar with the given name. It +// should be updated manually; for a callback-based approach, see +// NewCallbackGauge. Fields are ignored. func NewGauge(name string) metrics.Gauge { return &gauge{expvar.NewFloat(name)} } @@ -56,6 +57,18 @@ func (g *gauge) Add(delta float64) { g.v.Add(delta) } func (g *gauge) Set(value float64) { g.v.Set(value) } + +// PublishCallbackGauge publishes a Gauge as an expvar with the given name, +// whose value is determined at collect time by the passed callback function. +// The callback determines the value, and fields are ignored, so +// PublishCallbackGauge returns nothing. +func PublishCallbackGauge(name string, callback func() float64) { + expvar.Publish(name, callbackGauge(callback)) +} + +type callbackGauge func() float64 + +func (g callbackGauge) String() string { return strconv.FormatFloat(g(), 'g', -1, 64) } type histogram struct { mu sync.Mutex diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go index 87ef4eb..7bb9933 100644 --- a/metrics/prometheus/prometheus.go +++ b/metrics/prometheus/prometheus.go @@ -111,6 +111,29 @@ g.GaugeVec.With(prometheus.Labels(g.Pairs)).Add(delta) } +// RegisterCallbackGauge registers a Gauge with Prometheus whose value is +// determined at collect time by the passed callback function. The callback +// determines the value, and fields are ignored, so RegisterCallbackGauge +// returns nothing. +func RegisterCallbackGauge(namespace, subsystem, name, help string, callback func() float64) { + RegisterCallbackGaugeWithLabels(namespace, subsystem, name, help, prometheus.Labels{}, callback) +} + +// RegisterCallbackGaugeWithLabels is the same as RegisterCallbackGauge, but +// attaches a set of const label pairs to the metric. +func RegisterCallbackGaugeWithLabels(namespace, subsystem, name, help string, constLabels prometheus.Labels, callback func() float64) { + prometheus.MustRegister(prometheus.NewGaugeFunc( + prometheus.GaugeOpts{ + Namespace: namespace, + Subsystem: subsystem, + Name: name, + Help: help, + ConstLabels: constLabels, + }, + callback, + )) +} + type prometheusHistogram struct { *prometheus.SummaryVec Pairs map[string]string diff --git a/metrics/statsd/statsd.go b/metrics/statsd/statsd.go index 8771a8e..9146f25 100644 --- a/metrics/statsd/statsd.go +++ b/metrics/statsd/statsd.go @@ -30,14 +30,14 @@ type statsdCounter chan string // NewCounter returns a Counter that emits observations in the statsd protocol -// to the passed writer. Observations are buffered for the reporting interval -// or until the buffer exceeds a max packet size, whichever comes first. -// Fields are ignored. +// to the passed writer. Observations are buffered for the report interval or +// until the buffer exceeds a max packet size, whichever comes first. Fields +// are ignored. // // TODO: support for sampling. -func NewCounter(w io.Writer, key string, interval time.Duration) metrics.Counter { +func NewCounter(w io.Writer, key string, reportInterval time.Duration) metrics.Counter { c := make(chan string) - go fwd(w, key, interval, c) + go fwd(w, key, reportInterval, c) return statsdCounter(c) } @@ -48,14 +48,14 @@ type statsdGauge chan string // NewGauge returns a Gauge that emits values in the statsd protocol to the -// passed writer. Values are buffered for the reporting interval or until the +// passed writer. Values are buffered for the report interval or until the // buffer exceeds a max packet size, whichever comes first. Fields are // ignored. // // TODO: support for sampling. -func NewGauge(w io.Writer, key string, interval time.Duration) metrics.Gauge { +func NewGauge(w io.Writer, key string, reportInterval time.Duration) metrics.Gauge { g := make(chan string) - go fwd(w, key, interval, g) + go fwd(w, key, reportInterval, g) return statsdGauge(g) } @@ -72,6 +72,25 @@ func (g statsdGauge) Set(value float64) { g <- fmt.Sprintf("%f|g", value) +} + +// NewCallbackGauge emits values in the statsd protocol to the passed writer. +// It collects values every scrape interval from the callback. Values are +// buffered for the report interval or until the buffer exceeds a max packet +// size, whichever comes first. The report and scrape intervals may be the +// same. Fields are ignored. +func NewCallbackGauge(w io.Writer, key string, reportInterval, scrapeInterval time.Duration, callback func() float64) { + go fwd(w, key, reportInterval, emitEvery(scrapeInterval, callback)) +} + +func emitEvery(d time.Duration, f func() float64) <-chan string { + c := make(chan string) + go func() { + for range time.Tick(d) { + c <- fmt.Sprintf("%f|g", f()) + } + }() + return c } type statsdHistogram chan string @@ -93,9 +112,9 @@ // NewTimeHistogram(statsdHistogram, time.Millisecond) // // TODO: support for sampling. -func NewHistogram(w io.Writer, key string, interval time.Duration) metrics.Histogram { +func NewHistogram(w io.Writer, key string, reportInterval time.Duration) metrics.Histogram { h := make(chan string) - go fwd(w, key, interval, h) + go fwd(w, key, reportInterval, h) return statsdHistogram(h) } @@ -107,9 +126,9 @@ var tick = time.Tick -func fwd(w io.Writer, key string, interval time.Duration, c chan string) { +func fwd(w io.Writer, key string, reportInterval time.Duration, c <-chan string) { buf := &bytes.Buffer{} - tick := tick(interval) + tick := tick(reportInterval) for { select { case s := <-c: