- Persist and remove the seeded run parent directory after perf runs - Add regression coverage for seed cleanup, percentile clamping, and RAF reset
Unified · Split
| @@ -7,20 +7,23 @@ import { PERF_CATALOG_IDS } from "@t3tools/shared/perf/scenarioCatalog"; · 6 unmodified lines | |||
| 7 | import { seedPerfState } from "./seedPerfState.ts"; | 7 | import { seedPerfState } from "./seedPerfState.ts"; |
| 8 | 8 | ||
| 9 | describe("seedPerfState", () => { | 9 | describe("seedPerfState", () => { |
| 10 | const baseDirsToCleanup: string[] = []; | 10 | const runParentDirsToCleanup: string[] = []; |
| 11 | 11 | ||
| 12 | afterEach(async () => { | 12 | afterEach(async () => { |
| 13 | await Promise.all( | 13 | await Promise.all( |
| 14 | baseDirsToCleanup.splice(0).map((baseDir) => rm(baseDir, { recursive: true, force: true })), | 14 | runParentDirsToCleanup |
| 15 | .splice(0) | ||
| 16 | .map((runParentDir) => rm(runParentDir, { recursive: true, force: true })), | ||
| 15 | ); | 17 | ); |
| 16 | }); | 18 | }); |
| 17 | 19 | ||
| 18 | it("seeds large thread fixtures through the real event store and projection pipeline", async () => { | 20 | it("seeds large thread fixtures through the real event store and projection pipeline", async () => { |
| 19 | const seeded = await seedPerfState("large_threads"); | 21 | const seeded = await seedPerfState("large_threads"); |
| 20 | baseDirsToCleanup.push(seeded.baseDir); | 22 | runParentDirsToCleanup.push(seeded.runParentDir); |
| 21 | 23 | ||
| 22 | expect(seeded.snapshot.projects).toHaveLength(1); | 24 | expect(seeded.snapshot.projects).toHaveLength(1); |
| 23 | expect(seeded.snapshot.threads).toHaveLength(12); | 25 | expect(seeded.snapshot.threads).toHaveLength(12); |
| 26 | expect(seeded.baseDir).toBe(join(seeded.runParentDir, "base")); | ||
| 24 | 27 | ||
| 25 | const heavyThread = seeded.snapshot.threads.find( | 28 | const heavyThread = seeded.snapshot.threads.find( |
| 26 | (thread) => thread.id === PERF_CATALOG_IDS.largeThreads.heavyAThreadId, | 29 | (thread) => thread.id === PERF_CATALOG_IDS.largeThreads.heavyAThreadId, |
| @@ -34,7 +37,7 @@ describe("seedPerfState", () => { · 7 unmodified lines | |||
| 34 | 37 | ||
| 35 | it("enables assistant streaming in the burst base seed for websocket perf runs", async () => { | 38 | it("enables assistant streaming in the burst base seed for websocket perf runs", async () => { |
| 36 | const seeded = await seedPerfState("burst_base"); | 39 | const seeded = await seedPerfState("burst_base"); |
| 37 | baseDirsToCleanup.push(seeded.baseDir); | 40 | runParentDirsToCleanup.push(seeded.runParentDir); |
| 38 | 41 | ||
| 39 | const rawSettings = await readFile(join(seeded.baseDir, "userdata/settings.json"), "utf8"); | 42 | const rawSettings = await readFile(join(seeded.baseDir, "userdata/settings.json"), "utf8"); |
| 40 | expect(JSON.parse(rawSettings)).toMatchObject({ | 43 | expect(JSON.parse(rawSettings)).toMatchObject({ |
| @@ -34,6 +34,7 @@ import { ServerSettingsService, ServerSettingsLive } from "../../src/serverSetti · 33 unmodified lines | |||
| 34 | 34 | ||
| 35 | export interface PerfSeededState { | 35 | export interface PerfSeededState { |
| 36 | readonly scenarioId: PerfSeedScenarioId; | 36 | readonly scenarioId: PerfSeedScenarioId; |
| 37 | readonly runParentDir: string; | ||
| 37 | readonly baseDir: string; | 38 | readonly baseDir: string; |
| 38 | readonly workspaceRoot: string; | 39 | readonly workspaceRoot: string; |
| 39 | readonly snapshot: OrchestrationReadModel; | 40 | readonly snapshot: OrchestrationReadModel; |
| @@ -489,6 +490,7 @@ export async function seedPerfState(scenarioId: PerfSeedScenarioId): Promise<Per · 449 unmodified lines | |||
| 489 | 490 | ||
| 490 | return { | 491 | return { |
| 491 | scenarioId, | 492 | scenarioId, |
| 493 | runParentDir, | ||
| 492 | baseDir, | 494 | baseDir, |
| 493 | workspaceRoot, | 495 | workspaceRoot, |
| 494 | snapshot, | 496 | snapshot, |
| @@ -13,6 +13,7 @@ const seeded = await seedPerfState(scenarioId); · 12 unmodified lines | |||
| 13 | const payload = JSON.stringify( | 13 | const payload = JSON.stringify( |
| 14 | { | 14 | { |
| 15 | scenarioId: seeded.scenarioId, | 15 | scenarioId: seeded.scenarioId, |
| 16 | runParentDir: seeded.runParentDir, | ||
| 16 | baseDir: seeded.baseDir, | 17 | baseDir: seeded.baseDir, |
| 17 | workspaceRoot: seeded.workspaceRoot, | 18 | workspaceRoot: seeded.workspaceRoot, |
| 18 | projectTitle: seeded.snapshot.projects[0]?.title ?? null, | 19 | projectTitle: seeded.snapshot.projects[0]?.title ?? null, |
| @@ -1,11 +1,11 @@ | |||
| 1 | import { spawn, type ChildProcess } from "node:child_process"; | 1 | import { spawn, type ChildProcess } from "node:child_process"; |
| 2 | import { access, mkdir, writeFile } from "node:fs/promises"; | 2 | import { access, mkdir, rm, writeFile } from "node:fs/promises"; |
| 3 | import { createServer } from "node:net"; | 3 | import { createServer } from "node:net"; |
| 4 | import { join, resolve } from "node:path"; | 4 | import { join, resolve } from "node:path"; |
| 5 | import { fileURLToPath } from "node:url"; | 5 | import { fileURLToPath } from "node:url"; |
| 6 | import { once } from "node:events"; | 6 | import { once } from "node:events"; |
| 7 | 7 | ||
| 8 | import { chromium, type Page } from "playwright"; | 8 | import { chromium, type Browser, type BrowserContext, type Page } from "playwright"; |
| 9 | 9 | ||
| 10 | import { | 10 | import { |
| 11 | type BrowserPerfMetrics, | 11 | type BrowserPerfMetrics, |
| @@ -50,6 +50,7 @@ interface PerfSeedThreadSummary { · 38 unmodified lines | |||
| 50 | 50 | ||
| 51 | export interface PerfSeededState { | 51 | export interface PerfSeededState { |
| 52 | readonly scenarioId: PerfSeedScenarioId; | 52 | readonly scenarioId: PerfSeedScenarioId; |
| 53 | readonly runParentDir: string; | ||
| 53 | readonly baseDir: string; | 54 | readonly baseDir: string; |
| 54 | readonly workspaceRoot: string; | 55 | readonly workspaceRoot: string; |
| 55 | readonly projectTitle: string | null; | 56 | readonly projectTitle: string | null; |
| @@ -181,6 +182,10 @@ async function ensureArtifactDir(suite: string, scenarioId: string): Promise<str · 125 unmodified lines | |||
| 181 | return artifactDir; | 182 | return artifactDir; |
| 182 | } | 183 | } |
| 183 | 184 | ||
| 185 | async function cleanupPerfRunDir(runParentDir: string): Promise<void> { | ||
| 186 | await rm(runParentDir, { recursive: true, force: true }); | ||
| 187 | } | ||
| 188 | |||
| 184 | async function writeServerLogs( | 189 | async function writeServerLogs( |
| 185 | artifactDir: string, | 190 | artifactDir: string, |
| 186 | stdout: string, | 191 | stdout: string, |
| @@ -295,151 +300,174 @@ export async function startPerfAppHarness( · 108 unmodified lines | |||
| 295 | serverProcess.stderr?.on("data", (chunk) => { | 300 | serverProcess.stderr?.on("data", (chunk) => { |
| 296 | stderrBuffer += chunk.toString(); | 301 | stderrBuffer += chunk.toString(); |
| 297 | }); | 302 | }); |
| 303 | let browser: Browser | null = null; | ||
| 304 | let context: BrowserContext | null = null; | ||
| 305 | let page: Page | null = null; | ||
| 298 | 306 | ||
| 299 | await waitForServerReady(url, serverProcess); | 307 | try { |
| 300 | 308 | await waitForServerReady(url, serverProcess); | |
| 301 | const browser = await chromium.launch({ | ||
| 302 | headless: process.env[PERF_HEADFUL_ENV] !== "1", | ||
| 303 | }); | ||
| 304 | const context = await browser.newContext({ | ||
| 305 | viewport: { width: 1440, height: 960 }, | ||
| 306 | }); | ||
| 307 | await context.addInitScript(installBrowserPerfCollector, "[data-timeline-row-kind]"); | ||
| 308 | const page = await context.newPage(); | ||
| 309 | await page.goto(url, { waitUntil: "domcontentloaded" }); | ||
| 310 | |||
| 311 | const firstProjectTitle = | ||
| 312 | seededState.projectTitle ?? getPerfSeedScenario(options.seedScenarioId).project.title; | ||
| 313 | if (!firstProjectTitle) { | ||
| 314 | throw new Error(`Seed scenario '${options.seedScenarioId}' produced no projects.`); | ||
| 315 | } | ||
| 316 | await page.getByText(firstProjectTitle, { exact: true }).first().waitFor({ timeout: 45_000 }); | ||
| 317 | |||
| 318 | const sampler = options.serverSampler ?? new NoopServerSampler(); | ||
| 319 | if (serverProcess.pid) { | ||
| 320 | await sampler.start({ pid: serverProcess.pid }); | ||
| 321 | } | ||
| 322 | const runStartedAt = new Date().toISOString(); | ||
| 323 | 309 | ||
| 324 | let finishPromise: Promise<{ | 310 | browser = await chromium.launch({ |
| 325 | readonly artifactPath: string; | 311 | headless: process.env[PERF_HEADFUL_ENV] !== "1", |
| 326 | readonly artifact: PerfRunArtifact; | 312 | }); |
| 327 | readonly browserMetrics: BrowserPerfMetrics; | 313 | context = await browser.newContext({ |
| 328 | readonly serverMetrics: ReadonlyArray<PerfServerMetricSample> | null; | 314 | viewport: { width: 1440, height: 960 }, |
| 329 | }> | null = null; | 315 | }); |
| 316 | await context.addInitScript(installBrowserPerfCollector, "[data-timeline-row-kind]"); | ||
| 317 | page = await context.newPage(); | ||
| 318 | await page.goto(url, { waitUntil: "domcontentloaded" }); | ||
| 319 | |||
| 320 | const firstProjectTitle = | ||
| 321 | seededState.projectTitle ?? getPerfSeedScenario(options.seedScenarioId).project.title; | ||
| 322 | if (!firstProjectTitle) { | ||
| 323 | throw new Error(`Seed scenario '${options.seedScenarioId}' produced no projects.`); | ||
| 324 | } | ||
| 325 | await page.getByText(firstProjectTitle, { exact: true }).first().waitFor({ timeout: 45_000 }); | ||
| 330 | 326 | ||
| 331 | const teardown = async () => { | 327 | const sampler = options.serverSampler ?? new NoopServerSampler(); |
| 332 | await Promise.allSettled([context.close(), browser.close()]); | 328 | if (serverProcess.pid) { |
| 333 | await stopChildProcess(serverProcess); | 329 | await sampler.start({ pid: serverProcess.pid }); |
| 334 | }; | 330 | } |
| 331 | const runStartedAt = new Date().toISOString(); | ||
| 332 | const readyPage = page; | ||
| 333 | if (!readyPage) { | ||
| 334 | throw new Error("Perf app harness did not initialize a browser page."); | ||
| 335 | } | ||
| 335 | 336 | ||
| 336 | return { | 337 | let finishPromise: |
| 337 | seededState, | 338 | | Promise<{ |
| 338 | page, | 339 | readonly artifactPath: string; |
| 339 | url, | 340 | readonly artifact: PerfRunArtifact; |
| 340 | artifactDir, | 341 | readonly browserMetrics: BrowserPerfMetrics; |
| 341 | startAction: (name) => | 342 | readonly serverMetrics: ReadonlyArray<PerfServerMetricSample> | null; |
| 342 | invokeBrowserCollector( | 343 | }> |
| 343 | page, | 344 | | undefined; |
| 344 | (collectorName, actionName) => { | 345 | |
| 345 | (window as Window & Record<string, any>)[collectorName]?.startAction( | 346 | const teardown = async () => { |
| 346 | actionName as string, | 347 | await Promise.allSettled([ |
| 347 | ); | 348 | context ? context.close() : Promise.resolve(), |
| 348 | }, | 349 | browser ? browser.close() : Promise.resolve(), |
| 349 | name, | 350 | ]); |
| 350 | ), | 351 | await stopChildProcess(serverProcess); |
| 351 | endAction: (name) => | 352 | await cleanupPerfRunDir(seededState.runParentDir); |
| 352 | invokeBrowserCollector( | 353 | }; |
| 353 | page, | 354 | |
| 354 | (collectorName, actionName) => { | 355 | return { |
| 355 | return ( | 356 | seededState, |
| 356 | (window as Window & Record<string, any>)[collectorName]?.endAction( | 357 | page: readyPage, |
| 358 | url, | ||
| 359 | artifactDir, | ||
| 360 | startAction: (name) => | ||
| 361 | invokeBrowserCollector( | ||
| 362 | readyPage, | ||
| 363 | (collectorName, actionName) => { | ||
| 364 | (window as Window & Record<string, any>)[collectorName]?.startAction( | ||
| 357 | actionName as string, | 365 | actionName as string, |
| 358 | ) ?? null | 366 | ); |
| 359 | ); | 367 | }, |
| 360 | }, | 368 | name, |
| 361 | name, | 369 | ), |
| 362 | ), | 370 | endAction: (name) => |
| 363 | resetBrowserMetrics: () => | 371 | invokeBrowserCollector( |
| 364 | invokeBrowserCollector(page, (collectorName) => { | 372 | readyPage, |
| 365 | (window as Window & Record<string, any>)[collectorName]?.reset(); | 373 | (collectorName, actionName) => { |
| 366 | }), | 374 | return ( |
| 367 | sampleMountedRows: (label) => | 375 | (window as Window & Record<string, any>)[collectorName]?.endAction( |
| 368 | invokeBrowserCollector( | 376 | actionName as string, |
| 369 | page, | 377 | ) ?? null |
| 370 | (collectorName, sampleLabel) => { | 378 | ); |
| 371 | return ( | 379 | }, |
| 372 | (window as Window & Record<string, any>)[collectorName]?.sampleMountedRows( | 380 | name, |
| 373 | sampleLabel as string, | 381 | ), |
| 374 | ) ?? 0 | 382 | resetBrowserMetrics: () => |
| 375 | ); | 383 | invokeBrowserCollector(readyPage, (collectorName) => { |
| 376 | }, | 384 | (window as Window & Record<string, any>)[collectorName]?.reset(); |
| 377 | label, | 385 | }), |
| 378 | ), | 386 | sampleMountedRows: (label) => |
| 379 | snapshotBrowserMetrics: () => | 387 | invokeBrowserCollector( |
| 380 | invokeBrowserCollector(page, (collectorName) => { | 388 | readyPage, |
| 381 | return ((window as Window & Record<string, any>)[collectorName]?.snapshot() ?? { | 389 | (collectorName, sampleLabel) => { |
| 382 | actions: [], | 390 | return ( |
| 383 | longTasks: [], | 391 | (window as Window & Record<string, any>)[collectorName]?.sampleMountedRows( |
| 384 | rafGapsMs: [], | 392 | sampleLabel as string, |
| 385 | mountedRowSamples: [], | 393 | ) ?? 0 |
| 386 | }) as BrowserPerfMetrics; | 394 | ); |
| 387 | }), | 395 | }, |
| 388 | finishRun: async (finishOptions) => { | 396 | label, |
| 389 | if (finishPromise) { | 397 | ), |
| 390 | return await finishPromise; | 398 | snapshotBrowserMetrics: () => |
| 391 | } | 399 | invokeBrowserCollector(readyPage, (collectorName) => { |
| 400 | return ((window as Window & Record<string, any>)[collectorName]?.snapshot() ?? { | ||
| 401 | actions: [], | ||
| 402 | longTasks: [], | ||
| 403 | rafGapsMs: [], | ||
| 404 | mountedRowSamples: [], | ||
| 405 | }) as BrowserPerfMetrics; | ||
| 406 | }), | ||
| 407 | finishRun: async (finishOptions) => { | ||
| 408 | if (finishPromise) { | ||
| 409 | return await finishPromise; | ||
| 410 | } | ||
| 392 | 411 | ||
| 393 | finishPromise = (async () => { | 412 | finishPromise = (async () => { |
| 394 | const completedAt = new Date().toISOString(); | 413 | const completedAt = new Date().toISOString(); |
| 395 | const browserMetrics = await (async () => { | 414 | const browserMetrics: BrowserPerfMetrics = await (async () => { |
| 396 | try { | 415 | try { |
| 397 | return await invokeBrowserCollector(page, (collectorName) => { | 416 | return await invokeBrowserCollector(readyPage, (collectorName) => { |
| 398 | return ((window as Window & Record<string, any>)[collectorName]?.snapshot() ?? { | 417 | return ((window as Window & Record<string, any>)[collectorName]?.snapshot() ?? { |
| 418 | actions: [], | ||
| 419 | longTasks: [], | ||
| 420 | rafGapsMs: [], | ||
| 421 | mountedRowSamples: [], | ||
| 422 | }) as BrowserPerfMetrics; | ||
| 423 | }); | ||
| 424 | } catch { | ||
| 425 | return { | ||
| 399 | actions: [], | 426 | actions: [], |
| 400 | longTasks: [], | 427 | longTasks: [], |
| 401 | rafGapsMs: [], | 428 | rafGapsMs: [], |
| 402 | mountedRowSamples: [], | 429 | mountedRowSamples: [], |
| 403 | }) as BrowserPerfMetrics; | 430 | } satisfies BrowserPerfMetrics; |
| 404 | }); | 431 | } |
| 405 | } catch { | 432 | })(); |
| 406 | return { | 433 | const serverMetrics = await sampler.stop(); |
| 407 | actions: [], | 434 | await teardown(); |
| 408 | longTasks: [], | 435 | |
| 409 | rafGapsMs: [], | 436 | const basename = |
| 410 | mountedRowSamples: [], | 437 | finishOptions.artifactBasename ?? `${finishOptions.suite}-${finishOptions.scenarioId}`; |
| 411 | } satisfies BrowserPerfMetrics; | 438 | await writeServerLogs(artifactDir, stdoutBuffer, stderrBuffer, basename); |
| 412 | } | 439 | const artifact: PerfRunArtifact = { |
| 440 | suite: finishOptions.suite, | ||
| 441 | scenarioId: finishOptions.scenarioId, | ||
| 442 | startedAt: runStartedAt, | ||
| 443 | completedAt, | ||
| 444 | thresholds: finishOptions.thresholds, | ||
| 445 | summary: summarizeBrowserPerfMetrics(browserMetrics, finishOptions.actionSummary), | ||
| 446 | browserMetrics, | ||
| 447 | serverMetrics, | ||
| 448 | ...(finishOptions.metadata ? { metadata: finishOptions.metadata } : {}), | ||
| 449 | }; | ||
| 450 | const artifactPath = join(artifactDir, `${basename}.json`); | ||
| 451 | await writePerfArtifact(artifactPath, artifact); | ||
| 452 | |||
| 453 | return { | ||
| 454 | artifactPath, | ||
| 455 | artifact, | ||
| 456 | browserMetrics, | ||
| 457 | serverMetrics, | ||
| 458 | }; | ||
| 413 | })(); | 459 | })(); |
| 414 | const serverMetrics = await sampler.stop(); | 460 | |
| 415 | await teardown(); | 461 | return await finishPromise; |
| 416 | 462 | }, | |
| 417 | const basename = | 463 | }; |
| 418 | finishOptions.artifactBasename ?? `${finishOptions.suite}-${finishOptions.scenarioId}`; | 464 | } catch (error) { |
| 419 | await writeServerLogs(artifactDir, stdoutBuffer, stderrBuffer, basename); | 465 | await Promise.allSettled([ |
| 420 | const artifact: PerfRunArtifact = { | 466 | context ? context.close() : Promise.resolve(), |
| 421 | suite: finishOptions.suite, | 467 | browser ? browser.close() : Promise.resolve(), |
| 422 | scenarioId: finishOptions.scenarioId, | 468 | ]); |
| 423 | startedAt: runStartedAt, | 469 | await stopChildProcess(serverProcess); |
| 424 | completedAt, | 470 | await cleanupPerfRunDir(seededState.runParentDir); |
| 425 | thresholds: finishOptions.thresholds, | 471 | throw error; |
| 426 | summary: summarizeBrowserPerfMetrics(browserMetrics, finishOptions.actionSummary), | 472 | } |
| 427 | browserMetrics, | ||
| 428 | serverMetrics, | ||
| 429 | ...(finishOptions.metadata ? { metadata: finishOptions.metadata } : {}), | ||
| 430 | }; | ||
| 431 | const artifactPath = join(artifactDir, `${basename}.json`); | ||
| 432 | await writePerfArtifact(artifactPath, artifact); | ||
| 433 | |||
| 434 | return { | ||
| 435 | artifactPath, | ||
| 436 | artifact, | ||
| 437 | browserMetrics, | ||
| 438 | serverMetrics, | ||
| 439 | }; | ||
| 440 | })(); | ||
| 441 | |||
| 442 | return await finishPromise; | ||
| 443 | }, | ||
| 444 | }; | ||
| 445 | } | 473 | } |
| @@ -0,0 +1,52 @@ | |||
| 1 | import { afterEach, describe, expect, it, vi } from "vitest"; | ||
| 2 | |||
| 3 | import { percentile } from "../../../../test/perf/support/artifact"; | ||
| 4 | import { installBrowserPerfCollector } from "../../../../test/perf/support/browserMetrics"; | ||
| 5 | |||
| 6 | describe("percentile", () => { | ||
| 7 | it("returns the minimum value for the zero percentile", () => { | ||
| 8 | expect(percentile([9, 3, 6], 0)).toBe(3); | ||
| 9 | }); | ||
| 10 | }); | ||
| 11 | |||
| 12 | describe("installBrowserPerfCollector", () => { | ||
| 13 | afterEach(() => { | ||
| 14 | vi.unstubAllGlobals(); | ||
| 15 | }); | ||
| 16 | |||
| 17 | it("cancels the previous animation frame loop before reset starts a new one", () => { | ||
| 18 | let nextHandle = 1; | ||
| 19 | const callbacks = new Map<number, FrameRequestCallback>(); | ||
| 20 | const requestAnimationFrame = vi.fn((callback: FrameRequestCallback) => { | ||
| 21 | const handle = nextHandle++; | ||
| 22 | callbacks.set(handle, callback); | ||
| 23 | return handle; | ||
| 24 | }); | ||
| 25 | const cancelAnimationFrame = vi.fn((handle: number) => { | ||
| 26 | callbacks.delete(handle); | ||
| 27 | }); | ||
| 28 | |||
| 29 | vi.stubGlobal("window", { | ||
| 30 | requestAnimationFrame, | ||
| 31 | cancelAnimationFrame, | ||
| 32 | } as unknown as Window & typeof globalThis); | ||
| 33 | vi.stubGlobal("document", { | ||
| 34 | querySelectorAll: vi.fn(() => []), | ||
| 35 | } as unknown as Document); | ||
| 36 | vi.stubGlobal("PerformanceObserver", undefined); | ||
| 37 | |||
| 38 | installBrowserPerfCollector(); | ||
| 39 | |||
| 40 | const collector = window.__t3PerfCollector; | ||
| 41 | expect(collector).toBeDefined(); | ||
| 42 | expect(requestAnimationFrame).toHaveBeenCalledTimes(1); | ||
| 43 | |||
| 44 | collector?.reset(); | ||
| 45 | expect(cancelAnimationFrame).toHaveBeenCalledWith(1); | ||
| 46 | expect(requestAnimationFrame).toHaveBeenCalledTimes(2); | ||
| 47 | |||
| 48 | collector?.reset(); | ||
| 49 | expect(cancelAnimationFrame).toHaveBeenLastCalledWith(2); | ||
| 50 | expect(requestAnimationFrame).toHaveBeenCalledTimes(3); | ||
| 51 | }); | ||
| 52 | }); | ||
| @@ -1,141 +0,0 @@ | |||
| 1 | { | ||
| 2 | "suite": "virtualization", | ||
| 3 | "scenarioId": "large_threads", | ||
| 4 | "startedAt": "2026-04-01T20:57:00.745Z", | ||
| 5 | "completedAt": "2026-04-01T20:57:01.524Z", | ||
| 6 | "thresholds": { | ||
| 7 | "maxMountedTimelineRows": 140, | ||
| 8 | "threadSwitchP50Ms": 250, | ||
| 9 | "threadSwitchP95Ms": 500, | ||
| 10 | "maxLongTaskMs": 120, | ||
| 11 | "maxRafGapMs": 120, | ||
| 12 | "burstCompletionMs": 5000, | ||
| 13 | "longTasksOver50MsMax": 2 | ||
| 14 | }, | ||
| 15 | "summary": { | ||
| 16 | "maxMountedTimelineRows": 23, | ||
| 17 | "threadSwitchP50Ms": 49.5, | ||
| 18 | "threadSwitchP95Ms": 117, | ||
| 19 | "maxLongTaskMs": 0, | ||
| 20 | "longTasksOver50Ms": 0, | ||
| 21 | "maxRafGapMs": 25, | ||
| 22 | "burstCompletionMs": null | ||
| 23 | }, | ||
| 24 | "browserMetrics": { | ||
| 25 | "actions": [ | ||
| 26 | { | ||
| 27 | "name": "thread-switch-warmup-a", | ||
| 28 | "durationMs": 117, | ||
| 29 | "startedAtMs": 591, | ||
| 30 | "endedAtMs": 708 | ||
| 31 | }, | ||
| 32 | { | ||
| 33 | "name": "thread-switch-1", | ||
| 34 | "durationMs": 46.5, | ||
| 35 | "startedAtMs": 709.7999999523163, | ||
| 36 | "endedAtMs": 756.2999999523163 | ||
| 37 | }, | ||
| 38 | { | ||
| 39 | "name": "thread-switch-2", | ||
| 40 | "durationMs": 49.5, | ||
| 41 | "startedAtMs": 787.6000000238419, | ||
| 42 | "endedAtMs": 837.1000000238419 | ||
| 43 | }, | ||
| 44 | { | ||
| 45 | "name": "thread-switch-3", | ||
| 46 | "durationMs": 70.69999992847443, | ||
| 47 | "startedAtMs": 840.7000000476837, | ||
| 48 | "endedAtMs": 911.3999999761581 | ||
| 49 | }, | ||
| 50 | { | ||
| 51 | "name": "thread-switch-4", | ||
| 52 | "durationMs": 45.89999997615814, | ||
| 53 | "startedAtMs": 916, | ||
| 54 | "endedAtMs": 961.8999999761581 | ||
| 55 | }, | ||
| 56 | { | ||
| 57 | "name": "thread-switch-5", | ||
| 58 | "durationMs": 46.699999928474426, | ||
| 59 | "startedAtMs": 964.6000000238419, | ||
| 60 | "endedAtMs": 1011.2999999523163 | ||
| 61 | }, | ||
| 62 | { | ||
| 63 | "name": "thread-switch-6", | ||
| 64 | "durationMs": 49.89999997615814, | ||
| 65 | "startedAtMs": 1047.8999999761581, | ||
| 66 | "endedAtMs": 1097.7999999523163 | ||
| 67 | } | ||
| 68 | ], | ||
| 69 | "longTasks": [], | ||
| 70 | "rafGapsMs": [ | ||
| 71 | 0, 8, 0, 17, 0, 7.300000000000068, 0, 8.799999999999955, 0, 8.200000000000045, 0, | ||
| 72 | 9.099999999999909, 0, 7.7000000000000455, 0, 8.399999999999977, 0, 8.800000000000068, 0, 16, | ||
| 73 | 0, 8.399999999999977, 0, 9, 0, 8.299999999999955, 0, 7.600000000000023, 0, 17.100000000000023, | ||
| 74 | 0, 8.600000000000023, 0, 8.299999999999955, 0, 16.200000000000045, 0, 7.899999999999977, 0, | ||
| 75 | 9.299999999999955, 0, 7.399999999999977, 0, 9.200000000000045, 0, 16.700000000000045, 0, 8, 0, | ||
| 76 | 8.299999999999955, 0, 7.899999999999977, 0, 8.700000000000045, 0, 17.100000000000023, 0, | ||
| 77 | 8.199999999999932, 0, 16, 0, 16.600000000000023, 0, 8.399999999999977, 0, 8.899999999999977, | ||
| 78 | 0, 7.800000000000068, 0, 17.399999999999977, 0, 8.299999999999955, 0, 8.300000000000068, 0, | ||
| 79 | 7.699999999999932, 0, 16.700000000000045, 0, 8.100000000000023, 0, 17.399999999999977, 0, | ||
| 80 | 7.7999999999999545, 0, 8.600000000000136, 0, 8.199999999999818, 0, 8.900000000000091, 0, | ||
| 81 | 8.099999999999909, 0, 16.200000000000045, 0, 9.100000000000136, 0, 7.399999999999864, 0, 8.5, | ||
| 82 | 0, 9, 0, 25, 0, 7.600000000000136, 0, 9.199999999999818, 0 | ||
| 83 | ], | ||
| 84 | "mountedRowSamples": [ | ||
| 85 | { | ||
| 86 | "label": "heavy-a-open", | ||
| 87 | "count": 17, | ||
| 88 | "capturedAtMs": 708.5 | ||
| 89 | }, | ||
| 90 | { | ||
| 91 | "label": "thread-switch-1-rows", | ||
| 92 | "count": 17, | ||
| 93 | "capturedAtMs": 782 | ||
| 94 | }, | ||
| 95 | { | ||
| 96 | "label": "thread-switch-2-rows", | ||
| 97 | "count": 17, | ||
| 98 | "capturedAtMs": 837.7000000476837 | ||
| 99 | }, | ||
| 100 | { | ||
| 101 | "label": "thread-switch-3-rows", | ||
| 102 | "count": 17, | ||
| 103 | "capturedAtMs": 914.2000000476837 | ||
| 104 | }, | ||
| 105 | { | ||
| 106 | "label": "thread-switch-4-rows", | ||
| 107 | "count": 17, | ||
| 108 | "capturedAtMs": 962.5 | ||
| 109 | }, | ||
| 110 | { | ||
| 111 | "label": "thread-switch-5-rows", | ||
| 112 | "count": 17, | ||
| 113 | "capturedAtMs": 1034.1000000238419 | ||
| 114 | }, | ||
| 115 | { | ||
| 116 | "label": "thread-switch-6-rows", | ||
| 117 | "count": 17, | ||
| 118 | "capturedAtMs": 1098.3999999761581 | ||
| 119 | }, | ||
| 120 | { | ||
| 121 | "label": "scroll-start", | ||
| 122 | "count": 17, | ||
| 123 | "capturedAtMs": 1113.3999999761581 | ||
| 124 | }, | ||
| 125 | { | ||
| 126 | "label": "scroll-top", | ||
| 127 | "count": 23, | ||
| 128 | "capturedAtMs": 1148.3999999761581 | ||
| 129 | }, | ||
| 130 | { | ||
| 131 | "label": "scroll-bottom", | ||
| 132 | "count": 17, | ||
| 133 | "capturedAtMs": 1164.2000000476837 | ||
| 134 | } | ||
| 135 | ] | ||
| 136 | }, | ||
| 137 | "serverMetrics": null, | ||
| 138 | "metadata": { | ||
| 139 | "heavyThreadMessageCount": 2000 | ||
| 140 | } | ||
| 141 | } | ||
| @@ -1,139 +0,0 @@ | |||
| 1 | { | ||
| 2 | "suite": "websocket-application", | ||
| 3 | "scenarioId": "dense_assistant_stream", | ||
| 4 | "startedAt": "2026-04-01T20:57:03.910Z", | ||
| 5 | "completedAt": "2026-04-01T20:57:07.151Z", | ||
| 6 | "thresholds": { | ||
| 7 | "maxMountedTimelineRows": 140, | ||
| 8 | "threadSwitchP50Ms": 250, | ||
| 9 | "threadSwitchP95Ms": 500, | ||
| 10 | "maxLongTaskMs": 120, | ||
| 11 | "maxRafGapMs": 120, | ||
| 12 | "burstCompletionMs": 5000, | ||
| 13 | "longTasksOver50MsMax": 2 | ||
| 14 | }, | ||
| 15 | "summary": { | ||
| 16 | "maxMountedTimelineRows": 0, | ||
| 17 | "threadSwitchP50Ms": 51.199999928474426, | ||
| 18 | "threadSwitchP95Ms": 52.39999997615814, | ||
| 19 | "maxLongTaskMs": 0, | ||
| 20 | "longTasksOver50Ms": 0, | ||
| 21 | "maxRafGapMs": 16.200000000000045, | ||
| 22 | "burstCompletionMs": 3135 | ||
| 23 | }, | ||
| 24 | "browserMetrics": { | ||
| 25 | "actions": [ | ||
| 26 | { | ||
| 27 | "name": "thread-switch-burst-nav", | ||
| 28 | "durationMs": 51.199999928474426, | ||
| 29 | "startedAtMs": 1348.3999999761581, | ||
| 30 | "endedAtMs": 1399.5999999046326 | ||
| 31 | }, | ||
| 32 | { | ||
| 33 | "name": "thread-switch-burst-return", | ||
| 34 | "durationMs": 52.39999997615814, | ||
| 35 | "startedAtMs": 1401.1999999284744, | ||
| 36 | "endedAtMs": 1453.5999999046326 | ||
| 37 | }, | ||
| 38 | { | ||
| 39 | "name": "burst-completion", | ||
| 40 | "durationMs": 3135, | ||
| 41 | "startedAtMs": 332.2999999523163, | ||
| 42 | "endedAtMs": 3467.2999999523163 | ||
| 43 | } | ||
| 44 | ], | ||
| 45 | "longTasks": [], | ||
| 46 | "rafGapsMs": [ | ||
| 47 | 0, 7.899999999999977, 0, 9, 0, 7.300000000000011, 0, 8.899999999999977, 0, 7.800000000000011, | ||
| 48 | 0, 9.300000000000011, 0, 8.100000000000023, 0, 7.699999999999989, 0, 9.199999999999989, 0, | ||
| 49 | 7.800000000000011, 0, 7.899999999999977, 0, 9.300000000000011, 0, 7.300000000000011, 0, | ||
| 50 | 9.099999999999966, 0, 7.7000000000000455, 0, 8.199999999999989, 0, 8.399999999999977, 0, | ||
| 51 | 8.300000000000011, 0, 8.399999999999977, 0, 8.5, 0, 9.100000000000023, 0, 7.7000000000000455, | ||
| 52 | 0, 8.199999999999932, 0, 8.600000000000023, 0, 8.799999999999955, 0, 7.7000000000000455, 0, | ||
| 53 | 8.200000000000045, 0, 8.399999999999977, 0, 9.100000000000023, 0, 7.5, 0, 8.199999999999932, | ||
| 54 | 0, 9.300000000000068, 0, 7.399999999999977, 0, 8.600000000000023, 0, 8.399999999999977, 0, | ||
| 55 | 8.899999999999977, 0, 8.299999999999955, 0, 7.800000000000068, 0, 8, 0, 8.399999999999977, 0, | ||
| 56 | 8.799999999999955, 0, 7.7000000000000455, 0, 9.299999999999955, 0, 7.400000000000091, 0, | ||
| 57 | 9.299999999999955, 0, 8.299999999999955, 0, 7.7000000000000455, 0, 8.100000000000023, 0, | ||
| 58 | 8.899999999999977, 0, 8.700000000000045, 0, 7.2999999999999545, 0, 9.399999999999977, 0, | ||
| 59 | 8.200000000000045, 0, 8.100000000000023, 0, 7.699999999999932, 0, 9.300000000000068, 0, | ||
| 60 | 8.199999999999932, 0, 8.300000000000068, 0, 7.699999999999932, 0, 8.700000000000045, 0, | ||
| 61 | 8.699999999999932, 0, 7.600000000000023, 0, 8.100000000000023, 0, 8.399999999999977, 0, | ||
| 62 | 8.700000000000045, 0, 8, 0, 8.699999999999932, 0, 8.200000000000045, 0, 8.799999999999955, 0, | ||
| 63 | 8.5, 0, 7.800000000000068, 0, 8.5, 0, 8.600000000000023, 0, 8.399999999999977, 0, | ||
| 64 | 7.899999999999977, 0, 8.100000000000023, 0, 8.100000000000023, 0, 8.399999999999977, 0, | ||
| 65 | 9.299999999999955, 0, 8.100000000000023, 0, 7.5, 0, 9.200000000000045, 0, 8.5, 0, | ||
| 66 | 7.899999999999864, 0, 8.800000000000182, 0, 8.199999999999818, 0, 7.800000000000182, 0, 8.5, | ||
| 67 | 0, 8.199999999999818, 0, 8.900000000000091, 0, 8.299999999999955, 0, 7.900000000000091, 0, | ||
| 68 | 7.899999999999864, 0, 9.300000000000182, 0, 7.599999999999909, 0, 8.400000000000091, 0, 8, 0, | ||
| 69 | 8.399999999999864, 0, 9.200000000000045, 0, 7.400000000000091, 0, 9.299999999999955, 0, | ||
| 70 | 8.200000000000045, 0, 7.5, 0, 9.299999999999955, 0, 8.099999999999909, 0, 7.600000000000136, | ||
| 71 | 0, 9.299999999999955, 0, 8.200000000000045, 0, 8.399999999999864, 0, 7.5, 0, | ||
| 72 | 8.400000000000091, 0, 8.599999999999909, 0, 7.7999999999999545, 0, 8.5, 0, 9.300000000000182, | ||
| 73 | 0, 7.2999999999999545, 0, 9.399999999999864, 0, 7.5, 0, 8.100000000000136, 0, 8.5, 0, | ||
| 74 | 8.099999999999909, 0, 9.100000000000136, 0, 8.599999999999909, 0, 7.5, 0, 9.099999999999909, | ||
| 75 | 0, 7.600000000000136, 0, 8.399999999999864, 0, 8.100000000000136, 0, 8.399999999999864, 0, | ||
| 76 | 8.200000000000045, 0, 9.299999999999955, 0, 8, 0, 16.200000000000045, 0, 9.200000000000045, 0, | ||
| 77 | 8.299999999999955, 0, 8.299999999999955, 0, 8.400000000000091, 0, 7.900000000000091, 0, | ||
| 78 | 8.599999999999909, 0, 8.5, 0, 8.099999999999909, 0, 7.7000000000000455, 0, 8.700000000000045, | ||
| 79 | 0, 8.5, 0, 8.5, 0, 7.599999999999909, 0, 8.900000000000091, 0, 8.599999999999909, 0, | ||
| 80 | 8.400000000000091, 0, 8.299999999999955, 0, 7.7999999999999545, 0, 8.600000000000136, 0, | ||
| 81 | 8.200000000000045, 0, 8.799999999999955, 0, 7.599999999999909, 0, 8.100000000000136, 0, | ||
| 82 | 8.599999999999909, 0, 8.299999999999955, 0, 8.100000000000136, 0, 8.599999999999909, 0, | ||
| 83 | 9.099999999999909, 0, 8.200000000000045, 0, 7.400000000000091, 0, 8.899999999999864, 0, 8.5, | ||
| 84 | 0, 8.5, 0, 7.900000000000091, 0, 8.799999999999955, 0, 7.400000000000091, 0, | ||
| 85 | 9.299999999999955, 0, 7.400000000000091, 0, 9.299999999999955, 0, 8.299999999999955, 0, | ||
| 86 | 8.299999999999955, 0, 7.400000000000091, 0, 8.899999999999864, 0, 8.800000000000182, 0, | ||
| 87 | 7.599999999999909, 0, 8.099999999999909, 0, 8.200000000000045, 0, 9.400000000000091, 0, | ||
| 88 | 7.7000000000000455, 0, 8.899999999999864, 0, 7.400000000000091, 0, 8.399999999999864, 0, | ||
| 89 | 8.900000000000091, 0, 8, 0, 8.5, 0, 8.799999999999955, 0, 7.7000000000000455, 0, | ||
| 90 | 8.200000000000045, 0, 8.700000000000045, 0, 7.899999999999864, 0, 8.799999999999955, 0, | ||
| 91 | 8.400000000000091, 0, 8.099999999999909, 0, 8.200000000000045, 0, 8.200000000000045, 0, | ||
| 92 | 8.200000000000045, 0, 8.799999999999955, 0, 7.7999999999999545, 0, 8.600000000000136, 0, | ||
| 93 | 8.199999999999818, 0, 9.200000000000045, 0, 7.500000000000227, 0, 8.199999999999818, 0, | ||
| 94 | 9.099999999999909, 0, 7.700000000000273, 0, 8.899999999999636, 0, 7.600000000000364, 0, | ||
| 95 | 8.399999999999636, 0, 9.300000000000182, 0, 7.400000000000091, 0, 8.299999999999727, 0, | ||
| 96 | 9.300000000000182, 0, 8.300000000000182, 0, 7.799999999999727, 0, 8.900000000000091, 0, | ||
| 97 | 7.699999999999818, 0, 8.800000000000182, 0, 8.300000000000182, 0, 7.599999999999909, 0, | ||
| 98 | 8.799999999999727, 0, 7.900000000000091, 0, 9.099999999999909, 0, 8.200000000000273, 0, 8, 0, | ||
| 99 | 8.799999999999727, 0, 7.600000000000364, 0, 9.299999999999727, 0, 8.200000000000273, 0, | ||
| 100 | 7.699999999999818, 0, 8, 0, 8.599999999999909, 0, 9.099999999999909, 0, 7.300000000000182, 0, | ||
| 101 | 8.900000000000091, 0, 8.799999999999727, 0, 8.100000000000364, 0, 7.899999999999636, 0, 8, 0, | ||
| 102 | 8.400000000000091, 0, 9.099999999999909, 0, 7.5, 0, 8.800000000000182, 0, 7.900000000000091, | ||
| 103 | 0, 9.299999999999727, 0, 7.5, 0, 9.100000000000364, 0, 8.099999999999909, 0, | ||
| 104 | 7.799999999999727, 0, 8.700000000000273, 0, 7.799999999999727, 0, 9.300000000000182, 0, | ||
| 105 | 8.300000000000182, 0, 8, 0, 8.699999999999818, 0, 7.599999999999909, 0, 9.099999999999909, 0, | ||
| 106 | 8.200000000000273, 0, 7.599999999999909, 0, 9.199999999999818, 0, 8.200000000000273, 0, | ||
| 107 | 8.400000000000091, 0, 8.299999999999727, 0, 8.200000000000273, 0, 8.5, 0, 8.299999999999727, | ||
| 108 | 0, 8.400000000000091, 0, 8.300000000000182, 0, 7.5, 0, 8.5, 0, 8.099999999999909, 0, | ||
| 109 | 9.299999999999727, 0, 7.400000000000091, 0, 9, 0, 8, 0, 8.900000000000091, 0, | ||
| 110 | 8.300000000000182, 0, 8.299999999999727, 0, 7.5, 0, 8.599999999999909, 0, 8, 0, | ||
| 111 | 9.300000000000182, 0, 7.400000000000091, 0, 9.299999999999727, 0, 8.300000000000182, 0, | ||
| 112 | 7.599999999999909, 0, 8.800000000000182, 0, 8.099999999999909, 0, 8, 0, 8.800000000000182, 0, | ||
| 113 | 8.5, 0, 8.5, 0, 8.099999999999909, 0, 8.5, 0, 8, 0, 8.199999999999818, 0, 7.900000000000091, | ||
| 114 | 0, 8.599999999999909, 0, 8.599999999999909, 0, 8, 0, 9.100000000000364, 0, 8.199999999999818, | ||
| 115 | 0, 8.5, 0, 8, 0, 7.800000000000182, 0, 8.899999999999636, 0, 7.800000000000182, 0, | ||
| 116 | 9.099999999999909, 0, 8.300000000000182, 0, 7.799999999999727, 0, 9, 0, 8.300000000000182, 0, | ||
| 117 | 7.5, 0, 9.199999999999818, 0, 7.400000000000091, 0, 9.300000000000182, 0, 8, 0, | ||
| 118 | 7.799999999999727, 0, 9.200000000000273, 0, 8, 0, 7.599999999999909, 0, 8.400000000000091, 0, | ||
| 119 | 9.299999999999727, 0, 7.300000000000182, 0, 8.5, 0, 9.199999999999818, 0, 8.200000000000273, | ||
| 120 | 0, 8.400000000000091, 0, 7.5, 0, 9.199999999999818, 0, 8.300000000000182, 0, | ||
| 121 | 7.399999999999636, 0, 8.400000000000091, 0, 9, 0, 8.599999999999909, 0, 8.200000000000273, 0, | ||
| 122 | 7.5, 0, 9.299999999999727, 0, 8.200000000000273, 0, 8.400000000000091, 0, 7.399999999999636, | ||
| 123 | 0, 8.300000000000182, 0, 9.400000000000091, 0, 8, 0, 8.5, 0, 8.099999999999909, 0, 8, 0, 8, 0, | ||
| 124 | 8.900000000000091, 0, 7.900000000000091, 0, 8.299999999999727, 0, 8.300000000000182, 0, 8.5, | ||
| 125 | 0, 8.699999999999818, 0, 8.099999999999909, 0, 9, 0, 8.300000000000182, 0, 7.400000000000091, | ||
| 126 | 0, 9.099999999999909, 0, 7.800000000000182, 0, 9.099999999999909, 0, 8.199999999999818, 0, | ||
| 127 | 8.400000000000091, 0, 8.300000000000182, 0, 7.899999999999636, 0, 8.700000000000273, 0, 8.5, | ||
| 128 | 0, 7.400000000000091, 0, 8.299999999999727, 0, 8.300000000000182, 0, 9.299999999999727, 0, | ||
| 129 | 8.300000000000182, 0, 8.199999999999818, 0 | ||
| 130 | ], | ||
| 131 | "mountedRowSamples": [] | ||
| 132 | }, | ||
| 133 | "serverMetrics": null, | ||
| 134 | "metadata": { | ||
| 135 | "burstSeedThreadId": "perf-thread-burst", | ||
| 136 | "navigationThreadId": "perf-thread-light-01", | ||
| 137 | "sentinelText": "PERF_STREAM_SENTINEL:dense_assistant_stream:completed" | ||
| 138 | } | ||
| 139 | } | ||
| @@ -1,6 +1,6 @@ | |||
| 1 | import { spawn, type ChildProcess } from "node:child_process"; | 1 | import { spawn, type ChildProcess } from "node:child_process"; |
| 2 | import { once } from "node:events"; | 2 | import { once } from "node:events"; |
| 3 | import { access } from "node:fs/promises"; | 3 | import { access, rm } from "node:fs/promises"; |
| 4 | import { createServer } from "node:net"; | 4 | import { createServer } from "node:net"; |
| 5 | import { resolve } from "node:path"; | 5 | import { resolve } from "node:path"; |
| 6 | import { fileURLToPath } from "node:url"; | 6 | import { fileURLToPath } from "node:url"; |
| @@ -27,6 +27,7 @@ interface PerfSeedThreadSummary { · 20 unmodified lines | |||
| 27 | 27 | ||
| 28 | interface PerfSeededState { | 28 | interface PerfSeededState { |
| 29 | readonly scenarioId: PerfSeedScenarioId; | 29 | readonly scenarioId: PerfSeedScenarioId; |
| 30 | readonly runParentDir: string; | ||
| 30 | readonly baseDir: string; | 31 | readonly baseDir: string; |
| 31 | readonly workspaceRoot: string; | 32 | readonly workspaceRoot: string; |
| 32 | readonly projectTitle: string | null; | 33 | readonly projectTitle: string | null; |
| @@ -252,6 +253,10 @@ async function stopChildProcess(process: ChildProcess): Promise<void> { · 219 unmodified lines | |||
| 252 | } | 253 | } |
| 253 | } | 254 | } |
| 254 | 255 | ||
| 256 | async function cleanupPerfRunDir(runParentDir: string): Promise<void> { | ||
| 257 | await rm(runParentDir, { recursive: true, force: true }); | ||
| 258 | } | ||
| 259 | |||
| 255 | function openUrl(url: string): void { | 260 | function openUrl(url: string): void { |
| 256 | const command: [string, ...string[]] = | 261 | const command: [string, ...string[]] = |
| 257 | process.platform === "darwin" | 262 | process.platform === "darwin" |
| @@ -339,6 +344,7 @@ async function main(): Promise<void> { · 81 unmodified lines | |||
| 339 | shuttingDown = true; | 344 | shuttingDown = true; |
| 340 | process.stdout.write(`\nReceived ${signal}. Stopping perf app...\n`); | 345 | process.stdout.write(`\nReceived ${signal}. Stopping perf app...\n`); |
| 341 | await stopChildProcess(serverProcess); | 346 | await stopChildProcess(serverProcess); |
| 347 | await cleanupPerfRunDir(seededState.runParentDir); | ||
| 342 | process.exit(0); | 348 | process.exit(0); |
| 343 | }; | 349 | }; |
| 344 | 350 | ||
| @@ -349,17 +355,24 @@ async function main(): Promise<void> { · 4 unmodified lines | |||
| 349 | void shutdown("SIGTERM"); | 355 | void shutdown("SIGTERM"); |
| 350 | }); | 356 | }); |
| 351 | 357 | ||
| 352 | const url = `http://${options.host}:${port.toString()}`; | 358 | try { |
| 353 | await waitForServerReady(url, serverProcess); | 359 | const url = `http://${options.host}:${port.toString()}`; |
| 354 | printSeedSummary(seededState, url, options.providerScenarioId); | 360 | await waitForServerReady(url, serverProcess); |
| 361 | printSeedSummary(seededState, url, options.providerScenarioId); | ||
| 355 | 362 | ||
| 356 | if (options.openBrowser) { | 363 | if (options.openBrowser) { |
| 357 | openUrl(url); | 364 | openUrl(url); |
| 358 | } | 365 | } |
| 359 | 366 | ||
| 360 | const [exitCode] = (await once(serverProcess, "exit")) as [number | null]; | 367 | const [exitCode] = (await once(serverProcess, "exit")) as [number | null]; |
| 361 | if (!shuttingDown) { | 368 | if (!shuttingDown) { |
| 362 | process.exit(exitCode ?? 0); | 369 | await cleanupPerfRunDir(seededState.runParentDir); |
| 370 | process.exit(exitCode ?? 0); | ||
| 371 | } | ||
| 372 | } catch (error) { | ||
| 373 | await stopChildProcess(serverProcess); | ||
| 374 | await cleanupPerfRunDir(seededState.runParentDir); | ||
| 375 | throw error; | ||
| 363 | } | 376 | } |
| 364 | } | 377 | } |
| 365 | 378 | ||
| @@ -68,7 +68,10 @@ export function percentile(values: ReadonlyArray<number>, target: number): numbe · 67 unmodified lines | |||
| 68 | } | 68 | } |
| 69 | const sorted = values.toSorted((left, right) => left - right); | 69 | const sorted = values.toSorted((left, right) => left - right); |
| 70 | const clampedTarget = Math.min(Math.max(target, 0), 1); | 70 | const clampedTarget = Math.min(Math.max(target, 0), 1); |
| 71 | const index = Math.min(sorted.length - 1, Math.ceil(sorted.length * clampedTarget) - 1); | 71 | const index = Math.min( |
| 72 | sorted.length - 1, | ||
| 73 | Math.max(0, Math.ceil(sorted.length * clampedTarget) - 1), | ||
| 74 | ); | ||
| 72 | return sorted[index] ?? null; | 75 | return sorted[index] ?? null; |
| 73 | } | 76 | } |
| 74 | 77 | ||
| @@ -30,15 +30,16 @@ export function installBrowserPerfCollector( · 29 unmodified lines | |||
| 30 | const rafGapsMs: number[] = []; | 30 | const rafGapsMs: number[] = []; |
| 31 | const mountedRowSamples: Array<BrowserPerfMetrics["mountedRowSamples"][number]> = []; | 31 | const mountedRowSamples: Array<BrowserPerfMetrics["mountedRowSamples"][number]> = []; |
| 32 | let previousAnimationFrameTs = 0; | 32 | let previousAnimationFrameTs = 0; |
| 33 | let rafHandle = 0; | ||
| 33 | 34 | ||
| 34 | const animationFrameLoop = (timestampMs: number) => { | 35 | const animationFrameLoop = (timestampMs: number) => { |
| 35 | if (previousAnimationFrameTs > 0) { | 36 | if (previousAnimationFrameTs > 0) { |
| 36 | rafGapsMs.push(timestampMs - previousAnimationFrameTs); | 37 | rafGapsMs.push(timestampMs - previousAnimationFrameTs); |
| 37 | } | 38 | } |
| 38 | previousAnimationFrameTs = timestampMs; | 39 | previousAnimationFrameTs = timestampMs; |
| 39 | window.requestAnimationFrame(animationFrameLoop); | 40 | rafHandle = window.requestAnimationFrame(animationFrameLoop); |
| 40 | }; | 41 | }; |
| 41 | window.requestAnimationFrame(animationFrameLoop); | 42 | rafHandle = window.requestAnimationFrame(animationFrameLoop); |
| 42 | 43 | ||
| 43 | if (typeof PerformanceObserver !== "undefined") { | 44 | if (typeof PerformanceObserver !== "undefined") { |
| 44 | try { | 45 | try { |
| @@ -103,7 +104,8 @@ export function installBrowserPerfCollector( · 58 unmodified lines | |||
| 103 | rafGapsMs.length = 0; | 104 | rafGapsMs.length = 0; |
| 104 | mountedRowSamples.length = 0; | 105 | mountedRowSamples.length = 0; |
| 105 | previousAnimationFrameTs = 0; | 106 | previousAnimationFrameTs = 0; |
| 106 | window.requestAnimationFrame(animationFrameLoop); | 107 | window.cancelAnimationFrame(rafHandle); |
| 108 | rafHandle = window.requestAnimationFrame(animationFrameLoop); | ||
| 107 | }, | 109 | }, |
| 108 | }; | 110 | }; |
| 109 | } | 111 | } |