Repository navigation
Conversation
Single helper replacing e.target everywhere; with shadow DOM coming, composedPath()[0] is the element the event actually originated from.
Infrastructure for distributing styles as constructed stylesheets; nothing consumes it yet.
CSS is no longer injected into the host page's document.head. Styles are distributed as constructed stylesheets (adoptedStyleSheets) built from the Tracy.css registry, so host-page styling cannot be affected by Tracy and vice versa. This also makes Tracy styles work under CSP without 'unsafe-inline' style-src. - Bar: <tracy-bar> shadow host, panels live inside a <tracy-div id=tracy-debug> wrapper kept for backward compatibility of third-party panel CSS; the panel popup window adopts the stylesheets explicitly in toWindow() - BlueScreen: <tracy-bs> shadow host; the standalone error page keeps a document-level <style> so it renders without JavaScript - standalone dumps: <tracy-dump> custom element with shadow root; upgrade is deferred while the parser is still constructing children
The shadow boundary now provides the isolation, so the #tracy-debug and #tracy-bs selector prefixes and most of reset.css are no longer needed.
Layers are named tracy-base and tracy-components so they cannot collide with the host page's own cascade layers (e.g. Tailwind).
setPointerCapture() replaces the document-level mousemove/mouseup + touchmove/touchend pairs, works for touch input (which passive touchmove listeners could not preventDefault), guarantees drag end delivery when the pointer leaves the window, and removes the module-global dragging flag. options.start is no longer invoked twice per drag.
…ssets renderTemplate() no longer reads and minifies CSS/JS assets for templates that do not embed them (agent markdown, AJAX content) and the expensive object-graph traversal in findGeneratorsAndFibers() is computed once per exception and reused by all render targets. The agent console block is no longer emitted into .html files written by renderToFile().
…ustom properties Adds 'auto' theme support driven by prefers-color-scheme. The .tracy-light and .tracy-dark classes emitted by the Renderer keep working.
- BlueScreen panel callbacks may return a 'text' key with a markdown section included in renderAgent() output and .md log files - new AgentPanel interface formalizes getAgentInfo() for Bar panels, method_exists() discovery still works - Bar::renderAgent() isolates a throwing panel instead of aborting - info panel provides a System Info section for agents - redirect queue entries are logged to console in chronological order
…ception reports
- every logged exception appends a JSON Lines record (time, level, hash,
message, report filename, source) usable by log aggregators and AI agents
- Logger::$retention ('30 days') deletes old .html/.md exception reports,
checked opportunistically at most once a day
The header offers a button that copies the complete markdown report (same content renderAgent() produces) to the clipboard - ready to paste into an issue tracker or an AI assistant. The payload is embedded as an inert JSON script inside the shadow root; the button appears only when the Clipboard API is available.
Re-stamps request time and output-buffer level, clears timers, barDump data, the live dump snapshot and per-request strategy state, and flushes FileSession so deferred content of the previous request is persisted. Intended for FrankenPHP worker mode, RoadRunner and Swoole.
- Bar lives in the top layer via the Popover API, no more z-index battles with host-page overlays and modal dialogs (with graceful fallback) - toggles are keyboard-accessible (tabindex + Enter/Space activation) - collapsed content uses hidden='until-found' so browser find-in-page reaches it and auto-expands the match - Logger::$notifiers[] hooks for one-line Slack/Sentry/webhook integrations - mailers receive the exception report filename as a third argument
opcache_get_status() defaults to $include_scripts=true, which builds an entry for every script in the opcode cache - a cache-wide list, so every vhost sharing the PHP pool inflates it. The result was used only for a membership test against get_included_files(). opcache_is_script_cached() answers that question per file with one hash lookup, so $include_scripts can be false. Measured with 1365 cached scripts on PHP 8.5: 156.92ms -> 0.06ms, with the row reporting the same "100 % cached". jit, memory_usage, interned_strings_usage and opcache_statistics are all still returned, so nothing else changes.
adrianbj
added a commit
to adrianbj/TracyDebugger
that referenced
this pull request
Aug 18, 2026
…dies Measured on PHP 8.5 / Tracy 2.12 with a warm opcode cache, a near-empty front-end page: 250ms and 954KB of response, of which 205ms was spent in Bar::renderPanels() and ~740KB was bar markup. Same page now: ~150ms and 317KB. A page with Tracy disabled is 70ms, so panel rendering was ~80% of what Tracy added. Bundled Tracy (all four version trees, marked "TracyDebugger patch:"): - info panel: @opcache_get_status() materialises per-script stats for the whole opcode cache - cache-wide, so every vhost sharing the PHP pool inflates it. 156.92ms of the request with 1365 cached scripts. The row only needs "was each of this request's files cached", which opcache_is_script_cached() answers in 0.06ms with identical output (100% cached, 231 of 232 files, and both agree file-for-file when file_update_protection leaves some uncached). Submitted upstream as nette/tracy#623. - asset responses carried Cache-Control: max-age=864000 next to a stale Expires: Thu, 19 Nov 1981 left by PHP's session cache limiter. Tracy already removed Pragma there; remove Expires too, and both in the deferred-content branch. Console panel: - the PW API autocomplete list (~200KB of JSON with descriptions) was inlined into every page whether or not the console was opened. It is now fetched from a tracyAutocomplete endpoint on editor init, with the cache key in the URL and an ETag. Only the current page's fields stay inline. Rebuilding the list also fixes an index reset that let the 108 procedural functions overwrite the first 108 API variable entries. - 92.8KB of the panel's JS was identical on every page load. Moved to scripts/console-panel.js, loaded via tracyJSLoader and cached per module version; the 10 values that were interpolated through the JS now travel on the tracyConsole config object. Panel body: 102,064B -> 14,746B. Deferred panel bodies: - panels listed in TracyDebugger::$deferrablePanels render a loading shell and fetch their body from the tracyPanelContent endpoint when the tab is first opened. Only panels whose content does not depend on the request that rendered the bar qualify. ProcessWire Info (saves 12.9ms and 45KB), ProcessWire Logs (66KB), Tracy Logs (68KB) and Methods Info (7.5KB) - the log panels gather entries in getTab() for the tab count, so those two save markup but not the gathering. - deliberately not deferred: Debug Mode and Request Info (queries, timers, pages loaded, page/permission state all belong to this request), Panel Selector (tracyPanelsOnce is a single-use cookie the original request consumes), Tracy Exceptions (builds its own links from REQUEST_URI). Those need shutdown-time rendering, i.e. nette/tracy#613. - the shell keeps the panel's own h1, because Tracy's Panel.init() captures h1 elements as drag handles at first open; only .tracy-inner is swapped. README: note that the bar compresses 645KB -> 76KB and PW ships no compression config.
adrianbj
added a commit
to adrianbj/TracyDebugger
that referenced
this pull request
Oct 22, 2026
…e (plus the OPcache patch) (#119) * Remove Tracy's asset-response Expires/Pragma headers from TracyDebugger instead of patching core PHP's session cache limiter queues "Expires: Thu, 19 Nov 1981" and "Pragma: no-cache" before Tracy serves ?_tracy_bar= assets, which contradicted the Cache-Control Tracy sends with them. TracyDebugger now removes both headers itself just before Debugger::enable() on those requests, so the local patch in tracy-2.12.x DeferredContent.php is dropped (the bundle injection patch there remains for now). * Restore upstream's BlueScreen footer emoji in tracy-2.12.x The emoji was removed because a 4-byte character broke deferred content stored in a utf8 (mb3) DB session. Tracy 2.10+ stores deferred content with FileSession, and TracyDebugger only switches to NativeSession when use_php_session is on and SessionHandlerDB is not installed, so the content never reaches a DB session. Tested with SessionHandlerDB on a utf8mb3 sessions table and use_php_session on: the AJAX BlueScreen arrives intact. * Add TracyDebugger's bar JS through Debugger::$customJsFiles instead of patched core properties Tracy caches the bar bundle by URL for 10 days, but TracyDebugger's $customJsStr/$customCssStr/$customBodyStr content depended on the request (tracyHidden/tracyShow cookies, $showPanels, the session), so a browser kept whichever variant it fetched first. The same URL gave a different bundle with and without the tracyHidden cookie. - the JS is built in $barJs and added as a content-hashed file in the TracyDebugger cache dir (barJsFile()), via the official customJsFiles, for every bundled Tracy version - the tracyHidden/tracyShow checks read document.cookie in the browser - the Dumps Recorder localStorage key is cleared when that panel isn't in the bar, instead of checking the session and $showPanels - font-awesome is loaded for users who could open the File Editor (or when PW Info panel custom links are set), not based on $showPanels - the font-awesome <link> and the show button are created as elements, not via innerHTML tracy-2.12.x drops the $customCssStr/$customJsStr/$customBodyStr properties from Debugger.php, and DeferredContent.php is now upstream's. * Remove stale bar JS cache files when barJsFile() writes a new one Each change to what feeds TracyDebugger's bar JS (config, module version, a new front end/admin or privilege variant) left another bar-<md5>.js in the cache dir, and nothing removed them. Writing a new file now also deletes other bar-*.js files more than a day old; a variant still in use is simply written again by its next request. * Narrow Tracy's hover reset from TracyDebugger's main.js instead of patching reset.css Upstream's "tracy-div:not(a b) *:hover { color: inherit; background: transparent }" beats any lower-specificity color/background rule on hover, so with it the Console and File Editor's Ace editor lost its theme background and colors while hovered, as did keyboard-shortcut boxes, filter-box counters, Viewports headings and Validator highlights (62 elements with every front-end panel open). The colors come from the user's Ace theme, so overriding them in styles.css doesn't work. main.js now finds that rule in each <style class="tracy-debug"> sheet and sets its selector to "tracy-div:not(a b) > *:hover", the selector the core patch used, on load and after Tracy.BlueScreen.loadAjax. tracy-2.12.x's reset.css is upstream's again. * Upgrade the tracy-2.12.x core to Tracy 2.12.2 tracy-2.12.x is now the v2.12.2 release as exported by git archive (which also adds phpstan-stubs.php, previously left out) plus the three remaining TracyDebugger patches, re-applied unchanged: - Debugger::$disableShutdownHandler (Console) - the Info panel's OPcache row (nette/tracy#623) - the dump location below the dump, with its full path 2.12.2 adds PHP 8.6 support and hides /*sensitive{*/ regions in the BlueScreen's markdown output; no API TracyDebugger uses changed. The bar bundle URL now carries v=2.12.2, so browsers fetch a fresh bundle. * Move dump locations below the dump from TracyDebugger's main.js instead of patching Dumper/Renderer.php Tracy's global dump() and bdump() render without going through TracyDebugger's PHP (Tracy's functions.php is loaded first), so the layout is now changed client-side. main.js wraps Tracy.Dumper.init, which runs for the page, panels, popups, AJAX BlueScreens, the Console and the Dumps Recorder: each <a class="tracy-dump-location"> moves out of its <pre> into a div.tracy-dump-location-container after it, without the pin emoji, followed by the file's path in the markup Helpers::editorLink() gives. The path shown has Debugger::$editorMapping applied, read from the link's editor URL by matching it against the URL templates TracyDebugger sets Debugger::$editor to (window.TracyDebuggerEditorTemplates, in the bar JS). Tested on pwtest with dump(), bdump() and bd(), for an external editor and the online editor: the markup is identical to the patched core's. tracy-2.12.x's Renderer.php is upstream's again. * Skip Tracy's shutdown handler for the Console from TracyDebugger instead of patching Debugger.php As suggested in nette/tracy#279: TracyDebugger registers a shutdown function just before Debugger::enable(), so it runs before Tracy's. When CodeProcessor sets TracyDebugger::$skipTracyShutdownHandler (as before, when use_php_session is 1 or on Tracy 2.7), it keeps error_get_last() in TracyDebugger::$consoleLastError for tracyConsoleShutdownHandler, clears it, and turns the bar off, so Tracy's handler has nothing to log or render. This works the same for every bundled Tracy version. Tested on pwtest by running a fatal (time limit) and a clean Console run over curl, with and without X-Tracy-Ajax, with use_php_session 1 and the default: the response, status files, deferred AJAX content and log files match the patched core. Without the patch or this, Tracy logged each fatal as an exception file. tracy-2.12.x's Debugger.php is upstream's again; the copy now differs from the v2.12.2 release only in the Info panel's OPcache row. * Shorten the bar bundle's browser cache from 10 days to an hour Tracy serves ?_tracy_bar=js with Cache-Control: max-age=864000 under a URL that only changes with Tracy's version, so a TracyDebugger update (main.js, styles.css, ...) or a settings change that feeds the bundle (bar position, server type colors, ...) reached browsers up to 10 days late unless they hard-reloaded. On those requests a header_register_callback() now replaces it with max-age=3600 just before the headers go out. The bundle body, the content(-ajax) responses and normal pages are unchanged. * Always add dumps-polling.js and exception-loader.js to the bar bundle Both were added only when their panel (Dumps Recorder, Tracy Exceptions) was in $showPanels, which varies with the sticky/once panel cookies, so the bundle a browser cached decided whether they were there: with the Dumps Recorder switched on after a page had cached a bundle without it, the panel never polled. They are now always in the bundle, which is the same for every panel selection, and dumps-polling.js only polls when the Dumps Recorder panel is in the bar. * Use !empty() for the PW Info panel links check that gates font-awesome Since the font-awesome check no longer depends on $showPanels, the count($this->data['customPWInfoPanelLinks']) in it runs for every user without File Editor access, and count() throws a TypeError on PHP 8 if a cleared setting was saved as ''. * Fix the Console error handler logging @-suppressed errors on PHP 8 tracyConsoleErrorHandler skipped suppressed errors with error_reporting() === 0, but since PHP 8 the @ operator lowers error_reporting() to the fatal levels instead of 0, so every suppressed warning in Console code (and inside libraries it calls, e.g. Tracy's @http_response_code()) was shown as a red error block and logged. It now tests the error's own level against error_reporting(), as TD's dump error handler already does. * Bump to 5.0.73. * Run TracyDebugger's bar setup from Tracy's init hooks instead of polling every animation frame Several scripts waited for an element by calling requestAnimationFrame until it appeared, and some never appear: on every page with the bar, bsZIndex (waiting for a BlueScreen) and the File Editor's initializeEditor (waiting for its panel to be opened) ran 60 times a second for as long as the page was open, and a panel popped out into its own window ran seven such loops, since it has no bar. main.js adds tracyDebuggerOnBar() and tracyDebuggerOnPanelInit(), which run callbacks after Tracy.Debug.init and DebugPanel.prototype.init (also if they throw partway). The hide link, the logo changes, the Dumps Recorder state clean-up, dumps polling and the File Editor's recently opened list use them; the show button gets its click handler when it is created; and the BlueScreen's z-index is a CSS rule, so it applies to every BlueScreen rather than the first one. Measured on pwtest: 0 loops on a page (was 2) and in a popup (was 7). * Keep barJsFile() from deleting a variant in use, and handle a failed write The clean-up deleted any bar-*.js older than a day, including a variant still in use whose file was written long ago, so a concurrent bundle request could build without TracyDebugger's bar JS and browsers would cache that. A file in use is now written again at most hourly, which keeps its mtime recent, so only variants nothing has asked for in a day are removed. A failed write (unwritable cache dir, full disk) removes its .tmp file, logs an error and returns null, and the caller then adds no file rather than one Tracy can't read. * Only build the bar JS file on ?_tracy_bar= requests Tracy reads Debugger::$customJsFiles only when it serves the bar bundle, but every page view with the bar hashed TracyDebugger's bar JS and checked (sometimes wrote) its cache file. * Console: report fatal errors raised under @, and show @-suppressed errors in scream mode tracyConsoleShutdownHandler still returned early on the pre-PHP-8 error_reporting() === 0 check, which on PHP 7 hid a fatal error raised inside an @ expression; a fatal error is now always reported. And the error handler's @ check now gives way to Tracy's scream mode (forceScream), whose purpose is to show those errors.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
info.panel.phpcallsopcache_get_status()with the default$include_scripts = true, which builds a 7-field entry for every script in the opcode cache. The cache belongs to the PHP process pool rather than the application, so on a machine serving several sites every vhost inflates the cost. That whole array is then used for a single membership test againstget_included_files().Measured on PHP 8.5.9 with 1365 cached scripts, on a request that included 232 files:
In that app it was ~157 ms of a ~250 ms request — more than half the time spent rendering the entire Bar, inside a panel that is usually never opened. It grows with the size of the cache, so the busier the server, the worse it gets.
(For context, the
ReflectionClassloop forClasses + interfaces + traitsin the same file, which looks like the more obvious suspect, measured 0.1 ms.)Change
opcache_is_script_cached()answers exactly the question the row asks — "was this file served from the cache?" — with one hash lookup per included file, so$include_scriptscan befalse.jit,memory_usage,interned_strings_usageandopcache_statisticsare all still returned, so the rest of the panel is untouched.The rendered row does not change. Both implementations reported
100 % cached(231 of 232 files) on the same request. To check they agree when something genuinely isn't cached, I included a file that had just been written, soopcache.file_update_protectionskipped it: old and new code both reported99.1 %, and both identified the same two uncached files. The two path sets are directly comparable —$opcache['scripts']keys andget_included_files()are both resolved absolute paths.Side effect: the row no longer depends on
$opcache['scripts']existing at all, which was the subject of #271.Notes
opcache_is_script_cached()has been available since PHP 5.5.11, well below Tracy's floor.opcache.restrict_apisetting, so they succeed or fail together; the$opcache &&guard keeps the previous behaviour of showing–when the API is restricted or OPcache is off. The extrafunction_exists()only covers a build that has the status function but not the per-script one.