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

Deduplicate target_info and scope_info #838

Merged
merged 2 commits into from
Apr 23, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 64 additions & 11 deletions exporter/collector/googlemanagedprometheus/extra_metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ func addUntypedMetrics(m pmetric.Metrics) {
}
}

// resourceID identifies a resource. We only send one target info for each
// resourceID.
type resourceID struct {
serviceName, serviceInstanceID, serviceNamespace string
}

// addTargetInfoMetric inserts target_info for each resource.
// First, it extracts the target_info metric from each ResourceMetric associated with the input pmetric.Metrics
// and inserts it into a new ScopeMetric for that resource, as specified in
Expand All @@ -113,10 +119,17 @@ func (c Config) addTargetInfoMetric(m pmetric.Metrics) {
if !c.ExtraMetricsConfig.EnableTargetInfo {
return
}
ids := make(map[resourceID]struct{})
rms := m.ResourceMetrics()
// loop over input (original) resource metrics
for i := 0; i < rms.Len(); i++ {
rm := rms.At(i)
getResourceAttr := func(attr string) string {
if v, ok := rm.Resource().Attributes().Get(attr); ok {
return v.AsString()
}
return ""
}

// Keep track of the most recent time in this resource's metrics
// Use that time for the timestamp of the new metric
Expand Down Expand Up @@ -170,6 +183,17 @@ func (c Config) addTargetInfoMetric(m pmetric.Metrics) {
}
}

id := resourceID{
serviceName: getResourceAttr(semconv.AttributeServiceName),
serviceNamespace: getResourceAttr(semconv.AttributeServiceNamespace),
serviceInstanceID: getResourceAttr(semconv.AttributeServiceInstanceID),
}
if _, ok := ids[id]; ok {
// We've already added a resource with the same ID before, so skip this one.
continue
}
ids[id] = struct{}{}

// create the target_info metric as a Gauge with value 1
targetInfoMetric := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty()
targetInfoMetric.SetName("target_info")
Expand Down Expand Up @@ -201,33 +225,37 @@ func (c Config) addTargetInfoMetric(m pmetric.Metrics) {
}
}

// scopeID identifies a scope. We only send one scope info for each scopeID within a unique resource.
type scopeID struct {
resource resourceID
name, version string
}

// addScopeInfoMetric adds the otel_scope_info metric to a Metrics slice as specified in
// https://github.com/open-telemetry/opentelemetry-specification/blob/v1.16.0/specification/compatibility/prometheus_and_openmetrics.md#instrumentation-scope-1
// It also updates all other metrics with the corresponding scope_name and scope_version attributes, if they are present.
func (c Config) addScopeInfoMetric(m pmetric.Metrics) {
if !c.ExtraMetricsConfig.EnableScopeInfo {
return
}
ids := make(map[scopeID]struct{})
rms := m.ResourceMetrics()
for i := 0; i < rms.Len(); i++ {
sms := rms.At(i).ScopeMetrics()
rm := rms.At(i)
getResourceAttr := func(attr string) string {
if v, ok := rm.Resource().Attributes().Get(attr); ok {
return v.AsString()
}
return ""
}
sms := rm.ScopeMetrics()
for j := 0; j < sms.Len(); j++ {
sm := sms.At(j)
// If not present, skip this scope
if len(sm.Scope().Name()) == 0 && len(sm.Scope().Version()) == 0 {
continue
}

// Add otel_scope_info metric
scopeInfoMetric := sm.Metrics().AppendEmpty()
scopeInfoMetric.SetName("otel_scope_info")
dataPoint := scopeInfoMetric.SetEmptyGauge().DataPoints().AppendEmpty()
dataPoint.SetIntValue(1)
sm.Scope().Attributes().Range(func(k string, v pcommon.Value) bool {
dataPoint.Attributes().PutStr(k, v.AsString())
return true
})

// Keep track of the most recent time in this scope's metrics
// Use that time for the timestamp of the new metric
latestTime := time.Time{}
Expand Down Expand Up @@ -291,7 +319,32 @@ func (c Config) addScopeInfoMetric(m pmetric.Metrics) {
}
}
}
id := scopeID{
resource: resourceID{
serviceName: getResourceAttr(semconv.AttributeServiceName),
serviceNamespace: getResourceAttr(semconv.AttributeServiceNamespace),
serviceInstanceID: getResourceAttr(semconv.AttributeServiceInstanceID),
},
name: sm.Scope().Name(),
version: sm.Scope().Version(),
}
if _, ok := ids[id]; ok {
// We've already added a scope with the same ID before, so skip this one.
continue
}
ids[id] = struct{}{}

// Add otel_scope_info metric
scopeInfoMetric := sm.Metrics().AppendEmpty()
scopeInfoMetric.SetName("otel_scope_info")
dataPoint := scopeInfoMetric.SetEmptyGauge().DataPoints().AppendEmpty()
dataPoint.SetIntValue(1)
sm.Scope().Attributes().Range(func(k string, v pcommon.Value) bool {
dataPoint.Attributes().PutStr(k, v.AsString())
return true
})
dataPoint.Attributes().PutStr("otel_scope_name", sm.Scope().Name())
dataPoint.Attributes().PutStr("otel_scope_version", sm.Scope().Version())
dataPoint.SetTimestamp(pcommon.NewTimestampFromTime(latestTime))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,18 @@ import (
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
semconv "go.opentelemetry.io/collector/semconv/v1.18.0"
)

func testMetric(timestamp time.Time) pmetric.Metrics {
metrics := pmetric.NewMetrics()
return appendMetric(pmetric.NewMetrics(), timestamp)
}

func appendMetric(metrics pmetric.Metrics, timestamp time.Time) pmetric.Metrics {
rm := metrics.ResourceMetrics().AppendEmpty()

// foo-label should be copied to target_info, not locationLabel
rm.Resource().Attributes().PutStr(semconv.AttributeServiceName, "my-service")
rm.Resource().Attributes().PutStr(locationLabel, "us-east")
rm.Resource().Attributes().PutStr("foo-label", "bar")

Expand Down Expand Up @@ -71,6 +76,30 @@ func TestAddExtraMetrics(t *testing.T) {
return metrics
}(),
},
{
name: "deduplicate target info from multiple resource metric",
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableTargetInfo: true}},
input: func() pmetric.Metrics {
metrics := appendMetric(testMetric(timestamp), timestamp)
// Add a non-identifying attribute to the second resource, which should be ignored.
metrics.ResourceMetrics().At(1).Resource().Attributes().PutStr("ignored-label", "ignored-value")
return metrics
}(),
expected: func() pmetric.ResourceMetricsSlice {
metrics := appendMetric(testMetric(timestamp), timestamp).ResourceMetrics()
// Make sure it matches the input
metrics.At(1).Resource().Attributes().PutStr("ignored-label", "ignored-value")

// Insert a new, empty ScopeMetricsSlice for this resource that will hold target_info
sm := metrics.At(0).ScopeMetrics().AppendEmpty()
metric := sm.Metrics().AppendEmpty()
metric.SetName("target_info")
metric.SetEmptyGauge().DataPoints().AppendEmpty().SetIntValue(1)
metric.Gauge().DataPoints().At(0).Attributes().PutStr("foo-label", "bar")
metric.Gauge().DataPoints().At(0).SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
return metrics
}(),
},
{
name: "add scope info from scope metrics",
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableScopeInfo: true}},
Expand Down Expand Up @@ -123,6 +152,57 @@ func TestAddExtraMetrics(t *testing.T) {
return metrics
}(),
},
{
name: "add duplicate scope info with attributes",
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{EnableScopeInfo: true}},
input: func() pmetric.Metrics {
metrics := appendMetric(testMetric(timestamp), timestamp)
// set different attributes on scopes with the same name + version
metrics.ResourceMetrics().At(0).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "bar")
metrics.ResourceMetrics().At(1).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "not_bar")

