winx_code_agent/report/
lifecycle.rs1use std::collections::BTreeMap;
5
6use serde::Serialize;
7use serde_json::{Map, Value};
8
9use super::{latency, LatencySummary};
10
11const PHASE_FIELDS: [(&str, &str, &str); 10] = [
12 ("workspace_check", "phase_workspace_check_ms", "phase_workspace_check_calls"),
13 ("session_admission", "phase_session_admission_ms", "phase_session_admission_calls"),
14 ("catalog", "phase_catalog_ms", "phase_catalog_calls"),
15 ("runtime_inspect", "phase_runtime_inspect_ms", "phase_runtime_inspect_calls"),
16 ("runtime_capture", "phase_runtime_capture_ms", "phase_runtime_capture_calls"),
17 ("prompt_rpc", "phase_prompt_rpc_ms", "phase_prompt_rpc_calls"),
18 ("herdr_adapter", "phase_herdr_adapter_ms", "phase_herdr_adapter_calls"),
19 ("wait_idle", "phase_wait_idle_ms", "phase_wait_idle_calls"),
20 ("temp_audit", "phase_temp_audit_ms", "phase_temp_audit_calls"),
21 ("response_finalize", "phase_response_finalize_ms", "phase_response_finalize_calls"),
22];
23
24#[derive(Debug, Default, Serialize)]
25#[serde(rename_all = "camelCase")]
26pub struct WorkflowPhaseUsage {
27 pub calls: u64,
28 pub latency: LatencySummary,
29}
30
31#[derive(Default)]
32struct PhaseAccumulator {
33 calls: u64,
34 durations: Vec<u64>,
35}
36
37#[derive(Default)]
38pub(super) struct LifecycleAccumulator {
39 phases: BTreeMap<&'static str, PhaseAccumulator>,
40 pub abandoned: u64,
41 pub suppressed: u64,
42}
43
44impl LifecycleAccumulator {
45 pub fn record(&mut self, fields: &Map<String, Value>) {
46 for (name, duration_key, calls_key) in PHASE_FIELDS {
47 let Some(calls) =
48 fields.get(calls_key).and_then(Value::as_u64).filter(|calls| *calls > 0)
49 else {
50 continue;
51 };
52 let phase = self.phases.entry(name).or_default();
53 phase.calls = phase.calls.saturating_add(calls);
54 if let Some(duration) = fields.get(duration_key).and_then(Value::as_u64) {
55 phase.durations.push(duration);
56 }
57 }
58 self.abandoned = self.abandoned.saturating_add(
59 fields.get("phase_abandoned_count").and_then(Value::as_u64).unwrap_or(0),
60 );
61 self.suppressed = self.suppressed.saturating_add(
62 fields.get("phase_trace_suppressed_count").and_then(Value::as_u64).unwrap_or(0),
63 );
64 }
65
66 pub fn finish(self) -> BTreeMap<String, WorkflowPhaseUsage> {
67 self.phases
68 .into_iter()
69 .map(|(name, phase)| {
70 (
71 name.to_string(),
72 WorkflowPhaseUsage { calls: phase.calls, latency: latency(phase.durations) },
73 )
74 })
75 .collect()
76 }
77}