Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 3f51e17

Browse files
committed
Skip ConstLabels and namespace prefixes for "up" & internal metrics
Internal metrics consumed by Prometheus such as "up" indicate a status of the target, as either "up" with 1.0 or "down" with 0.0. This change ensures that no ConstLabels, nor namespace prefixes will be added to such metrics. This ensures that when one exports with the special name "up", that it passes through up to the Prometheus exporter, so: # HELP up up # TYPE up counter up{instance="localhost:9999"} 1 instead of: # HELP tests_up tests/up # TYPE tests_up counter tests_up{instance="localhost:9999",service="spanner"} 1 A further assertion can be added to ensure that "up" is a gauge, but I am not sure that it might be necessary, just in case some user wants to expose it as a counter. Updates open-telemetry/prometheus-interoperability-spec#8
1 parent b6359e3 commit 3f51e17

2 files changed

Lines changed: 110 additions & 0 deletions

File tree

prometheus.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,24 @@ func newCollector(opts Options, registrar prometheus.Registerer) *collector {
148148
reader: metricexport.NewReader()}
149149
}
150150

151+
func isInternalMetric(metricName string) bool {
152+
return metricName == "up"
153+
}
154+
151155
func (c *collector) toDesc(metric *metricdata.Metric) *prometheus.Desc {
156+
if isInternalMetric(metric.Descriptor.Name) {
157+
// Internal metrics should not have any namespace
158+
// prefixes, nor any const labels attached to them,
159+
// but instead just set as they are, to allow the exporter
160+
// to operate as if it were a passthrough.
161+
// See https://github.com/open-telemetry/wg-prometheus/issues/8
162+
return prometheus.NewDesc(
163+
metric.Descriptor.Name,
164+
metric.Descriptor.Description,
165+
toPromLabels(metric.Descriptor.LabelKeys),
166+
nil)
167+
}
168+
152169
var labels prometheus.Labels
153170
switch {
154171
case metric.Resource == nil:

prometheus_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,3 +609,96 @@ prom_counter 1
609609
}
610610

611611
}
612+
613+
func TestInternalMetricsWithoutConstLabels(t *testing.T) {
614+
constLabels := prometheus.Labels{
615+
"service": "spanner",
616+
}
617+
instanceLabel, _ := tag.NewKey("instance")
618+
619+
exporter, err := NewExporter(Options{
620+
ConstLabels: constLabels,
621+
})
622+
if err != nil {
623+
t.Fatalf("failed to create prometheus exporter: %v", err)
624+
}
625+
626+
names := []string{"tests/foo", "tests/bar", "tests/baz", "tests/up", "up"}
627+
628+
var measures mSlice
629+
for _, name := range names {
630+
measures.createAndAppend(name, name, "")
631+
}
632+
633+
var vc vCreator
634+
for _, m := range measures {
635+
vc.createAndAppend(m.Name(), m.Description(), []tag.Key{instanceLabel}, m, view.Count())
636+
}
637+
638+
if err := view.Register(vc...); err != nil {
639+
t.Fatalf("failed to create views: %v", err)
640+
}
641+
defer view.Unregister(vc...)
642+
643+
ctx, _ := tag.New(context.Background(), tag.Upsert(instanceLabel, "localhost:9999"))
644+
for _, m := range measures {
645+
stats.Record(ctx, m.M(1))
646+
}
647+
648+
srv := httptest.NewServer(exporter)
649+
defer srv.Close()
650+
651+
var i int
652+
var output string
653+
for {
654+
time.Sleep(10 * time.Millisecond)
655+
if i == 10 {
656+
t.Fatal("no output at /metrics (100ms wait)")
657+
}
658+
i++
659+
660+
resp, err := http.Get(srv.URL)
661+
if err != nil {
662+
t.Fatalf("failed to get /metrics: %v", err)
663+
}
664+
665+
body, err := ioutil.ReadAll(resp.Body)
666+
if err != nil {
667+
t.Fatalf("failed to read body: %v", err)
668+
}
669+
resp.Body.Close()
670+
671+
output = string(body)
672+
if output != "" {
673+
break
674+
}
675+
}
676+
677+
if strings.Contains(output, "collected before with the same name and label values") {
678+
t.Fatal("metric name and labels being duplicated but must be unique")
679+
}
680+
681+
if strings.Contains(output, "error(s) occurred") {
682+
t.Fatal("error reported by prometheus registry")
683+
}
684+
685+
want := `# HELP tests_bar bar
686+
# TYPE tests_bar counter
687+
tests_bar{instance="localhost:9999",service="spanner"} 1
688+
# HELP tests_baz baz
689+
# TYPE tests_baz counter
690+
tests_baz{instance="localhost:9999",service="spanner"} 1
691+
# HELP tests_foo foo
692+
# TYPE tests_foo counter
693+
tests_foo{instance="localhost:9999",service="spanner"} 1
694+
# HELP tests_up tests/up
695+
# TYPE tests_up counter
696+
tests_up{instance="localhost:9999",service="spanner"} 1
697+
# HELP up up
698+
# TYPE up counter
699+
up{instance="localhost:9999"} 1
700+
`
701+
if output != want {
702+
t.Fatalf("output differed from expected\nGot:\n%q\nWant:\n%q", output, want)
703+
}
704+
}

0 commit comments

Comments
 (0)