Skip to content

Commit 814307e

Browse files
perebajArthurSens
andauthored
[receiver/prometheusremotewrite] validate if scope is already present (#36927)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Continuing the work done in [this PR](#35656), addressing a TODO. --------- Co-authored-by: Arthur Silva Sens <[email protected]>
1 parent cab9457 commit 814307e

File tree

3 files changed

+105
-5
lines changed

3 files changed

+105
-5
lines changed

.chloggen/prwreceiver-checkscope.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
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: receiver/prometheusremotewrite
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Check if Scope is already present comparing with the received labels
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: [36927]
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+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

receiver/prometheusremotewritereceiver/receiver.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,26 @@ func addGaugeDatapoints(rm pmetric.ResourceMetrics, ls labels.Labels, ts writev2
234234
// In OTel name+type+unit is the unique identifier of a metric and we should not create
235235
// a new metric if it already exists.
236236

237-
// TODO: Check if Scope is already present by comparing labels "otel_scope_name" and "otel_scope_version"
238-
// with Scope.Name and Scope.Version. If it is present, we should append to the existing Scope.
239-
m := rm.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyGauge()
237+
scopeName := ls.Get("otel_scope_name")
238+
scopeVersion := ls.Get("otel_scope_version")
239+
// TODO: If the scope version or scope name is empty, get the information from the collector build tags.
240+
// More: https://opentelemetry.io/docs/specs/otel/compatibility/prometheus_and_openmetrics/#:~:text=Metrics%20which%20do%20not%20have%20an%20otel_scope_name%20or%20otel_scope_version%20label%20MUST%20be%20assigned%20an%20instrumentation%20scope%20identifying%20the%20entity%20performing%20the%20translation%20from%20Prometheus%20to%20OpenTelemetry%20(e.g.%20the%20collector%E2%80%99s%20prometheus%20receiver)
241+
242+
// Check if the name and version present in the labels are already present in the ResourceMetrics.
243+
// If it is not present, we should create a new ScopeMetrics.
244+
// Otherwise, we should append to the existing ScopeMetrics.
245+
for j := 0; j < rm.ScopeMetrics().Len(); j++ {
246+
scope := rm.ScopeMetrics().At(j)
247+
if scopeName == scope.Scope().Name() && scopeVersion == scope.Scope().Version() {
248+
addDatapoints(scope.Metrics().AppendEmpty().SetEmptyGauge().DataPoints(), ls, ts)
249+
return
250+
}
251+
}
252+
253+
scope := rm.ScopeMetrics().AppendEmpty()
254+
scope.Scope().SetName(scopeName)
255+
scope.Scope().SetVersion(scopeVersion)
256+
m := scope.Metrics().AppendEmpty().SetEmptyGauge()
240257
addDatapoints(m.DataPoints(), ls, ts)
241258
}
242259

receiver/prometheusremotewritereceiver/receiver_test.go

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,63 @@ func TestTranslateV2(t *testing.T) {
143143
expectedMetrics pmetric.Metrics
144144
expectedStats remote.WriteResponseStats
145145
}{
146+
{
147+
name: "duplicated scope name and version",
148+
request: &writev2.Request{
149+
Symbols: []string{
150+
"",
151+
"__name__", "test_metric",
152+
"job", "service-x/test",
153+
"instance", "107cn001",
154+
"otel_scope_name", "scope1",
155+
"otel_scope_version", "v1",
156+
"otel_scope_name", "scope2",
157+
"otel_scope_version", "v2",
158+
"d", "e",
159+
"foo", "bar",
160+
},
161+
Timeseries: []writev2.TimeSeries{
162+
{
163+
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
164+
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16}, // Same scope: scope_name: scope1. scope_version v1
165+
Samples: []writev2.Sample{{Value: 1, Timestamp: 1}},
166+
},
167+
{
168+
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
169+
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16}, // Same scope: scope_name: scope1. scope_version v1
170+
Samples: []writev2.Sample{{Value: 2, Timestamp: 2}},
171+
},
172+
{
173+
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
174+
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 11, 12, 13, 14, 17, 18}, // Different scope: scope_name: scope2. scope_version v2
175+
Samples: []writev2.Sample{{Value: 3, Timestamp: 3}},
176+
},
177+
},
178+
},
179+
expectedMetrics: func() pmetric.Metrics {
180+
expected := pmetric.NewMetrics()
181+
rm1 := expected.ResourceMetrics().AppendEmpty()
182+
rmAttributes1 := rm1.Resource().Attributes()
183+
rmAttributes1.PutStr("service.namespace", "service-x")
184+
rmAttributes1.PutStr("service.name", "test")
185+
rmAttributes1.PutStr("service.instance.id", "107cn001")
186+
sm1 := rm1.ScopeMetrics().AppendEmpty()
187+
sm1.Scope().SetName("scope1")
188+
sm1.Scope().SetVersion("v1")
189+
sm1Attributes := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty().Attributes()
190+
sm1Attributes.PutStr("d", "e")
191+
sm2Attributes := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty().Attributes()
192+
sm2Attributes.PutStr("d", "e")
193+
194+
sm2 := rm1.ScopeMetrics().AppendEmpty()
195+
sm2.Scope().SetName("scope2")
196+
sm2.Scope().SetVersion("v2")
197+
sm3Attributes := sm2.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty().Attributes()
198+
sm3Attributes.PutStr("foo", "bar")
199+
return expected
200+
}(),
201+
expectedStats: remote.WriteResponseStats{},
202+
},
146203
{
147204
name: "missing metric name",
148205
request: &writev2.Request{
@@ -185,8 +242,7 @@ func TestTranslateV2(t *testing.T) {
185242
sm1Attributes.PutStr("foo", "bar")
186243
// Since we don't check "scope_name" and "scope_version", we end up with duplicated scope metrics for repeated series.
187244
// TODO: Properly handle scope metrics.
188-
sm2 := rm1.ScopeMetrics().AppendEmpty()
189-
sm2Attributes := sm2.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty().Attributes()
245+
sm2Attributes := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty().Attributes()
190246
sm2Attributes.PutStr("d", "e")
191247
sm2Attributes.PutStr("foo", "bar")
192248

0 commit comments

Comments
 (0)