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
This commit is contained in:
Andrew Collington 2021-06-27 12:47:40 +01:00 committed by GitHub
parent d660fbee43
commit 52d691a82b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 194 additions and 44 deletions

View file

@ -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)

View file

@ -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}
/>
<StatisticsPanel
num_cached_scripts={props.overview.readable.num_cached_scripts}
@ -358,7 +362,7 @@ function Directives(props) {
}
return (
<tr key={directive.k}>
<td title={'View ' + directive.k + ' manual entry'}><a href={'http://php.net/manual/en/opcache.configuration.php#ini.'
<td title={'View ' + directive.k + ' manual entry'}><a href={'https://php.net/manual/en/opcache.configuration.php#ini.'
+ (directive.k).replace(/_/g,'-')} target="_blank">{dShow}</a></td>
<td>{vShow}</td>
</tr>
@ -380,7 +384,7 @@ function Functions(props) {
<thead><tr><th>Available functions</th></tr></thead>
<tbody>
{props.functions.map(f =>
<tr key={f}><td><a href={"http://php.net/"+f} title="View manual page" target="_blank">{f}</a></td></tr>
<tr key={f}><td><a href={"https://php.net/"+f} title="View manual page" target="_blank">{f}</a></td></tr>
)}
</tbody>
</table>
@ -618,6 +622,8 @@ function MemoryUsagePanel(props) {
<p><b>free memory:</b> {props.free}</p>
{ props.preload && <p><b>preload memory:</b> {props.preload}</p> }
<p><b>wasted memory:</b> {props.wasted} ({props.wastedPercent}%)</p>
{ props.jitBuffer && <p><b>jit buffer:</b> {props.jitBuffer}</p> }
{ props.jitBufferFree && <p><b>jit buffer free:</b> {props.jitBufferFree} ({100 - props.jitBufferFreePercentage}%)</p> }
</div>
</div>
);
@ -754,7 +760,7 @@ class CachedFiles extends React.Component {
<h3>{allFilesTotal} files cached{showingTotal !== allFilesTotal && `, ${showingTotal} showing due to filter '${this.state.searchTerm}'`}</h3>
{ this.state.searchTerm && showingTotal !== allFilesTotal &&
{ this.props.allow.invalidate && this.state.searchTerm && showingTotal !== allFilesTotal &&
<p><a href={`?invalidate_searched=${encodeURIComponent(this.state.searchTerm)}`} onClick={this.handleInvalidate}>Invalidate all matching files</a></p>
}

View file

@ -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__);

View file

@ -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
]
];

View file

@ -8,7 +8,7 @@
{
"name": "Andrew Collington",
"email": "andy@amnuts.com",
"homepage": "http://www.amnuts.com/",
"homepage": "https://blog.amnuts.com/",
"role": "Developer"
},
{

103
index.php
View file

@ -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", {

View file

@ -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",

View file

@ -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,