Skip to content

Commit a6321dd

Browse files
author
beorn7
committed
Create a "merge gatherer"
This allows to finally get rid of the infamous injection hook in the interface. The old SetMetricFamilyInjectionHook still exist as a deprecated function but is now implemented with the new plumbing under the hood. Now that we have multiple Gatherer implementation, I renamed push.Registry to push.FromGatherer. This commit also improves the consistency checks, which happened as a byproduct of the refactoring to allow checking in both the "merge gatherer" Gatherers as well as in the normal Registry.
1 parent 1dc03a7 commit a6321dd

File tree

8 files changed

+465
-215
lines changed

8 files changed

+465
-215
lines changed

prometheus/examples_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
package prometheus_test
1515

1616
import (
17+
"bytes"
1718
"fmt"
1819
"math"
1920
"net/http"
2021
"runtime"
2122
"sort"
23+
"strings"
2224

2325
dto "github.com/prometheus/client_model/go"
26+
"github.com/prometheus/common/expfmt"
2427

2528
"github.com/golang/protobuf/proto"
2629

@@ -638,3 +641,111 @@ func ExampleAlreadyRegisteredError() {
638641
}
639642
}
640643
}
644+
645+
func ExampleGatherers() {
646+
reg := prometheus.NewRegistry()
647+
temp := prometheus.NewGaugeVec(
648+
prometheus.GaugeOpts{
649+
Name: "temperature_kelvin",
650+
Help: "Temperature in Kelvin.",
651+
},
652+
[]string{"location"},
653+
)
654+
reg.MustRegister(temp)
655+
temp.WithLabelValues("outside").Set(273.14)
656+
temp.WithLabelValues("inside").Set(298.44)
657+
658+
var parser expfmt.TextParser
659+
660+
text := `
661+
# TYPE humidity_percent gauge
662+
# HELP humidity_percent Humidity in %.
663+
humidity_percent{location="outside"} 45.4
664+
humidity_percent{location="inside"} 33.2
665+
# TYPE temperature_kelvin gauge
666+
# HELP temperature_kelvin Temperature in Kelvin.
667+
temperature_kelvin{location="somewhere else"} 4.5
668+
`
669+
670+
parseText := func() ([]*dto.MetricFamily, error) {
671+
parsed, err := parser.TextToMetricFamilies(strings.NewReader(text))
672+
if err != nil {
673+
return nil, err
674+
}
675+
var result []*dto.MetricFamily
676+
for _, mf := range parsed {
677+
result = append(result, mf)
678+
}
679+
return result, nil
680+
}
681+
682+
gatherers := prometheus.Gatherers{
683+
reg,
684+
prometheus.GathererFunc(parseText),
685+
}
686+
687+
gathering, err := gatherers.Gather()
688+
if err != nil {
689+
fmt.Println(err)
690+
}
691+
692+
out := &bytes.Buffer{}
693+
for _, mf := range gathering {
694+
if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
695+
panic(err)
696+
}
697+
}
698+
fmt.Print(out.String())
699+
fmt.Println("----------")
700+
701+
// Note how the temperature_kelvin metric family has been merged from
702+
// different sources. Now try
703+
text = `
704+
# TYPE humidity_percent gauge
705+
# HELP humidity_percent Humidity in %.
706+
humidity_percent{location="outside"} 45.4
707+
humidity_percent{location="inside"} 33.2
708+
# TYPE temperature_kelvin gauge
709+
# HELP temperature_kelvin Temperature in Kelvin.
710+
# Duplicate metric:
711+
temperature_kelvin{location="outside"} 265.3
712+
# Wrong labels:
713+
temperature_kelvin 4.5
714+
`
715+
716+
gathering, err = gatherers.Gather()
717+
if err != nil {
718+
fmt.Println(err)
719+
}
720+
// Note that still as many metrics as possible are returned:
721+
out.Reset()
722+
for _, mf := range gathering {
723+
if _, err := expfmt.MetricFamilyToText(out, mf); err != nil {
724+
panic(err)
725+
}
726+
}
727+
fmt.Print(out.String())
728+
729+
// Output:
730+
// # HELP humidity_percent Humidity in %.
731+
// # TYPE humidity_percent gauge
732+
// humidity_percent{location="inside"} 33.2
733+
// humidity_percent{location="outside"} 45.4
734+
// # HELP temperature_kelvin Temperature in Kelvin.
735+
// # TYPE temperature_kelvin gauge
736+
// temperature_kelvin{location="inside"} 298.44
737+
// temperature_kelvin{location="outside"} 273.14
738+
// temperature_kelvin{location="somewhere else"} 4.5
739+
// ----------
740+
// 2 error(s) occurred:
741+
// * collected metric temperature_kelvin label:<name:"location" value:"outside" > gauge:<value:265.3 > was collected before with the same name and label values
742+
// * collected metric temperature_kelvin gauge:<value:4.5 > has label dimensions inconsistent with previously collected metrics in the same metric family
743+
// # HELP humidity_percent Humidity in %.
744+
// # TYPE humidity_percent gauge
745+
// humidity_percent{location="inside"} 33.2
746+
// humidity_percent{location="outside"} 45.4
747+
// # HELP temperature_kelvin Temperature in Kelvin.
748+
// # TYPE temperature_kelvin gauge
749+
// temperature_kelvin{location="inside"} 298.44
750+
// temperature_kelvin{location="outside"} 273.14
751+
}

