дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-08 19:02:17 +02:00
perf: measure the Git Log and splitter dragging
gitLog shows 20k generated commits served page by page through a fake vcs_log, walks the selection with the arrow keys and wheel-scrolls the list. resizePanels drags the side and bottom splitters with an editor, the Project tree and the Log open. The perf probe's fake backends can now be combined.
Цей коміт міститься в:
1 джерело
6999d4034f
коміт
f363f76e57
3 files changed
+188
-9
No files matched your search
@@ -46,6 +46,16 @@
|
||||
"consoleOutput.keyMissedFramePct": 10,
|
||||
"consoleOutput.tbtMs": 50,
|
||||
|
||||
"gitLog.showMs": 150,
|
||||
"gitLog.droppedFramesPct": 2,
|
||||
"gitLog.tbtMs": 50,
|
||||
"gitLog.arrowKeyP95Ms": 20,
|
||||
"gitLog.arrowKeyMissedFramePct": 5,
|
||||
|
||||
"resizePanels.droppedFramesPct": 2,
|
||||
"resizePanels.bottomDroppedFramesPct": 2,
|
||||
"resizePanels.tbtMs": 50,
|
||||
|
||||
"keyRepeat.p95Ms": 20,
|
||||
"keyRepeat.maxMs": 33,
|
||||
"keyRepeat.missedFramePct": 2,
|
||||
|
||||
@@ -267,6 +267,103 @@ const scroll: Scenario = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Git Log with 20,000 commits on branching lanes, loaded page by page as the
|
||||
* backend does: showing it, walking the selection with the arrow keys (the
|
||||
* details pane follows) and wheel-scrolling through it.
|
||||
*/
|
||||
const gitLog: Scenario = {
|
||||
id: "gitLog",
|
||||
title: "Show, navigate and scroll a 20k-commit Git Log",
|
||||
async run(h) {
|
||||
await h.page.evaluate(() =>
|
||||
(window as never as { __openideaPerf: { fakeGitLog(n: number): void } }).__openideaPerf.fakeGitLog(20_000),
|
||||
);
|
||||
const showMs = await h.measure(
|
||||
() =>
|
||||
h.page.evaluate(() =>
|
||||
(window as never as { __openideaPerf: { runAction(id: string): Promise<boolean> } }).__openideaPerf.runAction(
|
||||
"vcs.showLog",
|
||||
),
|
||||
),
|
||||
'[role="listbox"][aria-label="Commits"]',
|
||||
"order flow step 0",
|
||||
);
|
||||
const refresh = await h.refreshMs();
|
||||
const list = h.page.getByRole("listbox", { name: "Commits" });
|
||||
await list.getByRole("option").first().click();
|
||||
const keyLat = await h.pressMeasured(Array(40).fill("ArrowDown"), 50);
|
||||
const box = (await list.boundingBox())!;
|
||||
await h.page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
|
||||
const t0 = await h.now();
|
||||
await h.inPage((p) => p.startFrames());
|
||||
for (let i = 0; i < 120; i++) {
|
||||
await h.page.mouse.wheel(0, i < 80 ? 400 : -400);
|
||||
await h.page.waitForTimeout(16);
|
||||
}
|
||||
await h.page.waitForTimeout(200);
|
||||
const frames = await h.inPage((p) => p.stopFrames());
|
||||
return {
|
||||
showMs,
|
||||
...frameSummary(frames, refresh),
|
||||
tbtMs: totalBlockingTime(await h.longTasksSince(t0)),
|
||||
...keySummary(keyLat, refresh, "arrowKey"),
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Dragging the splitters with an editor, the Project tree and the Git Log
|
||||
* open: every pointer move resizes the islands, so each frame relays them out.
|
||||
*/
|
||||
const resizePanels: Scenario = {
|
||||
id: "resizePanels",
|
||||
title: "Drag the tool window splitters",
|
||||
async run(h) {
|
||||
await h.openFile(SAMPLE);
|
||||
await h.page.evaluate(() =>
|
||||
(window as never as { __openideaPerf: { fakeGitLog(n: number): void } }).__openideaPerf.fakeGitLog(2_000),
|
||||
);
|
||||
await h.measure(
|
||||
() =>
|
||||
h.page.evaluate(() =>
|
||||
(window as never as { __openideaPerf: { runAction(id: string): Promise<boolean> } }).__openideaPerf.runAction(
|
||||
"vcs.showLog",
|
||||
),
|
||||
),
|
||||
'[role="listbox"][aria-label="Commits"]',
|
||||
"order flow step 0",
|
||||
);
|
||||
const refresh = await h.refreshMs();
|
||||
const drag = async (name: string, dx: number, dy: number) => {
|
||||
const box = (await h.page.getByRole("separator", { name }).boundingBox())!;
|
||||
const x = box.x + box.width / 2;
|
||||
const y = box.y + box.height / 2;
|
||||
await h.page.mouse.move(x, y);
|
||||
await h.page.mouse.down();
|
||||
await h.inPage((p) => p.startFrames());
|
||||
// Out and back twice, one pointer move a frame.
|
||||
for (let i = 0; i < 120; i++) {
|
||||
const t = Math.sin((i / 60) * Math.PI);
|
||||
await h.page.mouse.move(x + dx * t, y + dy * t);
|
||||
await h.page.waitForTimeout(16);
|
||||
}
|
||||
await h.page.mouse.up();
|
||||
await h.page.waitForTimeout(200);
|
||||
return frameSummary(await h.inPage((p) => p.stopFrames()), refresh);
|
||||
};
|
||||
const t0 = await h.now();
|
||||
const side = await drag("Resize left tool window", 300, 0);
|
||||
const bottom = await drag("Resize bottom tool window", 0, -250);
|
||||
return {
|
||||
...side,
|
||||
tbtMs: totalBlockingTime(await h.longTasksSince(t0)),
|
||||
bottomFrameP95Ms: bottom.frameP95Ms,
|
||||
bottomDroppedFramesPct: bottom.droppedFramesPct,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
/** Cold load: navigation → Project tree painted. */
|
||||
const startup: Scenario = {
|
||||
id: "startup",
|
||||
@@ -453,6 +550,8 @@ export const SCENARIOS: Scenario[] = [
|
||||
typingAngular,
|
||||
completion,
|
||||
consoleOutput,
|
||||
gitLog,
|
||||
resizePanels,
|
||||
keyRepeat,
|
||||
scroll,
|
||||
tabs,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { executeAction } from "@/platform/actions";
|
||||
import { bindingsFor } from "@/platform/keymap";
|
||||
import { activeEditor } from "@/editor/api";
|
||||
import { docBackend, type MemoryBackend } from "@/editor/core/backend";
|
||||
import type { CompletionItem, CompletionKind } from "@/bindings";
|
||||
import type { Commit, CompletionItem, CompletionKind, LogQuery } from "@/bindings";
|
||||
import { setInvokeRoute } from "@/platform/tauriCore";
|
||||
import { editorService } from "./contributions";
|
||||
|
||||
@@ -30,6 +30,11 @@ export interface PerfProbe {
|
||||
* list); other language server requests report no server. `0` removes it.
|
||||
*/
|
||||
fakeCompletion(items: number): void;
|
||||
/**
|
||||
* Answers the Git Log with `commits` generated commits (a main line with
|
||||
* feature branches merging into it), paged like the backend. `0` removes it.
|
||||
*/
|
||||
fakeGitLog(commits: number): void;
|
||||
}
|
||||
|
||||
const KINDS: CompletionKind[] = [
|
||||
@@ -69,6 +74,52 @@ function completionItem(i: number): CompletionItem {
|
||||
};
|
||||
}
|
||||
|
||||
function commit(i: number, count: number): Commit {
|
||||
const id = (n: number) => n.toString(16).padStart(40, "0");
|
||||
// Newest first: parents have higher indices. Every 12th commit merges a branch
|
||||
// started 7 commits back; every third commit skips ahead, keeping a few lanes open.
|
||||
const merge = i % 12 === 0 && i + 7 < count;
|
||||
const next = i % 3 === 2 && i + 3 < count ? i + 3 : i + 1;
|
||||
const parents = i + 1 >= count ? [] : merge ? [id(i + 1), id(i + 7)] : [id(next)];
|
||||
const time = Date.UTC(2026, 10, 5) - i * 3_600_000;
|
||||
const summary = `${["feat", "fix", "refactor", "test"][i % 4]}(orders): ${merge ? "merge" : "update"} order flow step ${i}`;
|
||||
return {
|
||||
id: id(i),
|
||||
shortId: id(i).slice(-8),
|
||||
parents,
|
||||
author: ["Ada Lovelace", "Linus Torvalds", "Grace Hopper", "Ken Thompson"][i % 4]!,
|
||||
email: `dev${i % 4}@acme.dev`,
|
||||
committer: "CI",
|
||||
committerEmail: "[email protected]",
|
||||
timeMs: time,
|
||||
commitTimeMs: time,
|
||||
summary,
|
||||
message: summary,
|
||||
messageTruncated: false,
|
||||
refs: i === 0 ? ["HEAD -> main", "origin/main"] : i % 250 === 0 ? [`tag: v1.${count - i}`] : [],
|
||||
};
|
||||
}
|
||||
|
||||
type Route = (cmd: string, args: unknown) => Promise<unknown> | null;
|
||||
const routes = new Map<string, Route>();
|
||||
|
||||
/** Installs (or with `null` removes) one fake backend; the first that answers a command wins. */
|
||||
function setRoute(name: string, next: Route | null) {
|
||||
if (next) routes.set(name, next);
|
||||
else routes.delete(name);
|
||||
setInvokeRoute(
|
||||
routes.size
|
||||
? (cmd, args) => {
|
||||
for (const r of routes.values()) {
|
||||
const answer = r(cmd, args);
|
||||
if (answer) return answer;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
export function installPerfProbe(): () => void {
|
||||
const probe: PerfProbe = {
|
||||
seed(path, text) {
|
||||
@@ -90,15 +141,34 @@ export function installPerfProbe(): () => void {
|
||||
return !!view && syntaxParserRunning(view);
|
||||
},
|
||||
fakeCompletion(count) {
|
||||
if (!count) return setInvokeRoute(null);
|
||||
const items = Array.from({ length: count }, (_, i) => completionItem(i));
|
||||
setInvokeRoute((cmd, args) => {
|
||||
if (cmd === "lsp_completion") return Promise.resolve({ isIncomplete: false, items });
|
||||
if (cmd === "lsp_resolve_completion") return Promise.resolve((args as { item: CompletionItem }).item);
|
||||
if (cmd.startsWith("lsp_"))
|
||||
return Promise.reject({ kind: "unavailable", message: "No language server available" });
|
||||
return null;
|
||||
});
|
||||
setRoute(
|
||||
"completion",
|
||||
count
|
||||
? (cmd, args) => {
|
||||
if (cmd === "lsp_completion") return Promise.resolve({ isIncomplete: false, items });
|
||||
if (cmd === "lsp_resolve_completion") return Promise.resolve((args as { item: CompletionItem }).item);
|
||||
if (cmd.startsWith("lsp_"))
|
||||
return Promise.reject({ kind: "unavailable", message: "No language server available" });
|
||||
return null;
|
||||
}
|
||||
: null,
|
||||
);
|
||||
},
|
||||
fakeGitLog(count) {
|
||||
const commits = Array.from({ length: count }, (_, i) => commit(i, count));
|
||||
setRoute(
|
||||
"gitLog",
|
||||
count
|
||||
? (cmd, args) => {
|
||||
if (cmd !== "vcs_log") return null;
|
||||
const { skip, limit, range } = (args as { query: LogQuery }).query;
|
||||
// Outgoing commits (`@{u}..HEAD`) and the like: none.
|
||||
if (range) return Promise.resolve([]);
|
||||
return Promise.resolve(commits.slice(skip, limit ? skip + limit : undefined));
|
||||
}
|
||||
: null,
|
||||
);
|
||||
},
|
||||
editorState() {
|
||||
const e = activeEditor();
|
||||
|
||||
Reference in new issue
Заблокувати користувача