Skip to content
Open
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
25 changes: 25 additions & 0 deletions .chloggen/11754.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Use this changelog template to create an entry for release notes.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. receiver/otlp)
component: all

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add a registry so components can declare the telemetry metrics that require higher service metric levels, letting the service auto-drop those meters/instruments via default views.

# One or more tracking issues or pull requests related to the change
issues: [11754]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext: Also adds `component.MetricLevelConfig` so component packages can register their own minimum metric levels.

# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [api]
2 changes: 1 addition & 1 deletion cmd/otelcorecol/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ require (
go.opentelemetry.io/collector/config/configopaque v1.48.0 // indirect
go.opentelemetry.io/collector/config/configoptional v1.48.0 // indirect
go.opentelemetry.io/collector/config/configretry v1.48.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v0.142.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v1.48.0 // indirect
go.opentelemetry.io/collector/config/configtls v1.48.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.142.0 // indirect
go.opentelemetry.io/collector/connector/connectortest v0.142.0 // indirect
Expand Down
93 changes: 93 additions & 0 deletions component/metric_level.go
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't reviewed the rest of this PR but I think this should be on an xcomponent package instead of here since this would (at the beginning at least) an experimental package (there's also spec work about this, see open-telemetry/opentelemetry-specification/issues/4391)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm ok with that. Will implement the change.

Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package component // import "go.opentelemetry.io/collector/component"

import "sync"

// MetricLevelConfig declares the minimum service telemetry level required for
// a given meter or instrument. Components can register these declarations so
// the service can derive default views that drop metrics when the level is
// below the configured threshold.
type MetricLevelConfig struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we allow components to register views instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. I'm not against that. I was thinking about keeping this because maybe want to do something based on the stability metric level? Something we can add later. But I'm ok with the change. Let me know what do you think and I will be more than happy to change this.

// MeterName is the fully-qualified meter name emitting the metric.
MeterName string

// InstrumentName optionally scopes the config to a specific instrument. When
// empty, the config applies to the complete meter.
InstrumentName string

// Level is the minimum service telemetry level required for the meter or
// instrument to be enabled.
Level MetricLevel
}

// MetricLevel mirrors service::telemetry::metrics::level values.
type MetricLevel int32

// metricLevelRegistry is a global registry for metric level configurations.
var metricLevelRegistry = struct {
sync.RWMutex
configs []MetricLevelConfig
byMeter map[string][]int
}{byMeter: make(map[string][]int)}

// RegisterMetricLevelConfigs registers one or more MetricLevelConfig entries.
func RegisterMetricLevelConfigs(configs ...MetricLevelConfig) {
if len(configs) == 0 {
return
}

metricLevelRegistry.Lock()
defer metricLevelRegistry.Unlock()

for _, cfg := range configs {
if cfg.MeterName == "" {
panic("component: MetricLevelConfig requires MeterName")
}
idx := len(metricLevelRegistry.configs)
metricLevelRegistry.configs = append(metricLevelRegistry.configs, cfg)
metricLevelRegistry.byMeter[cfg.MeterName] = append(metricLevelRegistry.byMeter[cfg.MeterName], idx)
}
}

// RegisteredMetricLevelConfigs returns all registered metric level declarations.
// The returned slice is a copy to prevent external modification of the registry.
// For better performance when iterating, consider using RegisteredMetricLevelConfigsByMeter
// if you only need configs for specific meters.
func RegisteredMetricLevelConfigs() []MetricLevelConfig {
metricLevelRegistry.RLock()
defer metricLevelRegistry.RUnlock()

out := make([]MetricLevelConfig, len(metricLevelRegistry.configs))
copy(out, metricLevelRegistry.configs)
return out
}

// RegisteredMetricLevelConfigsByMeter returns all registered metric level declarations
// for the given meter name.
func RegisteredMetricLevelConfigsByMeter(meterName string) []MetricLevelConfig {
metricLevelRegistry.RLock()
defer metricLevelRegistry.RUnlock()

indices, exists := metricLevelRegistry.byMeter[meterName]
if !exists {
return nil
}

configs := make([]MetricLevelConfig, 0, len(indices))
for _, idx := range indices {
configs = append(configs, metricLevelRegistry.configs[idx])
}
return configs
}

// ResetMetricLevelRegistryForTesting resets the global registry for testing.
// This function is exported for use in test files (including tests in other packages)
// and should never be called from production code.
func ResetMetricLevelRegistryForTesting() {
metricLevelRegistry.Lock()
defer metricLevelRegistry.Unlock()
metricLevelRegistry.configs = nil
metricLevelRegistry.byMeter = make(map[string][]int)
}
191 changes: 191 additions & 0 deletions component/metric_level_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package component

