This module provides real-time monitoring of your Drupal website's performance, tracking database queries, PHP execution, and frontend rendering. It sends instant alerts via email or Slack when performance issues are detected, without requiring heavy external installations.
Real-time Performance Monitor finds out why a Drupal site is slow and tells you where to fix it. It traces requests from the browser through PHP and SQL to external APIs, records every duplicate query, slow function, uncacheable page and heavy asset, and alerts you by email, Slack or the Drupal log. It runs inside Drupal, with no external agent or SaaS account.
It works like a lightweight, self-hosted Application Performance Monitoring (APM) tool in the style of Dynatrace or New Relic, built specifically for Drupal. It understands Drupal concepts that generic tools do not: routes and bundles, Page Cache and Dynamic Page Cache headers, cache bins served by Redis, Memcache or MySQL, cache contexts that make a page uncacheable, and the PHP code that issued each SQL statement.
Why use it
- Find N+1 and duplicate SQL, and why it happens. Every repeated statement is shown with the PHP class, method, file and line that ran it. It also says whether the data should have come from Redis, Memcache or a MySQL cache table.
- See where a slow page spends its time. Each traced request is split into PHP code, SQL and external HTTP calls. A timeline covers routing, controller, rendering and post-response work.
- Catch pages that silently stop caching. You are alerted when a page comes back
UNCACHEABLEfrom Page Cache or Dynamic Page Cache. The alert names the likely culprit:max-age 0, auserorsessioncache context, too many cache tags, or the kill switch. - Measure what real visitors experience. Core Web Vitals (LCP, INP, CLS, TTFB) come from real browsers, along with the slowest CSS, JS, fonts and images, and JavaScript errors. Each page view is linked to the backend trace that produced it.
- Get told about regressions. Baseline-based problem detection compares every request group with its own 7-day history, and gives root-cause hints such as a slower SQL statement, a new N+1 loop, a cache hit drop or a recent deployment.
- Prove a release made things better or worse. Deployment markers from Drush or CI/CD come with a before/after comparison of each release.
Features
Alerts (email, Slack, watchdog)
Alert Triggered when What the alert contains Duplicate SQL A statement runs at least N times in one request (default 5) Each repeated statement with its count and time, whether it repeats with identical arguments or is an N+1 pattern, its calling code, and its cache source: a Redis/Memcache miss that fell through to MySQL, a read from a MySQLcache_* table (the bin is not on Redis/Memcache), a cache-tag lookup, or a query with no cache layer. Includes a summary of the site's Redis/Memcache setup and connection status.
Slow SQL
A statement takes longer than the limit (default 100 ms)
The statement, its duration and caller, plus an automatic EXPLAIN that flags full table scans, unused or missing indexes, Using filesort and Using temporary
Slow PHP / high memory
The request exceeds the time limit (default 1000 ms) or the peak memory limit (default 256 MB)
Controller vs core/render time, peak memory, and the top functions when XHProf is installed
Uncacheable page
A successful, non-admin GET response is UNCACHEABLE in Page Cache or Dynamic Page Cache
Both cache headers, max-age, cache contexts, tag count and the likely culprits
Frontend
A real visitor's page load, LCP, INP, CLS or TTFB breaches its limit
Core Web Vitals with good/poor ratings, JS and CSS payload, unused CSS, render-blocking resources, the slowest and heaviest assets (server wait vs download), image checks (lazy loading, oversized, JPEG/PNG) and CSS/JS aggregation status
Problem detected
Behaviour deviates from its baseline (see Problems below)
The problem, its impact and root-cause analysis
Each channel (watchdog, email, Slack) has its own frequency cap per URL and alert type: always, daily, weekly, every two weeks or monthly. Paths can be excluded with wildcards.
APM dashboards
All dashboards are under Reports → Performance APM (/admin/reports/apm). Every tab has a ? link that opens a detailed help page for it.
/{lang}/case-studies/*|Case study page), falling back to route and bundle. Each group gets percentiles, failure %, cache hit %, SQL per request and time share.
Traces
One trace per sampled request, similar to Dynatrace PurePath. It shows the timing breakdown and request timeline, request attributes (route, controller, bundle, language, anonymous or logged-in, X-Drupal-Cache, X-Drupal-Dynamic-Cache), and the SQL run (count, time, duplicates, cache source, caller). It also shows outgoing HTTP calls, XHProf functions, warnings and errors logged during the request, and the real-user page views of that response. You can filter by cache status, user type, language, minimum time, 5xx and key requests.
Database
SQL statements across all traces, ranked by total time contribution, with executions per request (N+1 flag), identical repeats, cache source and calling code. Each statement links to every trace that ran it.
Code hotspots
Time spent per lifecycle phase, PHP time per controller, the slowest methods (XHProf), and external services with call count, latency and errors.
Frontend (RUM)
Core Web Vitals at the 75th percentile by page, browser and device. The resource waterfall splits each resource into DNS, TCP/TLS, server wait and download, and flags third-party resources. JavaScript errors are grouped by message, source and line.
Issue history
Every duplicate SQL, slow SQL, slow PHP / slow function, uncacheable page and heavy CSS/JS or slow-asset occurrence. Records are kept even when the alert itself was throttled, for 30 days by default (configurable). It shows the top duplicate statements, slowest functions and pages, JS/CSS size per page, and the assets taking the most time to load.
Problems
Automatic, baseline-based problem detection (see below)
Releases
Deployment markers with a before/after comparison of p50, p90, failure rate, SQL per request and cache hit rate, plus the request groups that regressed the most
Synthetic
Scheduled HTTP monitors for key URLs, with availability, response time, server wait, cache header and run history
SLOs
Service level objectives such as "95% of homepage requests under 1 s" or "availability 99.9%", with error budget tracking
Topology
A live dependency map: real users → page cache → PHP → Drupal → MySQL / Redis / Memcache → external services, with the health of each
Health report
Live checks of Page Cache and Dynamic Page Cache, max-age, Twig debug and cache, and cache bins still stored in MySQL. It also shows the Redis/Memcache hit ratio and evictions, CSS/JS aggregation, OPcache hit rate and memory, PHP-FPM workers and "max children reached", the last cron run, queue backlog, dblog volume and available profilers.
Problem detection and root-cause analysis
On cron, at most every 5 minutes, the last hour of each request group is compared with its own 7-day baseline. A problem is opened when:
- p90 response time is higher than the baseline by a configurable factor (default 1.5×) and by at least 200 ms
- the 5xx failure rate is at least 5% and 5 points above the baseline
- real-user LCP (p75) for a page is above 4 seconds
- a synthetic monitor fails twice in a row
Each problem comes with root-cause hints:
- which layer grew the most (PHP code, SQL or external calls)
- whether SQL executions per request jumped (a new N+1 loop)
- which SQL statement added the most time
- whether the Drupal cache hit ratio dropped
- whether a deployment happened shortly before
Problems resolve automatically when the condition clears.
How data is collected
- Backend traces: Drupal's database query log, kernel events and the
http_clientmiddleware capture SQL, timing and outgoing HTTP calls. A logger records warnings and errors per request. A trace ID is added to each response asServer-Timing: rpm;desc=…, including page-cache hits. - Sampling: a configurable share of requests is traced (default 25%). Slow requests, 5xx responses, requests that logged errors, and synthetic checks are always kept. Traces are written after the response has been sent.
- Real User Monitoring: a small script uses the browser's
PerformanceObserverand Resource Timing APIs. For a configurable share of page views (default 25%), plus every page view with a JavaScript error, it sends a beacon when the page is hidden. The beacon endpoint is rate-limited per IP and validates and sanitizes all input. - Storage: data is kept in the site's own database, in 9 tables prefixed
rpm_. Old rows are purged on cron: traces and RUM after 7 days by default, issue history after 30 days. Both periods are configurable.
Requirements
- Drupal 10, 11 or 12
- MySQL or MariaDB for the automatic
EXPLAINon slow queries. The other features work on any database Drupal supports. - Optional: the Redis or Memcache module, for cache-source attribution and hit-ratio statistics
- Optional: the XHProf PHP extension, for function-level hotspots
- Optional: PHP-FPM, for the worker statistics in the Health report
Installation
- Install as you would normally install a contributed Drupal module:
composer require drupal/realtime_performance_monitor, thendrush en realtime_performance_monitor. - If you are updating from an earlier release, run
drush updbto create the APM tables. - Grant the View performance APM dashboards permission to the roles that need the dashboards. The permission is marked as restricted, because traces contain URLs and SQL text.
- Make sure cron runs regularly. Problem detection, synthetic monitors and data purging run on cron.
Configuration
Go to Configuration → Development → Real-time Performance Monitor (/admin/config/development/real-time-performance-monitor). The page starts with quick links to every dashboard.
- Thresholds: the slow SQL limit, duplicate query limit, PHP time and memory limits, and the page load, LCP, INP, CLS and TTFB limits
- Cache layer: uncacheable-page alerts, and an optional
Server-Timingheader that shows Drupal, controller and SQL time in the browser DevTools - APM: the trace and RUM sample rates, retention periods, request naming rules, key requests, SLOs, problem detection sensitivity, synthetic monitors and the deployment API token
- Notifications: watchdog, email recipients and a Slack webhook, each with its own frequency cap
Example request naming rules
/admin/*|Admin
/jsonapi/*|JSON:API
/{lang}/case-studies/*|Case study page
/user/login|Login pageExample SLOs
Homepage under 1 s|/|1000|95 Availability|*|avail|99.9
Drush commands
Command Purposedrush rpm:deployment 2.14 --description="Sprint 42"
Record a deployment marker, typically right after drush deploy. Alias: rpm-deploy.
drush rpm:detect-problems
Run problem detection now
drush rpm:synthetic
Run all synthetic monitors now
Deployment markers from CI/CD
curl -X POST https://example.com/api/real-time-performance-monitor/deployment \
-H "X-RPM-Token: $RPM_TOKEN" \
-H "Content-Type: application/json" \
-d '{"release":"2.14","description":"Sprint 42"}'The API is disabled until a token is set on the settings page.
Performance and privacy considerations
- Overhead: the module enables Drupal's database query log, which records a backtrace for every query. Traced requests also write a few rows to the database after the response has been sent. On busy production sites, start with a low trace sample rate and increase it as needed. Monitoring can be switched off completely with one checkbox.
- What is stored: URL paths, route names, SQL statements with placeholders (no argument values), the host and path of outgoing HTTP calls (query strings are dropped, because they often carry API keys), log messages, and JavaScript error messages. The module's tables store no user IDs, IP addresses, cookies or form data; only an anonymous vs logged-in flag is kept per trace. Core's flood table briefly holds visitor IPs for rate limiting.
- Server-Timing header: the optional detailed
Server-Timingheader exposes internal timings to anyone. Enable it on non-production environments, or behind a CDN that strips it. The trace ID entry is always added while tracing is on. - Public endpoints: the RUM and telemetry endpoints are public by design, because browsers must be able to post to them. They are rate-limited, size-limited and sanitized.
Limitations
- Only requests handled by Drupal are traced. Drush, cron and queue workers are not traced, and a CDN, load balancer or Varnish in front of Drupal is not visible.
- Redis and Memcache calls are not timed individually. Their time is counted as PHP code, and "miss" in cache-source labels is inferred from the cache configuration.
- INP, LCP and long tasks are only reported by Chromium-based browsers.
- Synthetic monitors are HTTP checks. They do not execute JavaScript or follow scripted click paths.
- Values on the dashboards are based on sampled traces.
Similar and complementary tools
- Webprofiler is excellent for inspecting a single page in depth while developing. Real-time Performance Monitor watches all traffic continuously, keeps history and alerts you.
- Commercial APM tools such as Dynatrace, New Relic and Blackfire offer deeper, infrastructure-wide tracing with an installed agent. This module covers the Drupal-specific parts without an agent or external account, and it works alongside those tools.
Ask your assistant about Real-time Performance Monitor
Check compatibility with your Drupal and PHP version, maintenance and security coverage, from current release data. How it works
Depends on
Dependencies of the latest stable release
No dependencies recorded for this project.
Required by
Tracked projects that depend on this one
No tracked projects depend on this one yet.