From 5522663f92fedddb00ba11af7f4282b9e298f603 Mon Sep 17 00:00:00 2001 From: jif-oai Date: Thu, 8 Jan 2026 15:39:57 +0000 Subject: [PATCH] feat: add a few metrics (#8910) --- codex-rs/core/src/codex.rs | 26 ++++++++++++++ codex-rs/core/src/context_manager/history.rs | 2 +- codex-rs/core/src/context_manager/mod.rs | 1 + codex-rs/core/src/tasks/compact.rs | 18 +++++----- docs/telemetry.md | 37 +++++++++++--------- 5 files changed, 59 insertions(+), 25 deletions(-) diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index c561c9cfa6..21a72b00c5 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -645,6 +645,18 @@ impl Session { session_configuration.session_source.clone(), ); config.features.emit_metrics(&otel_manager); + otel_manager.counter( + "codex.session.started", + 1, + &[( + "is_git", + if get_git_repo_root(&session_configuration.cwd).is_some() { + "true" + } else { + "false" + }, + )], + ); otel_manager.conversation_starts( config.model_provider.name.as_str(), @@ -1757,6 +1769,7 @@ mod handlers { use codex_protocol::protocol::TurnAbortReason; use codex_protocol::protocol::WarningEvent; + use crate::context_manager::is_user_turn_boundary; use codex_protocol::user_input::UserInput; use codex_rmcp_client::ElicitationAction; use codex_rmcp_client::ElicitationResponse; @@ -2111,6 +2124,18 @@ mod handlers { .terminate_all_processes() .await; info!("Shutting down Codex instance"); + let turn_count = sess + .clone_history() + .await + .get_history() + .iter() + .filter(|item| is_user_turn_boundary(item)) + .count(); + sess.services.otel_manager.counter( + "conversation.turn.count", + i64::try_from(turn_count).unwrap_or(0), + &[], + ); // Gracefully flush and shutdown rollout recorder on session end so tests // that inspect the rollout file do not race with the background writer. @@ -2834,6 +2859,7 @@ pub(super) fn get_last_assistant_message_from_turn(responses: &[ResponseItem]) - #[cfg(test)] pub(crate) use tests::make_session_and_context; +use crate::git_info::get_git_repo_root; #[cfg(test)] pub(crate) use tests::make_session_and_context_with_rx; diff --git a/codex-rs/core/src/context_manager/history.rs b/codex-rs/core/src/context_manager/history.rs index 8f16f731f4..d129f0eb3f 100644 --- a/codex-rs/core/src/context_manager/history.rs +++ b/codex-rs/core/src/context_manager/history.rs @@ -332,7 +332,7 @@ fn is_session_prefix(text: &str) -> bool { lowered.starts_with("") } -fn is_user_turn_boundary(item: &ResponseItem) -> bool { +pub(crate) fn is_user_turn_boundary(item: &ResponseItem) -> bool { let ResponseItem::Message { role, content, .. } = item else { return false; }; diff --git a/codex-rs/core/src/context_manager/mod.rs b/codex-rs/core/src/context_manager/mod.rs index 5089b5e8b8..baae93c775 100644 --- a/codex-rs/core/src/context_manager/mod.rs +++ b/codex-rs/core/src/context_manager/mod.rs @@ -2,3 +2,4 @@ mod history; mod normalize; pub(crate) use history::ContextManager; +pub(crate) use history::is_user_turn_boundary; diff --git a/codex-rs/core/src/tasks/compact.rs b/codex-rs/core/src/tasks/compact.rs index 4b5f0d1cfb..00d882f607 100644 --- a/codex-rs/core/src/tasks/compact.rs +++ b/codex-rs/core/src/tasks/compact.rs @@ -29,16 +29,18 @@ impl SessionTask for CompactTask { session.as_ref(), &ctx.client.get_provider(), ) { - let _ = session - .services - .otel_manager - .counter("codex.task.compact.remote", 1, &[]); + let _ = session.services.otel_manager.counter( + "codex.task.compact", + 1, + &[("type", "remote")], + ); crate::compact_remote::run_remote_compact_task(session, ctx).await } else { - let _ = session - .services - .otel_manager - .counter("codex.task.compact.local", 1, &[]); + let _ = session.services.otel_manager.counter( + "codex.task.compact", + 1, + &[("type", "local")], + ); crate::compact::run_compact_task(session, ctx, input).await } diff --git a/docs/telemetry.md b/docs/telemetry.md index 289d6c9a08..435dc3e4d9 100644 --- a/docs/telemetry.md +++ b/docs/telemetry.md @@ -29,20 +29,25 @@ This section list all the metrics exported by Codex when locally installed. ## Metrics catalog -Each metric includes the required fields plus the global context above. +Each metric includes the required fields plus the global context above. Every metrics are prefixed by `codex.`. -| Metric | Type | Fields | Description | -| ------------------------- | --------- | ------------------------------------- | ------------------------------------------------------------------------------- | -| `approval.requested` | counter | `tool`, `approved` | Tool approval request result (`approved`: `yes` or `no`). | -| `auth.completed` | counter | `status` | Authentication completed (only for ChatGPT authentication). | -| `conversation.compact` | counter | `status`, `number` | Compaction event including the status and the compaction number in the session. | -| `conversation.turn.count` | counter | `role` | User/assistant turns per session. | -| `feature.duration_ms` | histogram | `feature`, `status` | End-to-end feature latency. | -| `feature.used` | counter | `feature` | Feature usage through `/` (e.g., `/undo`, `/review`, ...). | -| `features.state` | counter | `key`, `value` | Feature values that differ from defaults (emit one row per non-default). | -| `mcp.call` | counter | `status` | MCP tool invocation result (`ok` or error string). | -| `model.call.duration_ms` | histogram | `provider`, `status`, `attempt` | Model API request duration. | -| `session.started` | counter | `is_git` | New session created. | -| `tool.call` | counter | `tool`, `status` | Tool invocation result (`ok` or error string). | -| `tool.call.duration_ms` | histogram | `tool`, `status` | Tool execution time. | -| `user.feedback.submitted` | counter | `category`, `include_logs`, `success` | Feedback submission via `/feedback`. | +| Metric | Type | Fields | Description | +| ----------------- | ------- | -------------- | ------------------------------------------------------------------------ | +| `features.state` | counter | `key`, `value` | Feature values that differ from defaults (emit one row per non-default). | +| `session.started` | counter | `is_git` | New session created. | +| `task.compact` | counter | `type` | Number of compaction per type (`remote` or `local`) | +| `task.user_shell` | counter | | Number of user shell actions (`!` in the TUI for example) | +| `task.review` | counter | | Number of reviews triggered | +| `task.undo` | counter | | Number of undo made | + +### Metrics to be added + +| Metric | Type | Fields | Description | +| ------------------------- | --------- | ------------------------------------- | --------------------------------------------------------- | +| `approval.requested` | counter | `tool`, `approved` | Tool approval request result (`approved`: `yes` or `no`). | +| `conversation.turn.count` | counter | | User/assistant turns per session. | +| `mcp.call` | counter | `status` | MCP tool invocation result (`ok` or error string). | +| `model.call.duration_ms` | histogram | `provider`, `status`, `attempt` | Model API request duration. | +| `tool.call` | counter | `tool`, `status` | Tool invocation result (`ok` or error string). | +| `tool.call.duration_ms` | histogram | `tool`, `status` | Tool execution time. | +| `user.feedback.submitted` | counter | `category`, `include_logs`, `success` | Feedback submission via `/feedback`. |