Files
yvgude--lean-ctx/rust/tests/context_radar_perf.rs
T
wehub-resource-sync 26382a7ac6
CI / Clippy (push) Failing after 15m13s
CI / Test (ubuntu-latest) (push) Failing after 16m1s
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (no embeddings / no ORT) (push) Has been cancelled
CI / Format (push) Has been cancelled
CI / Cookbook (Node) (push) Has been cancelled
CI / Pi Extension (Node) (push) Has been cancelled
CI / Rust SDK (lean-ctx-client) (push) Has been cancelled
CI / Embed SDK (lean-ctx-sdk) (push) Has been cancelled
CI / Python SDK (leanctx) (push) Has been cancelled
CI / Hermes Plugin (Python) (push) Has been cancelled
CI / SDK Conformance Matrix (push) Has been cancelled
CI / Coverage (push) Has been cancelled
CI / cargo-deny (push) Has been cancelled
CI / Adversarial Safety (push) Has been cancelled
CI / Benchmarks (push) Has been cancelled
CI / Output-Quality Gate (eval A/B) (push) Has been cancelled
CI / Documentation (push) Has been cancelled
CI / CI Green (push) Has been cancelled
JetBrains Plugin / Actionlint (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL / Analyze (rust) (push) Has been cancelled
JetBrains Plugin / Validation (push) Has been cancelled
JetBrains Plugin / Build (push) Has been cancelled
JetBrains Plugin / Test (push) Has been cancelled
Security Check / Security Scan (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:30 +08:00

495 lines
16 KiB
Rust

use std::io::Write;
use std::time::Instant;
use lean_ctx::core::context_radar::{ContextRadar, RadarEvent, default_window_for_client};
fn make_event(event_type: &str, tokens: usize, tool_name: Option<&str>) -> RadarEvent {
RadarEvent {
ts: 1700000000,
event_type: event_type.to_string(),
tokens,
tool_name: tool_name.map(String::from),
detail: None,
content: None,
model: None,
conversation_id: None,
}
}
fn write_jsonl(dir: &std::path::Path, events: &[RadarEvent]) {
let path = dir.join("context_radar.jsonl");
let mut f = std::fs::File::create(&path).unwrap();
for ev in events {
let line = serde_json::to_string(ev).unwrap();
writeln!(f, "{line}").unwrap();
}
}
// ---------------------------------------------------------------------------
// Performance: load + budget_breakdown with increasing event counts
// ---------------------------------------------------------------------------
#[test]
fn perf_radar_load_100_events() {
let dir = tempfile::tempdir().unwrap();
let events: Vec<RadarEvent> = (0..100)
.map(|i| make_event("mcp_call", 50 + i % 200, Some("ctx_read")))
.collect();
write_jsonl(dir.path(), &events);
let start = Instant::now();
let radar = ContextRadar::load(dir.path(), 200_000);
let elapsed = start.elapsed();
assert_eq!(radar.events.len(), 100);
let b = radar.budget_breakdown();
assert!(b.lean_ctx_tool_tokens > 0);
assert!(elapsed.as_millis() < 100, "100 events took {elapsed:?}");
}
#[test]
fn perf_radar_load_10k_events() {
let dir = tempfile::tempdir().unwrap();
let events: Vec<RadarEvent> = (0..10_000)
.map(|i| {
let types = [
"user_message",
"agent_response",
"mcp_call",
"shell",
"native_tool",
];
make_event(types[i % types.len()], 100 + i % 500, None)
})
.collect();
write_jsonl(dir.path(), &events);
let start = Instant::now();
let radar = ContextRadar::load(dir.path(), 200_000);
let elapsed = start.elapsed();
assert_eq!(radar.events.len(), 10_000);
let b = radar.budget_breakdown();
assert!(b.tracked_total > 0);
assert!(
elapsed.as_millis() < 500,
"10k events took {elapsed:?} — should be <500ms"
);
}
#[test]
fn perf_radar_load_50k_events() {
let dir = tempfile::tempdir().unwrap();
let events: Vec<RadarEvent> = (0..50_000)
.map(|i| make_event("agent_response", 200 + i % 300, None))
.collect();
write_jsonl(dir.path(), &events);
let start = Instant::now();
let radar = ContextRadar::load(dir.path(), 200_000);
let elapsed = start.elapsed();
assert_eq!(radar.events.len(), 50_000);
assert!(
elapsed.as_millis() < 2000,
"50k events took {elapsed:?} — should be <2s"
);
}
// ---------------------------------------------------------------------------
// Budget breakdown correctness: mixed event types
// ---------------------------------------------------------------------------
#[test]
fn breakdown_mixed_event_types() {
let mut radar = ContextRadar::new(200_000);
radar.events = vec![
make_event("user_message", 100, None),
make_event("compaction", 0, None),
make_event("compaction", 0, None),
make_event("user_message", 500, None),
make_event("agent_response", 2000, None),
make_event("mcp_call", 300, Some("ctx_read")),
make_event("mcp_call", 150, Some("other_tool")),
make_event("shell", 400, None),
make_event("native_tool", 250, None),
make_event("thinking", 1000, None),
];
let b = radar.budget_breakdown();
assert_eq!(
b.user_message_tokens, 500,
"current window: only after last compaction"
);
assert_eq!(b.agent_response_tokens, 2000);
assert_eq!(b.lean_ctx_tool_tokens, 300);
assert_eq!(b.other_mcp_tokens, 150);
assert_eq!(b.shell_tokens, 400);
assert_eq!(b.native_read_tokens, 250);
assert_eq!(b.thinking_tokens, 1000);
assert_eq!(b.compaction_count, 2);
assert_eq!(b.tracked_total, 500 + 2000 + 300 + 150 + 400 + 250);
assert_eq!(b.available, 200_000 - b.tracked_total);
assert_eq!(
b.session_user_tokens, 600,
"session total includes pre-compaction"
);
}
#[test]
fn breakdown_lean_ctx_detection_by_detail() {
let mut radar = ContextRadar::new(200_000);
radar.events.push(RadarEvent {
ts: 1000,
event_type: "mcp_call".to_string(),
tokens: 500,
tool_name: Some("some_tool".to_string()),
detail: Some("lean-ctx server".to_string()),
content: None,
model: None,
conversation_id: None,
});
let b = radar.budget_breakdown();
assert_eq!(
b.lean_ctx_tool_tokens, 500,
"detail containing 'lean-ctx' → lean_ctx bucket"
);
assert_eq!(b.other_mcp_tokens, 0);
}
#[test]
fn breakdown_lean_ctx_detection_by_tool_prefix() {
let mut radar = ContextRadar::new(200_000);
radar.events.push(RadarEvent {
ts: 1000,
event_type: "mcp_call".to_string(),
tokens: 300,
tool_name: Some("ctx_search".to_string()),
detail: None,
content: None,
model: None,
conversation_id: None,
});
let b = radar.budget_breakdown();
assert_eq!(
b.lean_ctx_tool_tokens, 300,
"tool_name ctx_* → lean_ctx bucket"
);
}
// ---------------------------------------------------------------------------
// format_display output sanity
// ---------------------------------------------------------------------------
#[test]
fn format_display_includes_all_categories() {
let mut radar = ContextRadar::new(200_000);
radar.events = vec![
make_event("user_message", 1000, None),
make_event("agent_response", 5000, None),
make_event("shell", 200, None),
];
let display = radar.format_display();
assert!(display.contains("CONTEXT RADAR"));
assert!(display.contains("User Messages"));
assert!(display.contains("Agent Responses"));
assert!(display.contains("Shell Output"));
assert!(display.contains("TRACKED"));
assert!(display.contains("Available"));
}
// ---------------------------------------------------------------------------
// Default window sizes for all supported IDEs
// ---------------------------------------------------------------------------
#[test]
fn default_window_all_ides() {
// If a detected model file exists on the system, default_window_for_client
// returns that model's window for all clients regardless of the client name.
if lean_ctx::hook_handlers::load_detected_model().is_some() {
let w = default_window_for_client("cursor");
assert!(
(128_000..=2_000_000).contains(&w),
"window {w} out of range"
);
return;
}
assert_eq!(default_window_for_client("cursor"), 200_000);
assert_eq!(default_window_for_client("claude-code"), 200_000);
assert_eq!(default_window_for_client("claude"), 200_000);
assert_eq!(default_window_for_client("codex"), 200_000);
assert_eq!(default_window_for_client("gemini"), 1_000_000);
assert_eq!(default_window_for_client("windsurf"), 128_000);
assert_eq!(default_window_for_client("copilot"), 128_000);
assert_eq!(default_window_for_client("zed"), 128_000);
assert_eq!(default_window_for_client("unknown"), 200_000);
}
// ---------------------------------------------------------------------------
// Proxy introspection: all three providers
// ---------------------------------------------------------------------------
#[test]
fn introspect_anthropic_large_request() {
use lean_ctx::proxy::introspect::{Provider, analyze_request};
let system = "a]".repeat(10_000);
let user_text = "b".repeat(20_000);
let assistant_text = "c".repeat(8_000);
let tools: Vec<serde_json::Value> = (0..60)
.map(|i| {
serde_json::json!({
"name": format!("tool_{i}"),
"description": format!("This is tool number {i} with a medium-length description for testing."),
"input_schema": { "type": "object", "properties": { "arg": { "type": "string" } } }
})
})
.collect();
let body = serde_json::json!({
"model": "claude-sonnet-4-20250514",
"system": system,
"messages": [
{"role": "user", "content": user_text},
{"role": "assistant", "content": assistant_text}
],
"tools": tools,
});
let start = Instant::now();
let b = analyze_request(&body, Provider::Anthropic);
let elapsed = start.elapsed();
assert!(
b.system_prompt_tokens >= 2000,
"system={}",
b.system_prompt_tokens
);
assert!(
b.user_message_tokens >= 4000,
"user={}",
b.user_message_tokens
);
assert!(
b.assistant_message_tokens >= 1500,
"assistant={}",
b.assistant_message_tokens
);
assert_eq!(b.tool_definition_count, 60);
assert!(b.tool_definition_tokens > 0);
assert!(b.total_input_tokens > 7000);
assert!(elapsed.as_millis() < 50, "introspect took {elapsed:?}");
}
#[test]
fn introspect_openai_with_tool_results() {
use lean_ctx::proxy::introspect::{Provider, analyze_request};
let body = serde_json::json!({
"model": "gpt-4o",
"messages": [
{"role": "system", "content": "You are an assistant that uses tools."},
{"role": "user", "content": "Read the file src/main.rs"},
{"role": "assistant", "content": null, "tool_calls": [
{"id": "call_1", "type": "function", "function": {"name": "read", "arguments": "{\"path\":\"src/main.rs\"}"}}
]},
{"role": "tool", "content": "fn main() { println!(\"Hello, world!\"); }", "tool_call_id": "call_1"},
{"role": "assistant", "content": "The file contains a simple Hello World program."}
]
});
let b = analyze_request(&body, Provider::OpenAi);
assert!(b.system_prompt_tokens > 0);
assert!(b.user_message_tokens > 0);
assert!(b.tool_result_tokens > 0);
assert!(b.assistant_message_tokens > 0);
assert_eq!(b.message_count, 5);
}
#[test]
fn introspect_gemini_with_function_response() {
use lean_ctx::proxy::introspect::{Provider, analyze_request};
let body = serde_json::json!({
"systemInstruction": {
"parts": [{"text": "You are a coding assistant with deep knowledge of Rust programming language."}]
},
"contents": [
{"role": "user", "parts": [{"text": "Search for functions that handle authentication in the codebase."}]},
{"role": "model", "parts": [{"functionCall": {"name": "search", "args": {"query": "fn auth"}}}]},
{"role": "user", "parts": [{"functionResponse": {"name": "search", "response": {"results": "fn authenticate() {} fn authorize() {}"}}}]},
{"role": "model", "parts": [{"text": "I found two authentication-related functions in the codebase: authenticate() and authorize()."}]}
],
"tools": [{"functionDeclarations": [
{"name": "search", "description": "Search the codebase", "parameters": {"type": "object"}},
{"name": "read", "description": "Read a file", "parameters": {"type": "object"}}
]}]
});
let b = analyze_request(&body, Provider::Gemini);
assert!(
b.system_prompt_tokens > 0,
"system={}",
b.system_prompt_tokens
);
assert!(b.user_message_tokens > 0, "user={}", b.user_message_tokens);
assert!(
b.tool_result_tokens > 0,
"tool_result={}",
b.tool_result_tokens
);
assert!(
b.assistant_message_tokens > 0,
"assistant={}",
b.assistant_message_tokens
);
assert_eq!(b.tool_definition_count, 2);
assert_eq!(b.message_count, 4);
}
// ---------------------------------------------------------------------------
// IntrospectState thread safety
// ---------------------------------------------------------------------------
#[test]
fn introspect_state_concurrent_recording() {
use lean_ctx::proxy::introspect::{IntrospectState, Provider, analyze_request};
use std::sync::Arc;
let state = Arc::new(IntrospectState::default());
let mut handles = vec![];
for i in 0..10 {
let s = Arc::clone(&state);
handles.push(std::thread::spawn(move || {
let body = serde_json::json!({
"model": format!("model-{i}"),
"system": format!("System prompt number {i} for testing concurrent access."),
"messages": [{"role": "user", "content": format!("Message {i}")}]
});
let b = analyze_request(&body, Provider::Anthropic);
s.record(b);
}));
}
for h in handles {
h.join().unwrap();
}
assert_eq!(
state
.total_requests
.load(std::sync::atomic::Ordering::Relaxed),
10,
);
assert!(
state
.total_system_prompt_tokens
.load(std::sync::atomic::Ordering::Relaxed)
> 0,
);
assert!(state.last_breakdown.lock().unwrap().is_some());
}
// ---------------------------------------------------------------------------
// End-to-end: JSONL write → load → breakdown pipeline
// ---------------------------------------------------------------------------
#[test]
fn e2e_jsonl_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let radar_path = dir.path().join("context_radar.jsonl");
let events = vec![
make_event("user_message", 999, None),
make_event("compaction", 0, None),
make_event("user_message", 100, None),
make_event("mcp_call", 50, Some("ctx_read")),
make_event("mcp_call", 75, Some("oplane")),
make_event("shell", 200, None),
make_event("agent_response", 1500, None),
];
{
let mut f = std::fs::OpenOptions::new()
.create(true)
.append(true)
.open(&radar_path)
.unwrap();
for ev in &events {
let line = serde_json::to_string(ev).unwrap();
writeln!(f, "{line}").unwrap();
}
}
let radar = ContextRadar::load(dir.path(), 128_000);
assert_eq!(radar.events.len(), 7);
let b = radar.budget_breakdown();
assert_eq!(
b.user_message_tokens, 100,
"current window after compaction"
);
assert_eq!(b.lean_ctx_tool_tokens, 50);
assert_eq!(b.other_mcp_tokens, 75);
assert_eq!(b.shell_tokens, 200);
assert_eq!(b.agent_response_tokens, 1500);
assert_eq!(b.compaction_count, 1);
let event_total = 100 + 50 + 75 + 200 + 1500;
assert_eq!(
b.tracked_total,
event_total + b.system_prompt_tokens,
"tracked_total = current window events + rules tokens"
);
assert_eq!(b.window_size, 128_000);
assert_eq!(
b.session_user_tokens, 1099,
"session includes pre-compaction"
);
}
// ---------------------------------------------------------------------------
// Performance: budget_breakdown with many events
// ---------------------------------------------------------------------------
#[test]
fn perf_budget_breakdown_100k_events() {
let mut radar = ContextRadar::new(1_000_000);
radar.events = (0..100_000)
.map(|i| {
let types = [
"user_message",
"agent_response",
"mcp_call",
"shell",
"native_tool",
"thinking",
];
RadarEvent {
ts: 1700000000 + i as u64,
event_type: types[i % types.len()].to_string(),
tokens: 50 + i % 500,
tool_name: if i % 3 == 0 {
Some("ctx_read".to_string())
} else {
None
},
detail: None,
content: None,
model: None,
conversation_id: None,
}
})
.collect();
let start = Instant::now();
let b = radar.budget_breakdown();
let elapsed = start.elapsed();
assert!(b.tracked_total > 0);
assert!(
elapsed.as_millis() < 50,
"budget_breakdown on 100k events took {elapsed:?}"
);
}