Skip to content

Performance: add a My Jetpack scenario (timing + bundle-size) to CodeVitals tracking - #50322

Merged
LiamSarsfield merged 9 commits into
trunkfrom
forms-717-my-jetpack-performance-scenario
Jul 13, 2026
Merged

Performance: add a My Jetpack scenario (timing + bundle-size) to CodeVitals tracking#50322
LiamSarsfield merged 9 commits into
trunkfrom
forms-717-my-jetpack-performance-scenario

Conversation

@LiamSarsfield

@LiamSarsfield LiamSarsfield commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Fixes FORMS-717

Why: My Jetpack ships the heaviest Jetpack admin bundle, but nothing tracks how long it takes to load or how big it is. This adds a My Jetpack scenario to the tools/performance CodeVitals pipeline so a load-time or bundle-size regression shows up as a trend break instead of a user complaint.

Merge gate: satisfied. The jetpack-production mirror the fixture clones now renders My Jetpack (verified 2026-07-10 against a clean checkout of mirror commit 9ef44a8: full run, 5/5 iterations, all capture guards pass). Earlier revisions held this PR back because the perf runner aborts the whole run on any scenario failure, so merging while My Jetpack rendered blank would also have stopped the already-working Dashboard/Forms posts. That precondition is now met.

Proposed changes

  • Add a myJetpack scenario that measures admin.php?page=my-jetpack and posts LCP, TTFB, FCP, and bundle size (decodedBytesKB) to four new production keys (my-jetpack-connection-sim-*).
  • Reuse the existing jetpack-connected Docker fixture as a page-targeted scenario, the same way formsResponses does, so no new service is added.
  • Flip Jetpack offline mode off in the shared fixture so My Jetpack initializes. The fixture URL is localhost (no dot), which Jetpack reads as a local site and skips init. Two filters are needed because Status::is_offline_mode() falls back to the stored option: jetpack_offline_mode => __return_false and pre_option_jetpack_offline_mode => __return_zero (guards against a reused/dirty fixture DB).
  • Add unit coverage and README docs.

Slice 1 of FORMS-717. Settings, Stats, frontend, and control scenarios stay out of scope.

One caveat: that offline flip runs install-wide, so it nudges the existing Dashboard and Forms trends by one small step at the landing commit (+9 resources, +107 KB decoded; timing stays within noise). Those keys should get a CodeVitals annotation at that commit so the step is not misread as a regression.

Related product discussion/links

Does this pull request change what data or activity we track or use?

Yes, narrowly: it adds four internal performance-telemetry keys (my-jetpack-connection-sim-*: LCP, TTFB, FCP, decodedBytesKB) to CodeVitals. These are aggregate page-load numbers captured from a synthetic Docker fixture, extending the existing Dashboard/Forms perf trends. No real users, no PII, no cookies, no end-user tracking. No Privacy Updates label needed.

Testing instructions

All commands run from tools/performance/.

One-time setup:

cd tools/performance
pnpm install
pnpm exec playwright install chromium --with-deps   # headless browser used to load the page
pnpm calibrate                                       # normalizes CPU speed so timings are comparable across machines

1. Unit tests (no Docker needed, ~seconds):

pnpm test:unit        # expect 110 passing

2. Measure just the My Jetpack page locally, without publishing anything:

SCENARIO=my-jetpack pnpm test -- --skip-codevitals

--skip-codevitals runs the full Docker fixture and browser measurement but does not send results to the CodeVitals server, so no token is required and nothing is published. Drop SCENARIO=my-jetpack to run every scenario.

3. Inspect the payload a real post would send, without posting:

pnpm report:dry       # exits 0, prints the four keys, no token needed
Results from a local 5-iteration run against a clean mirror checkout (`9ef44a8`, 2026-07-10; medians, all in range)
metric median min max stdDev
lcp 684 656 908 93
ttfb 64 61 103 16
fcp 176 176 228 20
decodedBytesKB 5866 5866 5866 0
resources 92 (all 5 iterations)

minResourceCount is 64 (70% of the steady 92), so a truncated capture fails instead of posting an undercounted bundle size.

Known, not caused by this PR: the formsResponses scenario cannot be measured in the local fixture because its GET /wp/v2/settings request hangs server-side past 60s (with offline on or off, so the flip is not the cause; re-verified 2026-07-10 on the current mirror). Flagged so a local Forms failure is not read as a regression from this change. Fixture realism is tracked in BOOST-456.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!

