-
Notifications
You must be signed in to change notification settings - Fork 3k
[small bug fix] [metrics transform processor] check all data points for matching metric label values #3435
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
[small bug fix] [metrics transform processor] check all data points for matching metric label values #3435
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -60,7 +60,7 @@ type internalOperation struct { | |
type internalFilter interface { | ||
getMatches(toMatch metricNameMapping) []*match | ||
getSubexpNames() []string | ||
labelMatched(metric *metricspb.Metric) bool | ||
labelMatched(metric *metricspb.Metric) *metricspb.Metric | ||
} | ||
|
||
type match struct { | ||
|
@@ -78,8 +78,9 @@ func (f internalFilterStrict) getMatches(toMatch metricNameMapping) []*match { | |
if metrics, ok := toMatch[f.include]; ok { | ||
matches := make([]*match, 0, 10) | ||
for _, metric := range metrics { | ||
if f.labelMatched(metric) { | ||
matches = append(matches, &match{metric: metric}) | ||
matchedMetric := f.labelMatched(metric) | ||
if matchedMetric != nil { | ||
matches = append(matches, &match{metric: matchedMetric}) | ||
} | ||
} | ||
return matches | ||
|
@@ -92,9 +93,13 @@ func (f internalFilterStrict) getSubexpNames() []string { | |
return nil | ||
} | ||
|
||
func (f internalFilterStrict) labelMatched(metric *metricspb.Metric) bool { | ||
func (f internalFilterStrict) labelMatched(metric *metricspb.Metric) *metricspb.Metric { | ||
metricWithMatchedLabel := proto.Clone(metric).(*metricspb.Metric) | ||
var timeSeriesWithMatchedLabel []*metricspb.TimeSeries | ||
labelIndexValueMap := make(map[int]string) | ||
|
||
if len(f.matchLabels) == 0 { | ||
return true | ||
return metric | ||
} | ||
hossain-rayhan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
for key, value := range f.matchLabels { | ||
|
@@ -106,18 +111,35 @@ func (f internalFilterStrict) labelMatched(metric *metricspb.Metric) bool { | |
} | ||
|
||
keyFound = true | ||
if len(metric.Timeseries) > 0 && metric.Timeseries[0].LabelValues[idx].Value != value { | ||
return false | ||
} | ||
labelIndexValueMap[idx] = value | ||
} | ||
|
||
// if a label-key is not found then return false only if the given label-value is non-empty. If a given label-value is empty | ||
// and the key is not found then return true. In this approach we can make sure certain key is not present which is a valid use case. | ||
// if a label-key is not found then return nil only if the given label-value is non-empty. If a given label-value is empty | ||
// and the key is not found then move forward. In this approach we can make sure certain key is not present which is a valid use case. | ||
if !keyFound && value != "" { | ||
return false | ||
return nil | ||
} | ||
} | ||
|
||
for _, timeseries := range metric.Timeseries { | ||
allValuesMatched := true | ||
for index, value := range labelIndexValueMap { | ||
if timeseries.LabelValues[index].Value != value { | ||
allValuesMatched = false | ||
break | ||
} | ||
} | ||
if allValuesMatched { | ||
timeSeriesWithMatchedLabel = append(timeSeriesWithMatchedLabel, timeseries) | ||
} | ||
} | ||
return true | ||
|
||
if len(timeSeriesWithMatchedLabel) == 0 { | ||
return nil | ||
} | ||
|
||
metricWithMatchedLabel.Timeseries = timeSeriesWithMatchedLabel | ||
return metricWithMatchedLabel | ||
} | ||
|
||
type internalFilterRegexp struct { | ||
|
@@ -130,8 +152,9 @@ func (f internalFilterRegexp) getMatches(toMatch metricNameMapping) []*match { | |
for name, metrics := range toMatch { | ||
if submatches := f.include.FindStringSubmatchIndex(name); submatches != nil { | ||
for _, metric := range metrics { | ||
if f.labelMatched(metric) { | ||
matches = append(matches, &match{metric: metric, pattern: f.include, submatches: submatches}) | ||
matchedMetric := f.labelMatched(metric) | ||
if matchedMetric != nil { | ||
matches = append(matches, &match{metric: matchedMetric, pattern: f.include, submatches: submatches}) | ||
} | ||
} | ||
} | ||
|
@@ -143,9 +166,13 @@ func (f internalFilterRegexp) getSubexpNames() []string { | |
return f.include.SubexpNames() | ||
} | ||
|
||
func (f internalFilterRegexp) labelMatched(metric *metricspb.Metric) bool { | ||
func (f internalFilterRegexp) labelMatched(metric *metricspb.Metric) *metricspb.Metric { | ||
metricWithMatchedLabel := proto.Clone(metric).(*metricspb.Metric) | ||
var timeSeriesWithMatchedLabel []*metricspb.TimeSeries | ||
labelIndexValueMap := make(map[int]*regexp.Regexp) | ||
|
||
if len(f.matchLabels) == 0 { | ||
return true | ||
return metric | ||
} | ||
|
||
for key, value := range f.matchLabels { | ||
|
@@ -157,18 +184,35 @@ func (f internalFilterRegexp) labelMatched(metric *metricspb.Metric) bool { | |
} | ||
|
||
keyFound = true | ||
if len(metric.Timeseries) > 0 && !value.MatchString(metric.Timeseries[0].LabelValues[idx].Value) { | ||
return false | ||
} | ||
labelIndexValueMap[idx] = value | ||
} | ||
|
||
// if a label-key is not found then return false only if the given label-value is non-empty. If a given label-value is empty | ||
// and the key is not found then return true. In this approach we can make sure certain key is not present which is a valid use case. | ||
// if a label-key is not found then return nil only if the given label-value is non-empty. If a given label-value is empty | ||
// and the key is not found then move forward. In this approach we can make sure certain key is not present which is a valid use case. | ||
if !keyFound && !value.MatchString("") { | ||
return false | ||
return nil | ||
} | ||
} | ||
|
||
for _, timeseries := range metric.Timeseries { | ||
allValuesMatched := true | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like there's a lot of duplicated code, can you clean it up? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. I think much of this commonality could be extracted by defining a type StringMatcher interface {
MatchString(string) bool
} All of the values in
At that point the labelMatched(labelMatchers []StringMatcher, metric *metricspb.Metric) *metricspb.Metric {...} Since the only value used from the receiver in either of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tried to follow your suggestion. Pushed an update. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Copied your suggested code @Aneurysm9 . Please check. |
||
for index, value := range labelIndexValueMap { | ||
if !value.MatchString(timeseries.LabelValues[index].Value) { | ||
allValuesMatched = false | ||
break | ||
} | ||
} | ||
if allValuesMatched { | ||
timeSeriesWithMatchedLabel = append(timeSeriesWithMatchedLabel, timeseries) | ||
} | ||
} | ||
return true | ||
|
||
if len(timeSeriesWithMatchedLabel) == 0 { | ||
return nil | ||
} | ||
|
||
metricWithMatchedLabel.Timeseries = timeSeriesWithMatchedLabel | ||
return metricWithMatchedLabel | ||
} | ||
|
||
type metricNameMapping map[string][]*metricspb.Metric | ||
|
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.
Why do you need to clone? Can you avoid 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.
Rather than cloning the whole metric object, now I am copying the Resource and MetricDescriptor. We don't want to modify the incoming pdata.Metric, thats way generating a new Metric and returning it. Pushed an update.
Uh oh!
There was an error while loading. Please reload this page.
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.
@hossain-rayhan Was there any specific reasons to clone the objects. Like were you seeing an error or test failures ?
I am asking because cloning seems to be reason for why updates are not working with experimental_match_label.
cc @anuraaga @Aneurysm9
My observation is as follows - When we returns a cloned "MetricDescriptor" object, the update at this point fails -
https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/processor/metricstransformprocessor/metrics_transform_processor.go#L423
This is because, the above line is updating a cloned object rather than updating the original "MetricDescriptor" object.
one such example is here - #7071
Please let me know what do you think ?