Add wrapping of Registerers with labels and prefix
Essentially middleware for Registerers! Signed-off-by: beorn7 <beorn@soundcloud.com>
This commit is contained in:
parent
7858729281
commit
84d7aa0cd9
|
@ -17,7 +17,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
|
|
@ -539,6 +539,11 @@ func processMetric(
|
|||
registeredDescIDs map[uint64]struct{},
|
||||
) error {
|
||||
desc := metric.Desc()
|
||||
// Wrapped metrics collected by an unchecked Collector can have an
|
||||
// invalid Desc.
|
||||
if desc.err != nil {
|
||||
return desc.err
|
||||
}
|
||||
dtoMetric := &dto.Metric{}
|
||||
if err := metric.Write(dtoMetric); err != nil {
|
||||
return fmt.Errorf("error collecting metric %v: %s", desc, err)
|
||||
|
|
|
@ -17,9 +17,9 @@ import (
|
|||
"fmt"
|
||||
"sort"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
// ValueType is an enumeration of metric types that represent a simple value.
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
// WrapWith returns a Registerer wrapping the provided Registerer. Collectors
|
||||
// registered with the returned Registerer will be registered with the wrapped
|
||||
// Registerer in a modified way. The modified Collector adds the provided Labels
|
||||
// to all Metrics it collects (as ConstLabels). The Metrics collected by the
|
||||
// unmodified Collector must not duplicate any of those labels.
|
||||
//
|
||||
// WrapWith provides a way to add fixed labels to a subset of Collectors. It
|
||||
// should not be used to add fixed labels to all metrics exposed.
|
||||
func WrapWith(labels Labels, reg Registerer) Registerer {
|
||||
return &wrappingRegisterer{
|
||||
wrappedRegisterer: reg,
|
||||
labels: labels,
|
||||
}
|
||||
}
|
||||
|
||||
// WrapWithPrefix returns a Registerer wrapping the provided Registerer. Collectors
|
||||
// registered with the returned Registerer will be registered with the wrapped
|
||||
// Registerer in a modified way. The modified Collector adds the provided prefix
|
||||
// to the name of all Metrics it collects.
|
||||
//
|
||||
// WrapWithPrefix is useful to have one place to prefix all metrics of a
|
||||
// sub-system. To make this work, register metrics of the sub-system with the
|
||||
// wrapping Registerer returned by WrapWithPrefix.
|
||||
func WrapWithPrefix(prefix string, reg Registerer) Registerer {
|
||||
return &wrappingRegisterer{
|
||||
wrappedRegisterer: reg,
|
||||
prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
type wrappingRegisterer struct {
|
||||
wrappedRegisterer Registerer
|
||||
prefix string
|
||||
labels Labels
|
||||
}
|
||||
|
||||
func (r *wrappingRegisterer) Register(c Collector) error {
|
||||
return r.wrappedRegisterer.Register(&wrappingCollector{
|
||||
wrappedCollector: c,
|
||||
prefix: r.prefix,
|
||||
labels: r.labels,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *wrappingRegisterer) MustRegister(cs ...Collector) {
|
||||
for _, c := range cs {
|
||||
if err := r.Register(c); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *wrappingRegisterer) Unregister(c Collector) bool {
|
||||
return r.wrappedRegisterer.Unregister(&wrappingCollector{
|
||||
wrappedCollector: c,
|
||||
prefix: r.prefix,
|
||||
labels: r.labels,
|
||||
})
|
||||
}
|
||||
|
||||
type wrappingCollector struct {
|
||||
wrappedCollector Collector
|
||||
prefix string
|
||||
labels Labels
|
||||
}
|
||||
|
||||
func (c *wrappingCollector) Collect(ch chan<- Metric) {
|
||||
wrappedCh := make(chan Metric)
|
||||
go func() {
|
||||
c.wrappedCollector.Collect(wrappedCh)
|
||||
close(wrappedCh)
|
||||
}()
|
||||
for m := range wrappedCh {
|
||||
ch <- &wrappingMetric{
|
||||
wrappedMetric: m,
|
||||
prefix: c.prefix,
|
||||
labels: c.labels,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *wrappingCollector) Describe(ch chan<- *Desc) {
|
||||
wrappedCh := make(chan *Desc)
|
||||
go func() {
|
||||
c.wrappedCollector.Describe(wrappedCh)
|
||||
close(wrappedCh)
|
||||
}()
|
||||
for desc := range wrappedCh {
|
||||
ch <- wrapDesc(desc, c.prefix, c.labels)
|
||||
}
|
||||
}
|
||||
|
||||
type wrappingMetric struct {
|
||||
wrappedMetric Metric
|
||||
prefix string
|
||||
labels Labels
|
||||
}
|
||||
|
||||
func (m *wrappingMetric) Desc() *Desc {
|
||||
return wrapDesc(m.wrappedMetric.Desc(), m.prefix, m.labels)
|
||||
}
|
||||
|
||||
func (m *wrappingMetric) Write(out *dto.Metric) error {
|
||||
if err := m.wrappedMetric.Write(out); err != nil {
|
||||
return err
|
||||
}
|
||||
if len(m.labels) == 0 {
|
||||
// No wrapping labels.
|
||||
return nil
|
||||
}
|
||||
for ln, lv := range m.labels {
|
||||
out.Label = append(out.Label, &dto.LabelPair{
|
||||
Name: proto.String(ln),
|
||||
Value: proto.String(lv),
|
||||
})
|
||||
}
|
||||
sort.Sort(labelPairSorter(out.Label))
|
||||
return nil
|
||||
}
|
||||
|
||||
func wrapDesc(desc *Desc, prefix string, labels Labels) *Desc {
|
||||
constLabels := Labels{}
|
||||
for _, lp := range desc.constLabelPairs {
|
||||
constLabels[*lp.Name] = *lp.Value
|
||||
}
|
||||
for ln, lv := range labels {
|
||||
if _, alreadyUsed := constLabels[ln]; alreadyUsed {
|
||||
return &Desc{
|
||||
fqName: desc.fqName,
|
||||
help: desc.help,
|
||||
variableLabels: desc.variableLabels,
|
||||
constLabelPairs: desc.constLabelPairs,
|
||||
err: fmt.Errorf("attempted wrapping with already existing label name %q", ln),
|
||||
}
|
||||
}
|
||||
constLabels[ln] = lv
|
||||
}
|
||||
// NewDesc will do remaining validations.
|
||||
newDesc := NewDesc(prefix+desc.fqName, desc.help, desc.variableLabels, constLabels)
|
||||
// Propagate errors if there was any. This will override any errer
|
||||
// created by NewDesc above, i.e. earlier errors get precedence.
|
||||
if desc.err != nil {
|
||||
newDesc.err = desc.err
|
||||
}
|
||||
return newDesc
|
||||
}
|
|
@ -0,0 +1,322 @@
|
|||
// Copyright 2018 The Prometheus Authors
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package prometheus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
dto "github.com/prometheus/client_model/go"
|
||||
)
|
||||
|
||||
// uncheckedCollector wraps a Collector but its Describe method yields no Desc.
|
||||
type uncheckedCollector struct {
|
||||
c Collector
|
||||
}
|
||||
|
||||
func (u uncheckedCollector) Describe(_ chan<- *Desc) {}
|
||||
func (u uncheckedCollector) Collect(c chan<- Metric) {
|
||||
u.c.Collect(c)
|
||||
}
|
||||
|
||||
func toMetricFamilies(cs ...Collector) []*dto.MetricFamily {
|
||||
reg := NewRegistry()
|
||||
reg.MustRegister(cs...)
|
||||
out, err := reg.Gather()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func TestWrap(t *testing.T) {
|
||||
|
||||
simpleCnt := NewCounter(CounterOpts{
|
||||
Name: "simpleCnt",
|
||||
Help: "helpSimpleCnt",
|
||||
})
|
||||
simpleCnt.Inc()
|
||||
|
||||
simpleGge := NewGauge(GaugeOpts{
|
||||
Name: "simpleGge",
|
||||
Help: "helpSimpleGge",
|
||||
})
|
||||
simpleGge.Set(3.14)
|
||||
|
||||
preCnt := NewCounter(CounterOpts{
|
||||
Name: "pre_simpleCnt",
|
||||
Help: "helpSimpleCnt",
|
||||
})
|
||||
preCnt.Inc()
|
||||
|
||||
barLabeledCnt := NewCounter(CounterOpts{
|
||||
Name: "simpleCnt",
|
||||
Help: "helpSimpleCnt",
|
||||
ConstLabels: Labels{"foo": "bar"},
|
||||
})
|
||||
barLabeledCnt.Inc()
|
||||
|
||||
bazLabeledCnt := NewCounter(CounterOpts{
|
||||
Name: "simpleCnt",
|
||||
Help: "helpSimpleCnt",
|
||||
ConstLabels: Labels{"foo": "baz"},
|
||||
})
|
||||
bazLabeledCnt.Inc()
|
||||
|
||||
labeledPreCnt := NewCounter(CounterOpts{
|
||||
Name: "pre_simpleCnt",
|
||||
Help: "helpSimpleCnt",
|
||||
ConstLabels: Labels{"foo": "bar"},
|
||||
})
|
||||
labeledPreCnt.Inc()
|
||||
|
||||
twiceLabeledPreCnt := NewCounter(CounterOpts{
|
||||
Name: "pre_simpleCnt",
|
||||
Help: "helpSimpleCnt",
|
||||
ConstLabels: Labels{"foo": "bar", "dings": "bums"},
|
||||
})
|
||||
twiceLabeledPreCnt.Inc()
|
||||
|
||||
barLabeledUncheckedCollector := uncheckedCollector{barLabeledCnt}
|
||||
|
||||
scenarios := map[string]struct {
|
||||
prefix string // First wrap with this prefix.
|
||||
labels Labels // Then wrap the result with these labels.
|
||||
labels2 Labels // If any, wrap the prefix-wrapped one again.
|
||||
preRegister []Collector
|
||||
toRegister []struct { // If there are any labels2, register every other with that one.
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}
|
||||
gatherFails bool
|
||||
output []Collector
|
||||
}{
|
||||
"wrap nothing": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"foo": "bar"},
|
||||
},
|
||||
"wrap with nothing": {
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, false}},
|
||||
output: []Collector{simpleGge, simpleCnt},
|
||||
},
|
||||
"wrap counter with prefix": {
|
||||
prefix: "pre_",
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, false}},
|
||||
output: []Collector{simpleGge, preCnt},
|
||||
},
|
||||
"wrap counter with label pair": {
|
||||
labels: Labels{"foo": "bar"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, false}},
|
||||
output: []Collector{simpleGge, barLabeledCnt},
|
||||
},
|
||||
"wrap counter with label pair and prefix": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"foo": "bar"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, false}},
|
||||
output: []Collector{simpleGge, labeledPreCnt},
|
||||
},
|
||||
"wrap counter with invalid prefix": {
|
||||
prefix: "1+1",
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, true}},
|
||||
output: []Collector{simpleGge},
|
||||
},
|
||||
"wrap counter with invalid label": {
|
||||
preRegister: []Collector{simpleGge},
|
||||
labels: Labels{"42": "bar"},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, true}},
|
||||
output: []Collector{simpleGge},
|
||||
},
|
||||
"counter registered twice but wrapped with different label values": {
|
||||
labels: Labels{"foo": "bar"},
|
||||
labels2: Labels{"foo": "baz"},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, false}, {simpleCnt, false}},
|
||||
output: []Collector{barLabeledCnt, bazLabeledCnt},
|
||||
},
|
||||
"counter registered twice but wrapped with different inconsistent label values": {
|
||||
labels: Labels{"foo": "bar"},
|
||||
labels2: Labels{"bar": "baz"},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, false}, {simpleCnt, true}},
|
||||
output: []Collector{barLabeledCnt},
|
||||
},
|
||||
"wrap counter with prefix and two labels": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"foo": "bar", "dings": "bums"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{simpleCnt, false}},
|
||||
output: []Collector{simpleGge, twiceLabeledPreCnt},
|
||||
},
|
||||
"wrap labeled counter with prefix and another label": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"dings": "bums"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{barLabeledCnt, false}},
|
||||
output: []Collector{simpleGge, twiceLabeledPreCnt},
|
||||
},
|
||||
"wrap labeled counter with prefix and inconsistent label": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"foo": "bums"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{barLabeledCnt, true}},
|
||||
output: []Collector{simpleGge},
|
||||
},
|
||||
"wrap labeled counter with prefix and the same label again": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"foo": "bar"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{barLabeledCnt, true}},
|
||||
output: []Collector{simpleGge},
|
||||
},
|
||||
"wrap labeled unchecked collector with prefix and another label": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"dings": "bums"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{barLabeledUncheckedCollector, false}},
|
||||
output: []Collector{simpleGge, twiceLabeledPreCnt},
|
||||
},
|
||||
"wrap labeled unchecked collector with prefix and inconsistent label": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"foo": "bums"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{barLabeledUncheckedCollector, false}},
|
||||
gatherFails: true,
|
||||
output: []Collector{simpleGge},
|
||||
},
|
||||
"wrap labeled unchecked collector with prefix and the same label again": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"foo": "bar"},
|
||||
preRegister: []Collector{simpleGge},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{barLabeledUncheckedCollector, false}},
|
||||
gatherFails: true,
|
||||
output: []Collector{simpleGge},
|
||||
},
|
||||
"wrap labeled unchecked collector with prefix and another label resulting in collision with pre-registered counter": {
|
||||
prefix: "pre_",
|
||||
labels: Labels{"dings": "bums"},
|
||||
preRegister: []Collector{twiceLabeledPreCnt},
|
||||
toRegister: []struct {
|
||||
collector Collector
|
||||
registrationFails bool
|
||||
}{{barLabeledUncheckedCollector, false}},
|
||||
gatherFails: true,
|
||||
output: []Collector{twiceLabeledPreCnt},
|
||||
},
|
||||
}
|
||||
|
||||
for n, s := range scenarios {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
reg := NewPedanticRegistry()
|
||||
for _, c := range s.preRegister {
|
||||
if err := reg.Register(c); err != nil {
|
||||
t.Fatal("error registering with unwrapped registry:", err)
|
||||
}
|
||||
}
|
||||
preReg := WrapWithPrefix(s.prefix, reg)
|
||||
lReg := WrapWith(s.labels, preReg)
|
||||
l2Reg := WrapWith(s.labels2, preReg)
|
||||
for i, tr := range s.toRegister {
|
||||
var err error
|
||||
if i%2 != 0 && len(s.labels2) != 0 {
|
||||
err = l2Reg.Register(tr.collector)
|
||||
} else {
|
||||
err = lReg.Register(tr.collector)
|
||||
}
|
||||
if tr.registrationFails && err == nil {
|
||||
t.Fatalf("registration with wrapping registry unexpectedly succeded for collector #%d", i)
|
||||
}
|
||||
if !tr.registrationFails && err != nil {
|
||||
t.Fatalf("registration with wrapping registry failed for collector #%d: %s", i, err)
|
||||
}
|
||||
}
|
||||
wantMF := toMetricFamilies(s.output...)
|
||||
gotMF, err := reg.Gather()
|
||||
if s.gatherFails && err == nil {
|
||||
t.Fatal("gathering unexpectedly succeded")
|
||||
}
|
||||
if !s.gatherFails && err != nil {
|
||||
t.Fatal("gathering failed:", err)
|
||||
}
|
||||
if !reflect.DeepEqual(gotMF, wantMF) {
|
||||
var want, got []string
|
||||
|
||||
for i, mf := range wantMF {
|
||||
want = append(want, fmt.Sprintf("%3d: %s", i, proto.MarshalTextString(mf)))
|
||||
}
|
||||
for i, mf := range gotMF {
|
||||
got = append(got, fmt.Sprintf("%3d: %s", i, proto.MarshalTextString(mf)))
|
||||
}
|
||||
|
||||
t.Fatalf(
|
||||
"unexpected output of gathering:\n\nWANT:\n%s\n\nGOT:\n%s\n",
|
||||
strings.Join(want, "\n"),
|
||||
strings.Join(got, "\n"),
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue