Methodology

How WP Footprint measures.

This page documents exactly what is measured, how it is aggregated, and how the A to E grade is computed. The method is public: you can critique it, verify it, redo the calculation yourself, and suggest improvements.

Active methodology: v4.1. Versioned on every change — full history lives in source.

In short

An isolated measurement per plugin, with a rolling 90-day median.

Each plugin is disabled in turn to isolate its own impact. Contributions from every contributing site are then consolidated into 90-day rolling medians, with a confidence interval, before being translated into a grade from A to E.

Differential measurement: baseline then disable

WP Footprint measures a page several times with every plugin active (the baseline). Then, for each plugin and the active theme, it replays the same page after temporarily disabling it via a MU-loader, and measures again.

A component's own impact is the difference: delta = median(baseline) − median(baseline without that component). This difference-based approach neutralizes the effect of the server, the theme, and the other plugins — that's what makes cross-site comparisons legitimate, without needing to deeply instrument PHP.

Multiple passes, median, confidence interval

To absorb the natural noise of a web server (cache, GC, DB latency), each measurement is repeated: up to 5 baseline passes and up to 3 passes per disabled component. The plugin can stop earlier — at 3 baseline or 2 component passes — when the sample is already tight (spread below max(50 ms, 10 % of the median)): no precision lost, just no wasted passes. Outliers are removed with a Tukey k=1.5 filter on the interquartile range, then we keep the median of remaining passes (median resists outliers, unlike mean).

A 95 % confidence interval half-width is computed via Student's t(0.975, n−1) — not a Gaussian approximation, which would understate uncertainty for small samples. A delta indistinguishable from noise does not count toward the public score.

Host-floor measurement

On every scan the plugin also runs 3 synthetic passes where WordPress is booted with no plugins active and a default theme. The request short-circuits early (action init priority 1) and returns a minimal body — no routing, no post lookup, no template rendering. What we capture is the raw WP+server boot time on this machine.

This measurement is independent of which URL was scanned and reproducible. It will be used to normalise scores per host: the same +90 ms delta means very different things on a 80 ms floor (+112 % overhead) versus a 250 ms floor (+36 %). For now the data is collected and archived; weighting will land in a later revision of the score calculation.

Isolation limits: non-measurable plugins

Some plugins can't be safely disabled: their API is called directly (at PHP level) by the theme or other plugins. Disabling them would fatal the request, making any differential measurement impossible. When the scanner detects this case (fatal on the first pass), it stops retrying, marks the component not measurable, and does not report it to Pulse. The other components are measured normally.

For popular plugins whose API is widely consumed by themes (ACF, Polylang, Yoast, Rank Math, Meta Box, Pods, WPML, The Events Calendar, Gravity Forms…), the MU-loader loads compatibility shims during the pass: the plugin's functions stay defined and return neutral values, so the page renders without fataling and the plugin's own cost is still measured correctly. The list of covered plugins is exposed in /api/v1/methodology under pipeline.isolation.compatibility_shims.covered_slugs.

The five measured metrics

Five quantities are captured per pass — two are normalized as a ratio of the baseline, three remain absolute counts:

  • PHP CPU — CPU time consumed via getrusage(), expressed as a fraction of the request's baseline CPU. Self-normalising across servers: 18 % CPU on a Xeon equals 18 % on shared hosting.
  • Memory — extra peak memory, as a fraction of baseline peak memory. Same reasoning: the share is meaningful, the absolute value depends on the server.
  • Added SQL queries — absolute count. 1 query is 1 query, machine-independent.
  • Added assets — scripts + styles enqueued by the component. Absolute count.
  • External HTTP calls — absolute count of outbound network calls.

The 0-100 per-component score

Convention: 100 = perfect, 0 = worst — same direction as a school grade. For each metric we apply a linear ramp between a low threshold (below it, the component loses 0 points) and a high threshold (above it, it loses the maximum weight). The five penalties are summed (capped at 100) then we return score = 100 − penalty. The score is recomputed server-side on every ingestion from the raw passes — the client never picks its own grade.

Metric Low (0 pt lost) High (max lost) Max weight
CPU (% baseline) 2 % 30 % 30 pts
Memory (% baseline) 2 % 40 % 20 pts
SQL queries 2 60 20 pts
Assets 1 15 15 pts
External HTTP 0 5 15 pts

Why CPU and memory as ratios while SQL/assets/HTTP stay absolute? The first two scale with the machine; normalising them against the baseline cancels the scale factor. The last three are integer counts, intrinsically scale-invariant.

The page-level global score

The big score on your scan report reflects the actual heaviness of the rendered page, not the sum of component-level impacts. The distinction matters: with ~10+ plugins, differential attribution counts shared hooks multiple times (two plugins that share a hook each get attributed the full cost). Summing those deltas and dividing by the baseline produced a ratio saturated at 100 % almost mechanically → 50 lost points before even checking whether the page was slow. Methodology v4.0 fixes this by reading the absolute baseline values directly.

