diff --git a/exporters/prometheus/config.go b/exporters/prometheus/config.go index 31b34ccf426..43183928064 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 resource 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 point +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 e51326aebc6..cde1950ad2c 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,6 +37,13 @@ import ( const ( targetInfoMetricName = "target_info" targetInfoDescription = "Target metadata" + + scopeInfoMetricName = "otel_scope_info" + scopeInfoDescription = "Instrumentation Scope metadata" +) + +var ( + scopeInfoKeys = []string{"otel_scope_name", "otel_scope_version"} ) // Exporter is a Prometheus Exporter that embeds the OTel metric.Reader @@ -53,7 +61,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 +83,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 { @@ -119,27 +131,48 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) { ch <- c.targetInfo } for _, scopeMetrics := range metrics.ScopeMetrics { + var keys, values = []string{}, []string{} + + if !c.disableScopeInfo { + var scopeInfo prometheus.Metric + if _, exist := c.scopeInfos[scopeMetrics.Scope]; exist { + scopeInfo = c.scopeInfos[scopeMetrics.Scope] + } else { + scopeInfo, err = createScopeInfoMetric(scopeMetrics.Scope) + if err != nil { + otel.Handle(err) + } + c.scopeInfos[scopeMetrics.Scope] = scopeInfo + } + ch <- scopeInfo + keys = append(keys, scopeInfoKeys...) + values = append(values, getScopeKeyValues(scopeMetrics.Scope)...) + } + 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 []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) + 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 +190,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 []string, name string) { valueType := prometheus.CounterValue if !sum.IsMonotonic { valueType = prometheus.GaugeValue @@ -168,6 +201,9 @@ func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata } for _, dp := range sum.DataPoints { keys, values := getAttrs(dp.Attributes) + 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 +214,12 @@ 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 []string, name string) { for _, dp := range gauge.DataPoints { keys, values := getAttrs(dp.Attributes) + 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 +265,21 @@ func (c *collector) createInfoMetric(name, description string, res *resource.Res return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), values...) } +func getScopeKeyValues(scope instrumentation.Scope) (values []string) { + values = []string{ + scope.Name, + scope.Version, + } + return +} + +func createScopeInfoMetric(scope instrumentation.Scope) (prometheus.Metric, error) { + values := getScopeKeyValues(scope) + + desc := prometheus.NewDesc(scopeInfoMetricName, scopeInfoDescription, scopeInfoKeys, nil) + return prometheus.NewConstMetric(desc, prometheus.GaugeValue, float64(1), values...) +} + 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 27441e74ea1..18dad74414a 100644 --- a/exporters/prometheus/exporter_test.go +++ b/exporters/prometheus/exporter_test.go @@ -262,7 +262,10 @@ func TestPrometheusExporter(t *testing.T) { metric.WithResource(res), metric.WithReader(exporter, customBucketsView, defaultView), ) - meter := provider.Meter("testmeter") + meter := provider.Meter( + "testmeter", + otelmetric.WithInstrumentationVersion("v0.1.0"), + ) tc.recordMetrics(ctx, meter) 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_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