From 52d691a82b3aa2407511761928f3279727c6e3f2 Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Sun, 27 Jun 2021 12:47:40 +0100 Subject: [PATCH] Adding JIT information (#75) * http to https (#73) * Added better jit-flag output in directives * Added additional jit information * Added a highlight/graph of jit buffer usage * Added jit buffer values to the 'memory usage' info panel * Added jit 'highlight' option * Fixed #69 * Updated README --- README.md | 26 +++++++-- build/_frontend/interface.jsx | 14 +++-- build/build.php | 4 +- build/template.phps | 7 ++- composer.json | 4 +- index.php | 103 ++++++++++++++++++++++++++++------ package.json | 2 +- src/Opcache/Service.php | 78 ++++++++++++++++++++++--- 8 files changed, 194 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index 45ced54..1a2b5b6 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,13 @@ There are two ways to getting started using this gui: #### Copy/clone this repo -The easiest way to start using the opcache-gui is to clone this repo, or simply to copy/paste/download the `index.php` file, to a location which your web server can load. Then point your browser to that location, such as `https://www.example.com/opcache/index.php`. +The easiest way to start using the opcache-gui is to clone this repo, or simply copy/paste/download the `index.php` file to a location which your web server can load. Then point your browser to that location, such as `https://www.example.com/opcache/index.php`. #### Install via composer You can include the files with [Composer](https://getcomposer.org/) by running the command `composer require amnuts/opcache-gui`. -Once in your `vendor` directory, there are numerous ways in which you can use the interface. For example if you're using a framework such as Symfony or Laravel, you could load opcache-gui into a `Controller`. Your requirements of setting it up within your framework of choice will vary, so it's not really possible to detail how to do that within this readme... but I have faith in your ability to figure it out! +Once in your `vendor` directory, there are numerous ways in which you can use the interface. For example, if you're using a framework such as Symfony or Laravel, you could load opcache-gui into a `Controller`. Your requirements of setting it up within your framework of choice will vary, so it's not really possible to detail how to do that within this readme... but I have faith in your ability to figure it out! The namespace used for the class is `Amnuts\Opcache`, so once the dependency is in your `autoload.php` you can use the `\Amnuts\Opcache\Service` class. For example, you could do something like: @@ -79,7 +79,8 @@ $options = [ 'highlight' => [ 'memory' => true, // show the memory chart/big number 'hits' => true, // show the hit rate chart/big number - 'keys' => true // show the keys used chart/big number + 'keys' => true, // show the keys used chart/big number + 'jit' => true // show the jit buffer chart/big number ] ]; ``` @@ -116,9 +117,9 @@ The core PHP template used in the build process, and that acts to pass various b ### Overview -The overview will show you all the core information. From here you'll be able to see what host and platform you're running on, what version of OPcache you're using, when it was last reset, the functions that are available, all the directives and all the statistics associated with the OPcache (number of hits, memory used, free and wasted memory, etc.) +The overview will show you all the core information. From here you'll be able to see what host and platform you're running on, what version of OPcache you're using, when it was last reset, the functions and directives available (with links to the php.net manual), and all the statistics associated with the OPcache (number of hits, memory used, free and wasted memory, and more). -![Screenshot of the Overview tab](http://amnuts.com/images/opcache/screenshot/overview-v3.0.0.png) +![Screenshot of the Overview tab](http://amnuts.com/images/opcache/screenshot/overview-v3.3.0.png) ### Cached files @@ -154,10 +155,23 @@ Resetting can be disabled with the use of the configuration options `allow_reset The interface can poll every so often to get a fresh look at the opcache. You can change how often this happens with the configuration option `refresh_time`, which is in seconds. -When the real-time updates are active the interface will automatically update all the values as needed. Also, if you choose to invalidate any files or reset the cache it will do this without reloading the page, so the search term you've entered, or the page you've navigated to do not get reset. If the real-time update is not on then the page will reload on any invalidation usage. +When the real-time updates are active, the interface will automatically update all the values as needed. + +Also, if you choose to invalidate any files or reset the cache it will do this without reloading the page, so the search term you've entered, or the page to which you've navigated do not get reset. If the real-time update is not on then the page will reload on any invalidation usage. ## Releases +**Version 3.3.0**\ +Mostly added JIT information for PHP 8: +* Added JIT buffer graph (optionally able to turn it off) +* Added JIT information to the memory usage panel +* Improved the JIT information shown in the directives +* Fixed a long outstanding interface bug that allowed you to see the 'invalidate all' link even if invalidation option was `false` + +If you want to enable JIT you have to put in a value for the [opcache.jit_buffer_size](https://www.php.net/manual/en/opcache.configuration.php#ini.opcache.jit-buffer-size) ini setting, else it's disabled by default. + +If you're not using PHP 8, the interface will compensate and not show the additional JIT information. + **Version 3.2.1**\ Minor maintenance release to: * Put back "spaceship operator" so PHP8 doesn't give deprecation warnings (must have been accidentally removed in a previous commit) diff --git a/build/_frontend/interface.jsx b/build/_frontend/interface.jsx index 6a74eed..495d273 100644 --- a/build/_frontend/interface.jsx +++ b/build/_frontend/interface.jsx @@ -264,7 +264,8 @@ function OverviewCounts(props) { const graphList = [ {id: 'memoryUsageCanvas', title: 'memory', show: props.highlight.memory, value: props.overview.used_memory_percentage}, {id: 'hitRateCanvas', title: 'hit rate', show: props.highlight.hits, value: props.overview.hit_rate_percentage}, - {id: 'keyUsageCanvas', title: 'keys', show: props.highlight.keys, value: props.overview.used_key_percentage} + {id: 'keyUsageCanvas', title: 'keys', show: props.highlight.keys, value: props.overview.used_key_percentage}, + {id: 'jitUsageCanvas', title: 'jit buffer', show: props.highlight.jit, value: props.overview.jit_buffer_used_percentage} ]; return ( @@ -287,6 +288,9 @@ function OverviewCounts(props) { wasted={props.overview.readable.wasted_memory} preload={props.overview.readable.preload_memory || null} wastedPercent={props.overview.wasted_percentage} + jitBuffer={props.overview.readable.jit_buffer_size || null} + jitBufferFree={props.overview.readable.jit_buffer_free || null} + jitBufferFreePercentage={props.overview.jit_buffer_used_percentage || null} /> - {dShow} {vShow} @@ -380,7 +384,7 @@ function Functions(props) { Available functions {props.functions.map(f => - {f} + {f} )} @@ -618,6 +622,8 @@ function MemoryUsagePanel(props) {

free memory: {props.free}

{ props.preload &&

preload memory: {props.preload}

}

wasted memory: {props.wasted} ({props.wastedPercent}%)

+ { props.jitBuffer &&

jit buffer: {props.jitBuffer}

} + { props.jitBufferFree &&

jit buffer free: {props.jitBufferFree} ({100 - props.jitBufferFreePercentage}%)

} ); @@ -754,7 +760,7 @@ class CachedFiles extends React.Component {

{allFilesTotal} files cached{showingTotal !== allFilesTotal && `, ${showingTotal} showing due to filter '${this.state.searchTerm}'`}

- { this.state.searchTerm && showingTotal !== allFilesTotal && + { this.props.allow.invalidate && this.state.searchTerm && showingTotal !== allFilesTotal &&

Invalidate all matching files

} diff --git a/build/build.php b/build/build.php index 89bf412..515fb0f 100644 --- a/build/build.php +++ b/build/build.php @@ -4,9 +4,9 @@ * OPcache GUI - build script * * @author Andrew Collington, andy@amnuts.com - * @version 3.2.1 + * @version 3.3.0 * @link https://github.com/amnuts/opcache-gui - * @license MIT, http://acollington.mit-license.org/ + * @license MIT, https://acollington.mit-license.org/ */ $parentPath = dirname(__DIR__); diff --git a/build/template.phps b/build/template.phps index 3367549..ac320c9 100644 --- a/build/template.phps +++ b/build/template.phps @@ -8,9 +8,9 @@ namespace Amnuts\Opcache; * A simple but effective single-file GUI for the OPcache PHP extension. * * @author Andrew Collington, andy@amnuts.com - * @version 3.2.1 + * @version 3.3.0 * @link https://github.com/amnuts/opcache-gui - * @license MIT, http://acollington.mit-license.org/ + * @license MIT, https://acollington.mit-license.org/ */ /* @@ -35,7 +35,8 @@ $options = [ 'highlight' => [ 'memory' => true, // show the memory chart/big number 'hits' => true, // show the hit rate chart/big number - 'keys' => true // show the keys used chart/big number + 'keys' => true, // show the keys used chart/big number + 'jit' => true // show the jit buffer chart/big number ] ]; diff --git a/composer.json b/composer.json index 6e9c69b..d408acc 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ { "name": "Andrew Collington", "email": "andy@amnuts.com", - "homepage": "http://www.amnuts.com/", + "homepage": "https://blog.amnuts.com/", "role": "Developer" }, { @@ -23,7 +23,7 @@ "require": { "ext-Zend-OPcache": "*", "php": ">=7.1.0", - "ext-json": "*" + "ext-json": "*" }, "autoload": { "psr-4" : { diff --git a/index.php b/index.php index 366bf19..fe3d27e 100644 --- a/index.php +++ b/index.php @@ -8,9 +8,9 @@ namespace Amnuts\Opcache; * A simple but effective single-file GUI for the OPcache PHP extension. * * @author Andrew Collington, andy@amnuts.com - * @version 3.2.1 + * @version 3.3.0 * @link https://github.com/amnuts/opcache-gui - * @license MIT, http://acollington.mit-license.org/ + * @license MIT, https://acollington.mit-license.org/ */ /* @@ -35,7 +35,8 @@ $options = [ 'highlight' => [ 'memory' => true, // show the memory chart/big number 'hits' => true, // show the hit rate chart/big number - 'keys' => true // show the keys used chart/big number + 'keys' => true, // show the keys used chart/big number + 'jit' => true // show the jit buffer chart/big number ] ]; @@ -57,7 +58,7 @@ header('Pragma: no-cache'); class Service { - const VERSION = '3.2.1'; + const VERSION = '3.3.0'; protected $data; protected $options; @@ -78,7 +79,47 @@ class Service 'highlight' => [ 'memory' => true, // show the memory chart/big number 'hits' => true, // show the hit rate chart/big number - 'keys' => true // show the keys used chart/big number + 'keys' => true, // show the keys used chart/big number + 'jit' => true // show the jit buffer chart/big number + ] + ]; + protected $jitModes = [ + [ + 'flag' => 'CPU-specific optimization', + 'value' => [ + 'Disable CPU-specific optimization', + 'Enable use of AVX, if the CPU supports it' + ] + ], + [ + 'flag' => 'Register allocation', + 'value' => [ + 'Do not perform register allocation', + 'Perform block-local register allocation', + 'Perform global register allocation' + ] + ], + [ + 'flag' => 'Trigger', + 'value' => [ + 'Compile all functions on script load', + 'Compile functions on first execution', + 'Profile functions on first request and compile the hottest functions afterwards', + 'Profile on the fly and compile hot functions', + 'Currently unused', + 'Use tracing JIT. Profile on the fly and compile traces for hot code segments' + ] + ], + [ + 'flag' => 'Optimization level', + 'value' => [ + 'No JIT', + 'Minimal JIT (call standard VM handlers)', + 'Inline VM handlers', + 'Use type inference', + 'Use call graph', + 'Optimize whole script' + ] ] ]; @@ -106,7 +147,6 @@ class Service 1 << 14 => '(unsafe) Collect constants', 1 << 15 => 'Inline functions' ]; - $this->options = array_merge($this->defaults, $options); $this->data = $this->compileState(); } @@ -150,10 +190,8 @@ class Service if ($name === null) { return $this->options; } - return (isset($this->options[$name]) - ? $this->options[$name] - : null - ); + + return $this->options[$name] ?? null; } /** @@ -331,12 +369,25 @@ class Service ]; } + if ($overview && !empty($status['jit'])) { + $overview['jit_buffer_used_percentage'] = ($status['jit']['buffer_size'] + ? round(100 * (($status['jit']['buffer_size'] - $status['jit']['buffer_free']) / $status['jit']['buffer_size'])) + : 0 + ); + $overview['readable'] = array_merge($overview['readable'], [ + 'jit_buffer_size' => $this->size($status['jit']['buffer_size']), + 'jit_buffer_free' => $this->size($status['jit']['buffer_free']) + ]); + } else { + $this->options['highlight']['jit'] = false; + } + $directives = []; ksort($config['directives']); foreach ($config['directives'] as $k => $v) { - if (in_array($k, ['opcache.max_file_size', 'opcache.memory_consumption']) && $v) { + if (in_array($k, ['opcache.max_file_size', 'opcache.memory_consumption', 'opcache.jit_buffer_size']) && $v) { $v = $this->size($v) . " ({$v})"; - } elseif ($k == 'opcache.optimization_level') { + } elseif ($k === 'opcache.optimization_level') { $levels = []; foreach ($this->optimizationLevels as $level => $info) { if ($level & $v) { @@ -344,6 +395,16 @@ class Service } } $v = $levels ?: 'none'; + } elseif ($k === 'opcache.jit') { + if (is_numeric($v)) { + $levels = []; + foreach (str_split((string)$v) as $type => $level) { + $levels[] = "{$level}: {$this->jitModes[$type]['value'][$level]} ({$this->jitModes[$type]['flag']})"; + } + $v = $levels; + } elseif (strtolower($v) === 'off' || $v === '') { + $v = 'Off'; + } } $directives[] = [ 'k' => $k, @@ -707,6 +768,11 @@ function OverviewCounts(props) { title: 'keys', show: props.highlight.keys, value: props.overview.used_key_percentage + }, { + id: 'jitUsageCanvas', + title: 'jit buffer', + show: props.highlight.jit, + value: props.overview.jit_buffer_used_percentage }]; return /*#__PURE__*/React.createElement("div", { id: "counts", @@ -732,7 +798,10 @@ function OverviewCounts(props) { free: props.overview.readable.free_memory, wasted: props.overview.readable.wasted_memory, preload: props.overview.readable.preload_memory || null, - wastedPercent: props.overview.wasted_percentage + wastedPercent: props.overview.wasted_percentage, + jitBuffer: props.overview.readable.jit_buffer_size || null, + jitBufferFree: props.overview.readable.jit_buffer_free || null, + jitBufferFreePercentage: props.overview.jit_buffer_used_percentage || null }), /*#__PURE__*/React.createElement(StatisticsPanel, { num_cached_scripts: props.overview.readable.num_cached_scripts, hits: props.overview.readable.hits, @@ -794,7 +863,7 @@ function Directives(props) { }, /*#__PURE__*/React.createElement("td", { title: 'View ' + directive.k + ' manual entry' }, /*#__PURE__*/React.createElement("a", { - href: 'http://php.net/manual/en/opcache.configuration.php#ini.' + directive.k.replace(/_/g, '-'), + href: 'https://php.net/manual/en/opcache.configuration.php#ini.' + directive.k.replace(/_/g, '-'), target: "_blank" }, dShow)), /*#__PURE__*/React.createElement("td", null, vShow)); }); @@ -813,7 +882,7 @@ function Functions(props) { }, /*#__PURE__*/React.createElement("thead", null, /*#__PURE__*/React.createElement("tr", null, /*#__PURE__*/React.createElement("th", null, "Available functions"))), /*#__PURE__*/React.createElement("tbody", null, props.functions.map(f => /*#__PURE__*/React.createElement("tr", { key: f }, /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement("a", { - href: "http://php.net/" + f, + href: "https://php.net/" + f, title: "View manual page", target: "_blank" }, f))))))); @@ -1045,7 +1114,7 @@ function MemoryUsagePanel(props) { className: "widget-header" }, "memory usage"), /*#__PURE__*/React.createElement("div", { className: "widget-value widget-info" - }, /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "total memory:"), " ", props.total), /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "used memory:"), " ", props.used), /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "free memory:"), " ", props.free), props.preload && /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "preload memory:"), " ", props.preload), /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "wasted memory:"), " ", props.wasted, " (", props.wastedPercent, "%)"))); + }, /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "total memory:"), " ", props.total), /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "used memory:"), " ", props.used), /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "free memory:"), " ", props.free), props.preload && /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "preload memory:"), " ", props.preload), /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "wasted memory:"), " ", props.wasted, " (", props.wastedPercent, "%)"), props.jitBuffer && /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "jit buffer:"), " ", props.jitBuffer), props.jitBufferFree && /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("b", null, "jit buffer free:"), " ", props.jitBufferFree, " (", 100 - props.jitBufferFreePercentage, "%)"))); } function StatisticsPanel(props) { @@ -1170,7 +1239,7 @@ class CachedFiles extends React.Component { onChange: e => { this.setSearchTerm(e.target.value); } - })), /*#__PURE__*/React.createElement("h3", null, allFilesTotal, " files cached", showingTotal !== allFilesTotal && `, ${showingTotal} showing due to filter '${this.state.searchTerm}'`), this.state.searchTerm && showingTotal !== allFilesTotal && /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("a", { + })), /*#__PURE__*/React.createElement("h3", null, allFilesTotal, " files cached", showingTotal !== allFilesTotal && `, ${showingTotal} showing due to filter '${this.state.searchTerm}'`), this.props.allow.invalidate && this.state.searchTerm && showingTotal !== allFilesTotal && /*#__PURE__*/React.createElement("p", null, /*#__PURE__*/React.createElement("a", { href: `?invalidate_searched=${encodeURIComponent(this.state.searchTerm)}`, onClick: this.handleInvalidate }, "Invalidate all matching files")), /*#__PURE__*/React.createElement("div", { diff --git a/package.json b/package.json index 0ddb442..c5e0c2c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "opcache-gui", "description": "A clean and responsive interface for Zend OPcache information, showing statistics, settings and cached files, and providing a real-time update for the information (using jQuery and React).", - "version": "3.2.1", + "version": "3.3.0", "main": "index.js", "devDependencies": { "@babel/cli": "^7.12.8", diff --git a/src/Opcache/Service.php b/src/Opcache/Service.php index 1774924..ffc0f8a 100644 --- a/src/Opcache/Service.php +++ b/src/Opcache/Service.php @@ -4,7 +4,7 @@ namespace Amnuts\Opcache; class Service { - const VERSION = '3.2.1'; + const VERSION = '3.3.0'; protected $data; protected $options; @@ -25,7 +25,47 @@ class Service 'highlight' => [ 'memory' => true, // show the memory chart/big number 'hits' => true, // show the hit rate chart/big number - 'keys' => true // show the keys used chart/big number + 'keys' => true, // show the keys used chart/big number + 'jit' => true // show the jit buffer chart/big number + ] + ]; + protected $jitModes = [ + [ + 'flag' => 'CPU-specific optimization', + 'value' => [ + 'Disable CPU-specific optimization', + 'Enable use of AVX, if the CPU supports it' + ] + ], + [ + 'flag' => 'Register allocation', + 'value' => [ + 'Do not perform register allocation', + 'Perform block-local register allocation', + 'Perform global register allocation' + ] + ], + [ + 'flag' => 'Trigger', + 'value' => [ + 'Compile all functions on script load', + 'Compile functions on first execution', + 'Profile functions on first request and compile the hottest functions afterwards', + 'Profile on the fly and compile hot functions', + 'Currently unused', + 'Use tracing JIT. Profile on the fly and compile traces for hot code segments' + ] + ], + [ + 'flag' => 'Optimization level', + 'value' => [ + 'No JIT', + 'Minimal JIT (call standard VM handlers)', + 'Inline VM handlers', + 'Use type inference', + 'Use call graph', + 'Optimize whole script' + ] ] ]; @@ -53,7 +93,6 @@ class Service 1 << 14 => '(unsafe) Collect constants', 1 << 15 => 'Inline functions' ]; - $this->options = array_merge($this->defaults, $options); $this->data = $this->compileState(); } @@ -97,10 +136,8 @@ class Service if ($name === null) { return $this->options; } - return (isset($this->options[$name]) - ? $this->options[$name] - : null - ); + + return $this->options[$name] ?? null; } /** @@ -278,12 +315,25 @@ class Service ]; } + if ($overview && !empty($status['jit'])) { + $overview['jit_buffer_used_percentage'] = ($status['jit']['buffer_size'] + ? round(100 * (($status['jit']['buffer_size'] - $status['jit']['buffer_free']) / $status['jit']['buffer_size'])) + : 0 + ); + $overview['readable'] = array_merge($overview['readable'], [ + 'jit_buffer_size' => $this->size($status['jit']['buffer_size']), + 'jit_buffer_free' => $this->size($status['jit']['buffer_free']) + ]); + } else { + $this->options['highlight']['jit'] = false; + } + $directives = []; ksort($config['directives']); foreach ($config['directives'] as $k => $v) { - if (in_array($k, ['opcache.max_file_size', 'opcache.memory_consumption']) && $v) { + if (in_array($k, ['opcache.max_file_size', 'opcache.memory_consumption', 'opcache.jit_buffer_size']) && $v) { $v = $this->size($v) . " ({$v})"; - } elseif ($k == 'opcache.optimization_level') { + } elseif ($k === 'opcache.optimization_level') { $levels = []; foreach ($this->optimizationLevels as $level => $info) { if ($level & $v) { @@ -291,6 +341,16 @@ class Service } } $v = $levels ?: 'none'; + } elseif ($k === 'opcache.jit') { + if (is_numeric($v)) { + $levels = []; + foreach (str_split((string)$v) as $type => $level) { + $levels[] = "{$level}: {$this->jitModes[$type]['value'][$level]} ({$this->jitModes[$type]['flag']})"; + } + $v = $levels; + } elseif (strtolower($v) === 'off' || $v === '') { + $v = 'Off'; + } } $directives[] = [ 'k' => $k,