-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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
[reciever/prometheusremotewritereciever] handle otel_scope_name and otel_scope_version in prw reciever #37791
Conversation
Should I also handle
found this in the OTLP spec here |
Not right now. That spec needs to be updated because scope attributes were made identifying |
There was a problem hiding this 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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this 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" |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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.
Hi @ArthurSens I have added tests and changelog entry as well. Please review in your own time. |
var ( | ||
buildVersion string = "" | ||
buildName string = "" | ||
) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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.
if settings.BuildInfo.Version != "" { | ||
buildVersion = settings.BuildInfo.Version | ||
} | ||
|
||
if settings.BuildInfo.Description != "" { | ||
buildName = settings.BuildInfo.Description | ||
} | ||
|
||
return &prometheusRemoteWriteReceiver{ |
There was a problem hiding this comment.
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.
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{ |
There was a problem hiding this comment.
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
Could someone with permissions trigger tests here? Maybe @dashpole :) |
Updated the test with the case |
m1 := sm1.Metrics().AppendEmpty().SetEmptyGauge() | ||
dp1 := m1.DataPoints().AppendEmpty() | ||
dp1.Attributes().PutStr("d", "e") | ||
dp1.Attributes().PutStr("foo", "bar") |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some suggestions ✌🏽
sm.Scope().SetName("") | ||
sm.Scope().SetVersion("") | ||
sm.Scope().SetName(defaultBuildName) | ||
sm.Scope().SetVersion(defaultBuildVersion) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 🙃
There was a problem hiding this comment.
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
There was a problem hiding this 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
const ( | ||
defaultBuildName = "defaultBuildName" | ||
defaultBuildVersion = "defaultBuildVersion" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we remove this?
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) |
There was a problem hiding this comment.
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)))
}```
// For job "service-x/test", no explicit otel_scope is provided. | ||
// fall back to BuildInfo defaults. |
There was a problem hiding this comment.
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 🤔
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") |
There was a problem hiding this comment.
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?
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") |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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.
buildNameOverride: "customBuildName", | ||
buildVersionOverride: "customBuildVersion", |
There was a problem hiding this comment.
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?
buildNameOverride string | ||
buildVersionOverride string |
There was a problem hiding this comment.
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
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
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>
01615f6
to
a70e789
Compare
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
Signed-off-by: Vandit Singh <vanditsinghkv@gmail.com>
I rebased the PR and did following changes
|
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 |
@perebaj Should I close this PR then? |
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