[PM-38460] experience impact metrics - #514
Conversation
🤖 Bitwarden Claude Code ReviewOverall 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 Code Review DetailsNo 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 The one unresolved existing thread ( |
blackwood
left a comment
There was a problem hiding this comment.
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 }); |
There was a problem hiding this comment.
💭 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?
There was a problem hiding this comment.
I think we want PerformanceObserver.supportedEntryTypes?
| if (cdp) { | ||
| if (cdp.poisoned) { | ||
| bucket.cdpPoisoned++; | ||
| return; |
There was a problem hiding this comment.
❓ Will any failure on any of the operations set poisoned on the whole capture?
There was a problem hiding this comment.
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.
Poisoned measures exclude a run from experience impact analysis. Their raw data is still included in the detailed output.
| function frame(now: number) { | ||
| const delta = now - lastFrame; | ||
| lastFrame = now; | ||
| if (delta < BACKGROUND_GAP_MS) { |
There was a problem hiding this comment.
❓ Any benefit to recording backgrounding of tabs in the in-page result?
|
|
||
| const snapshotLabel = `${testInfo.titlePath.join("_")}__run${testInfo.repeatEachIndex}`; | ||
|
|
||
| const measure = async ( |
There was a problem hiding this comment.
💭 What should measure() do if the page navigates mid-window and addInitScript rebuilds the agent underneath it?
There was a problem hiding this comment.
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:
browser-interactions-testing/benchmarks/fixtures.benchmark.ts
Lines 177 to 181 in 6156d50
| }; | ||
|
|
||
| /** Which CDP capture mode produced a result. */ | ||
| export type CaptureMode = "default" | "cpu" | "snapshot"; |
There was a problem hiding this comment.
❓ Is the mode v. tier distinction important?
There was a problem hiding this comment.
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. 🫨
|
|
||
| // --- Frame timeline --------------------------------------------------------- | ||
|
|
||
| function summarizeFrames(events: TraceEvent[]): CdpImpactResult["frames"] { |
There was a problem hiding this comment.
💭 Would named FrameStats etc.: GcStats, AllocationStats read any better than indexing a union type?
|
|
||
| if (!fs.existsSync(dir)) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
💭 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);There was a problem hiding this comment.
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(); | ||
| } |
There was a problem hiding this comment.
❓ 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?
There was a problem hiding this comment.
😭 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.
🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-38460
📔 Objective
Introduce benchmark measuring frame rate drops; includes initial pass at "experience impact" measurements.
📖 Documentation