mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Keep runtime summary metrics out of Statsig exports (#37874)
## What changed - Exclude conversation turn count, response-engine latency, and turn token usage metrics from the built-in Statsig exporter, alongside the existing API and tool-call exclusions. - Apply the Statsig exclusion list consistently to counters, histograms, gauges, observable gauges, and duration histograms. - Preserve all excluded metrics for custom OTLP exporters and extend the loopback coverage to verify they are exported. GitOrigin-RevId: e49f9aa1433126a3de8dbed76a4d2688f2831af8
This commit is contained in:
@@ -107,7 +107,7 @@ pub(super) struct MetricsClientInner {
|
||||
histograms: Mutex<HashMap<String, Histogram<f64>>>,
|
||||
duration_histograms: Mutex<HashMap<InstrumentKey, Histogram<f64>>>,
|
||||
runtime_reader: Option<Arc<ManualReader>>,
|
||||
runtime_only_metrics: &'static [&'static str],
|
||||
statsig_disabled_metrics: &'static [&'static str],
|
||||
default_tags: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ impl MetricsClientInner {
|
||||
}
|
||||
let attributes = self.attributes(tags)?;
|
||||
|
||||
if self.runtime_only_metrics.contains(&name) {
|
||||
if self.statsig_disabled_metrics.contains(&name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -156,6 +156,10 @@ impl MetricsClientInner {
|
||||
validate_metric_name(name)?;
|
||||
let attributes = self.attributes(tags)?;
|
||||
|
||||
if self.statsig_disabled_metrics.contains(&name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut histograms = self
|
||||
.histograms
|
||||
.lock()
|
||||
@@ -177,6 +181,10 @@ impl MetricsClientInner {
|
||||
validate_metric_name(name)?;
|
||||
let attributes = self.attributes(tags)?;
|
||||
|
||||
if self.statsig_disabled_metrics.contains(&name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut gauges = self
|
||||
.gauges
|
||||
.lock()
|
||||
@@ -206,6 +214,11 @@ impl MetricsClientInner {
|
||||
) -> Result<()> {
|
||||
validate_metric_name(name)?;
|
||||
let attributes = self.attributes(tags)?;
|
||||
|
||||
if self.statsig_disabled_metrics.contains(&name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let _gauge = self
|
||||
.meter
|
||||
.i64_observable_gauge(name.to_string())
|
||||
@@ -227,7 +240,7 @@ impl MetricsClientInner {
|
||||
validate_metric_name(name)?;
|
||||
let attributes = self.attributes(tags)?;
|
||||
|
||||
if self.runtime_only_metrics.contains(&name) {
|
||||
if self.statsig_disabled_metrics.contains(&name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -305,7 +318,7 @@ impl MetricsClient {
|
||||
exporter,
|
||||
export_interval,
|
||||
runtime_reader,
|
||||
runtime_only_metrics,
|
||||
statsig_disabled_metrics,
|
||||
default_tags,
|
||||
} = config;
|
||||
|
||||
@@ -351,7 +364,7 @@ impl MetricsClient {
|
||||
histograms: Mutex::new(HashMap::new()),
|
||||
duration_histograms: Mutex::new(HashMap::new()),
|
||||
runtime_reader,
|
||||
runtime_only_metrics,
|
||||
statsig_disabled_metrics,
|
||||
default_tags,
|
||||
}),
|
||||
active: None,
|
||||
|
||||
@@ -2,19 +2,32 @@ use crate::config::OtelExporter;
|
||||
use crate::metrics::Result;
|
||||
use crate::metrics::names::API_CALL_COUNT_METRIC;
|
||||
use crate::metrics::names::API_CALL_DURATION_METRIC;
|
||||
use crate::metrics::names::RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC;
|
||||
use crate::metrics::names::RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC;
|
||||
use crate::metrics::names::RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC;
|
||||
use crate::metrics::names::TOOL_CALL_COUNT_METRIC;
|
||||
use crate::metrics::names::TOOL_CALL_DURATION_METRIC;
|
||||
use crate::metrics::names::TURN_TOKEN_USAGE_METRIC;
|
||||
use crate::metrics::validation::validate_tag_key;
|
||||
use crate::metrics::validation::validate_tag_value;
|
||||
use opentelemetry_sdk::metrics::InMemoryMetricExporter;
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::Duration;
|
||||
|
||||
const RUNTIME_ONLY_METRICS: &[&str] = &[
|
||||
const CONVERSATION_TURN_COUNT_METRIC: &str = "codex.conversation.turn.count";
|
||||
|
||||
// Metrics intentionally not sent through Codex's built-in Statsig route.
|
||||
// Keep this as an exact-name list so custom OTLP exporters still receive them.
|
||||
const STATSIG_DISABLED_METRICS: &[&str] = &[
|
||||
API_CALL_COUNT_METRIC,
|
||||
API_CALL_DURATION_METRIC,
|
||||
CONVERSATION_TURN_COUNT_METRIC,
|
||||
RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC,
|
||||
RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC,
|
||||
RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC,
|
||||
TOOL_CALL_COUNT_METRIC,
|
||||
TOOL_CALL_DURATION_METRIC,
|
||||
TURN_TOKEN_USAGE_METRIC,
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -31,7 +44,7 @@ pub struct MetricsConfig {
|
||||
pub(crate) exporter: MetricsExporter,
|
||||
pub(crate) export_interval: Option<Duration>,
|
||||
pub(crate) runtime_reader: bool,
|
||||
pub(crate) runtime_only_metrics: &'static [&'static str],
|
||||
pub(crate) statsig_disabled_metrics: &'static [&'static str],
|
||||
pub(crate) default_tags: BTreeMap<String, String>,
|
||||
}
|
||||
|
||||
@@ -42,8 +55,8 @@ impl MetricsConfig {
|
||||
service_version: impl Into<String>,
|
||||
exporter: OtelExporter,
|
||||
) -> Self {
|
||||
let runtime_only_metrics = if matches!(exporter, OtelExporter::Statsig) {
|
||||
RUNTIME_ONLY_METRICS
|
||||
let statsig_disabled_metrics = if matches!(exporter, OtelExporter::Statsig) {
|
||||
STATSIG_DISABLED_METRICS
|
||||
} else {
|
||||
&[]
|
||||
};
|
||||
@@ -54,7 +67,7 @@ impl MetricsConfig {
|
||||
exporter: MetricsExporter::Otlp(exporter),
|
||||
export_interval: None,
|
||||
runtime_reader: false,
|
||||
runtime_only_metrics,
|
||||
statsig_disabled_metrics,
|
||||
default_tags: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
@@ -73,7 +86,7 @@ impl MetricsConfig {
|
||||
exporter: MetricsExporter::InMemory(exporter),
|
||||
export_interval: None,
|
||||
runtime_reader: false,
|
||||
runtime_only_metrics: &[],
|
||||
statsig_disabled_metrics: &[],
|
||||
default_tags: BTreeMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -551,8 +551,12 @@ mod tests {
|
||||
use crate::metrics::API_CALL_COUNT_METRIC;
|
||||
use crate::metrics::API_CALL_DURATION_METRIC;
|
||||
use crate::metrics::MetricsExporter;
|
||||
use crate::metrics::RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC;
|
||||
use crate::metrics::RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC;
|
||||
use crate::metrics::RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC;
|
||||
use crate::metrics::TOOL_CALL_COUNT_METRIC;
|
||||
use crate::metrics::TOOL_CALL_DURATION_METRIC;
|
||||
use crate::metrics::TURN_TOKEN_USAGE_METRIC;
|
||||
use opentelemetry_sdk::metrics::InMemoryMetricExporter;
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::path::PathBuf;
|
||||
@@ -658,7 +662,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn statsig_runtime_only_metrics_are_not_exported() -> Result<(), Box<dyn Error>> {
|
||||
fn statsig_disabled_metrics_are_not_exported() -> Result<(), Box<dyn Error>> {
|
||||
let exporter = InMemoryMetricExporter::default();
|
||||
let mut config = MetricsConfig::otlp(
|
||||
"test",
|
||||
@@ -671,8 +675,25 @@ mod tests {
|
||||
|
||||
metrics.counter(API_CALL_COUNT_METRIC, /*inc*/ 1, &[])?;
|
||||
metrics.record_duration(API_CALL_DURATION_METRIC, Duration::from_millis(100), &[])?;
|
||||
metrics.counter("codex.conversation.turn.count", /*inc*/ 1, &[])?;
|
||||
metrics.record_duration(
|
||||
RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC,
|
||||
Duration::from_millis(100),
|
||||
&[],
|
||||
)?;
|
||||
metrics.record_duration(
|
||||
RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC,
|
||||
Duration::from_millis(100),
|
||||
&[],
|
||||
)?;
|
||||
metrics.record_duration(
|
||||
RESPONSES_API_ENGINE_SERVICE_TTFT_DURATION_METRIC,
|
||||
Duration::from_millis(100),
|
||||
&[],
|
||||
)?;
|
||||
metrics.counter(TOOL_CALL_COUNT_METRIC, /*inc*/ 1, &[])?;
|
||||
metrics.record_duration(TOOL_CALL_DURATION_METRIC, Duration::from_millis(25), &[])?;
|
||||
metrics.histogram(TURN_TOKEN_USAGE_METRIC, /*value*/ 100, &[])?;
|
||||
metrics.counter("codex.turns", /*inc*/ 1, &[])?;
|
||||
metrics.shutdown()?;
|
||||
|
||||
|
||||
@@ -192,12 +192,33 @@ fn otlp_http_exporter_sends_metrics_to_collector() -> Result<()> {
|
||||
Duration::from_millis(100),
|
||||
&[("status", "200")],
|
||||
)?;
|
||||
metrics.counter("codex.conversation.turn.count", /*inc*/ 1, &[])?;
|
||||
metrics.record_duration(
|
||||
"codex.responses_api_engine_iapi_ttft.duration_ms",
|
||||
Duration::from_millis(100),
|
||||
&[],
|
||||
)?;
|
||||
metrics.record_duration(
|
||||
"codex.responses_api_engine_service_tbt.duration_ms",
|
||||
Duration::from_millis(100),
|
||||
&[],
|
||||
)?;
|
||||
metrics.record_duration(
|
||||
"codex.responses_api_engine_service_ttft.duration_ms",
|
||||
Duration::from_millis(100),
|
||||
&[],
|
||||
)?;
|
||||
metrics.counter("codex.tool.call", /*inc*/ 1, &[("tool", "test")])?;
|
||||
metrics.record_duration(
|
||||
"codex.tool.call.duration_ms",
|
||||
Duration::from_millis(42),
|
||||
&[("tool", "test")],
|
||||
)?;
|
||||
metrics.histogram(
|
||||
"codex.turn.token_usage",
|
||||
/*value*/ 100,
|
||||
&[("token_type", "total")],
|
||||
)?;
|
||||
metrics.gauge_with_description(
|
||||
"codex.active",
|
||||
"Number of active Codex operations.",
|
||||
@@ -243,11 +264,36 @@ fn otlp_http_exporter_sends_metrics_to_collector() -> Result<()> {
|
||||
"expected API-request duration not found; body prefix: {}",
|
||||
&body.chars().take(2000).collect::<String>()
|
||||
);
|
||||
assert!(
|
||||
body.contains("\"codex.conversation.turn.count\""),
|
||||
"expected conversation turn count not found; body prefix: {}",
|
||||
&body.chars().take(2000).collect::<String>()
|
||||
);
|
||||
assert!(
|
||||
body.contains("\"codex.responses_api_engine_iapi_ttft.duration_ms\""),
|
||||
"expected engine IAPI TTFT duration not found; body prefix: {}",
|
||||
&body.chars().take(2000).collect::<String>()
|
||||
);
|
||||
assert!(
|
||||
body.contains("\"codex.responses_api_engine_service_tbt.duration_ms\""),
|
||||
"expected engine service TBT duration not found; body prefix: {}",
|
||||
&body.chars().take(2000).collect::<String>()
|
||||
);
|
||||
assert!(
|
||||
body.contains("\"codex.responses_api_engine_service_ttft.duration_ms\""),
|
||||
"expected engine service TTFT duration not found; body prefix: {}",
|
||||
&body.chars().take(2000).collect::<String>()
|
||||
);
|
||||
assert!(
|
||||
body.contains("\"codex.tool.call\""),
|
||||
"expected tool-call counter not found; body prefix: {}",
|
||||
&body.chars().take(2000).collect::<String>()
|
||||
);
|
||||
assert!(
|
||||
body.contains("\"codex.turn.token_usage\""),
|
||||
"expected turn-token histogram not found; body prefix: {}",
|
||||
&body.chars().take(2000).collect::<String>()
|
||||
);
|
||||
assert!(
|
||||
body.contains("\"codex.tool.call.duration_ms\""),
|
||||
"expected tool-call duration not found; body prefix: {}",
|
||||
|
||||
Reference in New Issue
Block a user