Skip to content

Commit 2f33ee8

Browse files
Frapschenvincentfree
authored andcommitted
[connector/spanmetricsconnector] add separate additional dimensions for calls and duration metrics (open-telemetry#39134)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description fix: open-telemetry#36805
1 parent a0777d1 commit 2f33ee8

File tree

7 files changed

+133
-11
lines changed

7 files changed

+133
-11
lines changed

.chloggen/fix-36805.yaml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: spanmetricsconnector
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Separate Dimensions for calls and duration metrics
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [36805]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: |
19+
Add two new fields to the settings: `histogram.dimensions` and `calls_dimensions`.
20+
Use them to add independent dimensions to the duration and calls metrics.
21+
22+
# If your change doesn't affect end users or the exported elements of any package,
23+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
24+
# Optional: The change log or logs in which this entry should be included.
25+
# e.g. '[user]' or '[user, api]'
26+
# Include 'user' if the change is relevant to end users.
27+
# Include 'api' if there is a change to a library API.
28+
# Default: '[user]'
29+
change_logs: [user]

connector/spanmetricsconnector/README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,22 @@ The following settings can be optionally configured:
9292
- `disable` (default: `false`): Disable all histogram metrics.
9393
- `unit` (default: `ms`): The time unit for recording duration measurements.
9494
calculated from spans duration measurements. One of either: `ms` or `s`.
95+
- `dimensions`: additional attributes to add as dimensions to the `traces.span.metrics.duration` metric,
96+
which will be included _on top of_ the common and configured `dimensions` for span attributes and resource attributes.
9597
- `explicit`:
9698
- `buckets`: the list of durations defining the duration histogram time buckets. Default
9799
buckets: `[2ms, 4ms, 6ms, 8ms, 10ms, 50ms, 100ms, 200ms, 400ms, 800ms, 1s, 1400ms, 2s, 5s, 10s, 15s]`
98100
- `exponential`:
99101
- `max_size` (default: `160`) the maximum number of buckets per positive or negative number range.
100-
- `dimensions`: the list of dimensions to add together with the default dimensions defined above.
101-
102+
- `dimensions`: the list of dimensions to add to `traces.span.metrics.calls`, `traces.span.metrics.duration` and `traces.span.metrics.event` metrics with the default dimensions defined above.
102103
Each additional dimension is defined with a `name` which is looked up in the span's collection of attributes or
103104
resource attributes (AKA process tags) such as `ip`, `host.name` or `region`.
104105

105106
If the `name`d attribute is missing in the span, the optional provided `default` is used.
106107

107108
If no `default` is provided, this dimension will be **omitted** from the metric.
109+
- `calls_dimensions`: additional attributes to add as dimensions to the `traces.span.metrics.calls` metric,
110+
which will be included _on top of_ the common and configured `dimensions` for span attributes and resource attributes.
108111
- `exclude_dimensions`: the list of dimensions to be excluded from the default set of dimensions. Use to exclude unneeded data from metrics.
109112
- `dimensions_cache_size`: this setting is deprecated, please use aggregation_cardinality_limit instead.
110113
- `include_instrumentation_scope`: a list of instrumentation scope names to include from the traces.
@@ -120,7 +123,7 @@ The following settings can be optionally configured:
120123
- `enabled` (default: `false`): enabling will add spans as Exemplars to all metrics. Exemplars are only kept for one flush interval.rom 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.
121124
- `events`: Use to configure the events metric.
122125
- `enabled`: (default: `false`): enabling will add the events metric.
123-
- `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.
126+
- `dimensions`: (mandatory if `enabled`) the list of the span's event attributes to add as dimensions to the `traces.span.metrics.events` metric, which will be included _on top of_ the common and configured `dimensions` for span attributes and resource attributes.
124127
- `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.
125128
- `aggregation_cardinality_limit` (default: `0`): Defines the maximum number of unique combinations of dimensions that will be tracked for metrics aggregation. When the limit is reached, additional unique combinations will be dropped but registered under a new entry with `otel.metric.overflow="true"`. A value of `0` means no limit is applied.
126129

@@ -144,12 +147,18 @@ exporters:
144147
connectors:
145148
spanmetrics:
146149
histogram:
150+
dimensions:
151+
- name: url.scheme
152+
default: https
147153
explicit:
148-
buckets: [100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms]
154+
buckets: [100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms]
149155
dimensions:
150156
- name: http.method
151157
default: GET
152158
- name: http.status_code
159+
calls_dimensions:
160+
- name: http.url
161+
default: /ping
153162
exemplars:
154163
enabled: true
155164
exclude_dimensions: ['status.code']

connector/spanmetricsconnector/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type Config struct {
4343
// The dimensions will be fetched from the span's attributes. Examples of some conventionally used attributes:
4444
// https://github.com/open-telemetry/opentelemetry-collector/blob/main/model/semconv/opentelemetry.go.
4545
Dimensions []Dimension `mapstructure:"dimensions"`
46+
CallsDimensions []Dimension `mapstructure:"calls_dimensions"`
4647
ExcludeDimensions []string `mapstructure:"exclude_dimensions"`
4748

4849
// DimensionsCacheSize defines the size of cache for storing Dimensions, which helps to avoid cache memory growing
@@ -98,6 +99,7 @@ type HistogramConfig struct {
9899
Unit metrics.Unit `mapstructure:"unit"`
99100
Exponential *ExponentialHistogramConfig `mapstructure:"exponential"`
100101
Explicit *ExplicitHistogramConfig `mapstructure:"explicit"`
102+
Dimensions []Dimension `mapstructure:"dimensions"`
101103
// prevent unkeyed literal initialization
102104
_ struct{}
103105
}

connector/spanmetricsconnector/config_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,22 @@ func TestLoadConfig(t *testing.T) {
158158
id: component.NewIDWithName(metadata.Type, "invalid_delta_timestamp_cache_size"),
159159
errorMessage: "invalid delta timestamp cache size: 0, the maximum number of the items in the cache should be positive",
160160
},
161+
{
162+
id: component.NewIDWithName(metadata.Type, "separate_calls_and_duration_dimensions"),
163+
expected: &Config{
164+
AggregationTemporality: "AGGREGATION_TEMPORALITY_CUMULATIVE",
165+
Histogram: HistogramConfig{Disable: false, Unit: defaultUnit, Dimensions: []Dimension{{Name: "http.status_code", Default: (*string)(nil)}}},
166+
Dimensions: []Dimension{
167+
{Name: "http.method", Default: &defaultMethod},
168+
},
169+
CallsDimensions: []Dimension{
170+
{Name: "http.url", Default: (*string)(nil)},
171+
},
172+
ResourceMetricsCacheSize: defaultResourceMetricsCacheSize,
173+
MetricsFlushInterval: 60 * time.Second,
174+
Namespace: DefaultNamespace,
175+
},
176+
},
161177
}
162178

163179
for _, tt := range tests {

connector/spanmetricsconnector/connector.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ type connectorImp struct {
7474
// Event dimensions to add to the events metric.
7575
eDimensions []utilattri.Dimension
7676

77+
// Calls dimensions to add to the events metric.
78+
callsDimensions []utilattri.Dimension
79+
80+
// duration dimensions to add to the events metric.
81+
durationDimensions []utilattri.Dimension
82+
7783
events EventsConfig
7884

7985
// Tracks the last TimestampUnixNano for delta metrics so that they represent an uninterrupted series. Unused for cumulative span metrics.
@@ -144,6 +150,8 @@ func newConnector(logger *zap.Logger, config component.Config, clock clockwork.C
144150
ticker: clock.NewTicker(cfg.MetricsFlushInterval),
145151
done: make(chan struct{}),
146152
eDimensions: newDimensions(cfg.Events.Dimensions),
153+
callsDimensions: newDimensions(cfg.CallsDimensions),
154+
durationDimensions: newDimensions(cfg.Histogram.Dimensions),
147155
events: cfg.Events,
148156
}, nil
149157
}
@@ -390,9 +398,11 @@ func (p *connectorImp) aggregateMetrics(traces ptrace.Traces) {
390398
duration = float64(endTime-startTime) / float64(unitDivider)
391399
}
392400

393-
key := p.buildKey(serviceName, span, p.dimensions, resourceAttr)
401+
callsDimensions := p.dimensions
402+
callsDimensions = append(callsDimensions, p.callsDimensions...)
403+
key := p.buildKey(serviceName, span, callsDimensions, resourceAttr)
394404
attributesFun := func() pcommon.Map {
395-
return p.buildAttributes(serviceName, span, resourceAttr, p.dimensions, ils.Scope())
405+
return p.buildAttributes(serviceName, span, resourceAttr, callsDimensions, ils.Scope())
396406
}
397407

398408
// aggregate sums metrics
@@ -404,7 +414,13 @@ func (p *connectorImp) aggregateMetrics(traces ptrace.Traces) {
404414

405415
// aggregate histogram metrics
406416
if !p.config.Histogram.Disable {
407-
h, durationLimitReached := histograms.GetOrCreate(key, attributesFun, startTimestamp)
417+
durationDimensions := p.dimensions
418+
durationDimensions = append(durationDimensions, p.durationDimensions...)
419+
durationKey := p.buildKey(serviceName, span, durationDimensions, resourceAttr)
420+
attributesFun = func() pcommon.Map {
421+
return p.buildAttributes(serviceName, span, resourceAttr, durationDimensions, ils.Scope())
422+
}
423+
h, durationLimitReached := histograms.GetOrCreate(durationKey, attributesFun, startTimestamp)
408424
if !durationLimitReached && p.config.Exemplars.Enabled && !span.TraceID().IsEmpty() {
409425
p.addExemplar(span, duration, h)
410426
}
@@ -422,9 +438,9 @@ func (p *connectorImp) aggregateMetrics(traces ptrace.Traces) {
422438

423439
rscAndEventAttrs.EnsureCapacity(resourceAttr.Len() + event.Attributes().Len())
424440
resourceAttr.CopyTo(rscAndEventAttrs)
425-
// We cannot use CopyTo because it overrides the existing keys.
441+
// We cannot use event.Attributes().CopyTo(rscAdnEventAttrs) because it overrides the existing keys.
426442
event.Attributes().Range(func(k string, v pcommon.Value) bool {
427-
rscAndEventAttrs.PutStr(k, v.Str())
443+
v.CopyTo(rscAndEventAttrs.PutEmpty(k))
428444
return true
429445
})
430446

connector/spanmetricsconnector/connector_test.go

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,8 @@ func initSpan(span span, s ptrace.Span) {
400400
s.Attributes().PutEmpty(nullAttrName)
401401
s.Attributes().PutEmptyMap(mapAttrName)
402402
s.Attributes().PutEmptySlice(arrayAttrName)
403-
s.SetTraceID(pcommon.TraceID(span.traceID))
404-
s.SetSpanID(pcommon.SpanID(span.spanID))
403+
s.SetTraceID(span.traceID)
404+
s.SetSpanID(span.spanID)
405405

406406
e := s.Events().AppendEmpty()
407407
e.SetName("exception")
@@ -1871,6 +1871,46 @@ func TestDeltaTimestampCacheExpiry(t *testing.T) {
18711871
assert.Greater(t, serviceAStartTimestamp2, serviceATimestamp1) // These would be the same if nothing was evicted from the cache
18721872
}
18731873

1874+
func TestSeparateDimensions(t *testing.T) {
1875+
factory := NewFactory()
1876+
cfg := factory.CreateDefaultConfig().(*Config)
1877+
cfg.Namespace = ""
1878+
cfg.Dimensions = []Dimension{{Name: stringAttrName, Default: nil}}
1879+
cfg.CallsDimensions = []Dimension{{Name: intAttrName, Default: stringp("0")}}
1880+
cfg.Histogram.Dimensions = []Dimension{{Name: doubleAttrName, Default: stringp("0.0")}}
1881+
c, err := newConnector(zaptest.NewLogger(t), cfg, clockwork.NewFakeClock())
1882+
require.NoError(t, err)
1883+
err = c.ConsumeTraces(context.Background(), buildSampleTrace())
1884+
require.NoError(t, err)
1885+
metrics := c.buildMetrics()
1886+
for i := 0; i < metrics.ResourceMetrics().Len(); i++ {
1887+
rm := metrics.ResourceMetrics().At(i)
1888+
ism := rm.ScopeMetrics()
1889+
for ilmC := 0; ilmC < ism.Len(); ilmC++ {
1890+
m := ism.At(ilmC).Metrics()
1891+
for mC := 0; mC < m.Len(); mC++ {
1892+
metric := m.At(mC)
1893+
if metric.Name() == metricNameCalls {
1894+
assert.Equal(t, pmetric.MetricTypeSum, metric.Type())
1895+
for idp := 0; idp < metric.Sum().DataPoints().Len(); idp++ {
1896+
attrs := metric.Sum().DataPoints().At(idp).Attributes()
1897+
assert.Contains(t, attrs.AsRaw(), stringAttrName)
1898+
assert.Contains(t, attrs.AsRaw(), intAttrName) // only in traces.span.metrics.calls metric
1899+
}
1900+
}
1901+
if metric.Name() == metricNameDuration {
1902+
assert.Equal(t, pmetric.MetricTypeHistogram, metric.Type())
1903+
for idp := 0; idp < metric.Histogram().DataPoints().Len(); idp++ {
1904+
attrs := metric.Histogram().DataPoints().At(idp).Attributes()
1905+
assert.Contains(t, attrs.AsRaw(), stringAttrName)
1906+
assert.Contains(t, attrs.AsRaw(), doubleAttrName) // only in traces.span.metrics.duration
1907+
}
1908+
}
1909+
}
1910+
}
1911+
}
1912+
}
1913+
18741914
// Clock where Now() always returns a greater value than the previous return value
18751915
type alwaysIncreasingClock struct {
18761916
clockwork.Clock

connector/spanmetricsconnector/testdata/config.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,13 @@ spanmetrics/invalid_delta_timestamp_cache_size:
8888

8989
spanmetrics/default_delta_timestamp_cache_size:
9090
aggregation_temporality: "AGGREGATION_TEMPORALITY_DELTA"
91+
92+
spanmetrics/separate_calls_and_duration_dimensions:
93+
histogram:
94+
dimensions:
95+
- name: http.status_code
96+
dimensions:
97+
- name: http.method
98+
default: GET
99+
calls_dimensions:
100+
- name: http.url

0 commit comments

Comments
 (0)