[Feature] traceql comparison operators - #6474
Conversation
68f1bc4 to
21cd34c
Compare
| // Uses == for equality instead of = to distinguish from field-level equality | ||
| // ********************** | ||
| metricsFilterOperation: | ||
| EQ { $$ = OpEqual } |
There was a problem hiding this comment.
I picked = and not == to align with how we compare attributes
There was a problem hiding this comment.
I think starting with consistency is good. If for some reason it's not fitting well, we could change it or support both. Fwiw this operator is likely to not be used much.
| %type <scalarFilterOperation> scalarFilterOperation | ||
| %type <metricsAggregation> metricsAggregation | ||
| %type <metricsSecondStage> metricsSecondStage | ||
| %type <scalarFilterOperation> metricsFilterOperation |
There was a problem hiding this comment.
After we agree on syntax, we can add the feature to documentation
| }, | ||
| // Combined second stage tests (topk/bottomk with filter) | ||
| { | ||
| in: `{ } | rate() | topk(5) > 10`, |
There was a problem hiding this comment.
Can we invert the order?
{ } | rate() > 10 | topk(5)
There was a problem hiding this comment.
In the current solution, it is not possible. I can implement, but do we actually need this feature? 🤔
There was a problem hiding this comment.
I guess so? I think allowing both is the best user experience. Right now it will be confussing why we allow one form and not the other
There was a problem hiding this comment.
Yes agree, I would expect to be able to use the scalar filter on any part of the pipeline which is metrics. Also the queries { } | rate() | topk(5) > 10 and { } | rate() > 10 | topk(5) are meaningfully different.
I think we could add the scalar filter as another second stage function, so that { } | rate() > 10 | topk(5) < 11 is parsed as:
spanpipeine: { }
first stage: rate()
second stages: > 10, topk(5), < 11
An argument could be made to add the scalar filter inside a stage. Depending on if that has very large implementation benefits.
If this helps, here is a query with multiple metrics stages and regrouping that I would like to support eventually:
{ } | rate() by (pod) > 10 | topk(5) by (namespace) | sum() by (cluster) > 20
There was a problem hiding this comment.
I concur with the above. This feature would benefit from more time on drawing board to ensure more cases are supported, or there is a concrete plan on how to achieve that
There was a problem hiding this comment.
Added support for { } | rate() > 10 | topk(5) and minor refactoring for better support
There was a problem hiding this comment.
This is so close to being generalized for N stages:
// ChainedSecondStage chains two second stage elements together.
// The first element is processed, then its output is passed to the second.
// Example: {status=error} | rate() | topk(5) > 10
type ChainedSecondStage struct {
first secondStageElement
second secondStageElement
}
Encountered this somewhat quickly by trying to do a query with rate() > X | topk(5) < Y. I do think the PR as-is could be confusing to users, and it is worth generalizing. For example it is not clear why a comparison operator works with topk in the first query but not the second, without understanding the current parsing:
rate() | topk(5) < 100
rate() > 0 | topk(5) < 100
There was a problem hiding this comment.
Oh, I misunderstood you. Added, now it's even more clean
|
|
||
| exemplars := make([]Exemplar, 0, len(series.Exemplars)) | ||
| for i, e := range series.Exemplars { | ||
| if m.compare(e.Value) { |
There was a problem hiding this comment.
We need to guard here against NaN too since NaN != 10 is true
There was a problem hiding this comment.
Nice, catch! Fixed by dropping such exemplars
06a732d to
a5ebfea
Compare
|
+ review fixes |
a5ebfea to
bd1f47c
Compare
|
+ review fixes |
ca50186 to
75aea14
Compare
75aea14 to
17b15ff
Compare
5177cf6 to
858a04a
Compare
|
Do we need docs for this? |
mdisibio
left a comment
There was a problem hiding this comment.
This is looking great. 95% lgtm, just want to see if we can update a few small places.
|
|
||
| for key, series := range input { | ||
| hasValue := false | ||
| newValues := make([]float64, len(series.Values)) |
There was a problem hiding this comment.
For performance, I think it would be ok to edit the input series here. Is there any case where the same series is reused elsewhere?
There was a problem hiding this comment.
I think they should not? Calling Result twice with MetricsFilter will also have the same result due to IsNaN check.
Pushed in a separate commit
| } | ||
|
|
||
| func (c ChainedSecondStage) separator() string { | ||
| return c[0].separator() |
There was a problem hiding this comment.
This isn't used, which makes me consider if separator() shouldn't be part of the interface of individual elements. But part of the second stage, to be used in String() function above only.
i.e. instead of appending directly in expr.y with append($1, $3), we could add a helper that appends with a given separator.
| | spansetPipelineExpression { yylex.(*lexer).expr = newRootExpr($1) } | ||
| | scalarPipelineExpressionFilter { yylex.(*lexer).expr = newRootExpr($1) } | ||
| | spansetPipeline PIPE metricsAggregation { yylex.(*lexer).expr = newRootExprWithMetrics($1, $3) } | ||
| | spansetPipeline PIPE metricsAggregation metricsSecondStagePipeline { yylex.(*lexer).expr = newRootExprWithMetricsTwoStage($1, $3, secondStagePipeline($4)) } |
There was a problem hiding this comment.
This is fantastic 👍 One of these days we could rename metricsAggregation to metricsFirstStagePipeline and I think it would be very easy to understand.
858a04a to
33b0c79
Compare
33b0c79 to
d0d43a4
Compare
mdisibio
left a comment
There was a problem hiding this comment.
I'm loving this PR. BTW this works too {} | rate() by (resource.service.name) > 20 < 60 🚀
LGTM
d0d43a4 to
b92c0bf
Compare
|
No-op push to retrigger CI pipeline |
Introduce comparison operators to TraceQL Metrics.
Examples:
{} | rate() by (resource.service.name) < 0.01
{} | avg_over_time(span:duration) > 1s
What this PR does: it introduces comparison queries to TraceQL Metrics such as




{} | rate() > 10. It is sensitive to step variable, especially to queries like count_over_time().Examples:
No filters
More than 1
More than 3. In this example you can see that Grafana connects dots even when NaN is between them. This should be addressed in a separate issue (proposal is here #6486).
How it works if Tempo returns NaN (not included in this PR!)
Duration


Combination with topk
Which issue(s) this PR fixes:
Fixes # #6417
Checklist
CHANGELOG.mdupdated - the order of entries should be[CHANGE],[FEATURE],[ENHANCEMENT],[BUGFIX]