Skip to content

Commit e3062ec

Browse files
authored
Handle scope name version (#38413)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description This PR ensures that when Prometheus samples lack the otel_scope_name or otel_scope_version labels, the receiver fills in default values. It take care of part of #37277
1 parent 1419cfa commit e3062ec

File tree

3 files changed

+117
-72
lines changed

3 files changed

+117
-72
lines changed
+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
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: prometheusremotewritereciever
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: "Handle `otel_scope_name` and `otel_scope_version` labels in Prometheus Remote Write receiver properly if not present"
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: [37791]
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: if otel_scope_name or otel_scope_name is missing, use collector’s version and description according to the otel spec.
19+
# If your change doesn't affect end users or the exported elements of any package,
20+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
21+
# Optional: The change log or logs in which this entry should be included.
22+
# e.g. '[user]' or '[user, api]'
23+
# Include 'user' if the change is relevant to end users.
24+
# Include 'api' if there is a change to a library API.
25+
# Default: '[user]'
26+
change_logs: [user]

receiver/prometheusremotewritereceiver/receiver.go

+25-12
Original file line numberDiff line numberDiff line change
@@ -193,13 +193,13 @@ func (prw *prometheusRemoteWriteReceiver) translateV2(_ context.Context, req *wr
193193

194194
switch ts.Metadata.Type {
195195
case writev2.Metadata_METRIC_TYPE_COUNTER:
196-
addCounterDatapoints(rm, ls, ts)
196+
prw.addCounterDatapoints(rm, ls, ts)
197197
case writev2.Metadata_METRIC_TYPE_GAUGE:
198-
addGaugeDatapoints(rm, ls, ts)
198+
prw.addGaugeDatapoints(rm, ls, ts)
199199
case writev2.Metadata_METRIC_TYPE_SUMMARY:
200-
addSummaryDatapoints(rm, ls, ts)
200+
prw.addSummaryDatapoints(rm, ls, ts)
201201
case writev2.Metadata_METRIC_TYPE_HISTOGRAM:
202-
addHistogramDatapoints(rm, ls, ts)
202+
prw.addHistogramDatapoints(rm, ls, ts)
203203
default:
204204
badRequestErrors = errors.Join(badRequestErrors, fmt.Errorf("unsupported metric type %q for metric %q", ts.Metadata.Type, ls.Get(labels.MetricName)))
205205
}
@@ -225,19 +225,16 @@ func parseJobAndInstance(dest pcommon.Map, job, instance string) {
225225
}
226226
}
227227

228-
func addCounterDatapoints(_ pmetric.ResourceMetrics, _ labels.Labels, _ writev2.TimeSeries) {
228+
func (prw *prometheusRemoteWriteReceiver) addCounterDatapoints(_ pmetric.ResourceMetrics, _ labels.Labels, _ writev2.TimeSeries) {
229229
// TODO: Implement this function
230230
}
231231

232-
func addGaugeDatapoints(rm pmetric.ResourceMetrics, ls labels.Labels, ts writev2.TimeSeries) {
232+
func (prw *prometheusRemoteWriteReceiver) addGaugeDatapoints(rm pmetric.ResourceMetrics, ls labels.Labels, ts writev2.TimeSeries) {
233233
// TODO: Cache metric name+type+unit and look up cache before creating new empty metric.
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-
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)
237+
scopeName, scopeVersion := prw.extractScopeInfo(ls)
241238

242239
// Check if the name and version present in the labels are already present in the ResourceMetrics.
243240
// If it is not present, we should create a new ScopeMetrics.
@@ -257,11 +254,11 @@ func addGaugeDatapoints(rm pmetric.ResourceMetrics, ls labels.Labels, ts writev2
257254
addDatapoints(m.DataPoints(), ls, ts)
258255
}
259256

260-
func addSummaryDatapoints(_ pmetric.ResourceMetrics, _ labels.Labels, _ writev2.TimeSeries) {
257+
func (prw *prometheusRemoteWriteReceiver) addSummaryDatapoints(_ pmetric.ResourceMetrics, _ labels.Labels, _ writev2.TimeSeries) {
261258
// TODO: Implement this function
262259
}
263260

264-
func addHistogramDatapoints(_ pmetric.ResourceMetrics, _ labels.Labels, _ writev2.TimeSeries) {
261+
func (prw *prometheusRemoteWriteReceiver) addHistogramDatapoints(_ pmetric.ResourceMetrics, _ labels.Labels, _ writev2.TimeSeries) {
265262
// TODO: Implement this function
266263
}
267264

@@ -287,3 +284,19 @@ func addDatapoints(datapoints pmetric.NumberDataPointSlice, ls labels.Labels, ts
287284
}
288285
}
289286
}
287+
288+
// extractScopeInfo extracts the scope name and version from the labels. If the labels do not contain the scope name/version,
289+
// it will use the default values from the settings.
290+
func (prw *prometheusRemoteWriteReceiver) extractScopeInfo(ls labels.Labels) (string, string) {
291+
scopeName := prw.settings.BuildInfo.Description
292+
scopeVersion := prw.settings.BuildInfo.Version
293+
294+
if sName := ls.Get("otel_scope_name"); sName != "" {
295+
scopeName = sName
296+
}
297+
298+
if sVersion := ls.Get("otel_scope_version"); sVersion != "" {
299+
scopeVersion = sVersion
300+
}
301+
return scopeName, scopeVersion
302+
}

receiver/prometheusremotewritereceiver/receiver_test.go

+66-60
Original file line numberDiff line numberDiff line change
@@ -135,38 +135,34 @@ func TestTranslateV2(t *testing.T) {
135135
expectedStats remote.WriteResponseStats
136136
}{
137137
{
138-
name: "duplicated scope name and version",
138+
name: "missing metric name",
139139
request: &writev2.Request{
140-
Symbols: []string{
141-
"",
142-
"__name__", "test_metric",
143-
"job", "service-x/test",
144-
"instance", "107cn001",
145-
"otel_scope_name", "scope1",
146-
"otel_scope_version", "v1",
147-
"otel_scope_name", "scope2",
148-
"otel_scope_version", "v2",
149-
"d", "e",
150-
"foo", "bar",
151-
},
140+
Symbols: []string{"", "foo", "bar"},
152141
Timeseries: []writev2.TimeSeries{
153142
{
154-
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
155-
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16},
143+
LabelsRefs: []uint32{1, 2},
156144
Samples: []writev2.Sample{{Value: 1, Timestamp: 1}},
157145
},
146+
},
147+
},
148+
expectError: "missing metric name in labels",
149+
},
150+
{
151+
name: "duplicate label",
152+
request: &writev2.Request{
153+
Symbols: []string{"", "__name__", "test"},
154+
Timeseries: []writev2.TimeSeries{
158155
{
159-
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
160-
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16},
161-
Samples: []writev2.Sample{{Value: 2, Timestamp: 2}},
162-
},
163-
{
164-
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
165-
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 11, 12, 13, 14, 17, 18},
166-
Samples: []writev2.Sample{{Value: 3, Timestamp: 3}},
156+
LabelsRefs: []uint32{1, 2, 1, 2},
157+
Samples: []writev2.Sample{{Value: 1, Timestamp: 1}},
167158
},
168159
},
169160
},
161+
expectError: `duplicate label "__name__" in labels`,
162+
},
163+
{
164+
name: "valid request",
165+
request: writeV2RequestFixture,
170166
expectedMetrics: func() pmetric.Metrics {
171167
expected := pmetric.NewMetrics()
172168
rm1 := expected.ResourceMetrics().AppendEmpty()
@@ -176,61 +172,72 @@ func TestTranslateV2(t *testing.T) {
176172
rmAttributes1.PutStr("service.instance.id", "107cn001")
177173

178174
sm1 := rm1.ScopeMetrics().AppendEmpty()
179-
sm1.Scope().SetName("scope1")
180-
sm1.Scope().SetVersion("v1")
181-
175+
// Since we don't define the labels otel_scope_name and otel_scope_version, the default values coming from the receiver settings will be used.
176+
sm1.Scope().SetName("OpenTelemetry Collector")
177+
sm1.Scope().SetVersion("latest")
182178
dp1 := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
183179
dp1.SetTimestamp(pcommon.Timestamp(1 * int64(time.Millisecond)))
184180
dp1.SetDoubleValue(1.0)
185181
dp1.Attributes().PutStr("d", "e")
182+
dp1.Attributes().PutStr("foo", "bar")
186183

187184
dp2 := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
188185
dp2.SetTimestamp(pcommon.Timestamp(2 * int64(time.Millisecond)))
189186
dp2.SetDoubleValue(2.0)
190187
dp2.Attributes().PutStr("d", "e")
188+
dp2.Attributes().PutStr("foo", "bar")
191189

192-
sm2 := rm1.ScopeMetrics().AppendEmpty()
193-
sm2.Scope().SetName("scope2")
194-
sm2.Scope().SetVersion("v2")
190+
rm2 := expected.ResourceMetrics().AppendEmpty()
191+
rmAttributes2 := rm2.Resource().Attributes()
192+
rmAttributes2.PutStr("service.name", "foo")
193+
rmAttributes2.PutStr("service.instance.id", "bar")
195194

195+
sm2 := rm2.ScopeMetrics().AppendEmpty()
196+
sm2.Scope().SetName("OpenTelemetry Collector")
197+
sm2.Scope().SetVersion("latest")
196198
dp3 := sm2.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
197-
dp3.SetTimestamp(pcommon.Timestamp(3 * int64(time.Millisecond)))
198-
dp3.SetDoubleValue(3.0)
199+
dp3.SetTimestamp(pcommon.Timestamp(2 * int64(time.Millisecond)))
200+
dp3.SetDoubleValue(2.0)
201+
dp3.Attributes().PutStr("d", "e")
199202
dp3.Attributes().PutStr("foo", "bar")
200203

201204
return expected
202205
}(),
203206
expectedStats: remote.WriteResponseStats{},
204207
},
205208
{
206-
name: "missing metric name",
209+
name: "timeseries with different scopes",
207210
request: &writev2.Request{
208-
Symbols: []string{"", "foo", "bar"},
211+
Symbols: []string{
212+
"",
213+
"__name__", "test_metric", // 1, 2
214+
"job", "service-x/test", // 3, 4
215+
"instance", "107cn001", // 5, 6
216+
"otel_scope_name", "scope1", // 7, 8
217+
"otel_scope_version", "v1", // 9, 10
218+
"otel_scope_name", "scope2", // 11, 12
219+
"otel_scope_version", "v2", // 13, 14
220+
"d", "e", // 15, 16
221+
"foo", "bar", // 17, 18
222+
},
209223
Timeseries: []writev2.TimeSeries{
210224
{
211-
LabelsRefs: []uint32{1, 2},
225+
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
226+
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16},
212227
Samples: []writev2.Sample{{Value: 1, Timestamp: 1}},
213228
},
214-
},
215-
},
216-
expectError: "missing metric name in labels",
217-
},
218-
{
219-
name: "duplicate label",
220-
request: &writev2.Request{
221-
Symbols: []string{"", "__name__", "test"},
222-
Timeseries: []writev2.TimeSeries{
223229
{
224-
LabelsRefs: []uint32{1, 2, 1, 2},
225-
Samples: []writev2.Sample{{Value: 1, Timestamp: 1}},
230+
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
231+
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 15, 16},
232+
Samples: []writev2.Sample{{Value: 2, Timestamp: 2}},
233+
},
234+
{
235+
Metadata: writev2.Metadata{Type: writev2.Metadata_METRIC_TYPE_GAUGE},
236+
LabelsRefs: []uint32{1, 2, 3, 4, 5, 6, 11, 12, 13, 14, 17, 18},
237+
Samples: []writev2.Sample{{Value: 3, Timestamp: 3}},
226238
},
227239
},
228240
},
229-
expectError: `duplicate label "__name__" in labels`,
230-
},
231-
{
232-
name: "valid request",
233-
request: writeV2RequestFixture,
234241
expectedMetrics: func() pmetric.Metrics {
235242
expected := pmetric.NewMetrics()
236243
rm1 := expected.ResourceMetrics().AppendEmpty()
@@ -240,27 +247,26 @@ func TestTranslateV2(t *testing.T) {
240247
rmAttributes1.PutStr("service.instance.id", "107cn001")
241248

242249
sm1 := rm1.ScopeMetrics().AppendEmpty()
250+
sm1.Scope().SetName("scope1")
251+
sm1.Scope().SetVersion("v1")
252+
243253
dp1 := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
244254
dp1.SetTimestamp(pcommon.Timestamp(1 * int64(time.Millisecond)))
245255
dp1.SetDoubleValue(1.0)
246256
dp1.Attributes().PutStr("d", "e")
247-
dp1.Attributes().PutStr("foo", "bar")
248257

249258
dp2 := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
250259
dp2.SetTimestamp(pcommon.Timestamp(2 * int64(time.Millisecond)))
251260
dp2.SetDoubleValue(2.0)
252261
dp2.Attributes().PutStr("d", "e")
253-
dp2.Attributes().PutStr("foo", "bar")
254262

255-
rm2 := expected.ResourceMetrics().AppendEmpty()
256-
rmAttributes2 := rm2.Resource().Attributes()
257-
rmAttributes2.PutStr("service.name", "foo")
258-
rmAttributes2.PutStr("service.instance.id", "bar")
263+
sm2 := rm1.ScopeMetrics().AppendEmpty()
264+
sm2.Scope().SetName("scope2")
265+
sm2.Scope().SetVersion("v2")
259266

260-
dp3 := rm2.ScopeMetrics().AppendEmpty().Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
261-
dp3.SetTimestamp(pcommon.Timestamp(2 * int64(time.Millisecond)))
262-
dp3.SetDoubleValue(2.0)
263-
dp3.Attributes().PutStr("d", "e")
267+
dp3 := sm2.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty()
268+
dp3.SetTimestamp(pcommon.Timestamp(3 * int64(time.Millisecond)))
269+
dp3.SetDoubleValue(3.0)
264270
dp3.Attributes().PutStr("foo", "bar")
265271

266272
return expected

0 commit comments

Comments
 (0)