// Add a duplicate scope within the same resource
sm := metrics.ResourceMetrics().At(1).ScopeMetrics().AppendEmpty()
sm.Scope().SetName("myscope")
sm.Scope().SetVersion("v0.0.1")
return metrics
}(),
expected: func() pmetric.ResourceMetricsSlice {
// Make sure it matches the input
metrics := appendMetric(testMetric(timestamp), timestamp).ResourceMetrics()
metrics.At(0).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "bar")
// This scope attribute is on a duplicate scope_info metric, so it does not
metrics.At(1).ScopeMetrics().At(0).Scope().Attributes().PutStr("foo_attribute", "not_bar")
sm := metrics.At(1).ScopeMetrics().AppendEmpty()
sm.Scope().SetName("myscope")
sm.Scope().SetVersion("v0.0.1")

// Insert the scope_info metric into the existing ScopeMetricsSlice
sm = metrics.At(0).ScopeMetrics().At(0)
scopeInfoMetric := sm.Metrics().AppendEmpty()
scopeInfoMetric.SetName("otel_scope_info")
scopeInfoMetric.SetEmptyGauge().DataPoints().AppendEmpty().SetIntValue(1)
scopeInfoMetric.Gauge().DataPoints().At(0).Attributes().PutStr("foo_attribute", "bar")

// add otel_scope_* attributes to all metrics in this scope (including otel_scope_info)
for i := 0; i < sm.Metrics().Len(); i++ {
metric := sm.Metrics().At(i)
metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_name", "myscope")
metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_version", "v0.0.1")
scopeInfoMetric.Gauge().DataPoints().At(0).SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
}

// add otel_scope_* attributes to all metrics in the second scope
sm = metrics.At(1).ScopeMetrics().At(0)
for i := 0; i < sm.Metrics().Len(); i++ {
metric := sm.Metrics().At(i)
metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_name", "myscope")
metric.Gauge().DataPoints().At(0).Attributes().PutStr("otel_scope_version", "v0.0.1")
scopeInfoMetric.Gauge().DataPoints().At(0).SetTimestamp(pcommon.NewTimestampFromTime(timestamp))
}
return metrics
}(),
},
{
name: "add both scope info and target info",
config: Config{ExtraMetricsConfig: ExtraMetricsConfig{
Expand Down
Loading