Added "callback" metrics, e.g. GaugeFunc.

Change-Id: I449b558207963ce60572bd04c8102f1db684dd4c
This commit is contained in:
Bjoern Rabenstein 2014-06-23 14:15:35 +02:00
parent 5122dc6cc0
commit f9401ffab9
7 changed files with 161 additions and 4 deletions

View File

@ -147,3 +147,29 @@ func (m *CounterVec) WithLabelValues(lvs ...string) Counter {
func (m *CounterVec) With(labels Labels) Counter { func (m *CounterVec) With(labels Labels) Counter {
return m.MetricVec.With(labels).(Counter) return m.MetricVec.With(labels).(Counter)
} }
// CounterFunc is a Counter whose value is determined at collect time by calling a
// provided function.
//
// To create CounterFunc instances, use NewCounterFunc.
type CounterFunc interface {
Metric
Collector
}
// NewCounterFunc creates a new CounterFunc based on the provided
// CounterOpts. The value reported is determined by calling the given function
// from within the Write method. Take into account that metric collection may
// happen concurrently. If that results in concurrent calls to Write, like in
// the case where a CounterFunc is directly registered with Prometheus, the
// provided function must be concurrency-safe. The function should also honor
// the contract for a Counter (values only go up, not down), but compliance will
// not be checked.
func NewCounterFunc(opts CounterOpts, function func() float64) CounterFunc {
return newValueFunc(NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
opts.Help,
nil,
opts.ConstLabels,
), CounterValue, function)
}

View File

