Watch
0
0
Fork
You've already forked hyperhive
0

agents board: a turns tab, and turn duration bounds past 10 s

The per-agent /stats page shows what the swarm agents board lacked about
turns. The new tab reads the turn count and duration the harness already
records: average duration, cache hit rate, tokens per turn, turns per hour
by agent and by model, duration percentiles, and the wake source and result
mixes. The duration histogram used the SDK default buckets, which stop at
10 s while a turn averages 24 s, so every p95 read a flat 10 s; it now has
explicit bounds up to an hour.
This commit is contained in:
iris 2026-11-03 00:16:47 +01:00
commit aecde1dc75

View file

@ -43,6 +43,26 @@ use crate::turn_stats::TurnStatRow;
/// two batch on the same rhythm.
const EXPORT_INTERVAL: Duration = Duration::from_mins(1);
/// Bucket upper bounds of the turn-duration histogram, in milliseconds. The
/// SDK's default bounds stop at 10 s, but a turn takes about 24 s on average,
/// so with them every p95 read a flat 10 s. These run from half a second to an
/// hour.
const TURN_DURATION_BOUNDS_MS: [f64; 13] = [
500.0,
1_000.0,
2_500.0,
5_000.0,
10_000.0,
20_000.0,
30_000.0,
60_000.0,
120_000.0,
300_000.0,
600_000.0,
1_800_000.0,
3_600_000.0,
];
struct Instruments {
/// Kept alive for the process lifetime — the `PeriodicReader` only
/// exports while its provider lives.
@ -198,6 +218,7 @@ fn build() -> Option<Instruments> {
turn_duration_ms: meter
.u64_histogram("hyperhive.agent.turn.duration")
.with_unit("ms")
.with_boundaries(TURN_DURATION_BOUNDS_MS.to_vec())
.build(),
turn_count: meter.u64_counter("hyperhive.agent.turn.count").build(),
session_count: meter.u64_counter("hyperhive.agent.session.count").build(),
@ -267,3 +288,20 @@ fn build_provider(interval: Duration) -> anyhow::Result<SdkMeterProvider> {
pub fn enabled() -> bool {
std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").is_ok_and(|s| !s.trim().is_empty())
}
#[cfg(test)]
mod tests {
use super::TURN_DURATION_BOUNDS_MS;
#[test]
fn the_duration_bounds_ascend_and_reach_past_the_old_ten_second_ceiling() {
// The SDK drops bounds that are not strictly ascending, which would
// silently bring the default buckets (and the flat p95) back.
assert!(TURN_DURATION_BOUNDS_MS.windows(2).all(|w| w[0] < w[1]));
assert!(
TURN_DURATION_BOUNDS_MS
.last()
.is_some_and(|&b| b > 10_000.0)
);
}
}