Skip to content

Commit

Permalink
Export MetricVec (again)
Browse files Browse the repository at this point in the history
MetricVec was already exported in early versions of this library, but
nobody really used it to implement vectors of custom Metric
implementations. Now #796 has shown up with a fairly special use case
for which I'd prefer a custom implementation of a special
"auto-sampling histogram" outside of this library. Therefore, I'd like
to reinstate support for creating vectors of custom Metric
implementations.

I played around for quite some while with the option of a separate
package providing the tools one would need to create vectors of custom
Metric implementations. However, with the current structure of the
prometheus/client_golang/prometheus package, this leads to a lot of
complications with circular dependencies. (The new package would need
the primitives from the prometheus package, while the existing metric
vectors like GaugeVec need to import the new vector package to not
duplicate the implementation. Separating vector types from the main
prometheus package is out of the question at this point because that
would be a breaking change.)

Signed-off-by: beorn7 <beorn@grafana.com>
  • Loading branch information
beorn7 committed Sep 10, 2020
1 parent 65c5578 commit d1387ee
Show file tree
Hide file tree
Showing 8 changed files with 305 additions and 68 deletions.
20 changes: 10 additions & 10 deletions prometheus/counter.go
Expand Up @@ -163,7 +163,7 @@ func (c *counter) updateExemplar(v float64, l Labels) {
// (e.g. number of HTTP requests, partitioned by response code and
// method). Create instances with NewCounterVec.
type CounterVec struct {
*metricVec
*MetricVec
}

// NewCounterVec creates a new CounterVec based on the provided CounterOpts and
Expand All @@ -176,19 +176,19 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
opts.ConstLabels,
)
return &CounterVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
if len(lvs) != len(desc.variableLabels) {
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
}
result := &counter{desc: desc, labelPairs: makeLabelPairs(desc, lvs), now: time.Now}
result := &counter{desc: desc, labelPairs: MakeLabelPairs(desc, lvs), now: time.Now}
result.init(result) // Init self-collection.
return result
}),
}
}

// GetMetricWithLabelValues returns the Counter for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of
// values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new Counter is created.
//
// It is possible to call this method without using the returned Counter to only
Expand All @@ -202,7 +202,7 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
// Counter with the same label values is created later.
//
// An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc (minus any curried labels).
// number of variable labels in Desc (minus any curried labels).
//
// Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
Expand All @@ -211,27 +211,27 @@ func NewCounterVec(opts CounterOpts, labelNames []string) *CounterVec {
// with a performance overhead (for creating and processing the Labels map).
// See also the GaugeVec example.
func (v *CounterVec) GetMetricWithLabelValues(lvs ...string) (Counter, error) {
metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
if metric != nil {
return metric.(Counter), err
}
return nil, err
}

// GetMetricWith returns the Counter for the given Labels map (the label names
// must match those of the VariableLabels in Desc). If that label map is
// must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new Counter is created. Implications of
// creating a Counter without using it and keeping the Counter for later use are
// the same as for GetMetricWithLabelValues.
//
// An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc (minus any curried labels).
// with those of the variable labels in Desc (minus any curried labels).
//
// This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods.
func (v *CounterVec) GetMetricWith(labels Labels) (Counter, error) {
metric, err := v.metricVec.getMetricWith(labels)
metric, err := v.MetricVec.GetMetricWith(labels)
if metric != nil {
return metric.(Counter), err
}
Expand Down Expand Up @@ -275,7 +275,7 @@ func (v *CounterVec) With(labels Labels) Counter {
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
func (v *CounterVec) CurryWith(labels Labels) (*CounterVec, error) {
vec, err := v.curryWith(labels)
vec, err := v.MetricVec.CurryWith(labels)
if vec != nil {
return &CounterVec{vec}, err
}
Expand Down
2 changes: 1 addition & 1 deletion prometheus/desc.go
Expand Up @@ -51,7 +51,7 @@ type Desc struct {
// constLabelPairs contains precalculated DTO label pairs based on
// the constant labels.
constLabelPairs []*dto.LabelPair
// VariableLabels contains names of labels for which the metric
// variableLabels contains names of labels for which the metric
// maintains variable values.
variableLabels []string
// id is a hash of the values of the ConstLabels and fqName. This
Expand Down
163 changes: 163 additions & 0 deletions prometheus/example_metricvec_test.go
@@ -0,0 +1,163 @@
// Copyright 2020 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_test

import (
//lint:ignore SA1019 Need to keep deprecated package for compatibility.
"fmt"

"github.com/golang/protobuf/proto"

dto "github.com/prometheus/client_model/go"

"github.com/prometheus/client_golang/prometheus"
)

// Info implements an info pseudo-metric, which is modeled as a Gauge that
// always has a value of 1. In practice, you would just use a Gauge directly,
// but for this example, we pretend it would be useful to have a “native”
// implementation.
type Info struct {
desc *prometheus.Desc
labelPairs []*dto.LabelPair
}

func (i Info) Desc() *prometheus.Desc {
return i.desc
}

func (i Info) Write(out *dto.Metric) error {
out.Label = i.labelPairs
out.Gauge = &dto.Gauge{Value: proto.Float64(1)}
return nil
}

// InfoVec is the vector version for Info. As an info metric never changes, we
// wouldn't really need to wrap GetMetricWithLabelValues and GetMetricWith
// because Info has no additional methods compared to the vanilla Metric that
// the unwrapped MetricVec methods return. However, to demonstrate all there is
// to do to fully implement a vector for a custom Metric implementation, we do
// it in this example anyway.
type InfoVec struct {
*prometheus.MetricVec
}

func NewInfoVec(name, help string, labelNames []string) *InfoVec {
desc := prometheus.NewDesc(name, help, labelNames, nil)
return &InfoVec{
MetricVec: prometheus.NewMetricVec(desc, func(lvs ...string) prometheus.Metric {
if len(lvs) != len(labelNames) {
panic("inconsistent label cardinality")
}
return Info{desc: desc, labelPairs: prometheus.MakeLabelPairs(desc, lvs)}
}),
}
}

func (v *InfoVec) GetMetricWithLabelValues(lvs ...string) (Info, error) {
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
return metric.(Info), err
}

func (v *InfoVec) GetMetricWith(labels prometheus.Labels) (Info, error) {
metric, err := v.MetricVec.GetMetricWith(labels)
return metric.(Info), err
}

func (v *InfoVec) WithLabelValues(lvs ...string) Info {
i, err := v.GetMetricWithLabelValues(lvs...)
if err != nil {
panic(err)
}
return i
}

func (v *InfoVec) With(labels prometheus.Labels) Info {
i, err := v.GetMetricWith(labels)
if err != nil {
panic(err)
}
return i
}

func (v *InfoVec) CurryWith(labels prometheus.Labels) (*InfoVec, error) {
vec, err := v.MetricVec.CurryWith(labels)
if vec != nil {
return &InfoVec{vec}, err
}
return nil, err
}

func (v *InfoVec) MustCurryWith(labels prometheus.Labels) *InfoVec {
vec, err := v.CurryWith(labels)
if err != nil {
panic(err)
}
return vec
}

func ExampleMetricVec() {

infoVec := NewInfoVec(
"library_version_info",
"Versions of the libraries used in this binary.",
[]string{"library", "version"},
)

infoVec.WithLabelValues("prometheus/client_golang", "1.7.1")
infoVec.WithLabelValues("k8s.io/client-go", "0.18.8")

// Just for demonstration, let's check the state of the InfoVec by
// registering it with a custom registry and then let it collect the
// metrics.
reg := prometheus.NewRegistry()
reg.MustRegister(infoVec)

metricFamilies, err := reg.Gather()
if err != nil || len(metricFamilies) != 1 {
panic("unexpected behavior of custom test registry")
}
fmt.Println(proto.MarshalTextString(metricFamilies[0]))

// Output:
// name: "library_version_info"
// help: "Versions of the libraries used in this binary."
// type: GAUGE
// metric: <
// label: <
// name: "library"
// value: "k8s.io/client-go"
// >
// label: <
// name: "version"
// value: "0.18.8"
// >
// gauge: <
// value: 1
// >
// >
// metric: <
// label: <
// name: "library"
// value: "prometheus/client_golang"
// >
// label: <
// name: "version"
// value: "1.7.1"
// >
// gauge: <
// value: 1
// >
// >
}
20 changes: 10 additions & 10 deletions prometheus/gauge.go
Expand Up @@ -132,7 +132,7 @@ func (g *gauge) Write(out *dto.Metric) error {
// (e.g. number of operations queued, partitioned by user and operation
// type). Create instances with NewGaugeVec.
type GaugeVec struct {
*metricVec
*MetricVec
}

// NewGaugeVec creates a new GaugeVec based on the provided GaugeOpts and
Expand All @@ -145,19 +145,19 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
opts.ConstLabels,
)
return &GaugeVec{
metricVec: newMetricVec(desc, func(lvs ...string) Metric {
MetricVec: NewMetricVec(desc, func(lvs ...string) Metric {
if len(lvs) != len(desc.variableLabels) {
panic(makeInconsistentCardinalityError(desc.fqName, desc.variableLabels, lvs))
}
result := &gauge{desc: desc, labelPairs: makeLabelPairs(desc, lvs)}
result := &gauge{desc: desc, labelPairs: MakeLabelPairs(desc, lvs)}
result.init(result) // Init self-collection.
return result
}),
}
}

// GetMetricWithLabelValues returns the Gauge for the given slice of label
// values (same order as the VariableLabels in Desc). If that combination of
// values (same order as the variable labels in Desc). If that combination of
// label values is accessed for the first time, a new Gauge is created.
//
// It is possible to call this method without using the returned Gauge to only
Expand All @@ -172,35 +172,35 @@ func NewGaugeVec(opts GaugeOpts, labelNames []string) *GaugeVec {
// example.
//
// An error is returned if the number of label values is not the same as the
// number of VariableLabels in Desc (minus any curried labels).
// number of variable labels in Desc (minus any curried labels).
//
// Note that for more than one label value, this method is prone to mistakes
// caused by an incorrect order of arguments. Consider GetMetricWith(Labels) as
// an alternative to avoid that type of mistake. For higher label numbers, the
// latter has a much more readable (albeit more verbose) syntax, but it comes
// with a performance overhead (for creating and processing the Labels map).
func (v *GaugeVec) GetMetricWithLabelValues(lvs ...string) (Gauge, error) {
metric, err := v.metricVec.getMetricWithLabelValues(lvs...)
metric, err := v.MetricVec.GetMetricWithLabelValues(lvs...)
if metric != nil {
return metric.(Gauge), err
}
return nil, err
}

// GetMetricWith returns the Gauge for the given Labels map (the label names
// must match those of the VariableLabels in Desc). If that label map is
// must match those of the variable labels in Desc). If that label map is
// accessed for the first time, a new Gauge is created. Implications of
// creating a Gauge without using it and keeping the Gauge for later use are
// the same as for GetMetricWithLabelValues.
//
// An error is returned if the number and names of the Labels are inconsistent
// with those of the VariableLabels in Desc (minus any curried labels).
// with those of the variable labels in Desc (minus any curried labels).
//
// This method is used for the same purpose as
// GetMetricWithLabelValues(...string). See there for pros and cons of the two
// methods.
func (v *GaugeVec) GetMetricWith(labels Labels) (Gauge, error) {
metric, err := v.metricVec.getMetricWith(labels)
metric, err := v.MetricVec.GetMetricWith(labels)
if metric != nil {
return metric.(Gauge), err
}
Expand Down Expand Up @@ -244,7 +244,7 @@ func (v *GaugeVec) With(labels Labels) Gauge {
// registered with a given registry (usually the uncurried version). The Reset
// method deletes all metrics, even if called on a curried vector.
func (v *GaugeVec) CurryWith(labels Labels) (*GaugeVec, error) {
vec, err := v.curryWith(labels)
vec, err := v.MetricVec.CurryWith(labels)
if vec != nil {
return &GaugeVec{vec}, err
}
Expand Down

0 comments on commit d1387ee

Please sign in to comment.