Codebase list golang-github-go-kit-kit / a1d8b3f metrics / prometheus / prometheus.go
a1d8b3f

Tree @a1d8b3f (Download .tar.gz)

prometheus.go @a1d8b3fraw · history · blame

// Package prometheus implements a Prometheus backend for package metrics.
package prometheus

import (
	"time"

	"github.com/prometheus/client_golang/prometheus"

	"github.com/peterbourgon/gokit/metrics"
)

// Prometheus has strong opinions about the dimensionality of fields. Users
// must predeclare every field key they intend to use. On every observation,
// fields with keys that haven't been predeclared will be silently dropped,
// and predeclared field keys without values will receive the value
// PrometheusLabelValueUnknown.
var PrometheusLabelValueUnknown = "unknown"

type prometheusCounter struct {
	*prometheus.CounterVec
	Pairs map[string]string
}

// NewCounter returns a new Counter backed by a Prometheus metric. The counter
// is automatically registered via prometheus.Register.
func NewCounter(namespace, subsystem, name, help string, fieldKeys []string) metrics.Counter {
	return NewCounterWithLabels(namespace, subsystem, name, help, fieldKeys, prometheus.Labels{})
}

// NewCounterWithLabels is the same as NewCounter, but attaches a set of const
// label pairs to the metric.
func NewCounterWithLabels(namespace, subsystem, name, help string, fieldKeys []string, constLabels prometheus.Labels) metrics.Counter {
	m := prometheus.NewCounterVec(
		prometheus.CounterOpts{
			Namespace:   namespace,
			Subsystem:   subsystem,
			Name:        name,
			Help:        help,
			ConstLabels: constLabels,
		},
		fieldKeys,
	)
	prometheus.MustRegister(m)

	p := map[string]string{}
	for _, fieldName := range fieldKeys {
		p[fieldName] = PrometheusLabelValueUnknown
	}

	return prometheusCounter{
		CounterVec: m,
		Pairs:      p,
	}
}

func (c prometheusCounter) With(f metrics.Field) metrics.Counter {
	return prometheusCounter{
		CounterVec: c.CounterVec,
		Pairs:      merge(c.Pairs, f),
	}
}

func (c prometheusCounter) Add(delta uint64) {
	c.CounterVec.With(prometheus.Labels(c.Pairs)).Add(float64(delta))
}

type prometheusGauge struct {
	*prometheus.GaugeVec
	Pairs map[string]string
}

// NewGauge returns a new Gauge backed by a Prometheus metric. The gauge is
// automatically registered via prometheus.Register.
func NewGauge(namespace, subsystem, name, help string, fieldKeys []string) metrics.Gauge {
	return NewGaugeWithLabels(namespace, subsystem, name, help, fieldKeys, prometheus.Labels{})
}

// NewGaugeWithLabels is the same as NewGauge, but attaches a set of const
// label pairs to the metric.
func NewGaugeWithLabels(namespace, subsystem, name, help string, fieldKeys []string, constLabels prometheus.Labels) metrics.Gauge {
	m := prometheus.NewGaugeVec(
		prometheus.GaugeOpts{
			Namespace:   namespace,
			Subsystem:   subsystem,
			Name:        name,
			Help:        help,
			ConstLabels: constLabels,
		},
		fieldKeys,
	)
	prometheus.MustRegister(m)

	return prometheusGauge{
		GaugeVec: m,
		Pairs:    pairsFrom(fieldKeys),
	}
}

func (g prometheusGauge) With(f metrics.Field) metrics.Gauge {
	return prometheusGauge{
		GaugeVec: g.GaugeVec,
		Pairs:    merge(g.Pairs, f),
	}
}

func (g prometheusGauge) Set(value float64) {
	g.GaugeVec.With(prometheus.Labels(g.Pairs)).Set(value)
}

func (g prometheusGauge) Add(delta float64) {
	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
}

// NewHistogram returns a new Histogram backed by a Prometheus summary. It
// uses a 10-second max age for bucketing. The histogram is automatically
// registered via prometheus.Register.
func NewHistogram(namespace, subsystem, name, help string, fieldKeys []string) metrics.Histogram {
	return NewHistogramWithLabels(namespace, subsystem, name, help, fieldKeys, prometheus.Labels{})
}

// NewHistogramWithLabels is the same as NewHistogram, but attaches a set of
// const label pairs to the metric.
func NewHistogramWithLabels(namespace, subsystem, name, help string, fieldKeys []string, constLabels prometheus.Labels) metrics.Histogram {
	m := prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Namespace:   namespace,
			Subsystem:   subsystem,
			Name:        name,
			Help:        help,
			ConstLabels: constLabels,
			MaxAge:      10 * time.Second,
		},
		fieldKeys,
	)
	prometheus.MustRegister(m)

	return prometheusHistogram{
		SummaryVec: m,
		Pairs:      pairsFrom(fieldKeys),
	}
}

func (h prometheusHistogram) With(f metrics.Field) metrics.Histogram {
	return prometheusHistogram{
		SummaryVec: h.SummaryVec,
		Pairs:      merge(h.Pairs, f),
	}
}

func (h prometheusHistogram) Observe(value int64) {
	h.SummaryVec.With(prometheus.Labels(h.Pairs)).Observe(float64(value))
}

func pairsFrom(fieldKeys []string) map[string]string {
	p := map[string]string{}
	for _, fieldName := range fieldKeys {
		p[fieldName] = PrometheusLabelValueUnknown
	}
	return p
}

func merge(orig map[string]string, f metrics.Field) map[string]string {
	if _, ok := orig[f.Key]; !ok {
		return orig
	}

	newPairs := make(map[string]string, len(orig))
	for k, v := range orig {
		newPairs[k] = v
	}

	newPairs[f.Key] = f.Value
	return newPairs
}