grok-build-upstream-mirror/crates/codegen/xai-grok-pager/benches/resize.rs
grokkybara[bot] dd04f397b1 Synced from monorepo
Synced from monorepo

Changes:
- Temporarily disable session share link creation in the TUI
- Do not approve plan on empty Enter from the revise prompt
- Expose chat product Skills via ACP available_commands_update
- Return immediately from a blocking wait on an already-completed ACP task
- Split headless pager module for clearer structure
- Stop git worktree prune from removing user registrations on resume
- Use compaction sampler tokenizer for item token counts
- Opt-in extra root CAs via GROK_EXTRA_CA_BUNDLE
- Cancel all session subagents when the user stops
- Let the session persistence actor exit when its session ends
- Make fullscreen terminal resize much cheaper on long sessions
- Report honestly from kill_task when an ACP task does not exist
- Hide /usage for external-auth deployments
- Forward the history-load trailer’s computer_reason to the client
- Remove ineffective no-op tool reminder
- Declare slash-command screen-mode support in one place
- Keep settings enum picker on the committed value until Enter
- Reap a PTY’s full process tree
- Stream tool calls from headless mode over ACP
- Bridge gateway task lifecycle to ACP for chat session background tasks
- Don’t warn about truncated history on a suppressed replay
- Fit full-replace summarizer input and recover on context-length errors
- Stop dropping agents over an unrecognized frontmatter color
- Add /undo as a slash alias for /rewind
- Harden sleep/wake token-refresh paths against forced re-login
- Add session/list ACP method
- Give each sampling backend its own conversion module
- Treat an unenrolled child process as a lint error
- Suppress the cancelled marker on send-now wake turns
- Stop tearing down Roslyn on every edit, and read C# diagnostics

Source-Revision: 2a28b4a86cfc4a4c133c35b7fc2a6a9964387c39
2026-07-30 19:07:40 +00:00

195 lines
6.6 KiB
Rust

