-
Notifications
You must be signed in to change notification settings - Fork 5.5k
perf(quality-gates): Variance reduction - outlier filtering, warmup exclusion #41961
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
MajorLift
merged 25 commits into
main
from
jongsun/perf/quality-gates/variance-reduction
Apr 24, 2026
Merged
Changes from 9 commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
55bb98a
Add `trimmedCount` to `TimerStatistics` and `BenchmarkResults` types
MajorLift 80d89da
Export `trimOutliers` and expose IQR count in `calculateTimerStatistics`
MajorLift 01bc5ca
Add `WARMUP_RUNS`, `MIN_SAMPLES_FOR_VERDICT`, and `POWER_USER_NUM_BRO…
MajorLift 4008c78
Apply warm-up exclusion and IQR trimming in `runPageLoadBenchmark`, p…
MajorLift cd5b927
Increase default `browserLoads` to 15 for `POWER_USER_HOME` preset
MajorLift 10dbb46
Emit `trimmedCount` as derived Sentry tag per metric
MajorLift 56d4f41
Add unit tests for IQR outlier trimming edge cases
MajorLift 775bb15
ci: rebalance startupPowerUserHome to 15 browserLoads × 7 pageLoads
MajorLift 897ae1d
fix: resolve lint errors in statistics and outlier-trimming test
MajorLift 7cd855d
fix: filter measuredWebVitalsRuns by iteration instead of slice
MajorLift f7a7d21
fix: address Bugbot review #4156995681 — guard empty measuredResults …
MajorLift df553e4
[skip-e2e] [force-builds]
MajorLift 3909d41
Narrow `dataQuality` type to literal union
MajorLift e98d614
Remove unused code
MajorLift 47db254
Tighten test assertion
MajorLift ece9e74
Drop \`trimOutliers\` wrapper and fix reference aliasing in \`detectO…
MajorLift b0197ef
Add \`outliers\` to \`BenchmarkResults\` type; document IQR-only poli…
MajorLift 697c24f
Replace inline CV ladder with \`assessDataQuality\`, emit \`outliers\…
MajorLift 0bc831c
Spread input on \`detectOutliersZScore\` small-array early return
MajorLift 1a5457b
Collect \`outliers\` from \`TimerStatistics\` in \`convertTimerStatis…
MajorLift 451bc12
Fix prettier line-length violation in \`send-to-sentry.ts\`
MajorLift 0827815
Fix JSDoc lint errors on \`runPageLoadBenchmark\`
MajorLift a8c5df8
Populate \`outliers\` in \`runPageLoadBenchmark\` return for Sentry c…
MajorLift 4c40363
Spread \`outliers\` copy in \`runPageLoadBenchmark\` to avoid shared …
MajorLift f1fc8fe
Move \`browserLoads <= WARMUP_RUNS\` guard before benchmark loop in \…
MajorLift File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| import { trimOutliers } from './statistics'; | ||
|
|
||
| describe('trimOutliers (IQR-based)', () => { | ||
| describe('small sample edge cases', () => { | ||
| it('returns empty array unchanged', () => { | ||
| const result = trimOutliers([]); | ||
| expect(result.samples).toEqual([]); | ||
| expect(result.trimmedCount).toBe(0); | ||
| }); | ||
|
|
||
| it('returns array of 1 unchanged', () => { | ||
| const result = trimOutliers([500]); | ||
| expect(result.samples).toEqual([500]); | ||
| expect(result.trimmedCount).toBe(0); | ||
| }); | ||
|
|
||
| it('returns array of 2 unchanged', () => { | ||
| const result = trimOutliers([100, 200]); | ||
| expect(result.samples).toEqual([100, 200]); | ||
| expect(result.trimmedCount).toBe(0); | ||
| }); | ||
|
|
||
| it('returns array of 3 unchanged (below IQR threshold)', () => { | ||
| const result = trimOutliers([100, 200, 9000]); | ||
| expect(result.samples).toEqual([100, 200, 9000]); | ||
| expect(result.trimmedCount).toBe(0); | ||
| }); | ||
| }); | ||
|
|
||
| describe('no outliers', () => { | ||
| it('returns all values when distribution is tight', () => { | ||
| const samples = [100, 101, 99, 102, 98, 100, 101, 99]; | ||
| const result = trimOutliers(samples); | ||
| expect(result.trimmedCount).toBe(0); | ||
| expect(result.samples).toHaveLength(samples.length); | ||
| }); | ||
|
|
||
| it('returns all values when all samples are identical', () => { | ||
| const samples = [200, 200, 200, 200, 200, 200]; | ||
| const result = trimOutliers(samples); | ||
| expect(result.trimmedCount).toBe(0); | ||
| expect(result.samples).toHaveLength(samples.length); | ||
| }); | ||
| }); | ||
|
|
||
| describe('outlier removal', () => { | ||
| it('removes a single high outlier', () => { | ||
| const samples = [10, 11, 12, 10, 11, 12, 11, 1000]; | ||
| const result = trimOutliers(samples); | ||
| expect(result.samples).not.toContain(1000); | ||
| expect(result.trimmedCount).toBe(1); | ||
| }); | ||
|
|
||
| it('removes a single low outlier', () => { | ||
| const samples = [100, 102, 101, 103, 100, 101, 1]; | ||
| const result = trimOutliers(samples); | ||
| expect(result.samples).not.toContain(1); | ||
| expect(result.trimmedCount).toBe(1); | ||
| }); | ||
|
|
||
| it('removes multiple outliers on both ends', () => { | ||
| const samples = [1, 100, 101, 102, 100, 101, 103, 9999]; | ||
| const result = trimOutliers(samples); | ||
| expect(result.samples).not.toContain(1); | ||
| expect(result.samples).not.toContain(9999); | ||
| expect(result.trimmedCount).toBe(2); | ||
| }); | ||
|
|
||
| it('preserves non-outlier values exactly', () => { | ||
| const core = [100, 105, 95, 102, 98, 101]; | ||
| const samples = [...core, 5000]; | ||
| const result = trimOutliers(samples); | ||
| for (const v of core) { | ||
| expect(result.samples).toContain(v); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| describe('realistic benchmark scenario (n=15)', () => { | ||
| it('removes 0-3 outliers from a 15-sample benchmark run', () => { | ||
| // Simulates 15 independent browser-load sessions with 1-2 JIT/GC spikes | ||
| const normal = [ | ||
| 320, 330, 315, 325, 318, 322, 328, 316, 319, 324, 321, 323, 317, | ||
| ]; | ||
| const withSpikes = [...normal, 900, 850]; // two cold-start spikes | ||
| const result = trimOutliers(withSpikes); | ||
| expect(result.trimmedCount).toBeGreaterThanOrEqual(1); | ||
| expect(result.trimmedCount).toBeLessThanOrEqual(3); | ||
| expect(result.samples.length).toBeGreaterThanOrEqual(12); | ||
|
gauthierpetetin marked this conversation as resolved.
Outdated
MajorLift marked this conversation as resolved.
Outdated
|
||
| }); | ||
|
|
||
| it('does not over-trim a low-variance run', () => { | ||
| const stable = [ | ||
| 300, 302, 298, 301, 299, 303, 300, 301, 302, 298, 300, 301, 299, 302, | ||
| 300, | ||
| ]; | ||
| const result = trimOutliers(stable); | ||
| expect(result.trimmedCount).toBe(0); | ||
| expect(result.samples).toHaveLength(stable.length); | ||
| }); | ||
| }); | ||
|
|
||
| describe('input ordering', () => { | ||
| it('produces the same trimmedCount regardless of input order', () => { | ||
| const ordered = [10, 11, 12, 13, 14, 15, 1000]; | ||
| const shuffled = [1000, 13, 10, 15, 12, 11, 14]; | ||
| const r1 = trimOutliers(ordered); | ||
| const r2 = trimOutliers(shuffled); | ||
| expect(r1.trimmedCount).toBe(r2.trimmedCount); | ||
| expect(r1.samples.sort((a, b) => a - b)).toEqual( | ||
| r2.samples.sort((a, b) => a - b), | ||
| ); | ||
| }); | ||
|
|
||
| it('does not mutate the input array', () => { | ||
| const samples = [10, 11, 1000, 12, 13]; | ||
| const copy = [...samples]; | ||
| trimOutliers(samples); | ||
| expect(samples).toEqual(copy); | ||
| }); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.