prometheus/metric.go

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ import (
2222
const separatorByte byte = 255
2323

2424
// A Metric models a single sample value with its meta data being exported to
25-
// Prometheus. Implementers of Metric in this package inclued Gauge, Counter,
26-
// Untyped, and Summary. Users can implement their own Metric types, but that
27-
// should be rarely needed. See the example for SelfCollector, which is also an
28-
// example for a user-implemented Metric.
25+
// Prometheus. Implementations of Metric in this package are Gauge, Counter,
26+
// Histogram, Summary, and Untyped.
2927
type Metric interface {
3028
// Desc returns the descriptor for the Metric. This method idempotently
3129
// returns the same descriptor throughout the lifetime of the
@@ -36,16 +34,18 @@ type Metric interface {
3634
// Write encodes the Metric into a "Metric" Protocol Buffer data
3735
// transmission object.
3836
//
39-
// Implementers of custom Metric types must observe concurrency safety
40-
// as reads of this metric may occur at any time, and any blocking
41-
// occurs at the expense of total performance of rendering all
42-
// registered metrics. Ideally Metric implementations should support
43-
// concurrent readers.
37+
// Metric implementations must observe concurrency safety as reads of
38+
// this metric may occur at any time, and any blocking occurs at the
39+
// expense of total performance of rendering all registered
40+
// metrics. Ideally, Metric implementations should support concurrent
41+
// readers.
4442
//
45-
// While populating dto.Metric, it is recommended to sort labels
46-
// lexicographically. (Implementers may find LabelPairSorter useful for
47-
// that.) Callers of Write should still make sure of sorting if they
48-
// depend on it.
43+
// While populating dto.Metric, it is the responsibility of the
44+
// implementation to ensure validity of the Metric protobuf (like valid
45+
// UTF-8 strings or syntactically valid metric and label names). It is
46+
// recommended to sort labels lexicographically. (Implementers may find
47+
// LabelPairSorter useful for that.) Callers of Write should still make
48+
// sure of sorting if they depend on it.
4949
Write(*dto.Metric) error
5050
// TODO(beorn7): The original rationale of passing in a pre-allocated
5151
// dto.Metric protobuf to save allocations has disappeared. The

prometheus/promhttp/http_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,11 @@ func TestHandlerErrorHandling(t *testing.T) {
8888
ErrorLog: logger,
8989
ErrorHandling: PanicOnError,
9090
})
91-
wantMsg := `error gathering metrics: 1 error(s) occurred:
92-
* error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
91+
wantMsg := `error gathering metrics: error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
9392
`
9493
wantErrorBody := `An error has occurred during metrics gathering:
9594
96-
1 error(s) occurred:
97-
* error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
95+
error collecting metric Desc{fqName: "invalid_metric", help: "not helpful", constLabels: {}, variableLabels: []}: collect error
9896
`
9997
wantOKBody := `# HELP name docstring
10098
# TYPE name counter
@@ -110,10 +108,10 @@ the_count 0
110108
t.Errorf("got HTTP status code %d, want %d", got, want)
111109
}
112110
if got := logBuf.String(); got != wantMsg {
113-
t.Errorf("got log message %q, want %q", got, wantMsg)
111+
t.Errorf("got log message:\n%s\nwant log mesage:\n%s\n", got, wantMsg)
114112
}
115113
if got := writer.Body.String(); got != wantErrorBody {
116-
t.Errorf("got body %q, want %q", got, wantErrorBody)
114+
t.Errorf("got body:\n%s\nwant body:\n%s\n", got, wantErrorBody)
117115
}
118116
logBuf.Reset()
119117
writer.Body.Reset()

prometheus/push/examples_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func ExampleRegistry() {
4646
registry.MustRegister(completionTime)
4747

4848
completionTime.Set(float64(time.Now().Unix()))
49-
if err := push.Registry(
49+
if err := push.FromGatherer(
5050
"db_backup", push.HostnameGroupingKey(),
5151
"http://pushgateway:9091",
5252
registry,

prometheus/push/push.go

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,14 @@ import (
4444

4545
const contentTypeHeader = "Content-Type"
4646

47-
// Registry triggers a metric collection by the provided Gatherer (which is
48-
// usually implemented by a prometheus.Registry, thus the name of the function)
49-
// and pushes all gathered metrics to the Pushgateway specified by url, using
50-
// the provided job name and the (optional) further grouping labels (the
51-
// grouping map may be nil). See the Pushgateway documentation for detailed
52-
// implications of the job and other grouping labels. Neither the job name nor
53-
// any grouping label value may contain a "/". The metrics pushed must not
54-
// contain a job label of their own nor any of the grouping labels.
47+
// FromGatherer triggers a metric collection by the provided Gatherer (which is
48+
// usually implemented by a prometheus.Registry) and pushes all gathered metrics
49+
// to the Pushgateway specified by url, using the provided job name and the
50+
// (optional) further grouping labels (the grouping map may be nil). See the
51+
// Pushgateway documentation for detailed implications of the job and other
52+
// grouping labels. Neither the job name nor any grouping label value may
53+
// contain a "/". The metrics pushed must not contain a job label of their own
54+
// nor any of the grouping labels.
5555
//
5656
// You can use just host:port or ip:port as url, in which case 'http://' is
5757
// added automatically. You can also include the schema in the URL. However, do
@@ -60,18 +60,18 @@ const contentTypeHeader = "Content-Type"
6060
// Note that all previously pushed metrics with the same job and other grouping
6161
// labels will be replaced with the metrics pushed by this call. (It uses HTTP
6262
// method 'PUT' to push to the Pushgateway.)
63-
func Registry(job string, grouping map[string]string, url string, reg prometheus.Gatherer) error {
64-
return push(job, grouping, url, reg, "PUT")
63+
func FromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
64+
return push(job, grouping, url, g, "PUT")
6565
}
6666

67-
// RegistryAdd works like Registry, but only previously pushed metrics with the
68-
// same name (and the same job and other grouping labels) will be replaced. (It
69-
// uses HTTP method 'POST' to push to the Pushgateway.)
70-
func RegistryAdd(job string, grouping map[string]string, url string, reg prometheus.Gatherer) error {
71-
return push(job, grouping, url, reg, "POST")
67+
// AddFromGatherer works like FromGatherer, but only previously pushed metrics
68+
// with the same name (and the same job and other grouping labels) will be
69+
// replaced. (It uses HTTP method 'POST' to push to the Pushgateway.)
70+
func AddFromGatherer(job string, grouping map[string]string, url string, g prometheus.Gatherer) error {
71+
return push(job, grouping, url, g, "POST")
7272
}
7373

74-
func push(job string, grouping map[string]string, pushURL string, reg prometheus.Gatherer, method string) error {
74+
func push(job string, grouping map[string]string, pushURL string, g prometheus.Gatherer, method string) error {
7575
if !strings.Contains(pushURL, "://") {
7676
pushURL = "http://" + pushURL
7777
}
@@ -94,7 +94,7 @@ func push(job string, grouping map[string]string, pushURL string, reg prometheus
9494
}
9595
pushURL = fmt.Sprintf("%s/metrics/job/%s", pushURL, strings.Join(urlComponents, "/"))
9696

97-
mfs, err := reg.Gather()
97+
mfs, err := g.Gather()
9898
if err != nil {
9999
return err
100100
}
@@ -134,14 +134,14 @@ func push(job string, grouping map[string]string, pushURL string, reg prometheus
134134
return nil
135135
}
136136

137-
// Collectors works like Registry, but it does not use a Gatherer. Instead, it
138-
// collects from the provided collectors directly. It is a convenient way to
137+
// Collectors works like FromGatherer, but it does not use a Gatherer. Instead,
138+
// it collects from the provided collectors directly. It is a convenient way to
139139
// push only a few metrics.
140140
func Collectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {
141141
return pushCollectors(job, grouping, url, "PUT", collectors...)
142142
}
143143

144-
// AddCollectors works like RegistryAdd, but it does not use a Gatherer.
144+
// AddCollectors works like AddFromGatherer, but it does not use a Gatherer.
145145
// Instead, it collects from the provided collectors directly. It is a
146146
// convenient way to push only a few metrics.
147147
func AddCollectors(job string, grouping map[string]string, url string, collectors ...prometheus.Collector) error {

prometheus/push/push_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ func TestPush(t *testing.T) {
150150
}
151151

152152
// Push registry, all good.
153-
if err := Registry("testjob", HostnameGroupingKey(), pgwOK.URL, reg); err != nil {
153+
if err := FromGatherer("testjob", HostnameGroupingKey(), pgwOK.URL, reg); err != nil {
154154
t.Fatal(err)
155155
}
156156
if lastMethod != "PUT" {
@@ -161,7 +161,7 @@ func TestPush(t *testing.T) {
161161
}
162162

163163
// PushAdd registry, all good.
164-
if err := RegistryAdd("testjob", map[string]string{"a": "x", "b": "y"}, pgwOK.URL, reg); err != nil {
164+
if err := AddFromGatherer("testjob", map[string]string{"a": "x", "b": "y"}, pgwOK.URL, reg); err != nil {
165165
t.Fatal(err)
166166
}
167167
if lastMethod != "POST" {

0 commit comments

Comments
 (0)