Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
247 changes: 247 additions & 0 deletions cortex-cli/src/debug_cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ pub enum DebugSubcommand {

/// Wait for a condition (useful for scripts).
Wait(WaitArgs),

/// Show memory usage information for debugging and optimization.
Memory(MemoryArgs),
}

// =============================================================================
Expand Down Expand Up @@ -2652,6 +2655,249 @@ fn get_cortex_home_or_default() -> PathBuf {
.unwrap_or_else(|| PathBuf::from(".cortex"))
}

// =============================================================================
// Memory subcommand
// =============================================================================

/// Arguments for memory subcommand.
#[derive(Debug, Parser)]
pub struct MemoryArgs {
/// Output as JSON.
#[arg(long)]
pub json: bool,

/// Show detailed breakdown of memory usage.
#[arg(long, short = 'v')]
pub verbose: bool,
}

/// Memory debug output.
#[derive(Debug, Serialize)]
struct MemoryDebugOutput {
/// Current process memory usage
process: ProcessMemory,
/// System memory information
system: SystemMemory,
/// Heap statistics (if available)
#[serde(skip_serializing_if = "Option::is_none")]
heap: Option<HeapStats>,
}

/// Process memory information.
#[derive(Debug, Serialize)]
struct ProcessMemory {
/// Resident set size (physical memory used) in bytes
rss_bytes: u64,
/// Virtual memory size in bytes
virtual_bytes: u64,
/// Human-readable RSS
rss_human: String,
/// Human-readable virtual memory
virtual_human: String,
}

/// System memory information.
#[derive(Debug, Serialize)]
struct SystemMemory {
/// Total system memory in bytes
total_bytes: u64,
/// Available system memory in bytes
available_bytes: u64,
/// Used system memory in bytes
used_bytes: u64,
/// Memory usage percentage
usage_percent: f64,
/// Human-readable values
total_human: String,
available_human: String,
used_human: String,
}

/// Heap statistics (platform-dependent).
#[derive(Debug, Serialize)]
struct HeapStats {
/// Allocated bytes
allocated_bytes: u64,
/// Active allocations count
#[serde(skip_serializing_if = "Option::is_none")]
allocation_count: Option<u64>,
}

async fn run_memory(args: MemoryArgs) -> Result<()> {
// Get process memory info
let process_memory = get_process_memory();

// Get system memory info
let system_memory = get_system_memory();

// Get heap stats (optional, platform-dependent)
let heap = get_heap_stats();

let output = MemoryDebugOutput {
process: process_memory,
system: system_memory,
heap,
};

if args.json {
println!("{}", serde_json::to_string_pretty(&output)?);
} else {
println!("Memory Usage");
println!("{}", "=".repeat(50));
println!();

println!("Process Memory:");
println!(" RSS (Physical): {}", output.process.rss_human);
println!(" Virtual: {}", output.process.virtual_human);
println!();

println!("System Memory:");
println!(" Total: {}", output.system.total_human);
println!(" Used: {}", output.system.used_human);
println!(" Available: {}", output.system.available_human);
println!(" Usage: {:.1}%", output.system.usage_percent);

if let Some(ref heap) = output.heap {
println!();
println!("Heap Statistics:");
println!(" Allocated: {}", format_size(heap.allocated_bytes));
if let Some(count) = heap.allocation_count {
println!(" Allocations: {}", count);
}
}

if args.verbose {
println!();
println!("Tips for reducing memory usage:");
println!(" - Use smaller context windows with --model-context-window");
println!(" - Enable auto-compaction with model_auto_compact_token_limit");
println!(" - Close unused sessions with 'cortex delete <session-id>'");
}
}

Ok(())
}

/// Get process memory information.
fn get_process_memory() -> ProcessMemory {
#[cfg(target_os = "linux")]
{
// Read from /proc/self/statm
if let Ok(statm) = std::fs::read_to_string("/proc/self/statm") {
let parts: Vec<&str> = statm.split_whitespace().collect();
if parts.len() >= 2 {
let page_size = 4096u64; // Typical page size
let virtual_pages: u64 = parts[0].parse().unwrap_or(0);
let rss_pages: u64 = parts[1].parse().unwrap_or(0);
let rss_bytes = rss_pages * page_size;
let virtual_bytes = virtual_pages * page_size;
return ProcessMemory {
rss_bytes,
virtual_bytes,
rss_human: format_size(rss_bytes),
virtual_human: format_size(virtual_bytes),
};
}
}
}

#[cfg(target_os = "macos")]
{
// Use mach APIs via rusage
use std::mem::MaybeUninit;
unsafe {
let mut rusage = MaybeUninit::<libc::rusage>::uninit();
if libc::getrusage(libc::RUSAGE_SELF, rusage.as_mut_ptr()) == 0 {
let rusage = rusage.assume_init();
let rss_bytes = (rusage.ru_maxrss) as u64; // maxrss is in bytes on macOS
return ProcessMemory {
rss_bytes,
virtual_bytes: 0, // Not easily available
rss_human: format_size(rss_bytes),
virtual_human: "N/A".to_string(),
};
}
}
}

// Fallback
ProcessMemory {
rss_bytes: 0,
virtual_bytes: 0,
rss_human: "N/A".to_string(),
virtual_human: "N/A".to_string(),
}
}

/// Get system memory information.
fn get_system_memory() -> SystemMemory {
#[cfg(target_os = "linux")]
{
if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") {
let mut total: u64 = 0;
let mut available: u64 = 0;
let mut free: u64 = 0;
let mut buffers: u64 = 0;
let mut cached: u64 = 0;

for line in meminfo.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 2 {
let value: u64 = parts[1].parse().unwrap_or(0) * 1024; // Convert from kB to bytes
match parts[0] {
"MemTotal:" => total = value,
"MemAvailable:" => available = value,
"MemFree:" => free = value,
"Buffers:" => buffers = value,
"Cached:" => cached = value,
_ => {}
}
}
}

// If MemAvailable not present, estimate it
if available == 0 {
available = free + buffers + cached;
}

let used = total.saturating_sub(available);
let usage_percent = if total > 0 {
(used as f64 / total as f64) * 100.0
} else {
0.0
};

return SystemMemory {
total_bytes: total,
available_bytes: available,
used_bytes: used,
usage_percent,
total_human: format_size(total),
available_human: format_size(available),
used_human: format_size(used),
};
}
}

// Fallback for other platforms
SystemMemory {
total_bytes: 0,
available_bytes: 0,
used_bytes: 0,
usage_percent: 0.0,
total_human: "N/A".to_string(),
available_human: "N/A".to_string(),
used_human: "N/A".to_string(),
}
}

/// Get heap statistics (platform-dependent).
fn get_heap_stats() -> Option<HeapStats> {
// Rust doesn't expose jemalloc/system allocator stats directly
// This would require feature-gating with jemalloc or using platform-specific APIs
None
}

// =============================================================================
// Main entry point
// =============================================================================
Expand All @@ -2676,6 +2922,7 @@ impl DebugCli {
DebugSubcommand::Paths(args) => run_paths(args).await,
DebugSubcommand::System(args) => run_system(args).await,
DebugSubcommand::Wait(args) => run_wait(args).await,
DebugSubcommand::Memory(args) => run_memory(args).await,
}
}
}
Expand Down
Loading