Cache OS discovery for user agents and telemetry (#47038)

## Why

OS discovery can launch subprocesses on Linux. Repeating it for requests, analytics events, and metrics provider creation adds unnecessary work for stable metadata.

## What changed

Cache `os_info::get()` with a separate `LazyLock` in user-agent generation, analytics runtime metadata, and metrics OS resource attributes. Continue reading the user-agent originator and suffix on each call so updates remain effective.

## Testing

Add an isolated Linux regression test that races initial user-agent lookups and repeats calls, verifies `lsb_release` and `getconf` each run once, and checks that subsequent originator and suffix updates appear in the user agent.

GitOrigin-RevId: 337e45ff88b863d2a4aa387a8e7d2e291c045906
This commit is contained in:
Charlie Marsh
2026-09-21 14:04:41 +00:00
committed by copyberry
parent dd6eff3954
commit 87bc50f9d4
4 changed files with 77 additions and 3 deletions
+4 -1
View File
@@ -1,3 +1,4 @@
use std::sync::LazyLock;
use std::time::Instant;
use crate::facts::AppInvocation;
@@ -1496,7 +1497,9 @@ fn analytics_hook_source(source: HookSource) -> &'static str {
}
pub(crate) fn current_runtime_metadata() -> CodexRuntimeMetadata {
let os_info = os_info::get();
// Runtime metadata is stable; avoid launching OS discovery subprocesses per event.
static OS_INFO: LazyLock<os_info::Info> = LazyLock::new(os_info::get);
let os_info = &*OS_INFO;
CodexRuntimeMetadata {
codex_rs_version: env!("CARGO_PKG_VERSION").to_string(),
runtime_os: std::env::consts::OS.to_string(),
+4 -1
View File
@@ -148,8 +148,11 @@ pub fn is_first_party_chat_originator(originator_value: &str) -> bool {
}
pub fn get_codex_user_agent() -> String {
// OS discovery can spawn subprocesses on Linux. Reuse it across requests,
// while continuing to read the mutable originator and suffix below.
static OS_INFO: LazyLock<os_info::Info> = LazyLock::new(os_info::get);
let build_version = env!("CARGO_PKG_VERSION");
let os_info = os_info::get();
let os_info = &*OS_INFO;
let originator = originator();
let prefix = format!(
"{}/{build_version} ({} {}; {}) {}",
@@ -46,6 +46,71 @@ fn test_get_codex_user_agent() {
assert!(user_agent.starts_with(&prefix));
}
#[test]
#[cfg(target_os = "linux")]
fn os_discovery_is_cached_without_freezing_user_agent_overrides() {
use std::os::unix::fs::PermissionsExt;
const PROBE_DIR: &str = "CODEX_TEST_USER_AGENT_PROBE_DIR";
if std::env::var_os(PROBE_DIR).is_some() {
// Race the first lookup as well as exercising repeated requests.
std::thread::scope(|scope| {
for _ in 0..8 {
scope.spawn(|| {
for _ in 0..4 {
assert!(get_codex_user_agent().contains("Ubuntu 99.7.3"));
}
});
}
});
set_default_originator("cached-os-test".to_string()).expect("set originator");
*USER_AGENT_SUFFIX.lock().expect("suffix lock") = Some("updated-client".to_string());
let user_agent = get_codex_user_agent();
assert!(user_agent.starts_with("cached-os-test/"));
assert!(user_agent.ends_with(" (updated-client)"));
return;
}
// Use a fresh process so neither the cached OS nor the mutable overrides can
// be initialized by another test. Only the child's environment is changed.
let temp = tempfile::tempdir().expect("probe directory");
for (program, output) in [
("lsb_release", "Distributor ID: Ubuntu\nRelease: 99.7.3\n"),
("getconf", "64\n"),
] {
let script = temp.path().join(program);
std::fs::write(
&script,
format!(
"#!/bin/sh\nprintf '{program}\\n' >> \"${PROBE_DIR}/calls\"\nprintf '{output}'\n"
),
)
.expect("write probe");
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755))
.expect("make probe executable");
}
let output = std::process::Command::new(std::env::current_exe().expect("test executable"))
.args([
"--exact",
concat!(
module_path!(),
"::os_discovery_is_cached_without_freezing_user_agent_overrides"
)
.trim_start_matches("codex_login::"),
"--nocapture",
])
.env(PROBE_DIR, temp.path())
.env("PATH", temp.path())
.env_remove(CODEX_INTERNAL_ORIGINATOR_OVERRIDE_ENV_VAR)
.output()
.expect("run isolated user-agent test");
assert!(output.status.success(), "{output:?}");
let calls = std::fs::read_to_string(temp.path().join("calls")).expect("probe calls");
let mut calls = calls.lines().collect::<Vec<_>>();
calls.sort_unstable();
assert_eq!(calls, vec!["getconf", "lsb_release"]);
}
#[test]
fn is_first_party_originator_matches_known_values() {
assert_eq!(is_first_party_originator(DEFAULT_ORIGINATOR), true);
+4 -1
View File
@@ -36,6 +36,7 @@ use opentelemetry_semantic_conventions as semconv;
use std::collections::BTreeMap;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::LazyLock;
use std::sync::Mutex;
use std::sync::RwLock;
use std::sync::Weak;
@@ -537,7 +538,9 @@ impl MetricsClient {
}
fn os_resource_attributes() -> Vec<KeyValue> {
let os_info = os_info::get();
// Provider creation must not repeat OS discovery subprocesses.
static OS_INFO: LazyLock<os_info::Info> = LazyLock::new(os_info::get);
let os_info = &*OS_INFO;
let os_type_raw = os_info.os_type().to_string();
let os_type = sanitize_metric_tag_value(os_type_raw.as_str());
let os_version_raw = os_info.version().to_string();