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

[connector/spanmetrics] Improve consistency between metrics generated by spanmetricsconnector #34485

Merged
merged 10 commits into from
Sep 3, 2024
Merged
29 changes: 29 additions & 0 deletions .chloggen/feat_33227.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Use this changelog template to create an entry for release notes.

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

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: spanmetricsconnector

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Improve consistency between metrics generated by spanmetricsconnector. Added traces.span.metrics as default namespace

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [33227, 32818]

# (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: "Default namespace for the generated metrics is traces.span.metrics now. |
The deprecated metrics are: calls, duration and events. |
The feature flag connector.spanmetrics.legacyLatencyMetricNames was added to revert the behavior."

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# 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: [user]
4 changes: 3 additions & 1 deletion connector/spanmetricsconnector/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ The following settings can be optionally configured:
cumulative temporality to avoid memory leaks and correct metric timestamp resets.
- `aggregation_temporality` (default: `AGGREGATION_TEMPORALITY_CUMULATIVE`): Defines the aggregation temporality of the generated metrics.
One of either `AGGREGATION_TEMPORALITY_CUMULATIVE` or `AGGREGATION_TEMPORALITY_DELTA`.
- `namespace`: Defines the namespace of the generated metrics. If `namespace` provided, generated metric name will be added `namespace.` prefix.
- `namespace` (default: `traces.span.metrics`): Defines the namespace of the generated metrics. If `namespace` provided, generated metric name will be added `namespace.` prefix.
- `metrics_flush_interval` (default: `60s`): Defines the flush interval of the generated metrics.
- `metrics_expiration` (default: `0`): Defines the expiration time as `time.Duration`, after which, if no new spans are received, metrics will no longer be exported. Setting to `0` means the metrics will never expire (default behavior).
- `metric_timestamp_cache_size` (default `1000`): Only relevant for delta temporality span metrics. Controls the size of the cache used to keep track of a metric's TimestampUnixNano the last time it was flushed. When a metric is evicted from the cache, its next data point will indicate a "reset" in the series. Downstream components converting from delta to cumulative, like `prometheusexporter`, may handle these resets by setting cumulative counters back to 0.
Expand All @@ -122,6 +122,8 @@ The following settings can be optionally configured:
- `dimensions`: (mandatory if `enabled`) the list of the span's event attributes to add as dimensions to the events metric, which will be included _on top of_ the common and configured `dimensions` for span and resource attributes.
- `resource_metrics_key_attributes`: Filter the resource attributes used to produce the resource metrics key map hash. Use this in case changing resource attributes (e.g. process id) are breaking counter metrics.

The feature gate `connector.spanmetrics.legacyMetricNames` (disabled by default) controls the connector to use legacy metric names.

## Examples

The following is a simple example usage of the `spanmetrics` connector.
Expand Down
7 changes: 7 additions & 0 deletions connector/spanmetricsconnector/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ func TestLoadConfig(t *testing.T) {
{Name: "http.method", Default: &defaultMethod},
{Name: "http.status_code", Default: (*string)(nil)},
},
Namespace: DefaultNamespace,
DimensionsCacheSize: 1500,
ResourceMetricsCacheSize: 1600,
MetricsFlushInterval: 30 * time.Second,
Expand All @@ -70,6 +71,7 @@ func TestLoadConfig(t *testing.T) {
{
id: component.NewIDWithName(metadata.Type, "exponential_histogram"),
expected: &Config{
Namespace: DefaultNamespace,
AggregationTemporality: cumulative,
DimensionsCacheSize: defaultDimensionsCacheSize,
ResourceMetricsCacheSize: defaultResourceMetricsCacheSize,
Expand Down Expand Up @@ -103,6 +105,7 @@ func TestLoadConfig(t *testing.T) {
MetricsFlushInterval: 60 * time.Second,
Histogram: HistogramConfig{Disable: false, Unit: defaultUnit},
Exemplars: ExemplarsConfig{Enabled: true},
Namespace: DefaultNamespace,
},
},
{
Expand All @@ -114,6 +117,7 @@ func TestLoadConfig(t *testing.T) {
MetricsFlushInterval: 60 * time.Second,
Histogram: HistogramConfig{Disable: false, Unit: defaultUnit},
Exemplars: ExemplarsConfig{Enabled: true, MaxPerDataPoint: &defaultMaxPerDatapoint},
Namespace: DefaultNamespace,
},
},
{
Expand All @@ -125,6 +129,7 @@ func TestLoadConfig(t *testing.T) {
ResourceMetricsKeyAttributes: []string{"service.name", "telemetry.sdk.language", "telemetry.sdk.name"},
MetricsFlushInterval: 60 * time.Second,
Histogram: HistogramConfig{Disable: false, Unit: defaultUnit},
Namespace: DefaultNamespace,
},
},
{
Expand All @@ -136,6 +141,7 @@ func TestLoadConfig(t *testing.T) {
ResourceMetricsCacheSize: defaultResourceMetricsCacheSize,
MetricsFlushInterval: 60 * time.Second,
Histogram: HistogramConfig{Disable: false, Unit: defaultUnit},
Namespace: DefaultNamespace,
},
},
{
Expand All @@ -146,6 +152,7 @@ func TestLoadConfig(t *testing.T) {
ResourceMetricsCacheSize: defaultResourceMetricsCacheSize,
MetricsFlushInterval: 60 * time.Second,
Histogram: HistogramConfig{Disable: false, Unit: defaultUnit},
Namespace: DefaultNamespace,
},
extraAssertions: func(config *Config) {
assert.Equal(t, defaultDeltaTimestampCacheSize, config.GetDeltaTimestampCacheSize())
Expand Down
12 changes: 9 additions & 3 deletions connector/spanmetricsconnector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,22 +295,28 @@ func (p *connectorImp) buildMetrics() pmetric.Metrics {
return startTime
}

metricsNamespace := p.config.Namespace
if legacyMetricNamesFeatureGate.IsEnabled() && metricsNamespace == DefaultNamespace {
iblancasa marked this conversation as resolved.
Show resolved Hide resolved
metricsNamespace = ""
}

sums := rawMetrics.sums
metric := sm.Metrics().AppendEmpty()
metric.SetName(buildMetricName(p.config.Namespace, metricNameCalls))
metric.SetName(buildMetricName(metricsNamespace, metricNameCalls))
sums.BuildMetrics(metric, startTimeGenerator, timestamp, p.config.GetAggregationTemporality())

if !p.config.Histogram.Disable {
histograms := rawMetrics.histograms
metric = sm.Metrics().AppendEmpty()
metric.SetName(buildMetricName(p.config.Namespace, metricNameDuration))
metric.SetName(buildMetricName(metricsNamespace, metricNameDuration))
metric.SetUnit(p.config.Histogram.Unit.String())
histograms.BuildMetrics(metric, startTimeGenerator, timestamp, p.config.GetAggregationTemporality())
}

events := rawMetrics.events
if p.events.Enabled {
metric = sm.Metrics().AppendEmpty()
metric.SetName(buildMetricName(p.config.Namespace, metricNameEvents))
metric.SetName(buildMetricName(metricsNamespace, metricNameEvents))
events.BuildMetrics(metric, startTimeGenerator, timestamp, p.config.GetAggregationTemporality())
}

Expand Down
102 changes: 65 additions & 37 deletions connector/spanmetricsconnector/connector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"go.opentelemetry.io/collector/connector/connectortest"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/consumer/consumertest"
"go.opentelemetry.io/collector/featuregate"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/pmetric"
"go.opentelemetry.io/collector/pdata/ptrace"
Expand Down Expand Up @@ -1053,53 +1054,80 @@ func BenchmarkConnectorConsumeTraces(b *testing.B) {
}

func TestExcludeDimensionsConsumeTraces(t *testing.T) {
excludeDimensions := []string{"span.kind", "span.name", "totallyWrongNameDoesNotAffectAnything"}
p, err := newConnectorImp(stringp("defaultNullValue"), explicitHistogramsConfig, disabledExemplarsConfig, disabledEventsConfig, cumulative, 0, []string{}, 1000, clockwork.NewFakeClock(), excludeDimensions...)
require.NoError(t, err)
traces := buildSampleTrace()

// Test
ctx := metadata.NewIncomingContext(context.Background(), nil)
testcases := []struct {
dsc string
featureGateEnabled bool
}{
{
dsc: fmt.Sprintf("%s enabled", legacyMetricNamesFeatureGateID),
featureGateEnabled: true,
},
{
dsc: fmt.Sprintf("%s disabled", legacyMetricNamesFeatureGateID),
featureGateEnabled: false,
},
}

err = p.ConsumeTraces(ctx, traces)
require.NoError(t, err)
metrics := p.buildMetrics()
excludeDimensions := []string{"span.kind", "span.name", "totallyWrongNameDoesNotAffectAnything"}
for _, tc := range testcases {
tc := tc
t.Run(tc.dsc, func(t *testing.T) {
// Set feature gate value
previousValue := legacyMetricNamesFeatureGate.IsEnabled()
require.NoError(t, featuregate.GlobalRegistry().Set(legacyMetricNamesFeatureGate.ID(), tc.featureGateEnabled))
defer func() {
require.NoError(t, featuregate.GlobalRegistry().Set(legacyMetricNamesFeatureGate.ID(), previousValue))
}()

p, err := newConnectorImp(stringp("defaultNullValue"), explicitHistogramsConfig, disabledExemplarsConfig, disabledEventsConfig, cumulative, 0, []string{}, 1000, clockwork.NewFakeClock(), excludeDimensions...)
require.NoError(t, err)
traces := buildSampleTrace()

for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
rm := metrics.ResourceMetrics().At(i)
ism := rm.ScopeMetrics()
// Checking all metrics, naming notice: ilmC/mC - C here is for Counter.
for ilmC := 0; ilmC < ism.Len(); ilmC++ {
m := ism.At(ilmC).Metrics()
for mC := 0; mC < m.Len(); mC++ {
metric := m.At(mC)
// We check only sum and histogram metrics here, because for now only they are present in this module.
ctx := metadata.NewIncomingContext(context.Background(), nil)

switch metric.Type() {
case pmetric.MetricTypeExponentialHistogram, pmetric.MetricTypeHistogram:
{
dp := metric.Histogram().DataPoints()
for dpi := 0; dpi < dp.Len(); dpi++ {
for attributeKey := range dp.At(dpi).Attributes().AsRaw() {
assert.NotContains(t, excludeDimensions, attributeKey)
}
err = p.ConsumeTraces(ctx, traces)
require.NoError(t, err)
metrics := p.buildMetrics()

}
}
case pmetric.MetricTypeEmpty, pmetric.MetricTypeGauge, pmetric.MetricTypeSum, pmetric.MetricTypeSummary:
{
dp := metric.Sum().DataPoints()
for dpi := 0; dpi < dp.Len(); dpi++ {
for attributeKey := range dp.At(dpi).Attributes().AsRaw() {
assert.NotContains(t, excludeDimensions, attributeKey)
for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
rm := metrics.ResourceMetrics().At(i)
ism := rm.ScopeMetrics()
// Checking all metrics, naming notice: ilmC/mC - C here is for Counter.
for ilmC := 0; ilmC < ism.Len(); ilmC++ {
m := ism.At(ilmC).Metrics()
for mC := 0; mC < m.Len(); mC++ {
metric := m.At(mC)
// We check only sum and histogram metrics here, because for now only they are present in this module.

switch metric.Type() {
case pmetric.MetricTypeExponentialHistogram, pmetric.MetricTypeHistogram:
{
dp := metric.Histogram().DataPoints()
for dpi := 0; dpi < dp.Len(); dpi++ {
for attributeKey := range dp.At(dpi).Attributes().AsRaw() {
assert.NotContains(t, excludeDimensions, attributeKey)
}

}
}
case pmetric.MetricTypeEmpty, pmetric.MetricTypeGauge, pmetric.MetricTypeSum, pmetric.MetricTypeSummary:
{
dp := metric.Sum().DataPoints()
for dpi := 0; dpi < dp.Len(); dpi++ {
for attributeKey := range dp.At(dpi).Attributes().AsRaw() {
assert.NotContains(t, excludeDimensions, attributeKey)
}
}
}

}
}

}
}

}
}

})
}

}
Expand Down
19 changes: 19 additions & 0 deletions connector/spanmetricsconnector/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,28 @@ import (
"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/connector"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/featuregate"

"github.com/open-telemetry/opentelemetry-collector-contrib/connector/spanmetricsconnector/internal/metadata"
)

const (
DefaultNamespace = "traces.span.metrics"
legacyMetricNamesFeatureGateID = "connector.spanmetrics.legacyMetricNames"
)

var legacyMetricNamesFeatureGate *featuregate.Gate

func init() {
// TODO: Remove this feature gate when the legacy metric names are removed.
legacyMetricNamesFeatureGate = featuregate.GlobalRegistry().MustRegister(
legacyMetricNamesFeatureGateID,
featuregate.StageAlpha, // Alpha because we want it disabled by default.
featuregate.WithRegisterDescription("When enabled, connector uses legacy metric names."),
featuregate.WithRegisterReferenceURL("https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/33227"),
)
}

// NewFactory creates a factory for the spanmetrics connector.
func NewFactory() connector.Factory {
return connector.NewFactory(
Expand All @@ -33,6 +51,7 @@ func createDefaultConfig() component.Config {
ResourceMetricsCacheSize: defaultResourceMetricsCacheSize,
MetricsFlushInterval: 60 * time.Second,
Histogram: HistogramConfig{Disable: false, Unit: defaultUnit},
Namespace: DefaultNamespace,
}
}

Expand Down
2 changes: 2 additions & 0 deletions connector/spanmetricsconnector/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ require (
go.opentelemetry.io/collector/connector v0.108.2-0.20240829190554-7da6b618a7ee
go.opentelemetry.io/collector/consumer v0.108.2-0.20240829190554-7da6b618a7ee
go.opentelemetry.io/collector/consumer/consumertest v0.108.2-0.20240829190554-7da6b618a7ee
go.opentelemetry.io/collector/featuregate v1.14.2-0.20240829190554-7da6b618a7ee
go.opentelemetry.io/collector/pdata v1.14.2-0.20240829190554-7da6b618a7ee
go.opentelemetry.io/collector/semconv v0.108.2-0.20240829190554-7da6b618a7ee
go.uber.org/goleak v1.3.0
Expand All @@ -31,6 +32,7 @@ require (
github.com/go-viper/mapstructure/v2 v2.1.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions connector/spanmetricsconnector/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.