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

[reciever/prometheusremotewritereciever] handle otel_scope_name and otel_scope_version in prw reciever #37791

Conversation

Vandit1604
Copy link

@Vandit1604 Vandit1604 commented Feb 8, 2025

This PR ensures that when Prometheus samples lack the otel_scope_name or otel_scope_version labels, the receiver fills in default values. If otel_scope_name is missing, it defaults to opentelemetry-collector. If otel_scope_version is missing, it uses the collector’s build version. This change makes sure every metric has a complete instrumentation scope as required.

more here: Metrics which do not have an otel_scope_name or otel_scope_version label MUST be assigned an instrumentation scope identifying the entity performing the translation from Prometheus to OpenTelemetry

@Vandit1604
Copy link
Author

Should I also handle otel_scope_info in this?

Labels on otel_scope_info metric points other than otel_scope_name and otel_scope_version, MUST be added as scope attributes to the scope with the matching name and version.

found this in the OTLP spec here

@dashpole
Copy link
Contributor

Should I also handle otel_scope_info in this?

Not right now. That spec needs to be updated because scope attributes were made identifying

Copy link
Member

@ArthurSens ArthurSens left a comment

Choose a reason for hiding this comment

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

Thanks for starting the work here :)

We'll also need tests to verify that we're not breaking anything. Can you add one test to ensure that two time series without scope labels end up together and with the correct scope attributes?

// 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)
if scopeName == "" {
scopeName = "opentelemetry-collector"
Copy link
Member

@ArthurSens ArthurSens Feb 10, 2025

Choose a reason for hiding this comment

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

Can't we take the scope name from settings, just like you did with the version?

Copy link
Author

Choose a reason for hiding this comment

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

something like this ?

scopeName = prw.settings.BuildInfo.Command

Copy link
Contributor

Choose a reason for hiding this comment

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

Um, how about adding scopedetail func? Which could fetch data from collector setting

Copy link
Member

Choose a reason for hiding this comment

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

I think Vandit needs a bit more detail about what a scopedetails func is. Looking at your PR #37871, I like the idea, but let's explain why this is good. :)

This is also unrelated to this specific thread, so let's start a new review for this idea

Copy link
Member

Choose a reason for hiding this comment

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

something like this ?

scopeName = prw.settings.BuildInfo.Command

When reading the doc string for settings.BuildInfo.Description, I see:

Description is the full name of the collector, e.g. "OpenTelemetry Collector"

So I think the description is the most appropriate one.

Copy link
Contributor

Choose a reason for hiding this comment

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

Understood thanks :)

@@ -230,15 +234,24 @@ func addCounterDatapoints(_ pmetric.ResourceMetrics, _ labels.Labels, _ writev2.
// TODO: Implement this function
}

var defaultBuildVersion string = "unknown"
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if we want to do this. Have you seen this requirement somewhere?

Copy link
Author

Choose a reason for hiding this comment

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

Not specifically, I added this as a fallback in case settings.BuildInfo.Version is empty or unavailable. Is this not the correct approach? Happy to adjust if there's a better way to handle it.

Copy link
Member

@ArthurSens ArthurSens left a comment

Choose a reason for hiding this comment

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

I think we're over engineering a bit with the fallbacks, there's nothing in the spec asking for this so we can just use empty strings.

// 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)
if scopeName == "" {
scopeName = "opentelemetry-collector"
Copy link
Member

Choose a reason for hiding this comment

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

I think Vandit needs a bit more detail about what a scopedetails func is. Looking at your PR #37871, I like the idea, but let's explain why this is good. :)

This is also unrelated to this specific thread, so let's start a new review for this idea

// 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)
if scopeName == "" {
scopeName = "opentelemetry-collector"
Copy link
Member

Choose a reason for hiding this comment

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

something like this ?

scopeName = prw.settings.BuildInfo.Command

When reading the doc string for settings.BuildInfo.Description, I see:

Description is the full name of the collector, e.g. "OpenTelemetry Collector"

So I think the description is the most appropriate one.

@Vandit1604
Copy link
Author

Hi @ArthurSens I have added tests and changelog entry as well.

Please review in your own time.
Thank you!

Comment on lines 241 to 244
var (
buildVersion string = ""
buildName string = ""
)
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe, for better readability, consider moving these variables to the top of the file, closer to where they are used

Copy link
Contributor

Choose a reason for hiding this comment

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

According to this comment, we don't even need to set those vars. So, ignore my suggestion 🙃

expectedStats: remote.WriteResponseStats{},
},
{
name: "missing otel_scope_name/version falls back to buildName/buildVersion",
Copy link
Contributor

@perebaj perebaj Feb 13, 2025

Choose a reason for hiding this comment

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

Aren't both otel_scope_name/version falls back to buildName/buildVersion and valid request testing the same scenario? In both cases, we’re not passing otel_scope_name/version and expect the values to default to buildName/buildVersion. Instead, we could create a test case where we explicitly set settings.BuildInfo.Description/Version and validate whether the values are correctly assigned.

Comment on lines 31 to 30
if settings.BuildInfo.Version != "" {
buildVersion = settings.BuildInfo.Version
}

if settings.BuildInfo.Description != "" {
buildName = settings.BuildInfo.Description
}

return &prometheusRemoteWriteReceiver{
Copy link
Contributor

@perebaj perebaj Feb 13, 2025

Choose a reason for hiding this comment

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

Regardless of whether settings.BuildInfo.Version or settings.BuildInfo.Description are empty, we should always assign their values to buildName and version. The determining factor for whether we use them or not will be whether scope_name or version are empty. I think that we can simplify the ifs here.

Suggested change
if settings.BuildInfo.Version != "" {
buildVersion = settings.BuildInfo.Version
}
if settings.BuildInfo.Description != "" {
buildName = settings.BuildInfo.Description
}
return &prometheusRemoteWriteReceiver{
buildVersion = settings.BuildInfo.Version
buildName = settings.BuildInfo.Description
return &prometheusRemoteWriteReceiver{

Copy link
Member

Choose a reason for hiding this comment

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

True :)

We don't even need the extra variables, actually. We can update the function like this:

func (prwr * prometheusRemoteWriteReceiver) addGaugeDatapoints(..... {
  scopeName := prwr.settings.BuildInfo.Description
  scopeVersion := prwr.settings.BuildInfo.Version
}

If the settings are empty, the scope variables will be empty. If settings had proper content, then the scope will be populated correctly

@ArthurSens
Copy link
Member

Could someone with permissions trigger tests here? Maybe @dashpole :)

@Vandit1604
Copy link
Author

Updated the test with the case custom BuildInfo used when no scope provided to address @perebaj 's comment. Also updated the code to only override with collector build details if ls.Get() returns an empty string.

Comment on lines 245 to 252
m1 := sm1.Metrics().AppendEmpty().SetEmptyGauge()
dp1 := m1.DataPoints().AppendEmpty()
dp1.Attributes().PutStr("d", "e")
dp1.Attributes().PutStr("foo", "bar")
Copy link
Contributor

Choose a reason for hiding this comment

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

do you have any reason to change sm1Attributes := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty().Attributes() ->

m1 := sm1.Metrics().AppendEmpty().SetEmptyGauge()
dp1 := m1.DataPoints().AppendEmpty()
dp1.Attributes().PutStr(...)
dp1.Attributes().PutStr(...)

?

Seems that the previous implementation it's ok

Copy link
Contributor

@perebaj perebaj left a comment

Choose a reason for hiding this comment

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

Some suggestions ✌🏽

Comment on lines 326 to 325
sm.Scope().SetName("")
sm.Scope().SetVersion("")
sm.Scope().SetName(defaultBuildName)
sm.Scope().SetVersion(defaultBuildVersion)
Copy link
Contributor

@perebaj perebaj Feb 17, 2025

Choose a reason for hiding this comment

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

Did using the empty string instead of defaultBuildName/Version cause the test to fail?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hey @Vandit1604 I think that is only thing that is missing before we merge it 🙃

Copy link
Contributor

Choose a reason for hiding this comment

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

And some conflicts related to this PR that was merged recently

Copy link
Member

@ArthurSens ArthurSens left a comment

Choose a reason for hiding this comment

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

Thank you for being so diligent with the tests, I love it!

One extra comment besides the ones below: Could we keep all the scope-related tests together? What I mean is to move the test duplicated scope name and version further below, after valid request

Comment on lines 27 to 28
const (
defaultBuildName = "defaultBuildName"
defaultBuildVersion = "defaultBuildVersion"
)
Copy link
Member

Choose a reason for hiding this comment

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

Could we remove this?

Comment on lines 197 to 200
addCounterDatapoints(rm, ls, ts)
case writev2.Metadata_METRIC_TYPE_GAUGE:
addGaugeDatapoints(rm, ls, ts)
prw.addGaugeDatapoints(rm, ls, ts)
case writev2.Metadata_METRIC_TYPE_SUMMARY:
addSummaryDatapoints(rm, ls, ts)
Copy link
Member

Choose a reason for hiding this comment

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

Could we do the same for all other as well? I know they are not implemented yet, but we'll definitely do the same thing when implementing them.

What I mean that we have something like this:

		switch ts.Metadata.Type {
		case writev2.Metadata_METRIC_TYPE_COUNTER:
		-->	prw.addCounterDatapoints(rm, ls, ts) 
		case writev2.Metadata_METRIC_TYPE_GAUGE:
			prw.addGaugeDatapoints(rm, ls, ts)
		case writev2.Metadata_METRIC_TYPE_SUMMARY:
		-->	prw.addSummaryDatapoints(rm, ls, ts)
		case writev2.Metadata_METRIC_TYPE_HISTOGRAM:
		-->	prw.addHistogramDatapoints(rm, ls, ts)
		default:
			badRequestErrors = errors.Join(badRequestErrors, fmt.Errorf("unsupported metric type %q for metric %q", ts.Metadata.Type, ls.Get(labels.MetricName)))
		}```

Comment on lines 234 to 235
// For job "service-x/test", no explicit otel_scope is provided.
// fall back to BuildInfo defaults.
Copy link
Member

Choose a reason for hiding this comment

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

Actually I believe we don't want to fallback to anything. There's nothing in the specification saying that 🤔

Comment on lines 171 to 183
rmAttributes1.PutStr("service.namespace", "service-x")
rmAttributes1.PutStr("service.name", "test")
rmAttributes1.PutStr("service.instance.id", "107cn001")
sm1 := rm1.ScopeMetrics().AppendEmpty()
rm := expected.ResourceMetrics().AppendEmpty()
parseJobAndInstance(rm.Resource().Attributes(), "service-x/test", "107cn001")
Copy link
Member

Choose a reason for hiding this comment

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

hmmmm I see what you wanted to do here; it makes the code a lot easier to read, BUT! then we're not testing the function parseJobAndInstance() anymore.

If we accidentally break this function in the future, if we call it during the preparation of our expected result, we will never notice the difference.

Could we keep manually setting those attributes?

Comment on lines 223 to 237
rmAttributes1 := rm1.Resource().Attributes()
rmAttributes1.PutStr("service.namespace", "service-x")
rmAttributes1.PutStr("service.name", "test")
rmAttributes1.PutStr("service.instance.id", "107cn001")
parseJobAndInstance(rm1.Resource().Attributes(), "service-x/test", "107cn001")
Copy link
Member

Choose a reason for hiding this comment

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

same thing here

sm2Attributes := sm1.Metrics().AppendEmpty().SetEmptyGauge().DataPoints().AppendEmpty().Attributes()
sm2Attributes.PutStr("d", "e")
sm2Attributes.PutStr("foo", "bar")

// For job "foo" with instance "bar", fallback to BuildInfo.
Copy link
Member

Choose a reason for hiding this comment

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

No need for a fallback

expectedStats: remote.WriteResponseStats{},
},
{
name: "provided otel_scope_name and otel_scope_version",
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure if I'm missing something, but I think this test case is covered by the test case duplicated scope name and version, isn't it?

If that correct, could we remove this test case?

expectedStats: remote.WriteResponseStats{},
},
{
name: "missing otel_scope_name/version falls back to BuildInfo",
Copy link
Member

Choose a reason for hiding this comment

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

We don't need a fallback :)

But we could certify that all metrics with a missing scope would end up within the same scope with no attributes.

Comment on lines 337 to 338
buildNameOverride: "customBuildName",
buildVersionOverride: "customBuildVersion",
Copy link
Member

Choose a reason for hiding this comment

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

I don't really understand where this is coming from 🤔 could you explain?

Comment on lines 141 to 142
buildNameOverride string
buildVersionOverride string
Copy link
Member

Choose a reason for hiding this comment

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

What is this being used for? I don't really understand

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>

Unverified

This commit is not signed, but one or more authors requires that any commit attributed to them is signed.
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Vandit1604 and others added 4 commits February 23, 2025 21:38
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Co-authored-by: Jonathan  <perebaj@gmail.com>
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
@Vandit1604 Vandit1604 force-pushed the handle-prw-otel_scope_name-and-version branch from 01615f6 to a70e789 Compare February 23, 2025 17:02
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
@Vandit1604
Copy link
Author

I rebased the PR and did following changes

  • Using settings.BuildInfo to get collector's build details
  • The updated the tests accordingly.

@perebaj
Copy link
Contributor

perebaj commented Mar 6, 2025

Hey @Vandit1604 many thanks for your effort here, but this PR is a blocker for us. So we decided to go forward in a simpler way. But the others addXDataPoints should be implement using the extractScopeInfo approach

@Vandit1604
Copy link
Author

@perebaj Should I close this PR then?

@Vandit1604 Vandit1604 closed this Mar 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants