influxdb/kit/metric/client.go

153 lines
3.5 KiB
Go

package metric
import (
"time"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/prometheus/client_golang/prometheus"
)
// REDClient is a metrics client for collection RED metrics.
type REDClient struct {
metrics []metricCollector
}
// New creates a new REDClient.
func New(reg prometheus.Registerer, service string, opts ...ClientOptFn) *REDClient {
opt := metricOpts{
namespace: "service",
service: service,
counterMetrics: map[string]VecOpts{
"call_total": {
Help: "Number of calls",
LabelNames: []string{"method"},
CounterFn: func(vec *prometheus.CounterVec, o CollectFnOpts) {
vec.With(prometheus.Labels{"method": o.Method}).Inc()
},
},
"error_total": {
Help: "Number of errors encountered",
LabelNames: []string{"method", "code"},
CounterFn: func(vec *prometheus.CounterVec, o CollectFnOpts) {
if o.Err != nil {
vec.With(prometheus.Labels{
"method": o.Method,
"code": errors.ErrorCode(o.Err),
}).Inc()
}
},
},
},
histogramMetrics: map[string]VecOpts{
"duration": {
Help: "Duration of calls",
LabelNames: []string{"method"},
HistogramFn: func(vec *prometheus.HistogramVec, o CollectFnOpts) {
vec.
With(prometheus.Labels{"method": o.Method}).
Observe(time.Since(o.Start).Seconds())
},
},
},
}
for _, o := range opts {
o(&opt)
}
client := new(REDClient)
for metricName, vecOpts := range opt.counterMetrics {
client.metrics = append(client.metrics, &counter{
fn: vecOpts.CounterFn,
CounterVec: prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: opt.namespace,
Subsystem: opt.serviceName(),
Name: metricName,
Help: vecOpts.Help,
}, vecOpts.LabelNames),
})
}
for metricName, vecOpts := range opt.histogramMetrics {
client.metrics = append(client.metrics, &histogram{
fn: vecOpts.HistogramFn,
HistogramVec: prometheus.NewHistogramVec(prometheus.HistogramOpts{
Namespace: opt.namespace,
Subsystem: opt.serviceName(),
Name: metricName,
Help: vecOpts.Help,
}, vecOpts.LabelNames),
})
}
reg.MustRegister(client.collectors()...)
return client
}
type RecordFn func(err error, opts ...func(opts *CollectFnOpts)) error
// RecordAdditional provides an extension to the base method, err data provided
// to the metrics.
func RecordAdditional(props map[string]interface{}) func(opts *CollectFnOpts) {
return func(opts *CollectFnOpts) {
opts.AdditionalProps = props
}
}
// Record returns a record fn that is called on any given return err. If an error is encountered
// it will register the err metric. The err is never altered.
func (c *REDClient) Record(method string) RecordFn {
start := time.Now()
return func(err error, opts ...func(opts *CollectFnOpts)) error {
opt := CollectFnOpts{
Method: method,
Start: start,
Err: err,
}
for _, o := range opts {
o(&opt)
}
for _, metric := range c.metrics {
metric.collect(opt)
}
return err
}
}
func (c *REDClient) collectors() []prometheus.Collector {
var collectors []prometheus.Collector
for _, metric := range c.metrics {
collectors = append(collectors, metric)
}
return collectors
}
type metricCollector interface {
prometheus.Collector
collect(o CollectFnOpts)
}
type counter struct {
*prometheus.CounterVec
fn CounterFn
}
func (c *counter) collect(o CollectFnOpts) {
c.fn(c.CounterVec, o)
}
type histogram struct {
*prometheus.HistogramVec
fn HistogramFn
}
func (h *histogram) collect(o CollectFnOpts) {
h.fn(h.HistogramVec, o)
}