@github-actions github-actions Bot added the [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. label Jul 8, 2026
Base automatically changed from forms-704-add-a-bundle-size-metric-to-jetpack-codevitals to trunk July 8, 2026 14:02
@LiamSarsfield
LiamSarsfield force-pushed the forms-717-my-jetpack-performance-scenario branch 3 times, most recently from 6b34276 to 8c94a3d Compare July 9, 2026 08:45
@LiamSarsfield
LiamSarsfield marked this pull request as ready for review July 9, 2026 11:47
@LiamSarsfield
LiamSarsfield requested a review from a team as a code owner July 9, 2026 11:47
The fixture site URL (http://localhost:<port>) has no dot, so
Status::is_local_site() treats it as local and Jetpack enters offline
mode, which makes My Jetpack's Initializer::should_initialize() return
false — the page never registers. This fixture simulates a production,
connected site, so force offline mode off.

Install-wide: one WordPress install serves every scenario, so this shifts
what the existing Dashboard and Forms trends measure by one step at the
commit it lands (Jetpack runs more code paths when not offline). See the
offline-mode attribution note in tools/performance/README.md.
Add the My Jetpack admin page (admin.php?page=my-jetpack) as a scenario,
posting four production metrics (LCP, TTFB, FCP, decodedBytesKB). My Jetpack
is the heaviest Jetpack admin bundle, so it is where a bundle-size regression
is most damaging. Reuses the jetpack-connected WordPress instance (page-
targeted scenario), modeled on formsResponses.

- scenarios.js: new myJetpack scenario; four production keys under the same
  owner waiver as the Dashboard/Forms keys; minResourceCount floor 64
  (~70% of the observed ~92-resource load) guards a truncated capture.
- post-to-codevitals.test.js: pin the scenario's keys/types and navigation
  contract, mirroring the formsResponses config test.
- README.md: metrics table, offline-mode attribution note with measured
  before/after deltas, known fixture behavior, and the #50291 dependency.
Address review feedback on the My Jetpack CodeVitals scenario:

- Pin minResourceCount to its exact value (64) and exercise
  assertCaptureComplete at the 63/64 boundary, instead of the loose
  >=40 && <90 range a margin-eroding edit could pass silently.
- Add an 11-key dry-run that drives a myJetpack summary through the
  poster, proving the four my-jetpack-connection-sim-* keys reach the
  payload with the right values (parity with the Forms dry-run).
- Correct the scenarios.js comment: the .jp-admin-page frame renders
  with its children in one commit, so networkidle (not the frame
  selector) is the load-bearing capture-completeness gate.
Round-2 review accuracy fixes (no behavior change):

- expectUrlIncludes test comment: it does not catch the onboarding
  redirect (My Jetpack is a single-slug SPA, so every view keeps
  page=my-jetpack). Credit the .jp-admin-page selector as the real
  wrong-view guard and scope expectUrlIncludes to off-page redirects.
- Hydrate/networkidle comment: neither the .jp-admin-page frame nor
  networkidle alone guarantees the async card tail; state that
  completeness rests on networkidle plus the stable resource count,
  with the minResourceCount floor as the gross-truncation backstop.
- myJetpackSummary JSDoc: only LCP/decodedBytesKB track observed
  medians; label ttfb/fcp as in-range fillers.
- Pin the assertCaptureComplete 63-resource throw message, matching
  the sibling boundary tests.
My Jetpack joined Forms as a second consumer of the shared
SANITY_RANGES.decodedBytesKB row; the comment named only Forms, which
could mislead a future editor into tightening the bounds to one page's
profile and clipping the other. Documentation only.
- simulate-wpcom-connection: Status::is_offline_mode() falls back to the
  stored jetpack_offline_mode option when the filtered value is false, so
  the __return_false filter alone can be overridden by a dirty/reused
  fixture DB with the option set to 1. Add pre_option_jetpack_offline_mode
  => __return_zero so the option read can't silently re-enable offline mode
  and break every scenario's boot.
- README: the bundle-size metric line still said it is posted 'only for
  formsResponses', which this PR's own My Jetpack section below contradicts.
  Note both wp-build dashboards post it, not the Dashboard.
…ync)

#50291 merged to the monorepo on 2026-07-08; the pinned jetpack-production
mirror the fixture clones does not yet carry it. Reword the README from an
unmerged-PR blocker to a merged-but-unpropagated prerequisite, matching the
verified mirror state.
@LiamSarsfield
LiamSarsfield force-pushed the forms-717-my-jetpack-performance-scenario branch from 8c94a3d to c6a94d8 Compare July 9, 2026 12:03

@enejb enejb left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for this PR.

Tthe tests are thorough (110/110 locally) and the write-up is refreshingly honest about the install-wide side effect and the mirror dependency. Left two inline notes: one worth resolving before merge, and one reminder.

// offline by default): the simulate-wpcom-connection mu-plugin flips it. See the README
// offline-mode attribution note.
path: '/wp-admin/admin.php?page=my-jetpack',
waitForSelector: '#my-jetpack-container .jp-admin-page',

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One thing worth resolving before merge — and thank you for documenting the mirror dependency so clearly, it's what made this easy to spot.

The failure here isn't graceful. When this waitForSelector times out (which the README says is the expected state until the mirror carries #50291), measure-lcp.js still ends with process.exit( hasFailures ? 1 : 0 ), and run-performance-tests.js runs it via execFileSync — so the non-zero exit throws and the runner process.exit(1)s before the CodeVitals posting step. Net: Dashboard and Forms get measured fine but never posted — every trend gets a hole for that build, not just My Jetpack. (--allow-codevitals-failure can't rescue it either; it only guards the post step, which is never reached.)

Since HEAD is still "awaiting mirror sync," merging before the mirror updates would blank out posting for all three trends. Two ways out:

The per-scenario try/catch already signals the intent to isolate failures — it's just the terminal process.exit(1) + the sync short-circuit in the runner that undo it for the posting phase.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the thorough trace! You've read the mechanics exactly right. One mitigating detail: the poster already skips an errored scenario and posts the rest (post-to-codevitals.js), so the runner's short-circuit is the only gap.

Good news on both fronts since your review:

  1. The gate condition now holds. jetpack-production trunk picked up My Jetpack: register wp-build-polyfills so the app (and Boost) loads without Gutenberg on WP < 7.0 #50291. I reset my checkout to a clean origin/trunk and re-ran the full pipeline: My Jetpack renders and measures (median LCP 684 ms, 5/5 iterations, all four metrics in range). Forms still hits its pre-existing local hang (/wp/v2/settings stalls past 60 s; unrelated to this PR, measures fine in CI), and the dry-run poster skipped it with a warning while keeping the other keys. I've updated the README and PR description to match.

  2. On (b): agreed, and I'd like to do it as a follow-up. If the runner posts survivors and still exits non-zero, a retried red build re-appends them as duplicate trend points (CodeVitals is append-only, dedup off by default). If it exits zero, a scenario failure goes silent. That trade-off deserves its own PR, likely a per-scenario optional flag. I've filed it as FORMS-728 and will pick it up right after this lands.

Comment on lines +87 to +88
add_filter( 'jetpack_offline_mode', '__return_false' );
add_filter( 'pre_option_jetpack_offline_mode', '__return_zero' );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just a reminder rather than a change request: since this flip is install-wide, please make sure the CodeVitals annotation lands on the existing wp-admin-dashboard-* / forms-responses-* keys at the landing commit — the +9 resources / +107 KB step will read as a regression later without it. Easy to drop since it's a non-code action.

Also wanted to call out the nice detail here: using __return_zero (not __return_false) for the pre_option_ filter is exactly right — pre_option only short-circuits get_option when the value is !== false, so __return_false would quietly fall through to the stored option. The inline comment explaining that is much appreciated. 🙏

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Noted, and it won't get dropped: annotating the existing wp-admin-dashboard-* / forms-responses-* keys at the landing commit is on the landing checklist, and the PR description calls it out in the rollout notes for the same reason.

Thanks for the kind words on __return_zero! The pre_option short-circuit was subtle enough to bite silently, so the comment felt worth the lines.

jetpack-production trunk now carries #50291's WP_Build_Polyfills
registration; a clean checkout of mirror commit 9ef44a8 renders My
Jetpack and passes every capture guard (verified 2026-07-10, full
local run). Reword the README so the polyfill reads as a baseline
prerequisite instead of a pending blocker, and note that the
pre-existing local Forms /wp/v2/settings hang persists on the
current mirror.
…hift

The attribution note said to expect 'a one-commit step ... not a level
shift', which reads as if later points return to the old baseline. The
offline-mode filters are unconditional, so every measurement after the
landing commit runs the non-offline fixture: the Dashboard/Forms trends
settle at the new level permanently. Say that, so graph readers treat
it as a measurement-boundary change rather than an ongoing regression.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds My Jetpack page-load performance tracking into the existing tools/performance CodeVitals pipeline so regressions in load timing and overall runtime payload size are visible as trend changes (FORMS-717), reusing the existing simulated-connection WordPress fixture.

Changes:

  • Introduces a new myJetpack scenario targeting wp-admin/admin.php?page=my-jetpack, capturing LCP/TTFB/FCP plus decodedBytesKB to new production CodeVitals keys.
  • Updates shared safeguards/docs to reflect the new scenario and the broader applicability of the decodedBytesKB sanity range.
  • Forces Jetpack offline mode off in the simulated-connection mu-plugin so My Jetpack initializes in the localhost-based fixture; adds unit coverage to pin scenario config + posting payload.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tools/performance/scripts/scenarios.js Adds myJetpack scenario configuration and updates decodedBytesKB sanity-range commentary to cover both Forms and My Jetpack.
tools/performance/scripts/post-to-codevitals.test.js Extends unit tests to cover the new scenario’s metrics wiring, capture guards, and dry-run payload accumulation.
tools/performance/README.md Documents the new scenario, its prerequisites, and updates safeguard text to include the production-key waiver for myJetpack.
tools/performance/docker/mu-plugins/simulate-wpcom-connection.php Disables Jetpack offline mode (with option fallback defense) to ensure My Jetpack renders/initializes in the fixture.

@LiamSarsfield
LiamSarsfield merged commit 0f71baf into trunk Jul 13, 2026
74 checks passed
@LiamSarsfield
LiamSarsfield deleted the forms-717-my-jetpack-performance-scenario branch July 13, 2026 10:31
@github-actions github-actions Bot removed [Status] Needs Author Reply We need more details from you. This label will be auto-added until the PR meets all requirements. [Status] In Progress labels Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants