agent_effects/observer.rs
1//! Watching effects for metrics.
2//!
3//! An [`EffectObserver`] (`RuntimeBuilder::observer`) is told about every
4//! effect the runtime records and every transition it stores, after the
5//! write succeeds. That covers calls, recovery, compensation, approval and
6//! operator decisions alike. That is enough for every metric that matters:
7//!
8//! - counts by transition or by status reached (committed, failed, unknown,
9//! retries, compensations);
10//! - attempt duration ([`Observation::in_previous_status`] when leaving
11//! `Executing`);
12//! - time spent unknown (the same, when leaving `Unknown`);
13//! - end-to-end duration ([`Observation::since_created`] when settling);
14//! - levels such as "awaiting approval", as up/down counts. These are deltas
15//! since the observer started; for exact current numbers, query
16//! [`Runtime::pending`](crate::Runtime::pending).
17//!
18//! The core crate depends on no metrics library; `agent-effects-otel`
19//! implements an observer with OpenTelemetry. An observer that panics is
20//! caught and logged: a metrics bug must never break an effect whose
21//! transition is already stored.
22
23use std::time::Duration;
24
25use crate::state::{EffectStatus, Transition};
26use crate::store::EffectRecord;
27
28/// One stored transition.
29#[derive(Clone, Copy, Debug)]
30#[non_exhaustive]
31pub struct Observation<'a> {
32 /// The effect after the transition.
33 pub record: &'a EffectRecord,
34 /// What happened.
35 pub transition: Transition,
36 /// The status before.
37 pub from: EffectStatus,
38 /// The status after.
39 pub to: EffectStatus,
40 /// How long the effect was in `from`, e.g. an attempt's duration when
41 /// `from` is `Executing`.
42 pub in_previous_status: Duration,
43 /// How long since the effect was first recorded.
44 pub since_created: Duration,
45}
46
47/// Receives effect lifecycle events, for metrics. Both methods default to
48/// doing nothing.
49///
50/// Calls are synchronous and run on the runtime's tasks: keep them cheap
51/// (increment a counter, record a histogram), and hand anything slow to
52/// another task.
53pub trait EffectObserver: Send + Sync + 'static {
54 /// A new effect was recorded (`Pending`).
55 fn on_created(&self, record: &EffectRecord) {
56 let _ = record;
57 }
58
59 /// A transition was stored.
60 fn on_transition(&self, observation: &Observation<'_>) {
61 let _ = observation;
62 }
63}