import (
"sync"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestRegisterMetricLevelConfigs(t *testing.T) {
t.Cleanup(ResetMetricLevelRegistryForTesting)

RegisterMetricLevelConfigs(
MetricLevelConfig{
MeterName: "test/meter",
Level: MetricLevel(2),
},
MetricLevelConfig{
MeterName: "test/meter",
InstrumentName: "metric",
Level: MetricLevel(1),
},
)

configs := RegisteredMetricLevelConfigs()
require.Len(t, configs, 2)
assert.Equal(t, "test/meter", configs[0].MeterName)
assert.Equal(t, MetricLevel(2), configs[0].Level)
assert.Equal(t, "metric", configs[1].InstrumentName)
assert.Equal(t, MetricLevel(1), configs[1].Level)
}

func TestRegisterMetricLevelConfigsPanicsWithoutMeter(t *testing.T) {
t.Cleanup(ResetMetricLevelRegistryForTesting)

assert.Panics(t, func() {
RegisterMetricLevelConfigs(MetricLevelConfig{Level: MetricLevel(2)})
})
}

func TestRegisteredMetricLevelConfigsByMeter(t *testing.T) {
t.Cleanup(ResetMetricLevelRegistryForTesting)

RegisterMetricLevelConfigs(
MetricLevelConfig{
MeterName: "test/meter1",
Level: MetricLevel(1),
},
MetricLevelConfig{
MeterName: "test/meter1",
InstrumentName: "instrument1",
Level: MetricLevel(2),
},
MetricLevelConfig{
MeterName: "test/meter2",
Level: MetricLevel(1),
},
)

tests := []struct {
name string
meterName string
wantCount int
wantLevels []MetricLevel
}{
{
name: "existing meter with multiple configs",
meterName: "test/meter1",
wantCount: 2,
wantLevels: []MetricLevel{1, 2},
},
{
name: "existing meter with single config",
meterName: "test/meter2",
wantCount: 1,
wantLevels: []MetricLevel{1},
},
{
name: "non-existent meter",
meterName: "test/nonexistent",
wantCount: 0,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
configs := RegisteredMetricLevelConfigsByMeter(tt.meterName)
require.Len(t, configs, tt.wantCount)
for i, cfg := range configs {
assert.Equal(t, tt.meterName, cfg.MeterName)
if len(tt.wantLevels) > i {
assert.Equal(t, tt.wantLevels[i], cfg.Level)
}
}
})
}
}

func TestRegisterMetricLevelConfigsEmptySlice(t *testing.T) {
t.Cleanup(ResetMetricLevelRegistryForTesting)

initialCount := len(RegisteredMetricLevelConfigs())
RegisterMetricLevelConfigs()
assert.Len(t, RegisteredMetricLevelConfigs(), initialCount)
}

func TestRegisterMetricLevelConfigsConcurrent(t *testing.T) {
t.Cleanup(ResetMetricLevelRegistryForTesting)

const numGoroutines = 10
const configsPerGoroutine = 5

var wg sync.WaitGroup
wg.Add(numGoroutines)

for i := range numGoroutines {
go func(id int) {
defer wg.Done()
configs := make([]MetricLevelConfig, configsPerGoroutine)
for j := range configsPerGoroutine {
configs[j] = MetricLevelConfig{
MeterName: "test/concurrent",
InstrumentName: "instrument",
Level: MetricLevel(id*configsPerGoroutine + j),
}
}
RegisterMetricLevelConfigs(configs...)
}(i)
}

wg.Wait()

allConfigs := RegisteredMetricLevelConfigs()
require.GreaterOrEqual(t, len(allConfigs), numGoroutines*configsPerGoroutine)

var readWg sync.WaitGroup
readWg.Add(numGoroutines)
for range numGoroutines {
go func() {
defer readWg.Done()
configs := RegisteredMetricLevelConfigs()
assert.GreaterOrEqual(t, len(configs), numGoroutines*configsPerGoroutine)
}()
}
readWg.Wait()
}

func TestRegisteredMetricLevelConfigsReturnsCopy(t *testing.T) {
t.Cleanup(ResetMetricLevelRegistryForTesting)

RegisterMetricLevelConfigs(
MetricLevelConfig{
MeterName: "test/copy",
Level: MetricLevel(1),
},
)

configs1 := RegisteredMetricLevelConfigs()
configs2 := RegisteredMetricLevelConfigs()

configs1[0].MeterName = "modified"

assert.Equal(t, "test/copy", configs2[0].MeterName)
assert.Equal(t, "modified", configs1[0].MeterName)
}

