Commit Graph
6 Commits
Author SHA1 Message Date
Hugh KaznowskiandClaude Sonnet 5 8faf12f819 Add Duration-type metric param: elapsed time since previous same-label match (ticket 023)
MetricPrimitiveKind::Duration/MetricPrimitive::Duration(i64 ms) is a
new primitive kind. On a metric's Value-role param (ticket 022), kind
Duration is special-cased in apply_metric_to_log: instead of parsing
the captured text, the value is derived as elapsed time since the
previous counter event with the same label set, using the ingestion
timestamp. That "previous event" lookup is tracked via a new
IndexKey::LastMetricEvent/DataValue::LastEventTimestamp bookkeeping
entry rather than Bucket.values itself, because a label set's first
occurrence has nothing to diff against and pushes no Bucket entry at
all (per spec: omit, don't emit a bogus zero) — but it still needs to
be recorded so the *next* occurrence has something to diff against.

Duration values compose with the existing bucket/PromQL surface
unchanged (coerce_to_f64, filter comparisons, histogram()/gauge()).
On any other param (Label-role metric param, or a standalone
ParamDeclaration), Duration kind instead parses captured text as a
literal i64 millisecond count, same as Number. Profile round-trip
(ProfileMetricPrimitiveKind) and the Metrics.tsx declaration form are
updated to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 15:24:28 +01:00
Hugh KaznowskiandClaude Sonnet 5 0c57ed2953 Metrics rethink: counters as the primitive, histogram()/gauge() derived (ticket 022)
Every metric declaration is now explicitly documented and modeled as
a counter: a match is an occurrence/increment, optionally labeled.
The bucket representation (BucketValue { values: Vec<(Sequence,
Timestamp, MetricPrimitive)> }) already was this; what it lacked was
any way to capture a *value* distinct from the occurrence-count-of-1
marker. Adds RegexToMetricPrimitiveMatcher::role (ParamRole::Label,
the default, or ParamRole::Value — at most one per metric, enforced
at declare time) so a metric can designate one param's captured
number as its per-event value instead of a label.

promql.rs/engine.rs add two new PromQL-evaluable forms built on top
of that value, both consumed as regular PromqlSeriesDto series like
sum()/avg()/rate():
- histogram(metric_name[width]): buckets matching events' values into
  fixed-width buckets, one series per bucket labeled `le`.
- gauge(metric_name): the most-recent value per label combination in
  the queried range.

Existing raw counter queries (metrics with only Label-role params,
sum/avg/rate) are unchanged. Profile files saved before this ticket
still load (role defaults to Label via #[serde(default)]). Metrics.tsx
gains a label/value role picker per param, and the Docs panel explains
the model and the two derived query forms.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 15:18:25 +01:00
Hugh Kaznowski 51e577f01e Docs, ticks, working app 2026-09-05 15:05:39 +01:00
Hugh KaznowskiandClaude Sonnet 5 41362aa3d8 Add Dashboards UI: PromQL-backed bar/line/pie panels saved per profile (ticket 014)
Backend: replace Profile.dashboards' opaque JSON stub with typed
Dashboard/Panel structs, add ProfileStore CRUD (create/save/list/delete
dashboard) and matching /api/settings/profiles/:name/dashboards[/:id] routes,
with tests covering the HTTP round trip and persistence across a simulated
restart.

Frontend: add recharts (lightweight, React 19-compatible) and a Dashboards
component with a dashboard builder (create dashboards, add/remove/edit panels
with a PromQL query + chart type), live per-panel previews that respect the
active time range and show malformed-query errors inline instead of crashing,
and load/save wired to the new profile-backed persistence.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 13:29:45 +01:00
Hugh KaznowskiandClaude Sonnet 5 86a1a40243 Wire FilterBar query AST into logs/timeline queries (ticket 007)
Adds a filter evaluator in engine.rs that compiles a parsed FilterExpr
against the current index once per query (regex compiled once,
sequence/timestamp compared directly against IngestedLog fields,
other metric comparisons resolved to a HashSet<u64> of matching
sequences via a bucket scan) and evaluates it per log line with no
further index access. Wires this into EngineQuery::Logs/Timeline via
a new LogsParams::filter/TimelineParams::filter field, and adds
EngineResponse::Error so a malformed filter string surfaces as a
query-level error (mapped to 400 in api.rs).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 13:10:50 +01:00
Hugh KaznowskiandClaude Sonnet 5 4c8918ec8c Add profile (preset) CRUD and startup replay (ticket 009)
Introduces `src/profile.rs`: a `Profile` bundles param/metric declarations
plus a placeholder dashboards field, and `ProfileStore` persists all
profiles as a single profiles.json file, resolved relative to the settings
file's directory (same convention as ticket 010's CSS storage). Adds CRUD
(create/list/get/delete/set_active) that keeps Settings.profiles.names and
active_profile in sync, plus record_param_declaration/record_metric_declaration
hooks that ticket 004's HTTP API will call to persist newly-declared
params/metrics into the active profile.

Wires main.rs to replay the active profile's declarations into the engine
before spawning the ingest task, so backfill/live-derivation semantics
apply uniformly to every ingested line.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
2026-09-05 12:57:20 +01:00