@ -29,14 +29,15 @@ func NewCallbackMetric(desc *prometheus.Desc, callback func() float64) *Callback
return result return result
} }
// TODO: Come up with a better example.
// CallbackMetric is an example for a user-defined Metric that exports the // CallbackMetric is an example for a user-defined Metric that exports the
// result of a function call as a metric of type "untyped" without any // result of a function call as a metric of type "untyped" without any
// labels. It uses SelfCollector to turn the Metric into a Collector so that it // labels. It uses SelfCollector to turn the Metric into a Collector so that it
// can be registered with Prometheus. // can be registered with Prometheus.
// //
// Note that this is a pretty low-level approach. A more high-level approach is // Note that this example is pretty much academic as the prometheus package
// to implement a Collector directly and not an individual Metric, see the // already provides an UntypedFunc type.
// Collector examples.
type CallbackMetric struct { type CallbackMetric struct {
prometheus.SelfCollector prometheus.SelfCollector

View File

@ -18,6 +18,7 @@ import (
"fmt" "fmt"
"math" "math"
"net/http" "net/http"
"runtime"
"sort" "sort"
dto "github.com/prometheus/client_model/go" dto "github.com/prometheus/client_model/go"
@ -72,6 +73,24 @@ func ExampleGaugeVec() {
opsQueued.With(prometheus.Labels{"type": "delete", "user": "alice"}).Inc() opsQueued.With(prometheus.Labels{"type": "delete", "user": "alice"}).Inc()
} }
func ExampleGaugeFunc() {
if _, err := prometheus.Register(prometheus.NewGaugeFunc(
prometheus.GaugeOpts{
Subsystem: "runtime",
Name: "goroutines_count",
Help: "Number of goroutines that currently exist.",
},
func() float64 { return float64(runtime.NumGoroutine()) },
)); err == nil {
fmt.Println("GaugeFunc 'goroutines_count' registered.\n")
}
// Note that the count of goroutines is a gauge (and not a counter) as
// it can go up and down.
// Output:
// GaugeFunc 'goroutines_count' registered.
}
func ExampleCounter() { func ExampleCounter() {
pushCounter := prometheus.NewCounter(prometheus.CounterOpts{ pushCounter := prometheus.NewCounter(prometheus.CounterOpts{
Name: "repository_pushes", // Note: No help string... Name: "repository_pushes", // Note: No help string...

View File

@ -121,3 +121,27 @@ func (m *GaugeVec) WithLabelValues(lvs ...string) Gauge {
func (m *GaugeVec) With(labels Labels) Gauge { func (m *GaugeVec) With(labels Labels) Gauge {
return m.MetricVec.With(labels).(Gauge) return m.MetricVec.With(labels).(Gauge)
} }
// GaugeFunc is a Gauge whose value is determined at collect time by calling a
// provided function.
//
// To create GaugeFunc instances, use NewGaugeFunc.
type GaugeFunc interface {
Metric
Collector
}
// NewGaugeFunc creates a new GaugeFunc based on the provided GaugeOpts. The
// value reported is determined by calling the given function from within the
// Write method. Take into account that metric collection may happen
// concurrently. If that results in concurrent calls to Write, like in the case
// where a GaugeFunc is directly registered with Prometheus, the provided
// function must be concurrency-safe.
func NewGaugeFunc(opts GaugeOpts, function func() float64) GaugeFunc {
return newValueFunc(NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
opts.Help,
nil,
opts.ConstLabels,
), GaugeValue, function)
}

View File

@ -19,6 +19,8 @@ import (
"sync" "sync"
"testing" "testing"
"testing/quick" "testing/quick"
dto "github.com/prometheus/client_model/go"
) )
func listenGaugeStream(vals, result chan float64, done chan struct{}) { func listenGaugeStream(vals, result chan float64, done chan struct{}) {
@ -156,3 +158,25 @@ func TestGaugeVecConcurrency(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
func TestGaugeFunc(t *testing.T) {
gf := NewGaugeFunc(
GaugeOpts{
Name: "test_name",
Help: "test help",
ConstLabels: Labels{"a": "1", "b": "2"},
},
func() float64 { return 3.1415 },
)
if expected, got := `Desc{fqName: "test_name", help: "test help", constLabels: {a="1",b="2"}, variableLabels: []}`, gf.Desc().String(); expected != got {
t.Errorf("expected %q, got %q", expected, got)
}
m := &dto.Metric{}
gf.Write(m)
if expected, got := `label:<name:"a" value:"1" > label:<name:"b" value:"2" > gauge:<value:3.1415 > `, m.String(); expected != got {
t.Errorf("expected %q, got %q", expected, got)
}
}

View File

@ -119,3 +119,27 @@ func (m *UntypedVec) WithLabelValues(lvs ...string) Untyped {
func (m *UntypedVec) With(labels Labels) Untyped { func (m *UntypedVec) With(labels Labels) Untyped {
return m.MetricVec.With(labels).(Untyped) return m.MetricVec.With(labels).(Untyped)
} }
// UntypedFunc is an Untyped whose value is determined at collect time by
// calling a provided function.
//
// To create UntypedFunc instances, use NewUntypedFunc.
type UntypedFunc interface {
Metric
Collector
}
// NewUntypedFunc creates a new UntypedFunc based on the provided
// UntypedOpts. The value reported is determined by calling the given function
// from within the Write method. Take into account that metric collection may
// happen concurrently. If that results in concurrent calls to Write, like in
// the case where an UntypedFunc is directly registered with Prometheus, the
// provided function must be concurrency-safe.
func NewUntypedFunc(opts UntypedOpts, function func() float64) UntypedFunc {
return newValueFunc(NewDesc(
BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
opts.Help,
nil,
opts.ConstLabels,
), UntypedValue, function)
}

View File

@ -51,7 +51,7 @@ type value struct {
labelPairs []*dto.LabelPair labelPairs []*dto.LabelPair
} }
// newValue returns a newly allocated Value with the given Desc, ValueType, // newValue returns a newly allocated value with the given Desc, ValueType,
// sample value and label values. It panics if the number of label // sample value and label values. It panics if the number of label
// values is different from the number of variable labels in Desc. // values is different from the number of variable labels in Desc.
func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...string) *value { func newValue(desc *Desc, valueType ValueType, val float64, labelValues ...string) *value {
@ -106,6 +106,45 @@ func (v *value) Write(out *dto.Metric) {
populateMetric(v.valType, val, v.labelPairs, out) populateMetric(v.valType, val, v.labelPairs, out)
} }
// valueFunc is a generic metric for simple values retrieved on collect time
// from a function. It implements Metric and Collector. Its effective type is
// determined by ValueType. This is a low-level building block used by the
// library to back the implementations of CounterFunc, GaugeFunc, and
// UntypedFunc.
type valueFunc struct {
SelfCollector
desc *Desc
valType ValueType
function func() float64
labelPairs []*dto.LabelPair
}
// newValueFunc returns a newly allocated valueFunc with the given Desc and
// ValueType. The value reported is determined by calling the given function
// from within the Write method. Take into account that metric collection may
// happen concurrently. If that results in concurrent calls to Write, like in
// the case where a valueFunc is directly registered with Prometheus, the
// provided function must be concurrency-safe.
func newValueFunc(desc *Desc, valueType ValueType, function func() float64) *valueFunc {
result := &valueFunc{
desc: desc,
valType: valueType,
function: function,
labelPairs: makeLabelPairs(desc, nil),
}
result.Init(result)
return result
}
func (v *valueFunc) Desc() *Desc {
return v.desc
}
func (v *valueFunc) Write(out *dto.Metric) {
populateMetric(v.valType, v.function(), v.labelPairs, out)
}
// NewConstMetric returns a metric with one fixed value that cannot be // NewConstMetric returns a metric with one fixed value that cannot be
// changed. Users of this package will not have much use for it in regular // changed. Users of this package will not have much use for it in regular
// operations. However, when implementing custom Collectors, it is useful as a // operations. However, when implementing custom Collectors, it is useful as a