Replace Metrics with Collectors

- use MetricVec family instead of Metric
- dynamic label values instead of ConstLabels
- use dto.Metric to gain histrogram value in exporter_test
- remove hash calculations

Signed-off-by: Ivan Mikheykin <ivan.mikheykin@flant.com>
This commit is contained in:
Ivan Mikheykin 2018-11-27 15:41:04 +03:00
parent ef5d2c8a79
commit b638b9d808
2 changed files with 67 additions and 87 deletions

View file

@ -15,20 +15,17 @@ package main
import ( import (
"bufio" "bufio"
"bytes"
"encoding/binary"
"fmt" "fmt"
"hash/fnv"
"io" "io"
"net" "net"
"regexp" "regexp"
"sort"
"strconv" "strconv"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/log" "github.com/prometheus/common/log"
"github.com/prometheus/common/model"
"github.com/prometheus/statsd_exporter/pkg/mapper" "github.com/prometheus/statsd_exporter/pkg/mapper"
) )
@ -42,96 +39,82 @@ const (
var ( var (
illegalCharsRE = regexp.MustCompile(`[^a-zA-Z0-9_]`) illegalCharsRE = regexp.MustCompile(`[^a-zA-Z0-9_]`)
hash = fnv.New64a()
strBuf bytes.Buffer // Used for hashing.
intBuf = make([]byte, 8)
) )
// hashNameAndLabels returns a hash value of the provided name string and all func labelsNames(labels prometheus.Labels) []string {
// the label names and values in the provided labels map. names := make([]string, 0, len(labels))
// for labelName := range labels {
// Not safe for concurrent use! (Uses a shared buffer and hasher to save on names = append(names, labelName)
// allocations.) }
func hashNameAndLabels(name string, labels prometheus.Labels) uint64 { sort.Strings(names)
hash.Reset() return names
strBuf.Reset()
strBuf.WriteString(name)
hash.Write(strBuf.Bytes())
binary.BigEndian.PutUint64(intBuf, model.LabelsToSignature(labels))
hash.Write(intBuf)
return hash.Sum64()
} }
type CounterContainer struct { type CounterContainer struct {
Elements map[uint64]prometheus.Counter // metric name
Elements map[string]*prometheus.CounterVec
} }
func NewCounterContainer() *CounterContainer { func NewCounterContainer() *CounterContainer {
return &CounterContainer{ return &CounterContainer{
Elements: make(map[uint64]prometheus.Counter), Elements: make(map[string]*prometheus.CounterVec),
} }
} }
func (c *CounterContainer) Get(metricName string, labels prometheus.Labels, help string) (prometheus.Counter, error) { func (c *CounterContainer) Get(metricName string, labels prometheus.Labels, help string) (prometheus.Counter, error) {
hash := hashNameAndLabels(metricName, labels) counterVec, ok := c.Elements[metricName]
counter, ok := c.Elements[hash]
if !ok { if !ok {
counter = prometheus.NewCounter(prometheus.CounterOpts{ counterVec = prometheus.NewCounterVec(prometheus.CounterOpts{
Name: metricName, Name: metricName,
Help: help, Help: help,
ConstLabels: labels, }, labelsNames(labels))
}) if err := prometheus.Register(counterVec); err != nil {
if err := prometheus.Register(counter); err != nil {
return nil, err return nil, err
} }
c.Elements[hash] = counter c.Elements[metricName] = counterVec
} }
return counter, nil return counterVec.GetMetricWith(labels)
} }
type GaugeContainer struct { type GaugeContainer struct {
Elements map[uint64]prometheus.Gauge Elements map[string]*prometheus.GaugeVec
} }
func NewGaugeContainer() *GaugeContainer { func NewGaugeContainer() *GaugeContainer {
return &GaugeContainer{ return &GaugeContainer{
Elements: make(map[uint64]prometheus.Gauge), Elements: make(map[string]*prometheus.GaugeVec),
} }
} }
func (c *GaugeContainer) Get(metricName string, labels prometheus.Labels, help string) (prometheus.Gauge, error) { func (c *GaugeContainer) Get(metricName string, labels prometheus.Labels, help string) (prometheus.Gauge, error) {
hash := hashNameAndLabels(metricName, labels) gaugeVec, ok := c.Elements[metricName]
gauge, ok := c.Elements[hash]
if !ok { if !ok {
gauge = prometheus.NewGauge(prometheus.GaugeOpts{ gaugeVec = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Name: metricName, Name: metricName,
Help: help, Help: help,
ConstLabels: labels, }, labelsNames(labels))
}) if err := prometheus.Register(gaugeVec); err != nil {
if err := prometheus.Register(gauge); err != nil {
return nil, err return nil, err
} }
c.Elements[hash] = gauge c.Elements[metricName] = gaugeVec
} }
return gauge, nil return gaugeVec.GetMetricWith(labels)
} }
type SummaryContainer struct { type SummaryContainer struct {
Elements map[uint64]prometheus.Summary Elements map[string]*prometheus.SummaryVec
mapper *mapper.MetricMapper mapper *mapper.MetricMapper
} }
func NewSummaryContainer(mapper *mapper.MetricMapper) *SummaryContainer { func NewSummaryContainer(mapper *mapper.MetricMapper) *SummaryContainer {
return &SummaryContainer{ return &SummaryContainer{
Elements: make(map[uint64]prometheus.Summary), Elements: make(map[string]*prometheus.SummaryVec),
mapper: mapper, mapper: mapper,
} }
} }
func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping) (prometheus.Summary, error) { func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping) (prometheus.Summary, error) {
hash := hashNameAndLabels(metricName, labels) summaryVec, ok := c.Elements[metricName]
summary, ok := c.Elements[hash]
if !ok { if !ok {
quantiles := c.mapper.Defaults.Quantiles quantiles := c.mapper.Defaults.Quantiles
if mapping != nil && mapping.Quantiles != nil && len(mapping.Quantiles) > 0 { if mapping != nil && mapping.Quantiles != nil && len(mapping.Quantiles) > 0 {
@ -141,54 +124,51 @@ func (c *SummaryContainer) Get(metricName string, labels prometheus.Labels, help
for _, q := range quantiles { for _, q := range quantiles {
objectives[q.Quantile] = q.Error objectives[q.Quantile] = q.Error
} }
summary = prometheus.NewSummary( summaryVec = prometheus.NewSummaryVec(
prometheus.SummaryOpts{ prometheus.SummaryOpts{
Name: metricName, Name: metricName,
Help: help, Help: help,
ConstLabels: labels, Objectives: objectives,
Objectives: objectives, }, labelsNames(labels))
}) if err := prometheus.Register(summaryVec); err != nil {
if err := prometheus.Register(summary); err != nil {
return nil, err return nil, err
} }
c.Elements[hash] = summary c.Elements[metricName] = summaryVec
} }
return summary, nil return summaryVec.GetMetricWith(labels)
} }
type HistogramContainer struct { type HistogramContainer struct {
Elements map[uint64]prometheus.Histogram Elements map[string]*prometheus.HistogramVec
mapper *mapper.MetricMapper mapper *mapper.MetricMapper
} }
func NewHistogramContainer(mapper *mapper.MetricMapper) *HistogramContainer { func NewHistogramContainer(mapper *mapper.MetricMapper) *HistogramContainer {
return &HistogramContainer{ return &HistogramContainer{
Elements: make(map[uint64]prometheus.Histogram), Elements: make(map[string]*prometheus.HistogramVec),
mapper: mapper, mapper: mapper,
} }
} }
func (c *HistogramContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping) (prometheus.Histogram, error) { func (c *HistogramContainer) Get(metricName string, labels prometheus.Labels, help string, mapping *mapper.MetricMapping) (prometheus.Histogram, error) {
hash := hashNameAndLabels(metricName, labels) histogramVec, ok := c.Elements[metricName]
histogram, ok := c.Elements[hash]
if !ok { if !ok {
buckets := c.mapper.Defaults.Buckets buckets := c.mapper.Defaults.Buckets
if mapping != nil && mapping.Buckets != nil && len(mapping.Buckets) > 0 { if mapping != nil && mapping.Buckets != nil && len(mapping.Buckets) > 0 {
buckets = mapping.Buckets buckets = mapping.Buckets
} }
histogram = prometheus.NewHistogram( histogramVec := prometheus.NewHistogramVec(
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Name: metricName, Name: metricName,
Help: help, Help: help,
ConstLabels: labels, Buckets: buckets,
Buckets: buckets, }, labelsNames(labels))
}) if err := prometheus.Register(histogramVec); err != nil {
c.Elements[hash] = histogram
if err := prometheus.Register(histogram); err != nil {
return nil, err return nil, err
} }
c.Elements[metricName] = histogramVec
} }
return histogram, nil return histogramVec.GetMetricWith(labels)
} }
type Event interface { type Event interface {

View file

@ -20,6 +20,7 @@ import (
"time" "time"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"github.com/prometheus/statsd_exporter/pkg/mapper" "github.com/prometheus/statsd_exporter/pkg/mapper"
) )
@ -78,16 +79,6 @@ func TestInvalidUtf8InDatadogTagValue(t *testing.T) {
} }
} }
type MockHistogram struct {
prometheus.Metric
prometheus.Collector
value float64
}
func (h *MockHistogram) Observe(n float64) {
h.value = n
}
func TestHistogramUnits(t *testing.T) { func TestHistogramUnits(t *testing.T) {
events := make(chan Events, 1) events := make(chan Events, 1)
name := "foo" name := "foo"
@ -106,14 +97,23 @@ func TestHistogramUnits(t *testing.T) {
time.Sleep(time.Millisecond * 100) time.Sleep(time.Millisecond * 100)
close(events) close(events)
}() }()
mock := &MockHistogram{}
key := hashNameAndLabels(name, nil)
ex.Histograms.Elements[key] = mock
ex.Listen(events) ex.Listen(events)
if mock.value == 300 {
histogram, err := ex.Histograms.Get(name, prometheus.Labels{}, "", nil)
if err != nil {
t.Fatalf("Histogram not registered")
}
// check the state of the histogram by
// (ab)using its Write method (which is usually only used by Prometheus internally).
metric := &dto.Metric{}
histogram.Write(metric)
value := *metric.Histogram.SampleSum
if value == 300 {
t.Fatalf("Histogram observations not scaled into Seconds") t.Fatalf("Histogram observations not scaled into Seconds")
} else if mock.value != .300 { } else if value != .300 {
t.Fatalf("Received unexpected value for histogram observation %f != .300", mock.value) t.Fatalf("Received unexpected value for histogram observation %f != .300", value)
} }
} }