magepsycho / magento2-profiler-ui
Admin viewer for MagePsycho_Profiler JSON reports
Package info
github.com/MagePsycho/magento2-profiler-ui
Language:JavaScript
Type:magento2-module
pkg:composer/magepsycho/magento2-profiler-ui
Requires
README
Magento 2 Enhanced Profiler UI
Overview
Magento 2 Enhanced Profiler UI is the admin viewer for the json reports written by MagePsycho_Profiler.
System → Tools → Enhanced Profiler Reports
Pick a recorded run from the dropdown and get an interactive view of it: a collapsible tree, a sortable and filterable flat table, or a timeline of every individual call. Admin-side on purpose — the storefront may be headless, and profiler output is not something to expose there in any case.
It only reads what MagePsycho_Profiler writes. It adds nothing to the recording side, registers no plugins and touches no request path other than its own, so it can be left uninstalled in production without changing what gets profiled.
Why a separate module
MagePsycho_Profiler is meant to be safe to enable anywhere; it is an output type plus instrumentation and has no HTTP surface of its own. An admin UI is a different proposition — controllers, ACL, an attack surface — so it installs separately and can be left off in production.
Demo
Tree, Flat and Timeline views of a recorded run:
Key Features
- Admin page at System → Tools → Enhanced Profiler Reports, gated behind its own ACL resource
- Three views of the same run — Tree, Flat and Timeline — switched from one radio group
- Self column heat-shaded by magnitude, so time that belongs to no child timer is visible at a glance
- Sort on any column, filter by timer id, and narrow by minimum time with sliders seeded from the loaded report
- Timeline of every recorded call, ordered by start, indented by depth, coloured by subsystem with a legend built from the run
- Run picker shows the span count per report, so you know what you are opening
- Clear all reports empties the report directory and the index in one action
- Every cell rendered with
textContent— a report containing<script>stays text - Reads only what
MagePsycho_Profilerwrites; zero recording overhead, safe to leave uninstalled in production
Feature Highlights
The Report Table
| Column | Meaning |
|---|---|
| Timer Id | Leaf name in tree view, full a->b->c path in flat view |
| Cnt | Times the timer was started |
| Time | Total wall time, inclusive of children |
| Self | Time minus the sum of direct children — where work with no timer of its own shows up |
| Avg | Time / Cnt |
| Emalloc / RealMem | Memory deltas |
| % | Share of the root timer |
Self is the column worth learning. On a real POST /V1/integration/customer/token, magento measured 96.2ms while its children summed to 45.6ms — 53% of the request had no timer on it at all. That gap is invisible in the Time column and obvious in Self.
Tree, Flat And Timeline
Three modes, picked from the View switch.
Tree and Flat are deliberately exclusive, because a filtered tree lies: hide a parent and its children appear to hang off nothing.
Tree (the default) is structural browsing. Real HTML indentation with an expand/collapse control on every row that has children — no ├─ box-drawing, which is a terminal constraint the ASCII log has to live with and a web page does not. Expand all / Collapse all are in the toolbar; Collapse all keeps the roots open so the top level stays readable. Parent rows are bold, and hovering a row shows its full a->b->c path as a tooltip.
Flat is filtering and ranking. It shows the full timer path on every row, since indentation means nothing once rows are reordered or their parents filtered away.
Switching happens on its own: typing in the filter, dragging a slider or clicking a column header drops you into flat view; re-ticking Tree clears the filter, sliders and sort so the hierarchy is honest again.
The sliders are seeded from the loaded report, so their range always matches the data in front of you.
Timeline is one bar per recorded call, ordered by start, indented by depth, coloured by subsystem (SQL:, CACHE:, EVENT:, …) with a legend built from whatever the run actually contains. This is where you see when something happened rather than just how long it took in total — a burst of queries mid-request, a cache stampede, one slow call versus four hundred fast ones.
Every capture records spans, so Timeline works on any recent report — MAGE_PROFILER=json and MAGE_PROFILER=timeline are the same thing. The radio only disables for reports recorded with MAGE_PROFILER_MAX_SPANS=0, or from before spans were always captured. The Timer Id filter and the min-time slider both narrow the chart.
The x-axis uses wall_ms (first start to last end), not total_ms (the sum of root timers that the % column is a share of). On a CLI run those differ by ~40% because the roots are sequential.
Recording Something To Look At
The viewer reads the json output, so a run has to exist before there is anything in the dropdown:
MAGE_PROFILER=json bin/magento indexer:reindex # CLI MAGE_PROFILER=tabular,json bin/magento cache:clean # terminal table + report
document.cookie = 'MAGE_PROFILER=json; path=/'; // web + API, in the browser console
The cookie accepts the same values as the environment variable — tabular,json and timeline both work there too. json is what this viewer reads; tabular writes a plain-text log instead, which is worth adding on CLI (it prints to STDERR as the command finishes) and rarely worth it on a web request, where there is no terminal to print to.
Cookie activation is gated — developer mode, or a :<secret> suffix matching MAGE_PROFILER_SECRET. See MagePsycho_Profiler for the full activation and security rules.
Retention
MagePsycho_Profiler prunes as it writes: newest MAGE_PROFILER_KEEP runs (default 100), nothing older than MAGE_PROFILER_KEEP_DAYS (default 7). A browsing session with the cookie set writes one report per request, AJAX included, so this matters. Clear all reports empties the directory and the index.
🛠️ Installation
This module requires MagePsycho_Profiler; Composer pulls it in automatically.
1 Using Composer (Preferred)
composer require magepsycho/magento2-profiler-ui
2 Using Modman
modman init
modman clone git@github.com:MagePsycho/magento2-profiler-ui.git
3 Using Zip File
- Download the Extension Zip File
- Extract & upload the files to
/path/to/magento2/app/code/MagePsycho/ProfilerUi/
After installation by either means, activate the extension with following steps
- Enable the module
php bin/magento module:enable MagePsycho_ProfilerUi --clear-static-content
php bin/magento setup:upgrade
php bin/magento setup:di:compile
- Flush the store cache
php bin/magento cache:flush
- Deploy static content - in Production mode only
rm -rf pub/static/* var/view_preprocessed/*
php bin/magento setup:static-content:deploy
- Record a run, then open the viewer
MAGE_PROFILER=json php bin/magento cache:clean
System → Tools → Enhanced Profiler Reports
The extension creates no tables of its own.
Configuration
There is no store configuration. The viewer has nothing to configure — what it shows is decided by what MagePsycho_Profiler recorded, and that module's own settings live at Stores > Configuration > MagePsycho > Enhanced Profiler.
Access is controlled by role, not config: System > Permissions > User Roles > Role Resources → Enhanced Profiler Reports (MagePsycho_ProfilerUi::viewer).
The MagePsycho_Profiler environment variables that change what the viewer sees:
| Variable | Effect here | Default |
|---|---|---|
MAGE_PROFILER_REPORT_DIR |
Directory the run picker lists | var/log/profiler |
MAGE_PROFILER_MAX_SPANS |
0 records no spans, which disables the Timeline radio |
5000 |
MAGE_PROFILER_KEEP |
Newest N runs retained | 100 |
MAGE_PROFILER_KEEP_DAYS |
Maximum age retained | 7 |
Security
The viewer renders data derived from untrusted input, so a few things are deliberate rather than incidental:
- Every cell is written with
textContent, neverinnerHTML. Timer ids carry client-controlled strings — GraphQL operation names and unmatched REST paths both end up in them — so a report can legitimately contain<script>. Rendering it as markup would turn a profiling tool into stored XSS in the admin panel. - The
x-magento-initpayload is encoded withJSON_HEX_TAG|JSON_HEX_AMP|JSON_HEX_APOS|JSON_HEX_QUOT, so no literal<survives and a</script>breakout is impossible. HTML entities are not decoded inside<script>, soescapeHtml()would have produced invalid JSON — this is the correct guard. - The
fileparameter isbasename()d and must appear inindex.jsonlbefore anything is opened, on top ofDirectory\Read's ownPathValidator. Verified against traversal, encoded dots, absolute paths, null bytes andsub/../index.jsonl. - All three controllers require
MagePsycho_ProfilerUi::viewer; Clear all is POST + form key. - Reports live in
var/log/profiler/, which is outside the document root (document_root_is_pub), so they are reachable only through this ACL-gated controller. - Requests to the viewer's own route are not profiled — otherwise browsing reports would bury the run you opened the viewer to look at.
Developer Notes
Files
| Path | Role |
|---|---|
Model/ReportRepository.php |
Lists, reads and clears reports; owns the path guards |
Controller/Adminhtml/Report/{Index,Data,Clear}.php |
Page, JSON endpoint, clear action |
Block/Adminhtml/Report.php |
Run list + endpoints for x-magento-init |
view/adminhtml/templates/report.phtml |
Markup; all output escaped |
view/adminhtml/web/js/report.js |
Tree/flat/timeline rendering, sort, filter, sliders |
Admin route and ACL
| Piece | Value |
|---|---|
| Route | magepsycho_profiler/report/index |
| ACL resource | MagePsycho_ProfilerUi::viewer (under Magento_Backend::system) |
| Menu | Magento_Backend::system_tools → Enhanced Profiler Reports |
etc/module.xml sequences after Magento_Backend and MagePsycho_Profiler.
Static analysis
vendor/bin/phpstan analyse -c app/code/MagePsycho/ProfilerUi/phpstan.neon --memory-limit=1G vendor/bin/phpcs --standard=Magento2 --extensions=php,phtml app/code/MagePsycho/ProfilerUi/
Changelog
Version 1.0.0 (2026-08-08)
- Initial Release.
Authors
Contributors
To Contribute
Any contribution to the development of Magento 2 Enhanced Profiler UI is highly welcome.
The best possibility to provide any code is to open a pull request on GitHub.
Need Support?
If you encounter any problems or bugs, please create an issue on GitHub.
Please visit our store for more FREE / paid extensions OR contact us for customization / development services.



