Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

prevent conflict metric description #3469

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -53,6 +53,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
- Asynchronous callbacks are only called if they are registered with at least one instrument that does not use drop aggragation. (#3408)
- Do not report empty partial-success responses in the `go.opentelemetry.io/otel/exporters/otlp` exporters. (#3438, #3432)
- Handle partial success responses in `go.opentelemetry.io/otel/exporters/otlp/otlpmetric` exporters. (#3162, #3440)
- Prevent duplicate Prometheus description, unit, and type. (#3469)
- Prevents panic when using incorrect `attribute.Value.As[Type]Slice()`. (#3489)

## Removed
Expand Down
82 changes: 74 additions & 8 deletions exporters/prometheus/exporter.go
Expand Up @@ -16,6 +16,7 @@ package prometheus // import "go.opentelemetry.io/otel/exporters/prometheus"

import (
"context"
"errors"
"fmt"
"sort"
"strings"
Expand All @@ -24,9 +25,12 @@ import (
"unicode/utf8"

"github.com/prometheus/client_golang/prometheus"
dto "github.com/prometheus/client_model/go"
"google.golang.org/protobuf/proto"

"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/internal/global"
"go.opentelemetry.io/otel/metric/unit"
"go.opentelemetry.io/otel/sdk/instrumentation"
"go.opentelemetry.io/otel/sdk/metric"
Expand Down Expand Up @@ -62,6 +66,7 @@ type collector struct {
disableScopeInfo bool
createTargetInfoOnce sync.Once
scopeInfos map[instrumentation.Scope]prometheus.Metric
metricFamilies map[string]*dto.MetricFamily
}

// prometheus counters MUST have a _total suffix:
Expand All @@ -83,6 +88,7 @@ func New(opts ...Option) (*Exporter, error) {
withoutUnits: cfg.withoutUnits,
disableScopeInfo: cfg.disableScopeInfo,
scopeInfos: make(map[instrumentation.Scope]prometheus.Metric),
metricFamilies: make(map[string]*dto.MetricFamily),
}

if err := cfg.registerer.Register(collector); err != nil {
Expand Down Expand Up @@ -149,22 +155,30 @@ func (c *collector) Collect(ch chan<- prometheus.Metric) {
for _, m := range scopeMetrics.Metrics {
switch v := m.Data.(type) {
case metricdata.Histogram:
addHistogramMetric(ch, v, m, keys, values, c.getName(m))
addHistogramMetric(ch, v, m, keys, values, c.getName(m), c.metricFamilies)
case metricdata.Sum[int64]:
addSumMetric(ch, v, m, keys, values, c.getName(m))
addSumMetric(ch, v, m, keys, values, c.getName(m), c.metricFamilies)
case metricdata.Sum[float64]:
addSumMetric(ch, v, m, keys, values, c.getName(m))
addSumMetric(ch, v, m, keys, values, c.getName(m), c.metricFamilies)
case metricdata.Gauge[int64]:
addGaugeMetric(ch, v, m, keys, values, c.getName(m))
addGaugeMetric(ch, v, m, keys, values, c.getName(m), c.metricFamilies)
case metricdata.Gauge[float64]:
addGaugeMetric(ch, v, m, keys, values, c.getName(m))
addGaugeMetric(ch, v, m, keys, values, c.getName(m), c.metricFamilies)
}
}
}
}

func addHistogramMetric(ch chan<- prometheus.Metric, histogram metricdata.Histogram, m metricdata.Metrics, ks, vs [2]string, name string) {
func addHistogramMetric(ch chan<- prometheus.Metric, histogram metricdata.Histogram, m metricdata.Metrics, ks, vs [2]string, name string, mfs map[string]*dto.MetricFamily) {
// TODO(https://github.com/open-telemetry/opentelemetry-go/issues/3163): support exemplars
drop, help := validateMetrics(name, m.Description, dto.MetricType_HISTOGRAM.Enum(), mfs)
if drop {
return
}
if help != "" {
m.Description = help
}

for _, dp := range histogram.DataPoints {
keys, values := getAttrs(dp.Attributes, ks, vs)

Expand All @@ -185,15 +199,26 @@ 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, ks, vs [2]string, name string) {
func addSumMetric[N int64 | float64](ch chan<- prometheus.Metric, sum metricdata.Sum[N], m metricdata.Metrics, ks, vs [2]string, name string, mfs map[string]*dto.MetricFamily) {
valueType := prometheus.CounterValue
metricType := dto.MetricType_COUNTER
if !sum.IsMonotonic {
valueType = prometheus.GaugeValue
metricType = dto.MetricType_GAUGE
}
if sum.IsMonotonic {
// Add _total suffix for counters
name += counterSuffix
}

drop, help := validateMetrics(name, m.Description, metricType.Enum(), mfs)
if drop {
return
}
if help != "" {
m.Description = help
}

for _, dp := range sum.DataPoints {
keys, values := getAttrs(dp.Attributes, ks, vs)

Expand All @@ -207,7 +232,15 @@ 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) {
func addGaugeMetric[N int64 | float64](ch chan<- prometheus.Metric, gauge metricdata.Gauge[N], m metricdata.Metrics, ks, vs [2]string, name string, mfs map[string]*dto.MetricFamily) {
drop, help := validateMetrics(name, m.Description, dto.MetricType_GAUGE.Enum(), mfs)
if drop {
return
}
if help != "" {
m.Description = help
}

for _, dp := range gauge.DataPoints {
keys, values := getAttrs(dp.Attributes, ks, vs)

Expand Down Expand Up @@ -344,3 +377,36 @@ func sanitizeName(n string) string {

return b.String()
}

func validateMetrics(name, description string, metricType *dto.MetricType, mfs map[string]*dto.MetricFamily) (drop bool, help string) {
emf, exist := mfs[name]
if !exist {
mfs[name] = &dto.MetricFamily{
Name: proto.String(name),
Help: proto.String(description),
Type: metricType,
}
return false, ""
}
if emf.GetType() != *metricType {
global.Error(
errors.New("instrument type conflict"),
"Using existing type definition.",
"instrument", name,
"existing", emf.GetType(),
"dropped", *metricType,
)
return true, ""
}
if emf.GetHelp() != description {
global.Info(
"Instrument description conflict, using existing",
"instrument", name,
"existing", emf.GetHelp(),
"dropped", description,
)
return false, emf.GetHelp()
}

fatsheep9146 marked this conversation as resolved.
Show resolved Hide resolved
return false, ""
}