func TestRegisteredMetricLevelConfigsByMeterReturnsCopy(t *testing.T) {
t.Cleanup(ResetMetricLevelRegistryForTesting)

RegisterMetricLevelConfigs(
MetricLevelConfig{
MeterName: "test/copy",
Level: MetricLevel(1),
},
)

configs1 := RegisteredMetricLevelConfigsByMeter("test/copy")
configs2 := RegisteredMetricLevelConfigsByMeter("test/copy")

require.Len(t, configs1, 1)
require.Len(t, configs2, 1)

configs1[0].MeterName = "modified"

assert.Equal(t, "test/copy", configs2[0].MeterName)
assert.Equal(t, "modified", configs1[0].MeterName)
}
2 changes: 1 addition & 1 deletion exporter/debugexporter/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
go.opentelemetry.io/collector/component v1.48.0
go.opentelemetry.io/collector/component/componenttest v0.142.0
go.opentelemetry.io/collector/config/configoptional v1.48.0
go.opentelemetry.io/collector/config/configtelemetry v0.142.0
go.opentelemetry.io/collector/config/configtelemetry v1.48.0
go.opentelemetry.io/collector/confmap v1.48.0
go.opentelemetry.io/collector/consumer v1.48.0
go.opentelemetry.io/collector/exporter v1.48.0
Expand Down
3 changes: 3 additions & 0 deletions exporter/exporterhelper/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ require (
go.opentelemetry.io/collector/component/componenttest v0.142.0
go.opentelemetry.io/collector/config/configoptional v1.48.0
go.opentelemetry.io/collector/config/configretry v1.48.0
go.opentelemetry.io/collector/config/configtelemetry v1.48.0
go.opentelemetry.io/collector/confmap v1.48.0
go.opentelemetry.io/collector/confmap/xconfmap v0.142.0
go.opentelemetry.io/collector/consumer v1.48.0
Expand Down Expand Up @@ -89,6 +90,8 @@ replace go.opentelemetry.io/collector/pipeline => ../../pipeline

replace go.opentelemetry.io/collector/receiver => ../../receiver

replace go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry

replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry

replace go.opentelemetry.io/collector/consumer/xconsumer => ../../consumer/xconsumer
Expand Down
17 changes: 17 additions & 0 deletions exporter/exporterhelper/metric_levels.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package exporterhelper // import "go.opentelemetry.io/collector/exporter/exporterhelper"

import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/config/configtelemetry"
)

func init() {
component.RegisterMetricLevelConfigs(component.MetricLevelConfig{
MeterName: "go.opentelemetry.io/collector/exporter/exporterhelper",
InstrumentName: "otelcol_exporter_queue_batch_send_size_bytes",
Level: component.MetricLevel(configtelemetry.LevelDetailed),
})
}
3 changes: 3 additions & 0 deletions exporter/exporterhelper/xexporterhelper/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ require (
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.48.0 // indirect
go.opentelemetry.io/collector/config/configretry v1.48.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v1.48.0 // indirect
go.opentelemetry.io/collector/confmap v1.48.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.142.0 // indirect
go.opentelemetry.io/collector/extension v1.48.0 // indirect
Expand Down Expand Up @@ -101,6 +102,8 @@ replace go.opentelemetry.io/collector/exporter/xexporter => ../../xexporter

replace go.opentelemetry.io/collector/config/configretry => ../../../config/configretry

replace go.opentelemetry.io/collector/config/configtelemetry => ../../../config/configtelemetry

replace go.opentelemetry.io/collector/pipeline/xpipeline => ../../../pipeline/xpipeline

replace go.opentelemetry.io/collector/pipeline => ../../../pipeline
Expand Down
3 changes: 3 additions & 0 deletions exporter/exportertest/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require (
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/collector/client v1.48.0 // indirect
go.opentelemetry.io/collector/config/configoptional v1.48.0 // indirect
go.opentelemetry.io/collector/config/configtelemetry v1.48.0 // indirect
go.opentelemetry.io/collector/confmap v1.48.0 // indirect
go.opentelemetry.io/collector/confmap/xconfmap v0.142.0 // indirect
go.opentelemetry.io/collector/consumer/xconsumer v0.142.0 // indirect
Expand Down Expand Up @@ -81,6 +82,8 @@ replace go.opentelemetry.io/collector/extension => ../../extension

replace go.opentelemetry.io/collector/config/configretry => ../../config/configretry

replace go.opentelemetry.io/collector/config/configtelemetry => ../../config/configtelemetry

replace go.opentelemetry.io/collector/pipeline => ../../pipeline

replace go.opentelemetry.io/collector/pdata => ../../pdata
Expand Down
Loading
Loading