From b806238cc9d4343964f9aebf7508a1a29987942d Mon Sep 17 00:00:00 2001 From: Ziqi Zhao Date: Tue, 18 Oct 2022 13:07:08 +0800 Subject: [PATCH 1/5] prometheus exporter convert instrumentation scope to otel_scope_info metric Signed-off-by: Ziqi Zhao --- CHANGELOG.md | 2 + exporters/prometheus/config.go | 11 +++ exporters/prometheus/exporter.go | 65 ++++++++++++++-- exporters/prometheus/exporter_test.go | 75 ++++++++++++++++++- exporters/prometheus/testdata/counter.txt | 7 +- .../prometheus/testdata/custom_resource.txt | 5 +- .../prometheus/testdata/empty_resource.txt | 5 +- exporters/prometheus/testdata/gauge.txt | 5 +- exporters/prometheus/testdata/histogram.txt | 29 +++---- .../prometheus/testdata/sanitized_labels.txt | 5 +- .../prometheus/testdata/sanitized_names.txt | 45 +++++------ .../without_scope_and_target_info.txt | 3 + .../testdata/without_scope_info.txt | 6 ++ .../testdata/without_target_info.txt | 5 +- 14 files changed, 218 insertions(+), 50 deletions(-) create mode 100644 exporters/prometheus/testdata/without_scope_and_target_info.txt create mode 100644 exporters/prometheus/testdata/without_scope_info.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c083fba4d..9a14cff3f83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - The `WithView` `Option` is added to the `go.opentelemetry.io/otel/sdk/metric` package. This option is used to configure the view(s) a `MeterProvider` will use for all `Reader`s that are registered with it. (#3387) +- Add Instrumentation Scope and Version as info metric and label in Prometheus exporter. + This can be disabled using the `WithoutScopeInfo()` option added to that package.(#3273, #3357) ### Changed diff --git a/exporters/prometheus/config.go b/exporters/prometheus/config.go index 31b34ccf426..5d50564d34a 100644 --- a/exporters/prometheus/config.go +++ b/exporters/prometheus/config.go @@ -26,6 +26,7 @@ type config struct { disableTargetInfo bool withoutUnits bool aggregation metric.AggregationSelector + disableScopeInfo bool } // newConfig creates a validated config configured with options. @@ -105,3 +106,13 @@ func WithoutUnits() Option { return cfg }) } + +// WithoutScopeInfo configures the Exporter to not export the otel_scope_info metric. +// If not specified, the Exporter will create a otel_scope_info metric containing +// the metrics' Instrumentation Scope, and also add labels about Instrumentation Scope to all metric points. +func WithoutScopeInfo() Option { + return optionFunc(func(cfg config) config { + cfg.disableScopeInfo = true + return cfg + }) +} diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index 6920d2bb328..3fa7a8da4fe 100644 --- a/exporters/prometheus/exporter.go +++ b/exporters/prometheus/exporter.go @@ -28,6 +28,7 @@ import ( "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric/unit" + "go.opentelemetry.io/otel/sdk/instrumentation" "go.opentelemetry.io/otel/sdk/metric" "go.opentelemetry.io/otel/sdk/metric/metricdata" "go.opentelemetry.io/otel/sdk/resource" @@ -36,8 +37,13 @@ import ( const ( targetInfoMetricName = "target_info" targetInfoDescription = "Target metadata" + + scopeInfoMetricName = "otel_scope_info" + scopeInfoDescription = "Instrumentation Scope metadata" ) +var scopeInfoKeys = [2]string{"otel_scope_name", "otel_scope_version"} + // Exporter is a Prometheus Exporter that embeds the OTel metric.Reader // interface for easy instantiation with a MeterProvider. type Exporter struct { @@ -53,7 +59,9 @@ type collector struct { disableTargetInfo bool withoutUnits bool targetInfo prometheus.Metric + disableScopeInfo bool createTargetInfoOnce sync.Once + scopeInfos map[instrumentation.Scope]prometheus.Metric } // prometheus counters MUST have a _total suffix: @@ -73,6 +81,8 @@ func New(opts ...Option) (*Exporter, error) { reader: reader, disableTargetInfo: cfg.disableTargetInfo, withoutUnits: cfg.withoutUnits, + disableScopeInfo: cfg.disableScopeInfo, + scopeInfos: make(map[instrumentation.Scope]prometheus.Metric), } if err := cfg.registerer.Register(collector); err != nil { @@ -118,28 +128,50 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { if !c.disableTargetInfo { ch <- c.targetInfo } + for _, scopeMetrics := range metrics.ScopeMetrics { + var keys, values [2]string + + if !c.disableScopeInfo { + scopeInfo, ok := c.scopeInfos[scopeMetrics.Scope] + if !ok { + scopeInfo, err = createScopeInfoMetric(scopeMetrics.Scope) + if err != nil { + otel.Handle(err) + } + c.scopeInfos[scopeMetrics.Scope] = scopeInfo + } + ch <- scopeInfo + keys = scopeInfoKeys + values = [2]string{scopeMetrics.Scope.Name, scopeMetrics.Scope.Version} + } + for _, m := range scopeMetrics.Metrics { switch v := m.Data.(type) { case metricdata.Histogram: - addHistogramMetric(ch, v, m, c.getName(m)) + addHistogramMetric(ch, v, m, keys, values, c.getName(m)) case metricdata.Sum[int64]: - addSumMetric(ch, v, m, c.getName(m)) + addSumMetric(ch, v, m, keys, values, c.getName(m)) case metricdata.Sum[float64]: - addSumMetric(ch, v, m, c.getName(m)) + addSumMetric(ch, v, m, keys, values, c.getName(m)) case metricdata.Gauge[int64]: - addGaugeMetric(ch, v, m, c.getName(m)) + addGaugeMetric(ch, v, m, keys, values, c.getName(m)) case metricdata.Gauge[float64]: - addGaugeMetric(ch, v, m, c.getName(m)) + addGaugeMetric(ch, v, m, keys, values, c.getName(m)) } } } } -func addHistogramMetric(ch chan<- prometheus.Metric, histogram metricdata.Histogram, m metricdata.Metrics, name string) { +func addHistogramMetric(ch chan<- prometheus.Metric, histogram metricdata.Histogram, m metricdata.Metrics, ks, vs [2]string, name string) { // TODO(https://github.com/open-telemetry/opentelemetry-go/issues/3163): support exemplars for _, dp := range histogram.DataPoints { keys, values := getAttrs(dp.Attributes) + if ks[0] != "" { + keys = append(keys, ks[:]...) + values = append(values, vs[:]...) + } + desc := prometheus.NewDesc(name, m.Description, keys, nil) buckets := make(map[float64]uint64, len(dp.Bounds)) @@ -157,7 +189,7 @@ func addHistogramMetric(ch chan<- prometheus.Metric, histogram metricdata.Histog } } -func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata.Sum[N], m metricdata.Metrics, name string) { +func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata.Sum[N], m metricdata.Metrics, ks, vs [2]string, name string) { valueType := prometheus.CounterValue if !sum.IsMonotonic { valueType = prometheus.GaugeValue @@ -168,6 +200,11 @@ func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata } for _, dp := range sum.DataPoints { keys, values := getAttrs(dp.Attributes) + if ks[0] != "" { + keys = append(keys, ks[:]...) + values = append(values, vs[:]...) + } + desc := prometheus.NewDesc(name, m.Description, keys, nil) m, err := prometheus.NewConstMetric(desc, valueType, float64(dp.Value), values...) if err != nil { @@ -178,9 +215,14 @@ func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata } } -func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metricdata.Gauge[N], m metricdata.Metrics, name string) { +func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metricdata.Gauge[N], m metricdata.Metrics, ks, vs [2]string, name string) { for _, dp := range gauge.DataPoints { keys, values := getAttrs(dp.Attributes) + if ks[0] != "" { + keys = append(keys, ks[:]...) + values = append(values, vs[:]...) + } + desc := prometheus.NewDesc(name, m.Description, keys, nil) m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(dp.Value), values...) if err != nil { @@ -226,6 +268,13 @@ func (c *collector) createInfoMetric(name, description string, res *resource.Res return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), values...) } +func createScopeInfoMetric(scope instrumentation.Scope) (prometheus.Metric, error) { + + keys := scopeInfoKeys[:] + desc := prometheus.NewDesc(scopeInfoMetricName, scopeInfoDescription, keys, nil) + return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), scope.Name, scope.Version) +} + func sanitizeRune(r rune) rune { if unicode.IsLetter(r) || unicode.IsDigit(r) || r == ':' || r == '_' { return r diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index a21458158c9..8f6a62a4a41 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -221,6 +221,44 @@ func TestPrometheusExporter(t *testing.T) { counter.Add(ctx, 9, attrs...) }, }, + { + name: "without scope_info", + options: []Option{WithoutScopeInfo()}, + expectedFile: "testdata/without_scope_info.txt", + recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { + attrs := []attribute.KeyValue{ + attribute.Key("A").String("B"), + attribute.Key("C").String("D"), + } + gauge, err := meter.SyncInt64().UpDownCounter( + "bar", + instrument.WithDescription("a fun little gauge"), + instrument.WithUnit(unit.Dimensionless), + ) + require.NoError(t, err) + gauge.Add(ctx, 2, attrs...) + gauge.Add(ctx, -1, attrs...) + }, + }, + { + name: "without scope_info and target_info", + options: []Option{WithoutScopeInfo(), WithoutTargetInfo()}, + expectedFile: "testdata/without_scope_and_target_info.txt", + recordMetrics: func(ctx context.Context, meter otelmetric.Meter) { + attrs := []attribute.KeyValue{ + attribute.Key("A").String("B"), + attribute.Key("C").String("D"), + } + counter, err := meter.SyncInt64().Counter( + "bar", + instrument.WithDescription("a fun little counter"), + instrument.WithUnit(unit.Bytes), + ) + require.NoError(t, err) + counter.Add(ctx, 2, attrs...) + counter.Add(ctx, 1, attrs...) + }, + }, } for _, tc := range testCases { @@ -263,7 +301,10 @@ func TestPrometheusExporter(t *testing.T) { metric.WithReader(exporter), metric.WithView(customBucketsView, defaultView), ) - meter := provider.Meter("testmeter") + meter := provider.Meter( + "testmeter", + otelmetric.WithInstrumentationVersion("v0.1.0"), + ) tc.recordMetrics(ctx, meter) @@ -306,3 +347,35 @@ func TestSantitizeName(t *testing.T) { require.Equalf(t, test.want, sanitizeName(test.input), "input: %q", test.input) } } + +// func TestMetricWithSameName(t *testing.T) { +// exporter, err := New() +// assert.NoError(t, err) + +// provider := metric.NewMeterProvider( +// metric.WithReader(exporter), +// ) + +// httpCounter, err := provider.Meter("http"). +// SyncInt64().Counter( +// "error_count", +// instrument.WithUnit(unit.Dimensionless)) +// assert.NoError(t, err) +// httpCounter.Add(context.TODO(), 1, attribute.String("type", "bar1")) +// httpCounter.Add(context.TODO(), 2, attribute.String("type", "bar2")) + +// // sqlCounter, err := provider.Meter("sql"). +// // SyncInt64().UpDownCounter( +// // "error_count", +// // instrument.WithUnit(unit.Dimensionless)) +// // assert.NoError(t, err) +// // sqlCounter.Add(context.TODO(), 1) + +// t.Logf("serving metrics at localhost:2223/metrics") +// http.Handle("/metrics", promhttp.Handler()) +// err = http.ListenAndServe(":2223", nil) +// if err != nil { +// t.Fatalf("error serving http: %v", err) +// return +// } +// } diff --git a/exporters/prometheus/testdata/counter.txt b/exporters/prometheus/testdata/counter.txt index 5da63dd144a..79a1e7a5b37 100755 --- a/exporters/prometheus/testdata/counter.txt +++ b/exporters/prometheus/testdata/counter.txt @@ -1,7 +1,10 @@ # HELP foo_milliseconds_total a simple counter # TYPE foo_milliseconds_total counter -foo_milliseconds_total{A="B",C="D",E="true",F="42"} 24.3 -foo_milliseconds_total{A="D",C="B",E="true",F="42"} 5 +foo_milliseconds_total{A="B",C="D",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 24.3 +foo_milliseconds_total{A="D",C="B",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 5 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/custom_resource.txt b/exporters/prometheus/testdata/custom_resource.txt index 581833b56d0..9b2a19ad480 100755 --- a/exporters/prometheus/testdata/custom_resource.txt +++ b/exporters/prometheus/testdata/custom_resource.txt @@ -1,6 +1,9 @@ # HELP foo_total a simple counter # TYPE foo_total counter -foo_total{A="B",C="D",E="true",F="42"} 24.3 +foo_total{A="B",C="D",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 24.3 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge target_info{A="B",C="D",service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/empty_resource.txt b/exporters/prometheus/testdata/empty_resource.txt index 02c41c6bac6..e313006e34b 100755 --- a/exporters/prometheus/testdata/empty_resource.txt +++ b/exporters/prometheus/testdata/empty_resource.txt @@ -1,6 +1,9 @@ # HELP foo_total a simple counter # TYPE foo_total counter -foo_total{A="B",C="D",E="true",F="42"} 24.3 +foo_total{A="B",C="D",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 24.3 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge target_info 1 diff --git a/exporters/prometheus/testdata/gauge.txt b/exporters/prometheus/testdata/gauge.txt index cd75e5a6507..33d2b218b3f 100644 --- a/exporters/prometheus/testdata/gauge.txt +++ b/exporters/prometheus/testdata/gauge.txt @@ -1,6 +1,9 @@ # HELP bar_ratio a fun little gauge # TYPE bar_ratio gauge -bar_ratio{A="B",C="D"} .75 +bar_ratio{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} .75 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/histogram.txt b/exporters/prometheus/testdata/histogram.txt index 3f4a1366049..f8016f38583 100644 --- a/exporters/prometheus/testdata/histogram.txt +++ b/exporters/prometheus/testdata/histogram.txt @@ -1,18 +1,21 @@ # HELP histogram_baz_bytes a very nice histogram # TYPE histogram_baz_bytes histogram -histogram_baz_bytes_bucket{A="B",C="D",le="0"} 0 -histogram_baz_bytes_bucket{A="B",C="D",le="5"} 0 -histogram_baz_bytes_bucket{A="B",C="D",le="10"} 1 -histogram_baz_bytes_bucket{A="B",C="D",le="25"} 2 -histogram_baz_bytes_bucket{A="B",C="D",le="50"} 2 -histogram_baz_bytes_bucket{A="B",C="D",le="75"} 2 -histogram_baz_bytes_bucket{A="B",C="D",le="100"} 2 -histogram_baz_bytes_bucket{A="B",C="D",le="250"} 4 -histogram_baz_bytes_bucket{A="B",C="D",le="500"} 4 -histogram_baz_bytes_bucket{A="B",C="D",le="1000"} 4 -histogram_baz_bytes_bucket{A="B",C="D",le="+Inf"} 4 -histogram_baz_bytes_sum{A="B",C="D"} 236 -histogram_baz_bytes_count{A="B",C="D"} 4 +histogram_baz_bytes_bucket{A="B",C="D",le="0",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 0 +histogram_baz_bytes_bucket{A="B",C="D",le="5",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 0 +histogram_baz_bytes_bucket{A="B",C="D",le="10",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +histogram_baz_bytes_bucket{A="B",C="D",le="25",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 2 +histogram_baz_bytes_bucket{A="B",C="D",le="50",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 2 +histogram_baz_bytes_bucket{A="B",C="D",le="75",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 2 +histogram_baz_bytes_bucket{A="B",C="D",le="100",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 2 +histogram_baz_bytes_bucket{A="B",C="D",le="250",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 4 +histogram_baz_bytes_bucket{A="B",C="D",le="500",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 4 +histogram_baz_bytes_bucket{A="B",C="D",le="1000",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 4 +histogram_baz_bytes_bucket{A="B",C="D",le="+Inf",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 4 +histogram_baz_bytes_sum{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 236 +histogram_baz_bytes_count{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 4 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/sanitized_labels.txt b/exporters/prometheus/testdata/sanitized_labels.txt index 40be2b01aab..06eee59354e 100755 --- a/exporters/prometheus/testdata/sanitized_labels.txt +++ b/exporters/prometheus/testdata/sanitized_labels.txt @@ -1,6 +1,9 @@ # HELP foo_total a sanitary counter # TYPE foo_total counter -foo_total{A_B="Q",C_D="Y;Z"} 24.3 +foo_total{A_B="Q",C_D="Y;Z",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 24.3 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/sanitized_names.txt b/exporters/prometheus/testdata/sanitized_names.txt index 0d321b3fae9..d87e8101ce2 100644 --- a/exporters/prometheus/testdata/sanitized_names.txt +++ b/exporters/prometheus/testdata/sanitized_names.txt @@ -1,32 +1,35 @@ # HELP bar a fun little gauge # TYPE bar gauge -bar{A="B",C="D"} 75 +bar{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 75 # HELP _0invalid_counter_name_total a counter with an invalid name # TYPE _0invalid_counter_name_total counter -_0invalid_counter_name_total{A="B",C="D"} 100 +_0invalid_counter_name_total{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 100 # HELP invalid_gauge_name a gauge with an invalid name # TYPE invalid_gauge_name gauge -invalid_gauge_name{A="B",C="D"} 100 +invalid_gauge_name{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 100 # HELP invalid_hist_name a histogram with an invalid name # TYPE invalid_hist_name histogram -invalid_hist_name_bucket{A="B",C="D",le="0"} 0 -invalid_hist_name_bucket{A="B",C="D",le="5"} 0 -invalid_hist_name_bucket{A="B",C="D",le="10"} 0 -invalid_hist_name_bucket{A="B",C="D",le="25"} 1 -invalid_hist_name_bucket{A="B",C="D",le="50"} 1 -invalid_hist_name_bucket{A="B",C="D",le="75"} 1 -invalid_hist_name_bucket{A="B",C="D",le="100"} 1 -invalid_hist_name_bucket{A="B",C="D",le="250"} 1 -invalid_hist_name_bucket{A="B",C="D",le="500"} 1 -invalid_hist_name_bucket{A="B",C="D",le="750"} 1 -invalid_hist_name_bucket{A="B",C="D",le="1000"} 1 -invalid_hist_name_bucket{A="B",C="D",le="2500"} 1 -invalid_hist_name_bucket{A="B",C="D",le="5000"} 1 -invalid_hist_name_bucket{A="B",C="D",le="7500"} 1 -invalid_hist_name_bucket{A="B",C="D",le="10000"} 1 -invalid_hist_name_bucket{A="B",C="D",le="+Inf"} 1 -invalid_hist_name_sum{A="B",C="D"} 23 -invalid_hist_name_count{A="B",C="D"} 1 +invalid_hist_name_bucket{A="B",C="D",le="0",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 0 +invalid_hist_name_bucket{A="B",C="D",le="5",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 0 +invalid_hist_name_bucket{A="B",C="D",le="10",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 0 +invalid_hist_name_bucket{A="B",C="D",le="25",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="50",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="75",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="100",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="250",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="500",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="750",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="1000",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="2500",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="5000",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="7500",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="10000",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_bucket{A="B",C="D",le="+Inf",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +invalid_hist_name_sum{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 23 +invalid_hist_name_count{A="B",C="D",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 # HELP target_info Target metadata # TYPE target_info gauge target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/without_scope_and_target_info.txt b/exporters/prometheus/testdata/without_scope_and_target_info.txt new file mode 100644 index 00000000000..9a551fd8ef2 --- /dev/null +++ b/exporters/prometheus/testdata/without_scope_and_target_info.txt @@ -0,0 +1,3 @@ +# HELP bar_bytes_total a fun little counter +# TYPE bar_bytes_total counter +bar_bytes_total{A="B",C="D"} 3 diff --git a/exporters/prometheus/testdata/without_scope_info.txt b/exporters/prometheus/testdata/without_scope_info.txt new file mode 100644 index 00000000000..445743ac9a7 --- /dev/null +++ b/exporters/prometheus/testdata/without_scope_info.txt @@ -0,0 +1,6 @@ +# HELP bar_ratio a fun little gauge +# TYPE bar_ratio gauge +bar_ratio{A="B",C="D"} 1 +# HELP target_info Target metadata +# TYPE target_info gauge +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 diff --git a/exporters/prometheus/testdata/without_target_info.txt b/exporters/prometheus/testdata/without_target_info.txt index b434b536fac..69f0e836688 100755 --- a/exporters/prometheus/testdata/without_target_info.txt +++ b/exporters/prometheus/testdata/without_target_info.txt @@ -1,3 +1,6 @@ # HELP foo_total a simple counter # TYPE foo_total counter -foo_total{A="B",C="D",E="true",F="42"} 24.3 +foo_total{A="B",C="D",E="true",F="42",otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 24.3 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="testmeter",otel_scope_version="v0.1.0"} 1 From dbeea34d421614b8ba8b819fecdd729ca6a5ff3f Mon Sep 17 00:00:00 2001 From: Ziqi Zhao Date: Tue, 1 Nov 2022 13:34:45 +0800 Subject: [PATCH 2/5] fix for commits Signed-off-by: Ziqi Zhao --- exporters/prometheus/exporter.go | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index 3fa7a8da4fe..d7d40122ae3 100644 --- a/exporters/prometheus/exporter.go +++ b/exporters/prometheus/exporter.go @@ -166,11 +166,7 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { func addHistogramMetric(ch chan<- prometheus.Metric, histogram metricdata.Histogram, m metricdata.Metrics, ks, vs [2]string, name string) { // TODO(https://github.com/open-telemetry/opentelemetry-go/issues/3163): support exemplars for _, dp := range histogram.DataPoints { - keys, values := getAttrs(dp.Attributes) - if ks[0] != "" { - keys = append(keys, ks[:]...) - values = append(values, vs[:]...) - } + keys, values := getAttrs(dp.Attributes, ks, vs) desc := prometheus.NewDesc(name, m.Description, keys, nil) buckets := make(map[float64]uint64, len(dp.Bounds)) @@ -199,11 +195,7 @@ func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata name += counterSuffix } for _, dp := range sum.DataPoints { - keys, values := getAttrs(dp.Attributes) - if ks[0] != "" { - keys = append(keys, ks[:]...) - values = append(values, vs[:]...) - } + keys, values := getAttrs(dp.Attributes, ks, vs) desc := prometheus.NewDesc(name, m.Description, keys, nil) m, err := prometheus.NewConstMetric(desc, valueType, float64(dp.Value), values...) @@ -217,11 +209,7 @@ func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metricdata.Gauge[N], m metricdata.Metrics, ks, vs [2]string, name string) { for _, dp := range gauge.DataPoints { - keys, values := getAttrs(dp.Attributes) - if ks[0] != "" { - keys = append(keys, ks[:]...) - values = append(values, vs[:]...) - } + keys, values := getAttrs(dp.Attributes, ks, vs) desc := prometheus.NewDesc(name, m.Description, keys, nil) m, err := prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(dp.Value), values...) @@ -236,7 +224,7 @@ func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metric // getAttrs parses the attribute.Set to two lists of matching Prometheus-style // keys and values. It sanitizes invalid characters and handles duplicate keys // (due to sanitization) by sorting and concatenating the values following the spec. -func getAttrs(attrs attribute.Set) ([]string, []string) { +func getAttrs(attrs attribute.Set, ks, vs [2]string) ([]string, []string) { keysMap := make(map[string][]string) itr := attrs.Iter() for itr.Next() { @@ -259,11 +247,16 @@ func getAttrs(attrs attribute.Set) ([]string, []string) { }) values = append(values, strings.Join(vals, ";")) } + + if ks[0] != "" { + keys = append(keys, ks[:]...) + values = append(values, vs[:]...) + } return keys, values } func (c *collector) createInfoMetric(name, description string, res *resource.Resource) (prometheus.Metric, error) { - keys, values := getAttrs(*res.Set()) + keys, values := getAttrs(*res.Set(), [2]string{}, [2]string{}) desc := prometheus.NewDesc(name, description, keys, nil) return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), values...) } From 6025f00dd0f61c7d012eef0082567062f7635c9a Mon Sep 17 00:00:00 2001 From: Ziqi Zhao Date: Tue, 1 Nov 2022 16:20:55 +0800 Subject: [PATCH 3/5] fix for ci failed Signed-off-by: Ziqi Zhao --- exporters/prometheus/exporter.go | 1 - 1 file changed, 1 deletion(-) diff --git a/exporters/prometheus/exporter.go b/exporters/prometheus/exporter.go index d7d40122ae3..2cb23cc19fb 100644 --- a/exporters/prometheus/exporter.go +++ b/exporters/prometheus/exporter.go @@ -262,7 +262,6 @@ func (c *collector) createInfoMetric(name, description string, res *resource.Res } func createScopeInfoMetric(scope instrumentation.Scope) (prometheus.Metric, error) { - keys := scopeInfoKeys[:] desc := prometheus.NewDesc(scopeInfoMetricName, scopeInfoDescription, keys, nil) return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), scope.Name, scope.Version) From 8e216729ce8802f2c2196b77fe002cb1a3ef0ea0 Mon Sep 17 00:00:00 2001 From: Ziqi Zhao Date: Wed, 2 Nov 2022 13:17:18 +0800 Subject: [PATCH 4/5] add multi scopes test Signed-off-by: Ziqi Zhao --- exporters/prometheus/exporter_test.go | 68 +++++++++++-------- .../prometheus/testdata/multi_scopes.txt | 13 ++++ 2 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 exporters/prometheus/testdata/multi_scopes.txt diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index 8f6a62a4a41..59c63926d1d 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -21,6 +21,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/testutil" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/otel/attribute" @@ -348,34 +349,47 @@ func TestSantitizeName(t *testing.T) { } } -// func TestMetricWithSameName(t *testing.T) { -// exporter, err := New() -// assert.NoError(t, err) +func TestMultiScopes(t *testing.T) { + ctx := context.Background() + registry := prometheus.NewRegistry() + exporter, err := New(WithRegisterer(registry)) + require.NoError(t, err) -// provider := metric.NewMeterProvider( -// metric.WithReader(exporter), -// ) + res, err := resource.New(ctx, + // always specify service.name because the default depends on the running OS + resource.WithAttributes(semconv.ServiceNameKey.String("prometheus_test")), + // Overwrite the semconv.TelemetrySDKVersionKey value so we don't need to update every version + resource.WithAttributes(semconv.TelemetrySDKVersionKey.String("latest")), + ) + require.NoError(t, err) + res, err = resource.Merge(resource.Default(), res) -// httpCounter, err := provider.Meter("http"). -// SyncInt64().Counter( -// "error_count", -// instrument.WithUnit(unit.Dimensionless)) -// assert.NoError(t, err) -// httpCounter.Add(context.TODO(), 1, attribute.String("type", "bar1")) -// httpCounter.Add(context.TODO(), 2, attribute.String("type", "bar2")) + provider := metric.NewMeterProvider( + metric.WithReader(exporter), + metric.WithResource(res), + ) -// // sqlCounter, err := provider.Meter("sql"). -// // SyncInt64().UpDownCounter( -// // "error_count", -// // instrument.WithUnit(unit.Dimensionless)) -// // assert.NoError(t, err) -// // sqlCounter.Add(context.TODO(), 1) + fooCounter, err := provider.Meter("meterfoo", otelmetric.WithInstrumentationVersion("v0.1.0")). + SyncInt64().Counter( + "foo", + instrument.WithUnit(unit.Milliseconds), + instrument.WithDescription("meter foo counter")) + assert.NoError(t, err) + fooCounter.Add(ctx, 100, attribute.String("type", "foo")) -// t.Logf("serving metrics at localhost:2223/metrics") -// http.Handle("/metrics", promhttp.Handler()) -// err = http.ListenAndServe(":2223", nil) -// if err != nil { -// t.Fatalf("error serving http: %v", err) -// return -// } -// } + barCounter, err := provider.Meter("meterbar", otelmetric.WithInstrumentationVersion("v0.1.0")). + SyncInt64().Counter( + "bar", + instrument.WithUnit(unit.Milliseconds), + instrument.WithDescription("meter bar counter")) + assert.NoError(t, err) + barCounter.Add(ctx, 200, attribute.String("type", "bar")) + + file, err := os.Open("testdata/multi_scopes.txt") + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, file.Close()) }) + + err = testutil.GatherAndCompare(registry, file) + require.NoError(t, err) + +} diff --git a/exporters/prometheus/testdata/multi_scopes.txt b/exporters/prometheus/testdata/multi_scopes.txt new file mode 100644 index 00000000000..38d84c79ede --- /dev/null +++ b/exporters/prometheus/testdata/multi_scopes.txt @@ -0,0 +1,13 @@ +# HELP bar_milliseconds_total meter bar counter +# TYPE bar_milliseconds_total counter +bar_milliseconds_total{otel_scope_name="meterbar",otel_scope_version="v0.1.0",type="bar"} 200 +# HELP foo_milliseconds_total meter foo counter +# TYPE foo_milliseconds_total counter +foo_milliseconds_total{otel_scope_name="meterfoo",otel_scope_version="v0.1.0",type="foo"} 100 +# HELP otel_scope_info Instrumentation Scope metadata +# TYPE otel_scope_info gauge +otel_scope_info{otel_scope_name="meterfoo",otel_scope_version="v0.1.0"} 1 +otel_scope_info{otel_scope_name="meterbar",otel_scope_version="v0.1.0"} 1 +# HELP target_info Target metadata +# TYPE target_info gauge +target_info{service_name="prometheus_test",telemetry_sdk_language="go",telemetry_sdk_name="opentelemetry",telemetry_sdk_version="latest"} 1 From 138d7f2a67473e18b2c3c72fc75c321b16e07d8c Mon Sep 17 00:00:00 2001 From: Ziqi Zhao Date: Wed, 2 Nov 2022 13:31:18 +0800 Subject: [PATCH 5/5] fix ci failed Signed-off-by: Ziqi Zhao --- exporters/prometheus/exporter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exporters/prometheus/exporter_test.go b/exporters/prometheus/exporter_test.go index 59c63926d1d..f4769ce4ffd 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -363,6 +363,7 @@ func TestMultiScopes(t *testing.T) { ) require.NoError(t, err) res, err = resource.Merge(resource.Default(), res) + require.NoError(t, err) provider := metric.NewMeterProvider( metric.WithReader(exporter), @@ -391,5 +392,4 @@ func TestMultiScopes(t *testing.T) { err = testutil.GatherAndCompare(registry, file) require.NoError(t, err) - }