Move metric declarations and bucket computation to the profile actor (ticket 104)

Metric declare/update/remove, backfill, and per-line apply_all_metrics
now run on the profile actor (engine::profile) instead of inline in the
primary's ingest closure, per idea 0001's resolved decision that the
primary handles params only. The profile actor reuses EngineSnapshot as
its own snapshot type -- declare_metric/update_metric/apply_all_metrics
work unchanged -- and mirrors the primary's param declarations into its
own snapshot on every wake (and synchronously before handling a
DeclareMetric/UpdateMetric message, since there's no ordering guarantee
between the watch-wake and message branches in tokio::select!) so
metric label/value resolution keeps working without storing a second
copy of raw log text; backfill reads lines through its cloned primary
IndexReader instead.

EngineHandle gained a profile_reader field so MetricDeclarations/
BucketValues/Promql queries route to the profile actor's snapshot
instead of the primary's -- without it, metrics could be declared but
never queried, since the primary no longer computes or stores them.
DeclareMetric/UpdateMetric/RemoveMetricDeclaration messages are still
sent to the primary's mpsc channel and proxied to the profile actor by
handle.rs's run() loop; EngineHandle's write-side API is otherwise
unchanged (ticket 106 gives API handlers a direct route). Alert
evaluation still reads the primary's (now metric-less) snapshot
directly -- an accepted gap until ticket 105 moves it, per idea 0001's
resolved decision that alerts move with metric computation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TeyhRfMYjMditek6geDRoQ
This commit is contained in:
Hugh Kaznowski
2026-09-07 13:53:06 +01:00
co-authored by Claude Sonnet 5
parent 033e8bbab5
commit ce3e741cb0
6 changed files with 434 additions and 191 deletions
+15
View File
@@ -249,6 +249,21 @@ fn mime_for_extension(extension: &str) -> &'static str {
impl EngineHandle {
async fn ask(&self, query: EngineQuery) -> Result<EngineResponse, StatusCode> {
// Metric declarations and bucket data live on the profile actor (ticket 104), not the
// primary -- route those query variants through its reader instead.
if matches!(
query,
EngineQuery::MetricDeclarations
| EngineQuery::BucketValues { .. }
| EngineQuery::Promql { .. }
) {
let profile_handle = self.profile_reader.handle();
let guard = profile_handle
.enter()
.ok_or(StatusCode::SERVICE_UNAVAILABLE)?;
return Ok(guard.handle_query_ro(query));
}
let read_handle = self.reader.handle();
if let Some(sort_by) = query.sort_by() {
let needs_build = match read_handle.enter() {