Compare commits

...

1 Commits

Author SHA1 Message Date
Tom
b64328a549 Simplify turn duration metric tagging 2026-01-14 16:30:09 -08:00
3 changed files with 82 additions and 5 deletions

View File

@@ -3,6 +3,7 @@
use indexmap::IndexMap;
use std::collections::HashMap;
use std::sync::Arc;
use std::time::Instant;
use tokio::sync::Mutex;
use tokio::sync::Notify;
use tokio_util::sync::CancellationToken;
@@ -19,14 +20,12 @@ use crate::tasks::SessionTask;
pub(crate) struct ActiveTurn {
pub(crate) tasks: IndexMap<String, RunningTask>,
pub(crate) turn_state: Arc<Mutex<TurnState>>,
pub(crate) started_at: Instant,
}
impl Default for ActiveTurn {
fn default() -> Self {
Self {
tasks: IndexMap::new(),
turn_state: Arc::new(Mutex::new(TurnState::default())),
}
Self::new()
}
}
@@ -48,6 +47,14 @@ pub(crate) struct RunningTask {
}
impl ActiveTurn {
pub(crate) fn new() -> Self {
Self {
tasks: IndexMap::new(),
turn_state: Arc::new(Mutex::new(TurnState::default())),
started_at: Instant::now(),
}
}
pub(crate) fn add_task(&mut self, task: RunningTask) {
let sub_id = task.turn_context.sub_id.clone();
self.tasks.insert(sub_id, task);

View File

@@ -168,9 +168,11 @@ impl Session {
last_agent_message: Option<String>,
) {
let mut active = self.active_turn.lock().await;
let mut completed_turn = None;
let should_close_processes = if let Some(at) = active.as_mut()
&& at.remove_task(&turn_context.sub_id)
{
completed_turn = Some(at.started_at);
*active = None;
true
} else {
@@ -180,13 +182,20 @@ impl Session {
if should_close_processes {
self.close_unified_exec_processes().await;
}
if let Some(started_at) = completed_turn {
self.services.otel_manager.record_duration(
"codex.turn.end_to_end_duration",
started_at.elapsed(),
&[],
);
}
let event = EventMsg::TurnComplete(TurnCompleteEvent { last_agent_message });
self.send_event(turn_context.as_ref(), event).await;
}
async fn register_new_active_task(&self, task: RunningTask) {
let mut active = self.active_turn.lock().await;
let mut turn = ActiveTurn::default();
let mut turn = ActiveTurn::new();
turn.add_task(task);
*active = Some(turn);
}

View File

@@ -1,6 +1,7 @@
use crate::harness::attributes_to_map;
use crate::harness::build_metrics_with_defaults;
use crate::harness::find_metric;
use crate::harness::histogram_data;
use crate::harness::latest_metrics;
use codex_app_server_protocol::AuthMode;
use codex_otel::OtelManager;
@@ -11,6 +12,7 @@ use opentelemetry_sdk::metrics::data::AggregatedMetrics;
use opentelemetry_sdk::metrics::data::MetricData;
use pretty_assertions::assert_eq;
use std::collections::BTreeMap;
use std::time::Duration;
// Ensures OtelManager attaches metadata tags when forwarding metrics.
#[test]
@@ -102,3 +104,62 @@ fn manager_allows_disabling_metadata_tags() -> Result<()> {
Ok(())
}
// Ensures duration metrics from OtelManager emit histograms with metadata tags.
#[test]
fn manager_records_turn_end_to_end_duration_histogram() -> Result<()> {
let (metrics, exporter) = build_metrics_with_defaults(&[("service", "codex-cli")])?;
let manager = OtelManager::new(
ThreadId::new(),
"gpt-5.1",
"gpt-5.1",
Some("account-id".to_string()),
None,
Some(AuthMode::ApiKey),
true,
"tty".to_string(),
SessionSource::Cli,
)
.with_metrics(metrics);
manager.record_duration(
"codex.turn.end_to_end_duration",
Duration::from_millis(120),
&[],
);
manager.shutdown_metrics()?;
let resource_metrics = latest_metrics(&exporter);
let (_bounds, bucket_counts, sum, count) =
histogram_data(&resource_metrics, "codex.turn.end_to_end_duration");
assert_eq!(bucket_counts.iter().sum::<u64>(), 1);
assert_eq!(sum, 120.0);
assert_eq!(count, 1);
let metric = find_metric(&resource_metrics, "codex.turn.end_to_end_duration")
.expect("duration metric missing");
let attrs = match metric.data() {
AggregatedMetrics::F64(data) => match data {
MetricData::Histogram(histogram) => {
let points: Vec<_> = histogram.data_points().collect();
assert_eq!(points.len(), 1);
attributes_to_map(points[0].attributes())
}
_ => panic!("unexpected histogram aggregation"),
},
_ => panic!("unexpected metric data type"),
};
let expected = BTreeMap::from([
(
"app.version".to_string(),
env!("CARGO_PKG_VERSION").to_string(),
),
("auth_mode".to_string(), AuthMode::ApiKey.to_string()),
("model".to_string(), "gpt-5.1".to_string()),
("service".to_string(), "codex-cli".to_string()),
]);
assert_eq!(attrs, expected);
Ok(())
}