feat: add wordpress-performance-diagnostics skill
Paired skills.sh/autoskills.sh catalog scans (2026-08-15, reports in granja/_temp/codex-logs/) independently flagged wordpress/agent-skills' wp-performance module as the best net-new find of the whole scan: a measurement-first, backend-only WP diagnostic workflow (WP-CLI doctor/profile, headless Query Monitor, autoload/object-cache/cron checks) — a strong fit since this fleet's 13+ WordPress sites are all headless/jailed with no browser-first profiling access. Adapted from github.com/wordpress/agent-skills (skills/wp-performance) rather than installed verbatim: swapped the upstream's local/SSH assumptions for the fleet's actual `bastille cmd ... su -m www` remote-execution pattern (reusing wordpress-cli-remote-execution's convention exactly), added a jail/site selector step against the real granja/staging/gringo inventory, narrowed the always-collect baseline to four metrics (TTFB, autoload size, object-cache presence, cron health) with fleet-specific context for each, added redaction guidance for Valkey/DB credentials that diagnostic output can surface, and replaced the upstream's hardcoded "WordPress 7.0+" compatibility claim with per-site version verification. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
# Deeper diagnostic tools (still read-only, still backend-only)
|
||||
|
||||
Load this file only once the four core baseline metrics (TTFB, autoload
|
||||
size, object-cache presence, cron health — see `SKILL.md` Step 3) haven't
|
||||
explained the symptom. Every command here still goes through the fleet's
|
||||
`bastille cmd ... su -m www -c "wp ..."` wrapper from `SKILL.md` Step 2 —
|
||||
abbreviated to bare `wp ...` below for readability.
|
||||
|
||||
## `wp doctor` — quick production-readiness checks
|
||||
|
||||
Catches common footguns fast: autoload bloat, `SAVEQUERIES`/`WP_DEBUG`
|
||||
left on, stale plugin/core versions.
|
||||
|
||||
```bash
|
||||
wp doctor check
|
||||
wp doctor list # see available checks
|
||||
```
|
||||
|
||||
Install if missing (this is a write action — confirm it's acceptable
|
||||
before running it, per Step 0):
|
||||
|
||||
```bash
|
||||
wp package install wp-cli/doctor-command
|
||||
```
|
||||
|
||||
Checks especially relevant to performance:
|
||||
|
||||
- `autoload-options-size` — autoloaded-options threshold
|
||||
- `constant-savequeries-falsy` / `constant-wp-debug-falsy` — flags
|
||||
perf-costly debug constants left on in what should be production
|
||||
- cron checks (event count / duplicates)
|
||||
|
||||
Docs: default checks —
|
||||
https://make.wordpress.org/cli/handbook/doctor-default-checks/ ·
|
||||
customizing checks —
|
||||
https://make.wordpress.org/cli/handbook/guides/doctor/doctor-customize-config/
|
||||
|
||||
## `wp profile` — hook/stage-level profiling without a browser
|
||||
|
||||
Install if missing (write action, confirm first):
|
||||
|
||||
```bash
|
||||
wp package install wp-cli/profile-command
|
||||
```
|
||||
|
||||
Recommended sequence:
|
||||
|
||||
1. **Stage overview** — where time goes across bootstrap/main_query/template:
|
||||
```bash
|
||||
wp profile stage --fields=stage,time,cache_ratio [--url=<url>]
|
||||
```
|
||||
2. **Hook hotspots**:
|
||||
```bash
|
||||
wp profile hook --spotlight [--url=<url>]
|
||||
wp profile hook init --spotlight [--url=<url>] # drill into one hook
|
||||
```
|
||||
3. **Targeted eval**:
|
||||
```bash
|
||||
wp profile eval 'do_action("init");' --hook=init
|
||||
```
|
||||
|
||||
Use `--url=` to profile a specific site/route on a multisite-adjacent
|
||||
setup. `--skip-plugins`/`--skip-themes` can isolate a culprit component,
|
||||
but changes real behavior while active — note that in the report if you
|
||||
use it, don't silently profile a degraded version of the site.
|
||||
|
||||
Docs: https://wpcli.dev/docs/profile/stage ·
|
||||
https://wpcli.dev/docs/profile/hook ·
|
||||
https://wpcli.dev/docs/profile/eval
|
||||
|
||||
## Query Monitor, used headlessly
|
||||
|
||||
Query Monitor is normally UI-driven, but it exposes data through
|
||||
authenticated REST response headers/envelope — no browser required.
|
||||
|
||||
1. Confirm the plugin is active: `bastille cmd <jail> test -f
|
||||
<site-path>/wp-content/plugins/query-monitor/query-monitor.php`.
|
||||
2. Authenticate against the REST API — a nonce, or an Application
|
||||
Password (see Redaction in `SKILL.md`: never paste a generated
|
||||
Application Password into a report).
|
||||
3. Request a REST route and inspect response headers (`x-qm-overview-*`
|
||||
etc.), or request an enveloped response (`?_envelope`) to get a `qm`
|
||||
property containing DB query details, cache stats, and HTTP API call
|
||||
details in one payload.
|
||||
|
||||
Configuration constants: https://querymonitor.com/help/configuration-constants/ ·
|
||||
REST-API-specific docs: https://querymonitor.com/wordpress-debugging/rest-api-requests/ ·
|
||||
plugin page: https://wordpress.org/plugins/query-monitor/
|
||||
|
||||
Guardrails: Query Monitor adds real overhead — don't enable it in
|
||||
production without approval. If a hosting platform pre-installs it
|
||||
gated behind a capability, you may need `view_query_monitor` granted to
|
||||
the authenticating user.
|
||||
|
||||
## Server-Timing headers (if Performance Lab is present)
|
||||
|
||||
```bash
|
||||
curl -sS -D - -H 'Host: <domain>' http://<jail-ip>/ -o /dev/null | grep -i '^server-timing:'
|
||||
```
|
||||
|
||||
Requires the Performance Lab plugin (or a module of it) enabled —
|
||||
https://wordpress.org/plugins/performance-lab/. Don't enable experimental
|
||||
modules in production without approval; this is a nice-to-have on top of
|
||||
the four core baseline metrics, not a replacement for them.
|
||||
|
||||
Benchmarking guidance: https://make.wordpress.org/performance/handbook/measuring-performance/benchmarking-server-timing/
|
||||
|
||||
## General measurement discipline
|
||||
|
||||
- Always capture a baseline before changing anything.
|
||||
- Keep the test scenario fixed — same URL/route, same logged-in-or-not
|
||||
state, same underlying data.
|
||||
- Prefer multiple samples and a median over a single run.
|
||||
|
||||
Measuring-performance handbook:
|
||||
https://make.wordpress.org/performance/handbook/measuring-performance/
|
||||
@@ -0,0 +1,133 @@
|
||||
# Fix patterns by bottleneck category
|
||||
|
||||
Load this file once `references/diagnostic-tools.md` (or the Step 3
|
||||
baseline metrics) has pointed at a dominant bottleneck. Pick *one*
|
||||
primary category to fix at a time — trying to fix everything in one pass
|
||||
makes the before/after Verification step in `SKILL.md` meaningless, since
|
||||
you won't know which change caused which delta.
|
||||
|
||||
Every fix below is still gated by `SKILL.md`'s Step 0 (read-only-first)
|
||||
and Verification (re-measure the same way afterward) sections.
|
||||
|
||||
## Database / query performance
|
||||
|
||||
Use when profiling points at DB time or a high query count.
|
||||
|
||||
- Avoid N+1 query patterns — batch queries, prime caches, avoid per-row
|
||||
lookups inside a loop.
|
||||
- Prefer `fields => 'ids'` in `WP_Query`/`get_posts()` when only IDs are
|
||||
needed.
|
||||
- Avoid expensive meta queries where possible; consider indexing or a
|
||||
schema change for genuinely hot lookups.
|
||||
- Use object caching for repeated reads (see below) rather than
|
||||
re-querying the same data every request.
|
||||
|
||||
Backend-only tools: Query Monitor via REST (query lists, stack traces,
|
||||
slow/duplicate flags) — see `diagnostic-tools.md`. `wp db query` for
|
||||
targeted SQL/`EXPLAIN` — be careful running this in production, it's a
|
||||
direct DB access path.
|
||||
|
||||
Query Monitor plugin: https://wordpress.org/plugins/query-monitor/
|
||||
|
||||
## Autoloaded options
|
||||
|
||||
Autoloaded options load on *every* request, so a large autoload payload
|
||||
hurts every page, not just the page that set the option.
|
||||
|
||||
Quick checks (see `SKILL.md` Step 3 for the always-collect total):
|
||||
|
||||
```bash
|
||||
wp option list --autoload=on --fields=option_name,size_bytes | sort -n -k 2 | tail
|
||||
```
|
||||
|
||||
Fix patterns:
|
||||
|
||||
- Stop autoloading large blobs — store large/rarely-read data with
|
||||
`autoload=off` instead.
|
||||
- Move large computed data to transients or the persistent object cache
|
||||
(if the site has one — check first, see `SKILL.md` Step 3 metric 3)
|
||||
rather than an autoloaded option.
|
||||
- Remove stale options left behind by removed plugins/themes — confirm
|
||||
nothing still reads the option before deleting; a stale-looking option
|
||||
name isn't proof nothing depends on it.
|
||||
|
||||
Docs: `wp option list` — https://wpcli.dev/docs/option/list · `wp doctor`'s
|
||||
`autoload-options-size` check —
|
||||
https://make.wordpress.org/cli/handbook/doctor-default-checks/
|
||||
|
||||
## Object caching
|
||||
|
||||
Use when profiling shows repeated identical queries or a low cache hit
|
||||
rate.
|
||||
|
||||
- WordPress's default object cache is per-request memory only — it does
|
||||
not persist across requests unless a persistent drop-in
|
||||
(`wp-content/object-cache.php`) is present. On this fleet, that's
|
||||
currently only true for `menorca.pt`/`menorca.ro` (Redis Object Cache
|
||||
plugin against cerebro's Valkey) — see `SKILL.md` Step 3 metric 3
|
||||
before assuming a site has one.
|
||||
- `wp cache flush` (or the Redis-plugin-specific `wp redis flush`) can
|
||||
affect more than the one site you're working on — cerebro's Valkey DB 0
|
||||
is genuinely shared across sites by key-prefix, not fully isolated per
|
||||
site (confirmed in `docs/server-cerebro.md`: `wp redis enable`'s
|
||||
`FLUSHDB` clears the whole shared DB 0, not just the calling site's
|
||||
prefix). Never flush without explicit approval.
|
||||
|
||||
Fix patterns:
|
||||
|
||||
- Cache expensive computed results (transients, or the persistent object
|
||||
cache where one exists) with explicit invalidation — don't rely on
|
||||
natural expiry alone for data that changes on a known event.
|
||||
- Avoid unbounded caches — set expirations, or implement real
|
||||
invalidation hooks tied to the data changing.
|
||||
- If a site doesn't have a persistent object cache and profiling
|
||||
genuinely points there, adding one is an infra change (provisioning
|
||||
Valkey ACL credentials on cerebro, installing the PHP Redis extension
|
||||
in the jail — package name `php85-pecl-redis`, not `php85-redis`, per
|
||||
`bastille-jail-provisioning`) — coordinate rather than doing it as a
|
||||
drive-by inside a performance investigation.
|
||||
|
||||
WP-CLI cache commands: https://wpcli.dev/docs/cache · flush guardrails:
|
||||
https://wpcli.dev/docs/cache/flush
|
||||
|
||||
## Cron
|
||||
|
||||
Use when cron causes request-time spikes or slowness.
|
||||
|
||||
```bash
|
||||
wp cron event list
|
||||
wp cron test # spawning health
|
||||
wp cron event run --due-now
|
||||
```
|
||||
|
||||
Fix patterns:
|
||||
|
||||
- De-duplicate scheduled events and reduce frequency where the task
|
||||
genuinely doesn't need to run that often.
|
||||
- Ensure cron callbacks are idempotent and short — a task that's slow
|
||||
enough to matter for site performance is a candidate for moving off the
|
||||
request path entirely, not just running it less often.
|
||||
- Move heavy work (imports, RSS/content-agent jobs — several granja sites
|
||||
already run these hourly, per `docs/server-granja.md`) fully off the
|
||||
page-load request path; a badly-timed spawn on a real WP-Cron pseudo-cron
|
||||
setup can add real latency to whichever request happens to trigger it.
|
||||
|
||||
WP-CLI cron command package: https://github.com/wp-cli/cron-command
|
||||
|
||||
## Remote HTTP API calls
|
||||
|
||||
Use when profiling shows slow external requests (`wp_remote_get()` etc.).
|
||||
|
||||
Fix patterns:
|
||||
|
||||
- Add explicit timeouts and fail-fast behavior — a slow or hanging
|
||||
third-party API shouldn't be able to make every page load slow.
|
||||
- Cache responses where the data doesn't need to be live on every request
|
||||
(transients / persistent object cache where available).
|
||||
- Batch requests, and avoid calling a remote API on every page load when
|
||||
a periodic cron-driven refresh would do.
|
||||
- Move genuinely heavy remote work off the request path entirely (cron or
|
||||
a queue), same principle as the Cron section above.
|
||||
|
||||
Query Monitor can report HTTP API call timing via the REST envelope
|
||||
(`qm` property) — see `diagnostic-tools.md`.
|
||||
Reference in New Issue
Block a user