Codex Handbook
otel/tests/suite/send.rs 243 lines
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_otel::Result;use pretty_assertions::assert_eq;use std::collections::BTreeMap;// Ensures counters/histograms render with default + per-call tags.#[test]fn send_builds_payload_with_tags_and_histograms() -> Result<()> {    let (metrics, exporter) =        build_metrics_with_defaults(&[("service", "codex-cli"), ("env", "prod")])?;    metrics.counter_with_description(        "codex.turns",        "Total number of Codex turns.",        /*inc*/ 1,        &[("model", "gpt-5.1"), ("env", "dev")],    )?;    metrics.histogram(        "codex.tool_latency",        /*value*/ 25,        &[("tool", "shell")],    )?;    metrics.gauge_with_description(        "codex.active",        "Number of active Codex operations.",        /*value*/ 2,        &[("component", "test")],    )?;    metrics.shutdown()?;    let resource_metrics = latest_metrics(&exporter);    let counter = find_metric(&resource_metrics, "codex.turns").expect("counter metric missing");    assert_eq!(counter.description(), "Total number of Codex turns.");    let counter_attributes = match counter.data() {        opentelemetry_sdk::metrics::data::AggregatedMetrics::U64(data) => match data {            opentelemetry_sdk::metrics::data::MetricData::Sum(sum) => {                let points: Vec<_> = sum.data_points().collect();                assert_eq!(points.len(), 1);                assert_eq!(points[0].value(), 1);                attributes_to_map(points[0].attributes())            }            _ => panic!("unexpected counter aggregation"),        },        _ => panic!("unexpected counter data type"),    };    let expected_counter_attributes = BTreeMap::from([        ("service".to_string(), "codex-cli".to_string()),        ("env".to_string(), "dev".to_string()),        ("model".to_string(), "gpt-5.1".to_string()),    ]);    assert_eq!(counter_attributes, expected_counter_attributes);    let (bounds, bucket_counts, sum, count) =        histogram_data(&resource_metrics, "codex.tool_latency");    assert!(!bounds.is_empty());    assert_eq!(bucket_counts.iter().sum::<u64>(), 1);    assert_eq!(sum, 25.0);    assert_eq!(count, 1);    let histogram_attrs = attributes_to_map(        find_metric(&resource_metrics, "codex.tool_latency")            .and_then(|metric| match metric.data() {                opentelemetry_sdk::metrics::data::AggregatedMetrics::F64(                    opentelemetry_sdk::metrics::data::MetricData::Histogram(histogram),                ) => histogram                    .data_points()                    .next()                    .map(opentelemetry_sdk::metrics::data::HistogramDataPoint::attributes),                _ => None,            })            .expect("codex.tool_latency histogram attributes should exist"),    );    let expected_histogram_attributes = BTreeMap::from([        ("service".to_string(), "codex-cli".to_string()),        ("env".to_string(), "prod".to_string()),        ("tool".to_string(), "shell".to_string()),    ]);    assert_eq!(histogram_attrs, expected_histogram_attributes);    let gauge = find_metric(&resource_metrics, "codex.active").expect("gauge metric missing");    assert_eq!(gauge.description(), "Number of active Codex operations.");    let gauge_point = match gauge.data() {        opentelemetry_sdk::metrics::data::AggregatedMetrics::I64(data) => match data {            opentelemetry_sdk::metrics::data::MetricData::Gauge(gauge) => {                gauge.data_points().next().expect("gauge point")            }            _ => panic!("unexpected gauge aggregation"),        },        _ => panic!("unexpected gauge metric data type"),    };    assert_eq!(gauge_point.value(), 2);    assert_eq!(        attributes_to_map(gauge_point.attributes()),        BTreeMap::from([            ("component".to_string(), "test".to_string()),            ("env".to_string(), "prod".to_string()),            ("service".to_string(), "codex-cli".to_string()),        ])    );    Ok(())}// Ensures defaults merge per line and overrides take precedence.#[test]fn send_merges_default_tags_per_line() -> Result<()> {    let (metrics, exporter) = build_metrics_with_defaults(&[        ("service", "codex-cli"),        ("env", "prod"),        ("region", "us"),    ])?;    metrics.counter(        "codex.alpha",        /*inc*/ 1,        &[("env", "dev"), ("component", "alpha")],    )?;    metrics.counter(        "codex.beta",        /*inc*/ 2,        &[("service", "worker"), ("component", "beta")],    )?;    metrics.shutdown()?;    let resource_metrics = latest_metrics(&exporter);    let alpha_metric =        find_metric(&resource_metrics, "codex.alpha").expect("codex.alpha metric missing");    let alpha_point = match alpha_metric.data() {        opentelemetry_sdk::metrics::data::AggregatedMetrics::U64(data) => match data {            opentelemetry_sdk::metrics::data::MetricData::Sum(sum) => {                let points: Vec<_> = sum.data_points().collect();                assert_eq!(points.len(), 1);                points[0]            }            _ => panic!("unexpected counter aggregation"),        },        _ => panic!("unexpected counter data type"),    };    assert_eq!(alpha_point.value(), 1);    let alpha_attrs = attributes_to_map(alpha_point.attributes());    let expected_alpha_attrs = BTreeMap::from([        ("component".to_string(), "alpha".to_string()),        ("env".to_string(), "dev".to_string()),        ("region".to_string(), "us".to_string()),        ("service".to_string(), "codex-cli".to_string()),    ]);    assert_eq!(alpha_attrs, expected_alpha_attrs);    let beta_metric =        find_metric(&resource_metrics, "codex.beta").expect("codex.beta metric missing");    let beta_point = match beta_metric.data() {        opentelemetry_sdk::metrics::data::AggregatedMetrics::U64(data) => match data {            opentelemetry_sdk::metrics::data::MetricData::Sum(sum) => {                let points: Vec<_> = sum.data_points().collect();                assert_eq!(points.len(), 1);                points[0]            }            _ => panic!("unexpected counter aggregation"),        },        _ => panic!("unexpected counter data type"),    };    assert_eq!(beta_point.value(), 2);    let beta_attrs = attributes_to_map(beta_point.attributes());    let expected_beta_attrs = BTreeMap::from([        ("component".to_string(), "beta".to_string()),        ("env".to_string(), "prod".to_string()),        ("region".to_string(), "us".to_string()),        ("service".to_string(), "worker".to_string()),    ]);    assert_eq!(beta_attrs, expected_beta_attrs);    Ok(())}// Verifies enqueued metrics are delivered by the background worker.#[test]fn client_sends_enqueued_metric() -> Result<()> {    let (metrics, exporter) = build_metrics_with_defaults(&[])?;    metrics.counter("codex.turns", /*inc*/ 1, &[("model", "gpt-5.1")])?;    metrics.shutdown()?;    let resource_metrics = latest_metrics(&exporter);    let counter = find_metric(&resource_metrics, "codex.turns").expect("counter metric missing");    let points = match counter.data() {        opentelemetry_sdk::metrics::data::AggregatedMetrics::U64(data) => match data {            opentelemetry_sdk::metrics::data::MetricData::Sum(sum) => {                sum.data_points().collect::<Vec<_>>()            }            _ => panic!("unexpected counter aggregation"),        },        _ => panic!("unexpected counter data type"),    };    assert_eq!(points.len(), 1);    let point = points[0];    assert_eq!(point.value(), 1);    let attrs = attributes_to_map(point.attributes());    assert_eq!(attrs.get("model").map(String::as_str), Some("gpt-5.1"));    Ok(())}// Ensures shutdown flushes successfully with in-memory exporters.#[test]fn shutdown_flushes_in_memory_exporter() -> Result<()> {    let (metrics, exporter) = build_metrics_with_defaults(&[])?;    metrics.counter("codex.turns", /*inc*/ 1, &[])?;    metrics.shutdown()?;    let resource_metrics = latest_metrics(&exporter);    let counter = find_metric(&resource_metrics, "codex.turns").expect("counter metric missing");    let points = match counter.data() {        opentelemetry_sdk::metrics::data::AggregatedMetrics::U64(data) => match data {            opentelemetry_sdk::metrics::data::MetricData::Sum(sum) => {                sum.data_points().collect::<Vec<_>>()            }            _ => panic!("unexpected counter aggregation"),        },        _ => panic!("unexpected counter data type"),    };    assert_eq!(points.len(), 1);    Ok(())}// Ensures shutting down without recording metrics does not export anything.#[test]fn shutdown_without_metrics_exports_nothing() -> Result<()> {    let (metrics, exporter) = build_metrics_with_defaults(&[])?;    metrics.shutdown()?;    let finished = exporter.get_finished_metrics().unwrap();    assert!(finished.is_empty(), "expected no metrics exported");    Ok(())}