Add tickets to improve metric abstractions
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
# 037 — Unify param declarations: one `Param` type, referenced by name from metrics
|
||||
|
||||
## Summary
|
||||
|
||||
"Parameter" currently names two structurally near-identical but separate
|
||||
types: `ParamDeclaration` (`src/data_value.rs`, driven by the Params panel,
|
||||
used only to add a display column to the logs table) and
|
||||
`RegexToMetricPrimitiveMatcher` (`src/dto.rs`), embedded inline inside a
|
||||
metric declaration. A metric cannot reference an already-declared param by
|
||||
name — `src/engine.rs` says so explicitly ("referencing an already-declared
|
||||
`ParamDeclaration` by name isn't supported yet") — so the same
|
||||
regex/capture-group/kind gets redefined from scratch per metric, and nothing
|
||||
keeps a param declared in the Params panel consistent with a same-named
|
||||
matcher inside a metric.
|
||||
|
||||
## Scope
|
||||
|
||||
- Collapse `ParamDeclaration` and `RegexToMetricPrimitiveMatcher` into one
|
||||
`ParamDeclaration` type: `{ name, extractor (regex+capture_group |
|
||||
json_field_path), kind }`.
|
||||
- Change `MetricDeclaration` to reference params by name instead of embedding
|
||||
its own extractor: `label_params: Vec<String>`, `value_param:
|
||||
Option<String>`. Role (label vs. value) becomes a property of how a metric
|
||||
uses a param, not a property of the param itself.
|
||||
- Update `apply_metric_to_log` (`src/engine.rs`) to resolve `label_params`/
|
||||
`value_param` against the shared param declaration store instead of reading
|
||||
an inline matcher.
|
||||
- Update declare/validate paths (`validate_metric_request`, `engine.rs`) to
|
||||
reject a metric referencing an undeclared param name.
|
||||
- Update frontend types (`webapp/src/lib/api.ts`) and `Metrics.tsx`'s
|
||||
declaration form to pick existing declared params (e.g. a select populated
|
||||
from the Params panel) rather than re-entering a regex/capture-group/kind
|
||||
per metric.
|
||||
- Migration: existing stored `MetricDeclaration` records with inline matchers
|
||||
need their matchers turned into standalone `ParamDeclaration` entries
|
||||
(created implicitly, named e.g. `<metric_name>.<param_name>` if no matching
|
||||
declared param exists) so existing profiles keep working after upgrade.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- A param declared once in the Params panel can be selected as a label or
|
||||
value param by multiple metric declarations without re-entering its
|
||||
extractor.
|
||||
- Declaring a metric that references a nonexistent param name is rejected at
|
||||
declare-time with a clear error.
|
||||
- Existing profiles with metrics declared under the old inline-matcher shape
|
||||
load and behave identically after migration.
|
||||
- `Metrics.tsx` no longer has a separate "regex + capture group + kind" entry
|
||||
form duplicated from the Params panel's form.
|
||||
|
||||
## Depends on
|
||||
|
||||
None. [038](038-duration-as-derived-param-kind.md) and
|
||||
[039](039-metric-param-query-resolves-declared-params.md) build on this.
|
||||
@@ -0,0 +1,44 @@
|
||||
# 038 — Make Duration an explicit derived-param kind, not a hidden case of Value
|
||||
|
||||
## Summary
|
||||
|
||||
Today a `Value`-role param with `kind: Duration` silently means something
|
||||
different from every other kind: instead of parsing extracted text, it means
|
||||
"elapsed time since the previous same-labeled event"
|
||||
(`apply_metric_to_log`, `src/engine.rs`, backed by a `LastEventTimestamp`
|
||||
index entry). Same enum variant (`MetricPrimitiveKind::Duration`), two
|
||||
unrelated meanings depending on role — one is "parse this text as a
|
||||
duration," which doesn't apply here at all; the other is "compute elapsed
|
||||
time from event sequencing." This is a silent special case a reader has to
|
||||
already know about to understand the code.
|
||||
|
||||
## Scope
|
||||
|
||||
- Add an explicit extractor variant, e.g. `Extractor::ElapsedSinceLast {
|
||||
grouped_by: Vec<String> }`, distinct from `Regex`/`JsonPath`.
|
||||
- A param declared with this extractor is computed (elapsed time since the
|
||||
previous event sharing the given label set), never text-extracted.
|
||||
`MetricPrimitiveKind` no longer needs a role-dependent special case for
|
||||
`Duration`.
|
||||
- Update `apply_metric_to_log` to branch on extractor kind instead of on
|
||||
`(role, kind)` combination.
|
||||
- Update the Params panel / metric declaration UI (`Params.tsx`,
|
||||
`Metrics.tsx`) to offer "elapsed since previous event" as its own
|
||||
extractor option rather than a regex+kind combination that happens to mean
|
||||
something else.
|
||||
- Migration: existing declared/embedded params using `Value` role +
|
||||
`Duration` kind convert to the new extractor variant.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- A "duration" param's behavior (elapsed time since last matching event) is
|
||||
selectable directly in the UI without going through a regex field that is
|
||||
never actually used.
|
||||
- `apply_metric_to_log` has no branch that changes meaning based on role for
|
||||
a given kind.
|
||||
- Existing profiles using the old Value+Duration combination behave
|
||||
identically after migration.
|
||||
|
||||
## Depends on
|
||||
|
||||
[037](037-unify-param-declarations.md).
|
||||
@@ -0,0 +1,32 @@
|
||||
# 039 — `metric[param]` query syntax should resolve against declared params
|
||||
|
||||
## Summary
|
||||
|
||||
`compile_metric_comparison` (`src/engine.rs`) resolves the `param` in
|
||||
`metric[param] <op> value` by reading whatever key happens to be present in a
|
||||
bucket's `labels` map at query time. There's no link back to a metric's
|
||||
actual declared label params, so a typo or a param that was never a label on
|
||||
that metric fails silently (empty match) instead of being rejected up front.
|
||||
|
||||
## Scope
|
||||
|
||||
- Once [037](037-unify-param-declarations.md) lands, validate `metric[param]`
|
||||
filter expressions against the target metric's declared `label_params` at
|
||||
parse/compile time.
|
||||
- Return a clear filter-validation error when `param` is not one of the
|
||||
metric's label params, instead of silently matching nothing.
|
||||
- Confirm `src/filter_parser.rs` and `compile_metric_comparison` share the
|
||||
same notion of "param" as the Params panel and metric declarations after
|
||||
this change (no separate resolution path).
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- `metric[nonexistent_param] == "x"` is rejected with a validation error
|
||||
referencing the metric's actual label params, rather than silently
|
||||
returning no matches.
|
||||
- Existing valid filter queries using real label params continue to work
|
||||
unchanged.
|
||||
|
||||
## Depends on
|
||||
|
||||
[037](037-unify-param-declarations.md).
|
||||
@@ -0,0 +1,49 @@
|
||||
# 040 — One rate/window implementation, shared by PromQL and alerts
|
||||
|
||||
## Summary
|
||||
|
||||
"Rate over a window" is implemented twice with different semantics and no
|
||||
shared code:
|
||||
|
||||
- PromQL's `rate()` (`windowed_rate_series`, `src/engine.rs`) scans the
|
||||
trailing window at every historical point over indexed bucket data
|
||||
(occurrences-per-second, backfilled).
|
||||
- Alerts' `Threshold`/`Ratio` conditions (`src/alerts.rs`) maintain an
|
||||
independent live sliding `VecDeque` window over raw log-line matches
|
||||
(current count only, resets on process restart, no backfill), deliberately
|
||||
bypassing the bucket/PromQL pipeline for cost reasons (module doc: a
|
||||
`metric[param]` comparison would require a full bucket rescan per line).
|
||||
|
||||
These can and will disagree for equivalent-looking rate/ratio rules, since
|
||||
they read different data and compute the window differently.
|
||||
|
||||
## Scope
|
||||
|
||||
- Extract a single windowed-rate function, e.g. in a new `src/rate.rs`:
|
||||
`fn rate_at(points: &[(Timestamp, f64)], now: Timestamp, window_ms: i64) -> f64`.
|
||||
- Have PromQL's `rate()` evaluation call this shared function (behavior
|
||||
should be unchanged for existing dashboard queries).
|
||||
- Have alerts call the same function against the bucket store instead of
|
||||
maintaining a separate `VecDeque` over raw matches. If alerts' proportional
|
||||
per-line cost guarantee can't be met by reading buckets directly, solve
|
||||
that inside the shared module (e.g. an incrementally maintained window
|
||||
index) rather than keeping two implementations.
|
||||
- As a consequence, alert rate/ratio conditions gain backfilled history
|
||||
(they no longer reset to empty on process restart) — call this out in
|
||||
release notes since it changes alert-firing behavior on restart.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Only one windowed-rate implementation exists in the codebase.
|
||||
- A dashboard `rate()` chart and an alert `Threshold`/`Ratio` condition over
|
||||
the same metric/window agree on the computed rate for the same historical
|
||||
data, verified with a seeded fixture.
|
||||
- Alert conditions no longer silently reset their window on process restart
|
||||
(or this is an explicitly flagged, deliberate behavior change).
|
||||
- Existing dashboard `rate()` queries produce unchanged output for existing
|
||||
test fixtures.
|
||||
|
||||
## Depends on
|
||||
|
||||
None — independent of the param unification work
|
||||
([037](037-unify-param-declarations.md)–[039](039-metric-param-query-resolves-declared-params.md)).
|
||||
@@ -0,0 +1,32 @@
|
||||
# 041 — Stop silently coercing string metric values to 1.0 in sum()/avg()
|
||||
|
||||
## Summary
|
||||
|
||||
`coerce_to_f64` (`src/engine.rs`) maps every `MetricPrimitive::String` value
|
||||
to a flat `1.0`. This means `sum()`/`avg()` on a string-valued metric
|
||||
silently becomes an occurrence count instead of erroring or behaving
|
||||
predictably, with no signal to the user that their aggregate doesn't mean
|
||||
what it looks like it means.
|
||||
|
||||
## Scope
|
||||
|
||||
- At PromQL query-evaluation time, detect when the target metric's declared
|
||||
value kind is `String` and the requested aggregation is `sum`/`avg`.
|
||||
- Either reject the query with a clear error ("cannot sum/avg a string-typed
|
||||
metric; use count() or a numeric-typed metric"), or introduce an explicit
|
||||
`count()` aggregation so occurrence-counting is something a user opts into
|
||||
by name rather than something `sum()` does silently for the wrong metric
|
||||
kind.
|
||||
- Audit existing dashboards/panels for `sum()`/`avg()` over string-kind
|
||||
metrics that would now be rejected or need updating to `count()`.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Querying `sum()`/`avg()` over a string-kind metric either fails with a
|
||||
clear, actionable error or is redirected to an explicit `count()` semantic
|
||||
— it no longer silently returns a count while looking like a sum.
|
||||
- Numeric-kind metrics are unaffected.
|
||||
|
||||
## Depends on
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,38 @@
|
||||
# 042 — Verify/fix exact-timestamp merging for non-rate sum()/avg()
|
||||
|
||||
## Summary
|
||||
|
||||
Non-rate `sum()`/`avg()` queries merge per-label series via
|
||||
`merge_series_exact` (`src/engine.rs`), which only combines values whose
|
||||
timestamps match *exactly*. In practice, raw occurrence timestamps across
|
||||
different label groups essentially never land on the same millisecond, so
|
||||
`sum(requests) by (path)` over raw occurrence counts likely produces sparse,
|
||||
near-empty merged series rather than a meaningful "total requests over
|
||||
time," even though that's the obvious expectation for such a query. The
|
||||
existing code comment justifying exact-match merging ("nothing to carry
|
||||
forward") only holds if the query isn't meant to sum sparse counts over
|
||||
time — which is exactly the common case for occurrence-count dashboards.
|
||||
|
||||
## Scope
|
||||
|
||||
- Build a seeded fixture with multiple label groups whose events arrive at
|
||||
different (non-coincident) timestamps and confirm whether
|
||||
`sum(...) by (...)` currently produces the expected combined total or the
|
||||
suspected sparse/near-empty result.
|
||||
- If confirmed broken: bucket points into fixed time windows (the same way
|
||||
`rate()` already windows) before summing/averaging across label groups,
|
||||
instead of requiring exact timestamp coincidence.
|
||||
- Add a regression test covering multi-label-group `sum()`/`avg()` over
|
||||
time.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- `sum(metric) by (label)` over multiple label groups with staggered event
|
||||
timestamps produces a sensible combined time series (not sparse/empty),
|
||||
verified by a new test fixture.
|
||||
- `avg()` behaves analogously.
|
||||
- Existing single-label-group queries are unaffected.
|
||||
|
||||
## Depends on
|
||||
|
||||
None.
|
||||
@@ -0,0 +1,32 @@
|
||||
# 043 — Histogram queries ignore by() grouping, inconsistent with sum/avg/rate
|
||||
|
||||
## Summary
|
||||
|
||||
PromQL evaluation (`evaluate_promql`, `src/engine.rs`) has inconsistent
|
||||
grouping semantics across aggregation ops: `sum`/`avg`/`rate` respect the
|
||||
query's `by_labels` and group series accordingly, but `Histogram` collapses
|
||||
all label combinations into a single histogram regardless of `by()`. The
|
||||
same query language should not silently vary in what `by()` means depending
|
||||
on the aggregation used.
|
||||
|
||||
## Scope
|
||||
|
||||
- Decide the intended behavior: either (a) support `by()` grouping for
|
||||
histograms, producing one histogram per label group, or (b) explicitly
|
||||
reject/ignore `by()` on histogram queries with a clear validation error
|
||||
rather than silently accepting and discarding it.
|
||||
- Implement the chosen behavior in `evaluate_promql`'s histogram branch.
|
||||
- Update any dashboard panel UI (`Dashboards.tsx`) that lets a user combine
|
||||
histogram with `by()` to reflect the supported behavior (either offering
|
||||
grouped histograms or disabling `by()` for histogram panels).
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- `histogram(metric) by (label)` either produces one histogram per label
|
||||
group (verified by a seeded fixture) or is rejected/flagged clearly at
|
||||
query time — it no longer silently accepts and ignores `by()`.
|
||||
- `sum`/`avg`/`rate` behavior with `by()` is unchanged.
|
||||
|
||||
## Depends on
|
||||
|
||||
None.
|
||||
Reference in New Issue
Block a user