Same five dimensions, absolute this time: server time, peak memory, SQL queries, enqueued scripts/styles, external HTTP calls. Same linear ramps as the per-component score, but with more lenient thresholds (a whole site does more than a single plugin).

Metric Low (0 pt lost) High (max lost) Max weight
Server time 400 ms 3000 ms 30 pts
Peak memory 24 MB 192 MB 20 pts
SQL queries 80 1500 20 pts
Scripts + styles 15 120 15 pts
External HTTP 2 25 15 pts

From score to A–E grade

The score is mapped to an A to E letter using absolute bounds on the 0-100 scale. If the whole WordPress ecosystem optimised, every plugin could reach A — that's intentional: we grade an impact, not a relative ranking.

A 85 – 100 Negligible impact, very light component
B 65 – 84 Moderate, reasonable impact
C 45 – 64 Significant impact, worth watching
D 25 – 44 Heavy impact, alternative recommended
E 0 – 24 Very heavy impact, critical

Cross-site aggregation

When multiple sites measure the same version of a plugin, we keep the median per (plugin, version, page_type) — separately for the homepage and the admin, which are not comparable. Median resists outliers (misconfigured sites, interrupted measurements) better than the mean.

Raw scans are kept in the database for 3 years. If the methodology evolves we can retroactively recompute every score without asking sites to re-scan.

Consolidated score on the public page

The score displayed on a plugin or theme page is consolidated over the last 90 days. Every valid scan in that window feeds the median, regardless of which version it was collected on. Without this rolling window, every new release would reset the public score to zero and an actively maintained plugin would permanently appear as « not enough data yet ».

The version label shown on the card (e.g. « v4.1.0 ») is the most recent version represented in the window — so the reader can tell which release is currently being measured — while the score itself stays stable. Per-version details remain accessible in the history table further down the page.

Guards before publication

Three filters apply before a scan participates in the public median. Rejected scans are kept for audit but do not influence the displayed grade.

  • Baseline too light: if the baseline request consumes less than 50 ms of CPU, ratios become extreme and noisy → scan excluded.
  • Bloated baseline: if the baseline request exceeds 3 × the global median for the same page type, the site is likely already saturated with other plugins → scan excluded (otherwise a heavy plugin would look negligible).
  • Statistical plausibility: seven cross-metric heuristics (e.g. "50 MB allocated with no CPU spent" is physically impossible) score each scan 0-100. Below 40/100, the scan is rejected as suspicious.

Verified identity for contributing sites

Each contributing site generates an Ed25519 keypair locally and signs every submission. Identity is verified once on enrolment via a cryptographic challenge over admin-ajax.php. No shared secret — forging a fake identity requires owning a real WordPress domain, which makes gaming the ranking economically unattractive. See the privacy policy for what is sent (and, critically, what is not).

Components opted out of the public ranking

Site owners can flag a plugin or theme — typically an in-house module or a proprietary product — as private in the WordPress plugin's settings. The component is then sent to Pulse with a private: true flag.

The server computes and returns its score (so the local report is complete) but does not persist anything: no row in plugin_scans, no entry in the public catalogue, no contribution to community medians. The slug only transits over the signed request and is then forgotten. This split between “no score” and “not published” was introduced in methodology v3.2.

Publication threshold

As long as a plugin or theme version has fewer than 15 unique scans (distinct verified sites), its grade is not displayed publicly. WP Footprint Pulse shows "not enough data" instead, to avoid misleading conclusions on too small a sample.

Redo the calculation yourself

Every threshold, weight and guard described above is exposed as JSON on the public endpoint https://www.wpfootprint.com/api/v1/methodology, versioned by methodology. You can also read the scoring code directly in the repository — it lives in four files of under 200 lines each.

Owned limits

  • No fine-grained PHP tracing. Attribution is differential: if plugin A triggers a hook that runs plugin B's code, the measured impact may land on either side. That's the trade-off for staying non-intrusive (no Xdebug required).
  • Residual environment bias. CPU/memory ratios cancel the server frequency bias but remain sensitive to the major PHP version (PHP 7.4 doesn't distribute costs the way PHP 8.3 does). Stratification by PHP version is planned for v1.1.
  • Self-selected sample. Sites that install WP Footprint are likely more technical than average. The ranking is "true for the sites that scan", not for all WordPress.
  • No measurement of provided value. A heavy plugin doing a lot and a light plugin doing little will share the same grade if their deltas match. Categorisation will help but won't fully solve it.
  • Asynchronous plugins. Plugins that do most of their work in deferred cron or queues (workers, webhooks) will have an underestimated frontal impact on a single-page scan.
  • Non-isolable plugins. Some plugins fatal when disabled because their API is consumed at PHP level by the theme or another plugin. They are marked "not measurable" in the local report and excluded from the public ranking. Their list is reported back anonymously so we can prioritise new compatibility shims.

Suggestions and critiques welcome — write to [email protected]. Any accepted improvement is documented in the methodology changelog.