mirror of
https://github.com/openai/codex.git
synced 2026-10-19 17:28:46 +00:00
Record memory storage size after successful consolidation (#45956)
## What changed Emit a `codex.memory.storage_bytes` histogram after successful phase-two consolidation, including runs with no workspace changes. Sum regular-file sizes recursively from filesystem metadata, excluding `.git` entries and symbolic links. Stop the phase-two duration timer before measuring storage. ## Testing Add coverage for both memory versions, nested files, UTF-8 byte counts, Git metadata exclusion, and Unix symbolic links, including broken links and directory loops. Extend the workspace baseline reset test to verify the remaining storage size. GitOrigin-RevId: 9170c34d656728897790decd2e2cabb38a81836f
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
pub(crate) const MEMORY_STARTUP: &str = "codex.memory.startup";
|
||||
pub(crate) const MEMORY_STORAGE_BYTES: &str = "codex.memory.storage_bytes";
|
||||
|
||||
pub(crate) const MEMORY_PHASE_ONE_JOBS: &str = "codex.memory.phase1";
|
||||
pub(crate) const MEMORY_PHASE_ONE_E2E_MS: &str = "codex.memory.phase1.e2e_ms";
|
||||
|
||||
@@ -135,7 +135,7 @@ pub async fn run(
|
||||
{
|
||||
tracing::error!("Phase 2 no changes");
|
||||
// We check only after sync of the file system.
|
||||
job::succeed(
|
||||
if job::succeed(
|
||||
context.as_ref(),
|
||||
&db,
|
||||
&claim,
|
||||
@@ -143,7 +143,11 @@ pub async fn run(
|
||||
&raw_memories,
|
||||
"succeeded_no_workspace_changes",
|
||||
)
|
||||
.await;
|
||||
.await
|
||||
{
|
||||
drop(phase_two_e2e_timer);
|
||||
context.record_storage_size(&root).await;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -373,7 +377,6 @@ mod agent {
|
||||
let Some(db) = context.memory_store().await else {
|
||||
return;
|
||||
};
|
||||
let _phase_two_e2e_timer = phase_two_e2e_timer;
|
||||
let SpawnedConsolidationAgent { thread_id, thread } = agent;
|
||||
|
||||
// Loop the agent until we have the final status.
|
||||
@@ -457,6 +460,9 @@ mod agent {
|
||||
tracing::error!(
|
||||
"failed marking global memory consolidation job succeeded after resetting workspace baseline"
|
||||
);
|
||||
} else {
|
||||
drop(phase_two_e2e_timer);
|
||||
context.record_storage_size(&memory_root).await;
|
||||
}
|
||||
}
|
||||
} else if !agent_completed {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use crate::metrics::MEMORY_STORAGE_BYTES;
|
||||
use crate::workspace::memory_storage_bytes;
|
||||
use codex_core::CodexThread;
|
||||
use codex_core::ModelClient;
|
||||
use codex_core::NewThread;
|
||||
@@ -38,6 +40,7 @@ use codex_rollout_trace::InferenceTraceContext;
|
||||
use codex_state::MemoryStore;
|
||||
use codex_terminal_detection::user_agent;
|
||||
use futures::StreamExt;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
@@ -210,6 +213,36 @@ impl MemoryStartupContext {
|
||||
self.thread_id
|
||||
}
|
||||
|
||||
pub(crate) async fn record_storage_size(&self, root: &Path) {
|
||||
let bytes = match memory_storage_bytes(root).await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => {
|
||||
tracing::warn!("failed measuring memory storage size: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.session_telemetry.histogram_with_boundaries(
|
||||
MEMORY_STORAGE_BYTES,
|
||||
i64::try_from(bytes).unwrap_or(i64::MAX),
|
||||
// Log-spaced byte buckets cover small summaries through large memory collections.
|
||||
&[
|
||||
0.0,
|
||||
1_024.0,
|
||||
4_096.0,
|
||||
16_384.0,
|
||||
65_536.0,
|
||||
262_144.0,
|
||||
1_048_576.0,
|
||||
4_194_304.0,
|
||||
16_777_216.0,
|
||||
67_108_864.0,
|
||||
268_435_456.0,
|
||||
1_073_741_824.0,
|
||||
],
|
||||
&memory_metric_tags(self.version, &[]),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) async fn memory_store(&self) -> Option<MemoryStore> {
|
||||
match self
|
||||
.thread
|
||||
|
||||
@@ -48,6 +48,26 @@ pub async fn reset_memory_workspace_baseline(root: &Path) -> anyhow::Result<()>
|
||||
reset_git_repository(root).await
|
||||
}
|
||||
|
||||
/// Sums regular-file contents without reading them, excluding git metadata and symbolic links.
|
||||
pub(crate) async fn memory_storage_bytes(root: &Path) -> std::io::Result<u64> {
|
||||
let mut paths = vec![root.to_path_buf()];
|
||||
let mut bytes = 0_u64;
|
||||
while let Some(path) = paths.pop() {
|
||||
let metadata = tokio::fs::symlink_metadata(&path).await?;
|
||||
if metadata.is_file() {
|
||||
bytes = bytes.saturating_add(metadata.len());
|
||||
} else if metadata.is_dir() {
|
||||
let mut entries = tokio::fs::read_dir(&path).await?;
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
if entry.file_name() != ".git" {
|
||||
paths.push(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
/// Verifies that a completed consolidation run left the required memory artifacts in place.
|
||||
pub async fn validate_consolidation_artifacts(root: &Path) -> anyhow::Result<()> {
|
||||
validate_consolidation_artifacts_for_version(root, MemoryVersion::V1).await
|
||||
|
||||
@@ -48,12 +48,51 @@ async fn reset_memory_workspace_baseline_removes_generated_diff() {
|
||||
.expect("reset baseline");
|
||||
|
||||
assert!(!root.join(crate::workspace_diff::FILENAME).exists());
|
||||
assert_eq!(memory_storage_bytes(&root).await.expect("storage size"), 6);
|
||||
let diff = memory_workspace_diff(&root)
|
||||
.await
|
||||
.expect("load workspace diff");
|
||||
assert_eq!(diff.changes, Vec::new());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn memory_storage_size_counts_nested_files_but_not_git_or_symlinks() -> anyhow::Result<()> {
|
||||
let home = TempDir::new()?;
|
||||
for version in [MemoryVersion::V1, MemoryVersion::V2] {
|
||||
let root = home.path().join(version.directory_name());
|
||||
fs::create_dir_all(root.join("rollout_summaries"))?;
|
||||
fs::create_dir_all(root.join("extensions/notes"))?;
|
||||
fs::create_dir_all(root.join(".git/objects"))?;
|
||||
fs::write(
|
||||
root.join(".git/objects/baseline"),
|
||||
"git metadata is not memory",
|
||||
)?;
|
||||
let files = [
|
||||
("memory_summary.md", "summary"),
|
||||
("rollout_summaries/thread.md", "rollout"),
|
||||
("extensions/notes/note.md", "café"),
|
||||
];
|
||||
for (path, content) in files {
|
||||
fs::write(root.join(path), content)?;
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::symlink;
|
||||
symlink(root.join("memory_summary.md"), root.join("file-link"))?;
|
||||
symlink(&root, root.join("directory-loop"))?;
|
||||
symlink(root.join("missing"), root.join("broken-link"))?;
|
||||
}
|
||||
assert_eq!(
|
||||
memory_storage_bytes(&root).await?,
|
||||
files
|
||||
.iter()
|
||||
.map(|(_, content)| content.len() as u64)
|
||||
.sum::<u64>(),
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepare_memory_workspace_recovers_unusable_git_dir() {
|
||||
let home = TempDir::new().expect("tempdir");
|
||||
|
||||
Reference in New Issue
Block a user