Remove CDP profiling script; document sentinel design in BufferLine

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-06-06 16:22:34 +00:00
co-authored by Daniel Imms
parent b44d036524
commit 3b08b7dcef
2 changed files with 6 additions and 141 deletions
-140
View File
@@ -1,140 +0,0 @@
/**
* Copyright (c) 2026 The xterm.js authors. All rights reserved.
* @license MIT
*
* CPU profile `timeout 1s yes` on the demo terminal via CDP (Chromium).
*
* Usage:
* node bin/profile_yes_cdp.mjs [--out path.cpuprofile] [--label baseline]
*
* Requires demo server on port 3000 (npm start).
*/
import { chromium } from 'playwright';
import { writeFileSync, mkdirSync, readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = join(__dirname, '..');
function parseArgs() {
const args = process.argv.slice(2);
let out = join(root, 'out-test', 'yes.cpuprofile');
let label = 'profile';
for (let i = 0; i < args.length; i++) {
if (args[i] === '--out' && args[i + 1]) {
out = args[++i];
label = out.replace(/\.cpuprofile$/, '').split('/').pop() ?? label;
} else if (args[i] === '--label' && args[i + 1]) {
label = args[++i];
}
}
return { out, label };
}
const INTEREST = [
'copyFrom',
'_copySparseMapsFrom',
'_copyCellMapsFrom',
'BufferService.scroll'
];
export function summarizeCpuProfileFromObject(profile, label = 'profile') {
const nodes = profile.nodes ?? [];
const samples = profile.samples ?? [];
const timeDeltas = profile.timeDeltas ?? [];
const idToNode = new Map(nodes.map(n => [n.id, n]));
const stats = new Map(INTEREST.map(n => [n, { hitCount: 0, selfTimeUs: 0, totalTimeUs: 0 }]));
let totalSampleUs = 0;
for (let i = 0; i < samples.length; i++) {
const deltaUs = timeDeltas[i] ?? 0;
totalSampleUs += deltaUs;
const stack = [];
let nodeId = samples[i];
while (nodeId !== undefined) {
const node = idToNode.get(nodeId);
if (!node) break;
stack.push(node);
nodeId = node.parent;
}
for (const s of INTEREST) {
if (stack.some(n => (n.callFrame?.functionName ?? '').includes(s))) {
const st = stats.get(s);
st.hitCount++;
st.totalTimeUs += deltaUs;
}
}
const leaf = stack[0];
const fn = leaf?.callFrame?.functionName ?? '';
for (const s of INTEREST) {
if (fn.includes(s)) {
stats.get(s).selfTimeUs += deltaUs;
break;
}
}
}
const rows = [...stats.entries()]
.filter(([, v]) => v.hitCount > 0)
.map(([name, v]) => ({
name,
hitCount: v.hitCount,
selfMs: +(v.selfTimeUs / 1000).toFixed(2),
totalMs: +(v.totalTimeUs / 1000).toFixed(2),
pctTotal: totalSampleUs ? +((100 * v.totalTimeUs) / totalSampleUs).toFixed(2) : 0
}));
return { label, totalSampleMs: +(totalSampleUs / 1000).toFixed(2), rows };
}
export function summarizeCpuProfile(profilePath) {
const profile = JSON.parse(readFileSync(profilePath, 'utf8'));
return summarizeCpuProfileFromObject(profile, profilePath);
}
async function main() {
const { out, label } = parseArgs();
mkdirSync(dirname(out), { recursive: true });
const browser = await chromium.launch({ headless: true });
const page = await browser.newPage();
await page.goto('http://localhost:3000/', { waitUntil: 'networkidle' });
await page.waitForFunction(() => window.term && window.term.element, undefined, { timeout: 60000 });
const termInfo = await page.evaluate(() => ({
cols: window.term.cols,
rows: window.term.rows,
scrollback: window.term.options.scrollback
}));
console.log('Terminal:', JSON.stringify(termInfo));
const cdp = await page.context().newCDPSession(page);
await cdp.send('Profiler.enable');
await cdp.send('Profiler.setSamplingInterval', { interval: 100 });
await cdp.send('Profiler.start');
const textarea = page.locator('.xterm-helper-textarea');
await textarea.click();
await page.keyboard.type('timeout 1s yes', { delay: 5 });
await page.keyboard.press('Enter');
await page.waitForTimeout(2500);
const { profile } = await cdp.send('Profiler.stop');
writeFileSync(out, JSON.stringify(profile));
const summary = summarizeCpuProfileFromObject(profile, label);
const summaryPath = out.replace(/\.cpuprofile$/, '.summary.json');
writeFileSync(summaryPath, JSON.stringify({ ...summary, termInfo }, null, 2));
console.log('Wrote', out);
console.log('Summary:', JSON.stringify(summary, null, 2));
await browser.close();
}
main().catch(err => {
console.error(err);
process.exit(1);
});
+6 -1
View File
@@ -71,7 +71,12 @@ export interface IBufferLineStringCache {
* (if only one particular value is needed) or `loadCell`. For `loadCell` in a loop
* memory allocs / GC pressure can be greatly reduced by reusing the CellData object.
*/
/** Shared empty sparse maps for lines with no combining/extended data. */
/**
* Shared empty sparse maps for lines with no combining/extended data.
* Uses a sentinel + copy-on-write (not `undefined`) so `copyFrom` can detect empty maps
* with reference equality. `undefined` was explored but regressed sparse `copyFrom`;
* `Object.freeze` on these sentinels is safe with COW but did not improve perf.
*/
const EMPTY_SPARSE_MAP: {[index: number]: string} = Object.create(null);
const EMPTY_SPARSE_EXTENDED: {[index: number]: IExtendedAttrs | undefined} = Object.create(null);