//! Criterion benchmarks for the terminal-RESIZE path.
//!
//! Dragging a terminal edge sends a stream of `Event::Resize`, and the
//! reported symptom is that the drag gets laggier the longer a session runs.
//!
//! Regressions these guard against, each of which was measured on a real
//! session and removed:
//! - re-deriving an entry's source text per width instead of reusing its
//! cached line-width profile (makes the estimate pass O(conversation bytes)),
//! - building or cloning an `AppearanceConfig` per entry,
//! - running `warm_measure_pages_above` on every resize instead of once the
//! width settles.
use std::time::Duration;
use criterion::{Criterion, criterion_group, criterion_main};
use xai_grok_pager::scrollback::{RenderBlock, ScrollbackState};
/// Roughly a VS Code editor pane maximized on a laptop screen.
const VIEWPORT_WIDTH: u16 = 120;
const VIEWPORT_HEIGHT: u16 = 50;
/// ~3,200 entries / ~5 MB of text — the scale of a multi-hour session.
const TURNS: usize = 400;
fn agent_markdown(i: usize) -> String {
format!(
"Here is what I found for step {i}.\n\n\
The `ScrollbackState` keeps a layout cache keyed by width, so a resize \
invalidates every entry. That matters because the estimate pass has to \
re-derive each block's source text before it can compute a height.\n\n\
- first observation about entry {i}\n\
- second observation, slightly longer, about how the wrap cache is keyed \
on `(width, generation, theme)` and therefore misses after a drag\n\
- third observation\n\n\
```rust\n\
fn rebuild_layout_cache(&mut self, width: u16) {{\n\
\x20 for entry in self.entries.values() {{\n\
\x20 let renderer = EntryRenderer::new(entry, &theme)\n\
\x20 .with_appearance(self.appearance.clone());\n\
\x20 let height = renderer.estimate_height(width);\n\
\x20 }}\n\
}}\n\
```\n\n\
In short: the {i}th response re-wraps on every width change, and the \
syntax highlighting of the fence above is recomputed with it. \
{}\n",
"Additional prose so the message spans several wrapped rows. ".repeat(6)
)
}
fn thinking_text(i: usize) -> String {
format!(
"Considering approach {i}. {}",
"The user asked about resize latency, so I should look at the layout cache. ".repeat(8)
)
}
fn edit_texts(i: usize) -> (String, String) {
let old = format!(
"fn handler_{i}(req: Request) -> Response {{\n\
\x20 let body = req.body();\n\
\x20 let parsed = serde_json::from_slice(body)?;\n\
\x20 Response::ok(parsed)\n\
}}\n"
);
let new = format!(
"fn handler_{i}(req: Request) -> Response {{\n\
\x20 let body = req.body();\n\
\x20 let parsed: Payload = serde_json::from_slice(body)\n\
\x20 .map_err(|e| Error::BadRequest(e.to_string()))?;\n\
\x20 Response::ok(parsed)\n\
}}\n"
);
(old, new)
}
fn bash_output(i: usize) -> String {
(0..40)
.map(|l| format!("crates/codegen/xai-grok-pager/src/file_{i}_{l}.rs:{l}: match found"))
.collect::<Vec<_>>()
.join("\n")
}
/// Block mix and proportions follow what a real coding session produces.
fn build_session() -> (ScrollbackState, usize) {
let mut state = ScrollbackState::new();
let mut bytes = 0usize;
let mut push = |state: &mut ScrollbackState, block: RenderBlock, n: usize| {
bytes += n;
state.push_block(block);
};
for i in 0..TURNS {
let p = format!("please investigate issue {i} and report back with a plan");
push(&mut state, RenderBlock::user_prompt(p.clone()), p.len());
let t = thinking_text(i);
push(&mut state, RenderBlock::thinking(t.clone()), t.len());
let out = bash_output(i);
push(
&mut state,
RenderBlock::execute_with_output(
format!("rg -n 'pattern{i}' crates/"),
out.clone(),
None::<String>,
),
out.len(),
);
push(
&mut state,
RenderBlock::read(
format!("crates/codegen/xai-grok-pager/src/mod_{i}.rs"),
None,
),
64,
);
let (old, new) = edit_texts(i);
push(
&mut state,
RenderBlock::edit_with_hunks(
format!("crates/codegen/xai-grok-pager/src/mod_{i}.rs"),
xai_grok_pager::diff::diff_hunks_from_strings(&old, &new, 1),
),
old.len() + new.len(),
);
push(
&mut state,
RenderBlock::search(format!("fn handler_{i}"), 12, Vec::new()),
48,
);
let md = agent_markdown(i);
push(&mut state, RenderBlock::agent_message(md.clone()), md.len());
let t2 = thinking_text(i + 1);
push(&mut state, RenderBlock::thinking(t2.clone()), t2.len());
}
state.prepare_layout(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
(state, bytes)
}
fn bench_resize_step(c: &mut Criterion) {
let (mut state, bytes) = build_session();
eprintln!(
"resize corpus: {} entries, ~{:.1} MB text",
state.len(),
bytes as f64 / (1024.0 * 1024.0)
);
let mut g = c.benchmark_group("resize");
g.sample_size(20).warm_up_time(Duration::from_millis(500));
g.bench_function("width_step", |b| {
let mut w = VIEWPORT_WIDTH;
b.iter(|| {
w = if w == VIEWPORT_WIDTH {
VIEWPORT_WIDTH - 1
} else {
VIEWPORT_WIDTH
};
state.prepare_layout(w, VIEWPORT_HEIGHT);
});
});
g.finish();
}
fn bench_resize_drag(c: &mut Criterion) {
let (mut state, _) = build_session();
let mut g = c.benchmark_group("resize");
g.sample_size(10).warm_up_time(Duration::from_millis(500));
g.bench_function("drag_20_steps", |b| {
b.iter(|| {
for step in 0..20u16 {
state.prepare_layout(VIEWPORT_WIDTH - step, VIEWPORT_HEIGHT);
}
state.prepare_layout(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
});
});
g.finish();
}
fn bench_resize_noop(c: &mut Criterion) {
let (mut state, _) = build_session();
let mut g = c.benchmark_group("resize");
g.bench_function("same_width_noop", |b| {
b.iter(|| {
state.prepare_layout(VIEWPORT_WIDTH, VIEWPORT_HEIGHT);
});
});
g.finish();
}
criterion_group!(
benches,
bench_resize_step,
bench_resize_drag,
bench_resize_noop
);
criterion_main!(benches);