Skip to content
bradthxPublic

About

A PHP based WordPress benchmarking tool. Tests hosts for speed, caching behavior, and tech stack.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

2 Commits

Folders and files

Repository files navigation

WPBench

WPBench is a PHP-based benchmarking tool for WordPress and WooCommerce sites hosted anywhere. Give it a URL and it runs a full set of performance tests, including DNS, TLS, TTFB, HTTP timing, caching and header detection, and hosting provider identification. It also uses a real headless Chrome browser to measure page load performance and Core Web Vitals.

WPBench can run configurable load tests using either raw HTTP requests or full browser page loads to see how a site performs under sustained traffic and concurrency. Each test generates a self-contained HTML report, and you can compare 2–4 test runs side by side with metrics and charts to quickly see performance differences.

What it tests

  • HTTP timing: DNS lookup, connect, TLS handshake, TTFB, full download time, redirect chains. Standard and Thorough runs repeat the homepage fetch (2 and 3 times respectively) and average the results.
  • Headers: full raw dump, plus detection of what's caching your site at the edge (Cloudflare, Fastly, and friends), at the webserver (Varnish, LiteSpeed, Nginx), and via WP caching plugins (WP Rocket, W3 Total Cache, and so on).
  • Hosting provider fingerprinting: best effort identification of the managed WordPress host behind a response (WP Engine, Kinsta, Pantheon, SiteGround, Cloudways, Flywheel, Servebolt, WordPress.com/VIP, Automattic Pressable/Atomic) from known header and cookie signatures. A miss just means "couldn't tell," not "definitely not this host."
  • Tech stack detection: server software/version, leaked PHP version, response compression (Brotli/Gzip/Deflate/Zstandard), and a parsed breakdown of any Server-Timing header the backend sends (useful for seeing what a host's own edge/cache layer reports internally).
  • WordPress and WooCommerce fingerprinting: theme and plugin detection from public assets, generator tags, and the REST API; WooCommerce presence detected via the wc/v3/wc/store/v1 REST namespaces or plugin asset paths.
  • Real browser metrics: full page load, First Contentful Paint, Largest Contentful Paint, Cumulative Layout Shift, DOM interactive time, browser-measured backend TTFB, first vs third-party resource breakdown (request count and bytes), and a screenshot, all captured with headless Chrome. Core Web Vitals get rated good/needs improvement/poor against Google's published thresholds and shown as badges in the report.
  • Optional load test: a burst of concurrent requests to see how the host holds up under simultaneous visitors. Only runs on the Thorough complexity preset, and the concurrency level (how many simultaneous requests) is adjustable per run from the dashboard, from 1 up to a configurable ceiling (50 by default), so you can test the same host at different concurrency levels or match concurrency across hosts you're comparing.
  • Stress test: its own complexity preset that sustains a fixed number of concurrent connections against the site for a configurable duration (5 to 60 seconds), refiring each connection the instant it completes so the concurrency level stays constant for the whole window, rather than firing one single wave like the Thorough load test does. Reports how many transactions completed successfully, how many failed, requests per second, and the fastest/slowest/mean/median page load time across the whole run, so you can see how a host holds up under sustained load rather than just an instant.
  • Browser stress test: the same sustained load idea as Stress, but each concurrent "connection" is a real headless Chrome tab doing a full page load (HTML, CSS, JS, images, fonts, everything a real visitor's browser would fetch and render) rather than a bare HTTP request, so it measures render heavy sustained load instead of raw throughput. Every iteration clears cache and cookies first so each load behaves like a fresh visitor rather than benefiting from the browser's own caching. Since a real Chrome tab costs far more CPU and memory than a curl request, its default and maximum concurrency are both lower than the raw Stress preset.
  • Optional WooCommerce add on: shop, product, and cart pages, plus a simulated add to cart and checkout page load.

Complexity presets

Preset Homepage iterations Profile pages Browser metrics Load test Stress test
Quick 1 No Yes No No
Standard 2 Yes Yes No No
Thorough 3 Yes Yes Yes, adjustable concurrency (default 10, max 50) No
Stress 1 No No No Yes, raw HTTP, adjustable concurrency (default 20, max 100) and duration (5-60s, default 15s)
Browser Stress 1 No No No Yes, real Chrome page loads, adjustable concurrency (default 5, max 50) and duration (5-60s, default 15s)

Stress and Browser Stress both skip the profile page walk, headless browser metrics, and the Thorough load test entirely; the point of those presets is putting the host under sustained load, not a full page by page audit, so they go straight from the lightweight discovery steps (timing, headers, cache, tech stack, hosting provider) into the sustained burst.

Profile pages (Standard/Thorough) walk a small profile of extra pages beyond the homepage: an internal content page for a plain WordPress run, plus the shop/product/cart/checkout flow when the WooCommerce add on is turned on. These are defined in config/profiles/wordpress.json and config/profiles/woocommerce-addon.json.

Reports and comparisons

Every completed run gets its own static report.html, written next to its result.json and viewable at report.php?id=<run id> right from the dashboard. Reports are self contained (the screenshot is embedded as a base64 image) aside from linking back to the app's own stylesheet and a small chart script, so nothing external is needed to view one. A report includes: summary cards with Core Web Vitals rating badges, timing breakdown, redirect chain, page resource breakdown, caching/tech stack (including PHP version and compression), hosting provider, Server-Timing breakdown, WordPress/WooCommerce detection, profile page timings, load test results, stress test results (raw HTTP and/or real browser), screenshot, and raw headers.

To compare runs, check 2 to 4 completed rows in the dashboard's history table and hit "Compare selected". That takes you to compare.php?ids=a,b,c, which lays out the key metrics side by side (including hosting provider, server software/PHP version, compression, third-party bytes, WooCommerce detection, and load test concurrency/avg/p95) plus a bar chart of total load time.

Requirements

  • PHP 8.1 or newer, with the curl, json, pcntl, and posix extensions.
  • Composer.
  • A local Chrome or Chromium install (used headless for the browser based metrics), via chrome-php/chrome.

Setup

composer install
php -S localhost:8080 -t public

Then open http://localhost:8080 in your browser.

If Chrome/Chromium isn't on your PATH in a location chrome-php can auto-detect, point at it explicitly with the WPBENCH_CHROME_BINARY environment variable or the chrome_binary setting in config/config.php.

Deploying under Apache/mod_php

WPBench also runs fine behind Apache with mod_php rather than the PHP built-in server, with two things worth knowing:

  • The PHP process runs as whatever system user Apache runs as (often apache or www-data), not your own user. Make sure data/runs/ is writable by that user, e.g. chmod -R a+rwX data.
  • Headless Chrome's crash handler needs a writable HOME directory to start up at all; if Apache's service user has a read-only or non-existent home directory, the browser step fails immediately with a crashpad error. WPBench works around this itself by giving Chrome an isolated, writable temp profile directory and HOME/XDG_* env vars for every run, so no server configuration changes are needed for this specifically.

Configuration

All runtime settings live in config/config.php:

  • data_dir / profiles_dir — where run data and profile JSON files live.
  • chrome_binary / chrome_no_sandbox — headless Chrome setup.
  • http_timeout / browser_timeout — request and page load timeouts.
  • user_agent — the UA string WPBench identifies itself with.
  • complexity_presets — iteration counts and load test toggle per preset.
  • load_test_concurrency — default concurrency per preset (Thorough only by default).
  • load_test_concurrency_max — hard ceiling on the concurrency a run can request from the dashboard, regardless of what's typed in. Exists so the burst test can't be turned into a real denial of service tool against whatever URL it's pointed at.
  • stress_test — defaults and bounds for the Stress preset: default_concurrency/concurrency_max (20/100 by default) and default_duration_s/duration_min_s/duration_max_s (15s default, 5-60s range). Both concurrency and duration are adjustable per run from the dashboard and clamped server side to these bounds, for the same denial of service concern as load_test_concurrency_max, just doubly so since this one runs for a while instead of an instant.
  • browser_stress_test — the same shape of defaults and bounds as stress_test, for the Browser Stress preset. Its default and maximum concurrency (5/50 by default) are lower than the raw Stress preset's, since a real Chrome tab costs far more CPU and memory than a curl request; raise concurrency_max here if your hardware can take more.

Project layout

  • public/ — the web front end (index.php dashboard, compare.php, report.php) and JSON API endpoints (public/api/: start.php, status.php, abort.php, runs.php).
  • src/Run/ — run lifecycle: creating runs, reading/writing meta and status JSON, listing run history (RunManager), launching the background test process (ProcessLauncher).
  • src/Runner/ — TestRunner, which walks the step pipeline for a run and assembles result.json.
  • src/Http/ — the cURL based HTTP client used for all page fetches (CurlClient, CurlResponse, HeaderBag).
  • src/Detect/ — signature based detection: CacheDetector (edge/CDN, webserver, WP caching plugins), HeaderAnalyzer, TechDetector (server software, PHP version, compression, Server-Timing), HostingProviderDetector (managed WP host fingerprinting), WordPressDetector (theme/plugin/WooCommerce detection).
  • src/Checks/ — the individual test steps: ChromeBrowserCheck (headless browser metrics and screenshot), ProfilePagesCheck (profile page walk), LoadTestCheck (single wave concurrency burst for Thorough), StressTestCheck (sustained raw HTTP concurrency burst, used by the Stress preset), BrowserStressTestCheck (sustained real Chrome page load burst, used by the Browser Stress preset, orchestrating bin/browser-stress-worker.php worker subprocesses since PHP can't drive more than one blocking Chrome navigation at a time), and LoadStats (the shared timing statistics all three burst checks report through).
  • src/Report/ — ReportGenerator (builds report.html) and CoreWebVitals (rates raw metrics against Google's thresholds).
  • bin/ — CLI entry points: run-test.php (the background test runner script launched by ProcessLauncher) and browser-stress-worker.php (one concurrency slot's worth of sustained real Chrome navigation for the Browser Stress preset, launched by BrowserStressTestCheck).
  • config/ — app config (config.php) and test profile definitions (config/profiles/*.json).
  • data/runs/ — where run results and reports get written. Not tracked in git.
  • tests/ — PHPUnit tests, mirroring the src/ namespace layout.

Running the tests

vendor/bin/phpunit

License

GPL-3.0-or-later. See LICENSE for the full text. Copyright (C) 2026 Brad Boegler <bradthx@gmail.com>

About

A PHP based WordPress benchmarking tool. Tests hosts for speed, caching behavior, and tech stack.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages