mirror of
https://github.com/openai/codex.git
synced 2026-10-21 12:10:06 +00:00
Measure Codex home usage at app-server startup (#41360)
## What changed - Scan regular files under `CODEX_HOME` in a background blocking task when standalone app-server metrics are enabled. - Record `codex.app_server.codex_home.size_bytes` for the entire home and the `sessions` and `archived_sessions` subdirectories. - Avoid following symlinks, discard incomplete scans, and cancel scanning during shutdown. GitOrigin-RevId: 236fc0a5c156dbd4a99e9835135de194809b4a7b
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
//! Measures local CODEX_HOME storage once at standalone app-server startup.
|
||||
//!
|
||||
//! The background scan sums regular-file lengths, without reading file contents or
|
||||
//! following symlinks. Incomplete scans emit no samples, and shutdown cancels the scan.
|
||||
|
||||
use codex_otel::MetricsClient;
|
||||
use codex_rollout::ARCHIVED_SESSIONS_SUBDIR;
|
||||
use codex_rollout::SESSIONS_SUBDIR;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use tokio::task::JoinHandle;
|
||||
use tokio_util::sync::CancellationToken;
|
||||
|
||||
const SIZE_BYTES_METRIC: &str = "codex.app_server.codex_home.size_bytes";
|
||||
// Bucket sizes range from 1 MiB through 1 TiB; larger homes use the overflow bucket.
|
||||
const SIZE_BYTES_BOUNDARIES: &[f64] = &[
|
||||
1_048_576.0,
|
||||
10_485_760.0,
|
||||
104_857_600.0,
|
||||
1_073_741_824.0,
|
||||
10_737_418_240.0,
|
||||
107_374_182_400.0,
|
||||
1_099_511_627_776.0,
|
||||
];
|
||||
|
||||
pub(crate) fn spawn(
|
||||
codex_home: PathBuf,
|
||||
metrics: MetricsClient,
|
||||
shutdown: CancellationToken,
|
||||
) -> JoinHandle<()> {
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let sizes = match directory_sizes(&codex_home, &shutdown) {
|
||||
Ok(sizes) => sizes,
|
||||
Err(error) => {
|
||||
tracing::debug!(error_kind = ?error.kind(), "Skipping CODEX_HOME size metrics");
|
||||
return;
|
||||
}
|
||||
};
|
||||
for (directory, bytes) in [
|
||||
("codex_home", sizes.codex_home),
|
||||
(SESSIONS_SUBDIR, sizes.sessions),
|
||||
(ARCHIVED_SESSIONS_SUBDIR, sizes.archived_sessions),
|
||||
] {
|
||||
let _ = metrics.histogram_with_boundaries(
|
||||
SIZE_BYTES_METRIC,
|
||||
i64::try_from(bytes).unwrap_or(i64::MAX),
|
||||
SIZE_BYTES_BOUNDARIES,
|
||||
&[("directory", directory)],
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
struct DirectorySizes {
|
||||
codex_home: u64,
|
||||
sessions: u64,
|
||||
archived_sessions: u64,
|
||||
}
|
||||
|
||||
fn directory_sizes(codex_home: &Path, shutdown: &CancellationToken) -> io::Result<DirectorySizes> {
|
||||
let sessions = codex_home.join(SESSIONS_SUBDIR);
|
||||
let archived_sessions = codex_home.join(ARCHIVED_SESSIONS_SUBDIR);
|
||||
let mut sizes = DirectorySizes::default();
|
||||
let mut pending = vec![codex_home.to_path_buf()];
|
||||
while let Some(directory) = pending.pop() {
|
||||
if shutdown.is_cancelled() {
|
||||
return Err(io::ErrorKind::Interrupted.into());
|
||||
}
|
||||
for entry in std::fs::read_dir(directory)? {
|
||||
if shutdown.is_cancelled() {
|
||||
return Err(io::ErrorKind::Interrupted.into());
|
||||
}
|
||||
let entry = entry?;
|
||||
// DirEntry::metadata does not follow symbolic links.
|
||||
let metadata = entry.metadata()?;
|
||||
let path = entry.path();
|
||||
if metadata.is_dir() {
|
||||
pending.push(path);
|
||||
} else if metadata.is_file() {
|
||||
let bytes = metadata.len();
|
||||
sizes.codex_home = sizes.codex_home.saturating_add(bytes);
|
||||
if path.starts_with(&sessions) {
|
||||
sizes.sessions = sizes.sessions.saturating_add(bytes);
|
||||
} else if path.starts_with(&archived_sessions) {
|
||||
sizes.archived_sessions = sizes.archived_sessions.saturating_add(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(sizes)
|
||||
}
|
||||
@@ -96,6 +96,7 @@ mod attestation;
|
||||
mod auth_mode;
|
||||
mod bespoke_event_handling;
|
||||
mod code_mode_host;
|
||||
mod codex_home_metrics;
|
||||
mod command_exec;
|
||||
mod config_layer;
|
||||
mod config_manager;
|
||||
@@ -821,6 +822,15 @@ pub async fn run_main_with_transport_options(
|
||||
}
|
||||
transport_accept_handles.push(remote_control_accept_handle);
|
||||
|
||||
// Only the standalone server measures its local home, not embedded/cloud runtimes.
|
||||
if let Some(metrics) = otel.as_ref().and_then(codex_otel::OtelProvider::metrics) {
|
||||
codex_home_metrics::spawn(
|
||||
config.codex_home.to_path_buf(),
|
||||
metrics.clone(),
|
||||
transport_shutdown_token.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
let otel_reloader_handle = otel_reloader::spawn(
|
||||
otel,
|
||||
otel_logger_reload_handle,
|
||||
|
||||
Reference in New Issue
Block a user