From d000bdd1194148e9d95a82f38ccc397a1db74c1a Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Mon, 12 Apr 2021 18:56:19 -0700 Subject: [PATCH] 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 https://github.com/open-telemetry/wg-prometheus/issues/8 --- prometheus.go | 17 +++++++++ prometheus_test.go | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/prometheus.go b/prometheus.go index eae231a..9f68fa4 100644 --- a/prometheus.go +++ b/prometheus.go @@ -149,7 +149,24 @@ func newCollector(opts Options, registrar prometheus.Registerer) *collector { reader: metricexport.NewReader()} } +func isInternalMetric(metricName string) bool { + return metricName == "up" +} + func (c *collector) toDesc(metric *metricdata.Metric) *prometheus.Desc { + if isInternalMetric(metric.Descriptor.Name) { + // Internal metrics should not have any namespace + // prefixes, nor any const labels attached to them, + // but instead just set as they are, to allow the exporter + // to operate as if it were a passthrough. + // See https://github.com/open-telemetry/wg-prometheus/issues/8 + return prometheus.NewDesc( + metric.Descriptor.Name, + metric.Descriptor.Description, + toPromLabels(metric.Descriptor.LabelKeys), + nil) + } + return prometheus.NewDesc( metricName(c.opts.Namespace, metric), metric.Descriptor.Description, diff --git a/prometheus_test.go b/prometheus_test.go index f2fe1c0..395a4b4 100644 --- a/prometheus_test.go +++ b/prometheus_test.go @@ -527,3 +527,96 @@ prom_counter 1 } } + +func TestInternalMetricsWithoutConstLabels(t *testing.T) { + constLabels := prometheus.Labels{ + "service": "spanner", + } + instanceLabel, _ := tag.NewKey("instance") + + exporter, err := NewExporter(Options{ + ConstLabels: constLabels, + }) + if err != nil { + t.Fatalf("failed to create prometheus exporter: %v", err) + } + + names := []string{"tests/foo", "tests/bar", "tests/baz", "tests/up", "up"} + + var measures mSlice + for _, name := range names { + measures.createAndAppend(name, name, "") + } + + var vc vCreator + for _, m := range measures { + vc.createAndAppend(m.Name(), m.Description(), []tag.Key{instanceLabel}, m, view.Count()) + } + + if err := view.Register(vc...); err != nil { + t.Fatalf("failed to create views: %v", err) + } + defer view.Unregister(vc...) + + ctx, _ := tag.New(context.Background(), tag.Upsert(instanceLabel, "localhost:9999")) + for _, m := range measures { + stats.Record(ctx, m.M(1)) + } + + srv := httptest.NewServer(exporter) + defer srv.Close() + + var i int + var output string + for { + time.Sleep(10 * time.Millisecond) + if i == 10 { + t.Fatal("no output at /metrics (100ms wait)") + } + i++ + + resp, err := http.Get(srv.URL) + if err != nil { + t.Fatalf("failed to get /metrics: %v", err) + } + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("failed to read body: %v", err) + } + resp.Body.Close() + + output = string(body) + if output != "" { + break + } + } + + if strings.Contains(output, "collected before with the same name and label values") { + t.Fatal("metric name and labels being duplicated but must be unique") + } + + if strings.Contains(output, "error(s) occurred") { + t.Fatal("error reported by prometheus registry") + } + + want := `# HELP tests_bar tests/bar +# TYPE tests_bar counter +tests_bar{instance="localhost:9999",service="spanner"} 1 +# HELP tests_baz tests/baz +# TYPE tests_baz counter +tests_baz{instance="localhost:9999",service="spanner"} 1 +# HELP tests_foo tests/foo +# TYPE tests_foo counter +tests_foo{instance="localhost:9999",service="spanner"} 1 +# HELP tests_up tests/up +# TYPE tests_up counter +tests_up{instance="localhost:9999",service="spanner"} 1 +# HELP up up +# TYPE up counter +up{instance="localhost:9999"} 1 +` + if output != want { + t.Fatalf("output differed from expected\nGot:\n%q\nWant:\n%q", output, want) + } +}