Skip to content

[TraceQL Metrics] New baseline comparison function - #3695

Merged
mdisibio merged 23 commits into
grafana:mainfrom
mdisibio:baseline-compare
Jun 21, 2024
Merged

[TraceQL Metrics] New baseline comparison function#3695
mdisibio merged 23 commits into
grafana:mainfrom
mdisibio:baseline-compare

Conversation

@mdisibio

@mdisibio mdisibio commented May 21, 2024

Copy link
Copy Markdown
Contributor

What this PR does:
This adds a new metrics function compare which is used to split the stream of spans into two groups: a selection and a baseline. Then it returns time-series for all attributes found on the spans to highlight the differences between the two groups. This is kind of hard to describe so there are some example outputs below:

Function signature:
The function is used like other metrics functions, which it is placed after any search query, and converts it into a metrics query:
...any spanset pipeline... | compare({subset filters}, <topN>, <start timestamp>, <end timestamp>)

Example:
{ resource.service.name="a" && span.http.path="/myapi" } | compare({status=error})

Parameters:

  1. Required. The first parameter is a spanset filter for choosing the subset of spans. This filter is executed against the incoming spans. If it matches, then the span is considered to be part of the selection. Otherwise it is part of the baseline. Common filters are expected to be things like {status=error} (what is different about errors?) or {duration>1s} (what is different about slow spans?)
  2. Optional. The second parameter is the top N values to return per attribute. If an attribute exceeds this limit in either the selection group or baseline group, then only the top N values (based on frequency) are returned, and an error indicator for the attribute is included output (see below). Defaults to 10.
  3. Optional. Start and end timestamps in unix nanoseconds, which can be used to additionally subset the spans in time. These timestamps must both be given, or neither. These parameters are unlike any others in traceql and therefore kind of clunky. Maybe in the future we can fix this by adding the ability to check span:startTime directly in the language, so it could be part of the filter.

Output:
The outputs are flat time-series for each attribute/value found in the spans. This function has a built-in select(*) so there can be a lot. Each series has a label __meta_type which denotes which group it is in, either selection or baseline.

Example output series:

{ __meta_type="baseline", resource.cluster="prod" } 123
{ __meta_type="baseline", resource.cluster="qa" } 124
{ __meta_type="selection", resource.cluster="prod" } 456   <--- significant difference detected
{ __meta_type="selection", resource.cluster="qa" } 125
{ __meta_type="selection", resource.cluster="dev"} 126  <--- cluster=dev was found in the highlighted spans but not in the baseline

When an attribute reaches the cardinality limit there will also be present an error indicator. This example means the attribute resource.cluster had too many values.

{ __meta_error="__too_many_values__", resource.cluster=<nil> }

Remaining Work

  1. Not 100% settled on the meta labels and indication of attributes that reached max cardinality. Would appreciate feedback.
  2. This function has a built-in select(*) to select all attributes of all spans (yes all). So it is likely to exceed gRPC payloads when run as a range query. We don't have official support for instant queries, but you can emulate it by setting step equal to end-start, so effectively it is a range query that returns a single datapoint.

Which issue(s) this PR fixes:
Fixes #

Checklist

  • Tests updated
  • Documentation added
  • CHANGELOG.md updated - the order of entries should be [CHANGE], [FEATURE], [ENHANCEMENT], [BUGFIX]
@adrapereira

Copy link
Copy Markdown

Instead of requiring max cardinality what if we also make it optional and default to a sensible value?

I'm thinking it would be better to return the values Tempo got until max and an error instead of returning an error and nil when it reaches max cardinality. This way that attribute would still show some value, instead of none. Think of a graph with no data and an error label vs a graph with some data and an error/warning label, what would you prefer?

I agree that the timestamps in the function are ugly, would be more TraceQL-y to have it as span:startTime as you mention.

@mdisibio

Copy link
Copy Markdown
Contributor Author

Instead of requiring max cardinality what if we also make it optional and default to a sensible value?

Yep can do that. I think 10 is a sensible default.

I'm thinking it would be better to return the values Tempo got until max and an error instead of returning an error and nil when it reaches max cardinality. This way that attribute would still show some value, instead of none. Think of a graph with no data and an error label vs a graph with some data and an error/warning label, what would you prefer?

The main rationale was to avoid computing the exact topN values, which requires continuing to count and pass all values up to the query-frontend. There are two alternatives that are lossy but should be workable:

  1. Lossy topN - each job performs topN, and then the query-frontend performs topN again. This is lossy because low-rate but omnipresent values that might be the actual topN get overshadowed by bursty values.
  2. FirstN - also good performance, but not sure the usefulness.
@adrapereira

Copy link
Copy Markdown

My proposal was inline with your FirstN idea so either of your options would work for me, but curious about other opinions.

@mdisibio
mdisibio marked this pull request as ready for review June 17, 2024 13:17
@mdisibio
mdisibio requested a review from stoewer as a code owner June 17, 2024 13:17
Comment thread pkg/traceql/engine_metrics_compare.go
Comment thread pkg/traceql/engine_metrics_compare.go Outdated
Comment thread pkg/traceql/engine_metrics_compare.go
Comment thread tempodb/encoding/vparquet4/block_traceql.go Outdated
Comment thread pkg/traceql/engine_metrics_compare.go
Comment thread pkg/traceql/engine_metrics_compare.go

@mapno mapno left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@mdisibio
mdisibio merged commit 6b2c0b1 into grafana:main Jun 21, 2024
mattdurham pushed a commit to mattdurham/tempo that referenced this pull request Jun 18, 2026
* Initial working version of compare

* Clean/rename

* Redo meta labels for type and error. Add required parameter for max values

* Add selectAll support to vParquet4

* vp2 unsupported

* comment out test for now

* Rename select all field

* lint

* compare() return topN and make it optional

* Add callback version of AllAttributes to avoid map alloc

* Fix lookup table

* Hideous but working version with totals per attribute and classification

* less hideous, and restore full time series processing

* instant-ish query

* add selectall attributes

* Adding partial finished test for selectAll, blocked for now

* lint

* Review feedback, reenable using traceid and traceDuration in the filter

* changelog

* Finish vp4 selectall test, refactor some methods to share with test

* Fix comment and remove test hacks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants