Skip to content

Commit

Permalink
prometheus exporter convert instrumentation scope to otel_scope_info …
Browse files Browse the repository at this point in the history
…metric

Signed-off-by: Ziqi Zhao <zhaoziqi9146@gmail.com>
  • Loading branch information
fatsheep9146 committed Oct 23, 2022
1 parent ccbc38e commit 6310a13
Show file tree
Hide file tree
Showing 11 changed files with 142 additions and 50 deletions.
11 changes: 11 additions & 0 deletions exporters/prometheus/config.go
Expand Up @@ -26,6 +26,7 @@ type config struct {
disableTargetInfo bool
withoutUnits bool
aggregation metric.AggregationSelector
disableScopeInfo bool
}

// newConfig creates a validated config configured with options.
Expand Down Expand Up @@ -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
})
}
70 changes: 62 additions & 8 deletions exporters/prometheus/exporter.go
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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:
Expand All @@ -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 {
Expand Down Expand Up @@ -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))

Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion exporters/prometheus/exporter_test.go
Expand Up @@ -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)

Expand Down
7 changes: 5 additions & 2 deletions 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
5 changes: 4 additions & 1 deletion 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
5 changes: 4 additions & 1 deletion 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
5 changes: 4 additions & 1 deletion 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
29 changes: 16 additions & 13 deletions 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
5 changes: 4 additions & 1 deletion 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
Expand Down
45 changes: 24 additions & 21 deletions 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
5 changes: 4 additions & 1 deletion 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

0 comments on commit 6310a13

Please sign in to comment.