Skip to content

[PM-38460] experience impact metrics - #514

Open
audreyality wants to merge 6 commits into
mainfrom
autofill/pm-38460/benchmark-experience-metrics
Open

[PM-38460] experience impact metrics#514
audreyality wants to merge 6 commits into
mainfrom
autofill/pm-38460/benchmark-experience-metrics

Conversation

@audreyality

Copy link
Copy Markdown
Member

🎟️ Tracking

https://bitwarden.atlassian.net/browse/PM-38460

📔 Objective

Introduce benchmark measuring frame rate drops; includes initial pass at "experience impact" measurements.

📖 Documentation

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the new experience-impact benchmarking harness: the in-page agent, the CDP capture channel (default/cpu/snapshot modes), the impact reporter and result writer, the run-benchmark CLI wrapper, the reworked benchmark config with per-mode projects, and the restructured performance docs. This is test-only tooling with no production or extension-runtime impact; blast radius is confined to the benchmarks/ and instrumentation/ harness.

Code Review Details

No new findings at or above the confidence threshold.

The harness is careful where it matters: file reads in both reporters use path-containment checks plus O_NOFOLLOW and skip non-regular entries; the CDP capture brackets each window, detaches sessions in a finally/safeDetach path, bounds the tracingComplete wait with a timeout, and poisons rather than reports untrustworthy captures; snapshot filenames use a monotonic sequence to avoid collisions across repeatEach. Known duplication is already flagged with in-code FIXME markers.

The one unresolved existing thread (benchmarks/fixtures.benchmark.ts:209-211, in-page agent install comment) is now stale: the context fixture no longer installs the agent, so the flagged contradiction was addressed by commit c9304a2 ("fix instrumentation double-install"). Not re-raised.

Comment thread benchmarks/fixtures.benchmark.ts
@audreyality audreyality added the ai-review-vnext Request a Claude code review using the vNext workflow label Jul 23, 2026
Comment thread docs/performance.design.md Outdated
Comment thread docs/performance.md Outdated

@blackwood blackwood left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall structure is solid, the stress level definitions are purposeful, and the modes provide an important level of detail. Attention to both feature support and failure modes are thorough. Approved, with a handful of questions/thoughts that can be addressed at author discretion.

}
}
});
longTaskObserver.observe({ type: "longtask", buffered: true });

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 I'm not sure if .observe will throw here... the spec is somewhat ambiguous, it says it will "abort", only specifying that the user agent may log something. Would this compromise the ability to suss out an actual unsupported feature?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yuuuuuup.... 😬

if (cdp) {
if (cdp.poisoned) {
bucket.cdpPoisoned++;
return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Will any failure on any of the operations set poisoned on the whole capture?

@audreyality audreyality Jul 28, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Poisoning is a predictability signal that the extension uses to communicate that its measures are unreliable. BIT does not perform poisoning, it only looks for it.

https://github.com/bitwarden/clients/blob/main/apps/browser/src/autofill/content/performance.md#poisoning

Poisoned measures exclude a run from experience impact analysis. Their raw data is still included in the detailed output.

https://github.com/bitwarden/browser-interactions-testing/blob/autofill/pm-38460/benchmark-experience-metrics/docs/performance-output.md#predictability

function frame(now: number) {
const delta = now - lastFrame;
lastFrame = now;
if (delta < BACKGROUND_GAP_MS) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Any benefit to recording backgrounding of tabs in the in-page result?


const snapshotLabel = `${testInfo.titlePath.join("_")}__run${testInfo.repeatEachIndex}`;

const measure = async (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 What should measure() do if the page navigates mid-window and addInitScript rebuilds the agent underneath it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if I follow where you're going with this, the agent's producing inPage data. When we navigate, the PerformanceObserver is lost and, with it, the data it collected.

This is already accounted for in the design: https://github.com/bitwarden/browser-interactions-testing/blob/autofill/pm-38460/benchmark-experience-metrics/docs/performance.design.md#capturing-before-navigation

But I totally missed collecting the inPage data during navigation:

captures.push({
url,
timestamp: new Date().toISOString(),
results,
});

};

/** Which CDP capture mode produced a result. */
export type CaptureMode = "default" | "cpu" | "snapshot";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Is the mode v. tier distinction important?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier is deciding how much stress we're applying to the test. Mode is deciding what data we're collecting.

This matters because only the default mode is suitable for comparing benchmarks. The other two pause the runtime. CPU mode does this to collect the call stack. Snapshot mode traverses the heap. The modes are only useful for diagnosing why a benchmark reported a performance regression.

design notes: https://github.com/bitwarden/browser-interactions-testing/blob/autofill/pm-38460/benchmark-experience-metrics/docs/performance.design.md#separating-the-capture-modes
usage notes: https://github.com/bitwarden/browser-interactions-testing/blob/autofill/pm-38460/benchmark-experience-metrics/docs/performance.md#cdp-channel-benchmarks-only

Also worth noting: the "cpu" and "snapshot" modes generate a ton of data. Running them for a full benchmark suite (4 tiers, 10 runs each) generated 2.2 GB of data. 🫨

Comment thread instrumentation/cdp.ts

// --- Frame timeline ---------------------------------------------------------

function summarizeFrames(events: TraceEvent[]): CdpImpactResult["frames"] {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 Would named FrameStats etc.: GcStats, AllocationStats read any better than indexing a union type?


if (!fs.existsSync(dir)) {
return;
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💭 perf-summary-reporter.ts resolves the directory differently, not sure if this is an issue.

    const dir = path.isAbsolute(this.inputFolder)
      ? path.resolve(this.inputFolder)
      : path.join(__dirname, this.inputFolder);
    const outputPath = path.isAbsolute(this.outputFile)
      ? this.outputFile
      : path.join(__dirname, this.outputFile);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is, but perf-summary-reporter.ts is written that way because Akami pegged it as incorrect. It shouldn't harm anything to reuse the pattern.

// workload rather than the teardown, matching the trace window.
inPage = await readInPageImpact(page);
cdp = await capture.stop();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Won't a failed workload that throws never allow captures.push resolve? Do we want to capture partial captures in that case?

Also, should line 237 be wrapped in its own try/catch block?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😭 This whole section is kind of hard to read. Between the nesting induced by playwright and the abomination of prettier formatting, this code is nearly as bad as perl code. I wasn't happy about it, but I also didn't see any way that indirection would make it easier to read.

📝 Workloads that throw run the finally block, test the collected data to annotate the test, and then record the capture. That's all happening within the measure function.

📝 As I understand it, readInPageImpact(page) doesn't throw.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review-vnext Request a Claude code review using the vNext workflow

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants