dynamo-bench 1.3.0

Lightweight HTTP benchmarks for Dynamo endpoints
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

use crate::coding::common::{dedupe_paths, expand_user_path, home_dir};
use anyhow::{Result, bail};
use std::collections::VecDeque;
use std::fs;
use std::path::{Path, PathBuf};

const IGNORED_FILENAMES: &[&str] = &["history.jsonl"];

pub fn iter_ancestor_roots(start: &Path) -> Vec<PathBuf> {
    let mut roots = Vec::new();
    let mut current = start.to_path_buf();
    loop {
        roots.push(current.clone());
        let Some(parent) = current.parent() else {
            break;
        };
        if parent == current {
            break;
        }
        current = parent.to_path_buf();
    }
    dedupe_paths(roots)
}

pub fn claude_project_dir_for_root(root: &Path, home_dir: &Path) -> PathBuf {
    let encoded = root.to_string_lossy().replace('/', "-");
    home_dir.join(".claude").join("projects").join(encoded)
}

pub fn discover_trace_files(explicit_inputs: &[String], start_dir: &Path) -> Result<Vec<PathBuf>> {
    let Some(home_dir) = home_dir() else {
        bail!("could not resolve HOME for Claude trace discovery");
    };
    let claude_projects_root = home_dir.join(".claude").join("projects");
    let mut discovered = Vec::new();

    if !explicit_inputs.is_empty() {
        for raw_path in explicit_inputs {
            let input_path = expand_user_path(raw_path);
            let input_path = input_path.canonicalize().unwrap_or(input_path);
            if input_path.is_file() {
                if !is_trace_path(&input_path) {
                    bail!("not a Claude session trace file: {}", input_path.display());
                }
                discovered.push(input_path.clone());
                if let (Some(parent), Some(stem)) = (input_path.parent(), input_path.file_stem()) {
                    discovered.extend(scan_trace_dir(&parent.join(stem).join("subagents"))?);
                }
                continue;
            }

            if !input_path.exists() {
                bail!("input path does not exist: {}", input_path.display());
            }

            if !input_path.is_dir() {
                bail!("unsupported input path: {}", input_path.display());
            }

            let in_claude_tree =
                input_path == claude_projects_root || input_path.starts_with(&claude_projects_root);
            if in_claude_tree {
                let directory_hits = scan_trace_dir(&input_path)?;
                if !directory_hits.is_empty() {
                    discovered.extend(directory_hits);
                    continue;
                }
            } else {
                let repo_hits =
                    scan_trace_dir(&claude_project_dir_for_root(&input_path, &home_dir))?;
                if !repo_hits.is_empty() {
                    discovered.extend(repo_hits);
                    continue;
                }

                let directory_hits = scan_trace_dir(&input_path)?;
                if !directory_hits.is_empty() {
                    discovered.extend(directory_hits);
                    continue;
                }
            }

            bail!(
                "no Claude session traces found under input path or its encoded Claude project directory: {}",
                input_path.display()
            );
        }

        return Ok(dedupe_paths(discovered));
    }

    for candidate_root in iter_ancestor_roots(start_dir) {
        discovered.extend(scan_trace_dir(&claude_project_dir_for_root(
            &candidate_root,
            &home_dir,
        ))?);
    }
    discovered.extend(scan_trace_dir(&claude_projects_root)?);

    Ok(dedupe_paths(discovered))
}

fn scan_trace_dir(root: &Path) -> Result<Vec<PathBuf>> {
    if !root.exists() {
        return Ok(Vec::new());
    }

    let mut queue = VecDeque::from([root.to_path_buf()]);
    let mut discovered = Vec::new();
    while let Some(directory) = queue.pop_front() {
        for entry in fs::read_dir(&directory)? {
            let entry = entry?;
            let path = entry.path();
            let file_type = entry.file_type()?;
            if file_type.is_dir() {
                queue.push_back(path);
                continue;
            }
            if file_type.is_file() && is_trace_path(&path) {
                discovered.push(path);
            }
        }
    }

    discovered.sort();
    Ok(discovered)
}

fn is_trace_path(path: &Path) -> bool {
    path.extension().and_then(|value| value.to_str()) == Some("jsonl")
        && !IGNORED_FILENAMES
            .iter()
            .any(|ignored| path.file_name().and_then(|value| value.to_str()) == Some(ignored))
}

#[cfg(test)]
mod tests {
    use super::{iter_ancestor_roots, scan_trace_dir};
    use tempfile::TempDir;

    #[test]
    fn ancestors_walk_to_root() {
        let temp = TempDir::new().unwrap();
        let nested = temp.path().join("a").join("b").join("c");
        std::fs::create_dir_all(&nested).unwrap();

        let roots = iter_ancestor_roots(&nested);
        assert_eq!(roots.first().unwrap(), &nested.canonicalize().unwrap());
        let last = roots.last().unwrap();
        assert!(
            last.parent().is_none(),
            "expected filesystem root, got {}",
            last.display()
        );
    }

    #[test]
    fn scan_includes_subagent_traces() {
        let temp = TempDir::new().unwrap();
        let subagents = temp.path().join("session-1/subagents");
        std::fs::create_dir_all(&subagents).unwrap();
        std::fs::write(temp.path().join("session-1.jsonl"), "").unwrap();
        std::fs::write(subagents.join("agent-child.jsonl"), "").unwrap();

        let traces = scan_trace_dir(temp.path()).unwrap();

        assert_eq!(traces.len(), 2);
        assert!(
            traces
                .iter()
                .any(|path| path.ends_with("subagents/agent-child.jsonl"))
        );
    }
}