opcache-gui/index.php
Andrew Collington 64247d6ffc Squashed commit of the following:
commit 7dfd4b1d873de85460d5c4c28d7886dd384fcb4d
Author: Andrew Collington <andy@amnuts.com>
Date:   Thu Dec 28 19:35:54 2017 +0000

    Updated readme.

commit 34da481b9dbf273b3dbbf8efb2bf3f6dde4f382a
Author: Andrew Collington <andy@amnuts.com>
Date:   Thu Dec 28 19:18:50 2017 +0000

    Cookie now stores real-time state.
2017-12-28 19:37:52 +00:00

1262 lines
52 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace OpcacheGui;
/**
* OPcache GUI
*
* A simple but effective single-file GUI for the OPcache PHP extension.
*
* @author Andrew Collington, andy@amnuts.com
* @version 2.4.0
* @link https://github.com/amnuts/opcache-gui
* @license MIT, http://acollington.mit-license.org/
*/
/*
* User configuration
*/
$options = [
'allow_filelist' => true, // show/hide the files tab
'allow_invalidate' => true, // give a link to invalidate files
'allow_reset' => true, // give option to reset the whole cache
'allow_realtime' => true, // give option to enable/disable real-time updates
'refresh_time' => 5, // how often the data will refresh, in seconds
'size_precision' => 2, // Digits after decimal point
'size_space' => false, // have '1MB' or '1 MB' when showing sizes
'charts' => true, // show gauge chart or just big numbers
'debounce_rate' => 250, // milliseconds after key press to send keyup event when filtering
'cookie_name' => 'opcachegui', // name of cookie
'cookie_ttl' => 365 // days to store cookie
];
/*
* Shouldn't need to alter anything else below here
*/
if (!extension_loaded('Zend OPcache')) {
die('The Zend OPcache extension does not appear to be installed');
}
$ocEnabled = ini_get('opcache.enable');
if (empty($ocEnabled)) {
die('The Zend OPcache extension is installed but not turned on');
}
class OpCacheService
{
protected $data;
protected $options;
protected $defaults = [
'allow_filelist' => true,
'allow_invalidate' => true,
'allow_reset' => true,
'allow_realtime' => true,
'refresh_time' => 5,
'size_precision' => 2,
'size_space' => false,
'charts' => true,
'debounce_rate' => 250,
'cookie_name' => 'opcachegui',
'cookie_ttl' => 365
];
private function __construct($options = [])
{
$this->options = array_merge($this->defaults, $options);
$this->data = $this->compileState();
}
public static function init($options = [])
{
$self = new self($options);
if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
&& strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'
) {
if (isset($_GET['reset']) && $self->getOption('allow_reset')) {
echo '{ "success": "' . ($self->resetCache() ? 'yes' : 'no') . '" }';
} else if (isset($_GET['invalidate']) && $self->getOption('allow_invalidate')) {
echo '{ "success": "' . ($self->resetCache($_GET['invalidate']) ? 'yes' : 'no') . '" }';
} else {
echo json_encode($self->getData((empty($_GET['section']) ? null : $_GET['section'])));
}
exit;
} else if (isset($_GET['reset']) && $self->getOption('allow_reset')) {
$self->resetCache();
header('Location: ?');
exit;
} else if (isset($_GET['invalidate']) && $self->getOption('allow_invalidate')) {
$self->resetCache($_GET['invalidate']);
header('Location: ?');
exit;
}
return $self;
}
public function getOption($name = null)
{
if ($name === null) {
return $this->options;
}
return (isset($this->options[$name])
? $this->options[$name]
: null
);
}
public function getData($section = null, $property = null)
{
if ($section === null) {
return $this->data;
}
$section = strtolower($section);
if (isset($this->data[$section])) {
if ($property === null || !isset($this->data[$section][$property])) {
return $this->data[$section];
}
return $this->data[$section][$property];
}
return null;
}
public function canInvalidate()
{
return ($this->getOption('allow_invalidate') && function_exists('opcache_invalidate'));
}
public function resetCache($file = null)
{
$success = false;
if ($file === null) {
$success = opcache_reset();
} else if (function_exists('opcache_invalidate')) {
$success = opcache_invalidate(urldecode($file), true);
}
if ($success) {
$this->compileState();
}
return $success;
}
protected function size($size)
{
$i = 0;
$val = array('b', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
while (($size / 1024) > 1) {
$size /= 1024;
++$i;
}
return sprintf('%.'.$this->getOption('size_precision').'f%s%s',
$size, ($this->getOption('size_space') ? ' ' : ''), $val[$i]
);
}
protected function compileState()
{
$status = opcache_get_status();
$config = opcache_get_configuration();
$files = [];
if (!empty($status['scripts']) && $this->getOption('allow_filelist')) {
uasort($status['scripts'], function($a, $b) {
return $a['hits'] < $b['hits'];
});
foreach ($status['scripts'] as &$file) {
$file['full_path'] = str_replace('\\', '/', $file['full_path']);
$file['readable'] = [
'hits' => number_format($file['hits']),
'memory_consumption' => $this->size($file['memory_consumption'])
];
}
$files = array_values($status['scripts']);
}
$overview = array_merge(
$status['memory_usage'], $status['opcache_statistics'], [
'used_memory_percentage' => round(100 * (
($status['memory_usage']['used_memory'] + $status['memory_usage']['wasted_memory'])
/ $config['directives']['opcache.memory_consumption'])),
'hit_rate_percentage' => round($status['opcache_statistics']['opcache_hit_rate']),
'wasted_percentage' => round($status['memory_usage']['current_wasted_percentage'], 2),
'readable' => [
'total_memory' => $this->size($config['directives']['opcache.memory_consumption']),
'used_memory' => $this->size($status['memory_usage']['used_memory']),
'free_memory' => $this->size($status['memory_usage']['free_memory']),
'wasted_memory' => $this->size($status['memory_usage']['wasted_memory']),
'num_cached_scripts' => number_format($status['opcache_statistics']['num_cached_scripts']),
'hits' => number_format($status['opcache_statistics']['hits']),
'misses' => number_format($status['opcache_statistics']['misses']),
'blacklist_miss' => number_format($status['opcache_statistics']['blacklist_misses']),
'num_cached_keys' => number_format($status['opcache_statistics']['num_cached_keys']),
'max_cached_keys' => number_format($status['opcache_statistics']['max_cached_keys']),
'interned' => null,
'start_time' => date('Y-m-d H:i:s', $status['opcache_statistics']['start_time']),
'last_restart_time' => ($status['opcache_statistics']['last_restart_time'] == 0
? 'never'
: date('Y-m-d H:i:s', $status['opcache_statistics']['last_restart_time'])
)
]
]
);
if (!empty($status['interned_strings_usage'])) {
$overview['readable']['interned'] = [
'buffer_size' => $this->size($status['interned_strings_usage']['buffer_size']),
'strings_used_memory' => $this->size($status['interned_strings_usage']['used_memory']),
'strings_free_memory' => $this->size($status['interned_strings_usage']['free_memory']),
'number_of_strings' => number_format($status['interned_strings_usage']['number_of_strings'])
];
}
$directives = [];
ksort($config['directives']);
foreach ($config['directives'] as $k => $v) {
$directives[] = ['k' => $k, 'v' => $v];
}
$version = array_merge(
$config['version'],
[
'php' => phpversion(),
'server' => $_SERVER['SERVER_SOFTWARE'],
'host' => (function_exists('gethostname')
? gethostname()
: (php_uname('n')
?: (empty($_SERVER['SERVER_NAME'])
? $_SERVER['HOST_NAME']
: $_SERVER['SERVER_NAME']
)
)
)
]
);
return [
'version' => $version,
'overview' => $overview,
'files' => $files,
'directives' => $directives,
'blacklist' => $config['blacklist'],
'functions' => get_extension_funcs('Zend OPcache')
];
}
}
$opcache = OpCacheService::init($options);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>OPcache statistics on <?php echo $opcache->getData('version', 'host'); ?></title>
<script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
<script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
<script src="//cdn.jsdelivr.net/jquery/3.1.1/jquery.min.js"></script>
<style type="text/css">
body { font-family:sans-serif; font-size:90%; padding: 0; margin: 0 }
nav { padding-top: 20px; }
nav > ul { list-style-type: none; padding-left: 8px; margin: 0; border-bottom: 1px solid #ccc; }
nav > ul > li { display: inline-block; padding: 0; margin: 0 0 -1px 0; }
nav > ul > li > a { display: block; margin: 0 10px; padding: 15px 30px; border: 1px solid transparent; border-bottom-color: #ccc; text-decoration: none; }
nav > ul > li > a:hover { background-color: #f4f4f4; text-decoration: underline; }
nav > ul > li > a.active:hover { background-color: initial; }
nav > ul > li > a[data-for].active { border: 1px solid #ccc; border-bottom-color: #ffffff; border-top: 3px solid #6ca6ef; }
table { margin: 0 0 1em 0; border-collapse: collapse; border-color: #fff; width: 100%; table-layout: fixed; }
table caption { text-align: left; font-size: 1.5em; }
table tr { background-color: #99D0DF; border-color: #fff; }
table th { text-align: left; padding: 6px; background-color: #6ca6ef; color: #fff; border-color: #fff; font-weight: normal; }
table td { padding: 4px 6px; line-height: 1.4em; vertical-align: top; border-color: #fff; overflow: hidden; overflow-wrap: break-word; text-overflow: ellipsis;}
table tr:nth-child(odd) { background-color: #EFFEFF; }
table tr:nth-child(even) { background-color: #E0ECEF; }
#filelist table tr { background-color: #EFFEFF; }
#filelist table tr.alternate { background-color: #E0ECEF; }
td.pathname { width: 70%; }
footer { border-top: 1px solid #ccc; padding: 1em 2em; }
footer a { padding: 2em; text-decoration: none; opacity: 0.7; }
footer a:hover { opacity: 1; }
canvas { display: block; max-width: 100%; height: auto; margin: 0 auto; }
#tabs { padding: 2em; }
#tabs > div { display: none; }
#tabs > div#overview { display:block; }
#resetCache, #toggleRealtime, footer > a { background-position: 5px 50%; background-repeat: no-repeat; background-color: transparent; }
footer > a { background-position: 0 50%; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAQCAYAAAAbBi9cAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjE2MENCRkExNzVBQjExRTQ5NDBGRTUzMzQyMDVDNzFFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjE2MENCRkEyNzVBQjExRTQ5NDBGRTUzMzQyMDVDNzFFIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MTYwQ0JGOUY3NUFCMTFFNDk0MEZFNTMzNDIwNUM3MUUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MTYwQ0JGQTA3NUFCMTFFNDk0MEZFNTMzNDIwNUM3MUUiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7HtUU1AAABN0lEQVR42qyUvWoCQRSF77hCLLKC+FOlCKTyIbYQUuhbWPkSFnZ2NpabUvANLGyz5CkkYGMlFtFAUmiSM8lZOVkWsgm58K079+fMnTusZl92BXbgDrTtZ2szd8fas/XBOzmBKaiCEFyTkL4pc9L8vgpNJJDyWtDna61EoXpO+xcFfXUVqtrf7Vx7m9Pub/EatvgHoYXD4ylztC14BBVwydvydgDPHPgNaErN3jLKIxAUmEvAXK21I18SJpXBGAxyBAaMlblOWOs1bMXFkMGeBFsi0pJNe/QNuV7563+gs8LfhrRfE6GaHLuRqfnUiKi6lJ034B44EXL0baTTJWujNGkG3kBX5uRyZuRkPl3WzDTBtzjnxxiDDq83yNxUk7GYuXM53jeLuMNavvAXkv4zrJkTaeGHAAMAIal3icPMsyQAAAAASUVORK5CYII='); font-size: 80%; }
#resetCache { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NjBFMUMyMjI3NDlGMTFFNEE3QzNGNjQ0OEFDQzQ1MkMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NjBFMUMyMjM3NDlGMTFFNEE3QzNGNjQ0OEFDQzQ1MkMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MEUxQzIyMDc0OUYxMUU0QTdDM0Y2NDQ4QUNDNDUyQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2MEUxQzIyMTc0OUYxMUU0QTdDM0Y2NDQ4QUNDNDUyQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PplZ+ZkAAAD1SURBVHjazFPtDYJADIUJZAMZ4UbACWQENjBO4Ao6AW5AnODcADZQJwAnwJ55NbWhB/6zycsdpX39uDZNpsURtjgzwkDoCBecs5ITPGGMwCNAkIrQw+8ri36GhBHsavFdpILEo4wEpZxRigy009EhG760gr0VhFoyZfvJKPwsheIWIeGejBZRIxRVhMRFevbuUXBew/iE/lhlBduV0j8Jx+TvJEWPphq8n5li9utgaw6cW/h6NSt/JcnVBhQxotIgKTBrbNvIHo2G0x1rwlKqTDusxiAz6hHNL1zayTVqVKRKpa/LPljPH1sJh6l/oNSrZfwSYABtq3tFdZA5BAAAAABJRU5ErkJggg=='); }
#toggleRealtime { position: relative; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODE5RUU4NUE3NDlGMTFFNDkyMzA4QzY1RjRBQkIzQjUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODE5RUU4NUI3NDlGMTFFNDkyMzA4QzY1RjRBQkIzQjUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4MTlFRTg1ODc0OUYxMUU0OTIzMDhDNjVGNEFCQjNCNSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4MTlFRTg1OTc0OUYxMUU0OTIzMDhDNjVGNEFCQjNCNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpXjpvMAAAD2SURBVHjarFQBEcMgDKR3E1AJldA5wMEqAQmTgINqmILdFCChdUAdMAeMcukuSwnQbbnLlZLwJPkQIcrSiT/IGNQHNb8CGQDyRw+2QWUBqC+luzo4OKQZIAVrB+ssyKp3Bkijf0+ijzIh4wQppoBauMSjyDZfMSCDxYZMsfHF120T36AqWZMkgyguQ3GOfottJ5TKnHC+wfeRsC2oDVayPgr3bbN2tHBH3tWuJCPa0JUgKtFzMQrcZH3FNHAc0yOp1cCASALyngoN6lhDopkJWxdifwY9A3u7l29ImpxOFSWIOVsGwHKENIWxss2eBVKdOeeXAAMAk/Z9h4QhXmUAAAAASUVORK5CYII='); }
#counts { width: 270px; float: right; }
#counts > div > div { background-color: #ededed; margin-bottom: 10px; }
#counts > div > div > h3 { background-color: #cdcdcd; padding: 4px 6px; margin: 0; text-align: center; }
#counts > div > div > p { margin: 0; text-align: center; }
#counts > div > div > p span.large + span { font-size: 20pt; margin: 0; color: #6ca6ef; }
#counts > div > div > p span.large { color: #6ca6ef; font-size: 80pt; margin: 0; padding: 0; text-align: center; }
#info { margin-right: 280px; }
#frmFilter { width: 520px; }
.moreinfo > div { padding: 10px; }
.moreinfo > div > p { margin: 0; line-height: 1.75em; }
.metainfo { font-size: 80%; }
.hide { display: none; }
#toggleRealtime.pulse::before {
content: ""; position: absolute;
top: 13px; left: 3px; width: 18px; height: 18px;
z-index: 10; opacity: 0; background-color: transparent;
border: 2px solid rgb(255, 116, 0); border-radius: 100%;
-webkit-animation: pulse 1s linear 2;
-moz-animation: pulse 1s linear 2;
animation: pulse 1s linear 2;
}
@media screen and (max-width: 750px) {
#info { margin-right:auto; clear:both; }
nav > ul { border-bottom: 0; }
nav > ul > li { display: block; margin: 0; }
nav > ul > li > a { display: block; margin: 0 10px; padding: 10px 0 10px 30px; border: 0; }
nav > ul > li > a[data-for].active { border-bottom-color: #ccc; }
#counts { position:relative; display:block; width:100%; }
#toggleRealtime.pulse::before { top: 8px; }
}
@media screen and (max-width: 550px) {
#frmFilter { width: 100%; }
}
@keyframes pulse {
0% {transform: scale(1); opacity: 0;}
50% {transform: scale(1.3); opacity: 0.7;}
100% {transform: scale(1.6); opacity: 1;}
}
@-webkit-keyframes pulse {
0% {-webkit-transform: scale(1); opacity: 0;}
50% {-webkit-transform: scale(1.3); opacity: 0.7;}
100% {-webkit-transform: scale(1.6); opacity: 0;}
}
@-moz-keyframes pulse {
0% {-moz-transform: scale(1); opacity: 0;}
50% {-moz-transform: scale(1.3); opacity: 0.7;}
100% {-moz-transform: scale(1.6); opacity: 0;}
}
</style>
</head>
<body>
<header>
<nav>
<ul>
<li><a data-for="overview" href="#overview" class="active">Overview</a></li>
<?php if ($opcache->getOption('allow_filelist')): ?>
<li><a data-for="files" href="#files">File usage</a></li>
<?php endif; ?>
<?php if ($opcache->getOption('allow_reset')): ?>
<li><a href="?reset=1" id="resetCache" onclick="return confirm('Are you sure you want to reset the cache?');">Reset cache</a></li>
<?php endif; ?>
<?php if ($opcache->getOption('allow_realtime')): ?>
<li><a href="#" id="toggleRealtime">Enable real-time update</a></li>
<?php endif; ?>
</ul>
</nav>
</header>
<div id="tabs">
<div id="overview">
<div class="container">
<div id="counts"></div>
<div id="info">
<div id="generalInfo"></div>
<div id="directives"></div>
<div id="functions">
<table>
<thead>
<tr><th>Available functions</th></tr>
</thead>
<tbody>
<?php foreach ($opcache->getData('functions') as $func): ?>
<tr><td><a href="http://php.net/<?php echo $func; ?>" title="View manual page" target="_blank"><?php echo $func; ?></a></td></tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<br style="clear:both;" />
</div>
</div>
</div>
<div id="files">
<?php if ($opcache->getOption('allow_filelist')): ?>
<form action="#">
<label for="frmFilter">Start typing to filter on script path</label><br>
<input type="text" name="filter" id="frmFilter">
</form>
<?php endif; ?>
<div class="container" id="filelist"></div>
</div>
</div>
<footer>
<a href="https://github.com/amnuts/opcache-gui" target="_blank">https://github.com/amnuts/opcache-gui</a>
</footer>
<script type="text/javascript">
var realtime = false;
var opstate = <?php echo json_encode($opcache->getData()); ?>;
var canInvalidate = <?php echo json_encode($opcache->canInvalidate()); ?>;
var useCharts = <?php echo json_encode($opcache->getOption('charts')); ?>;
var allowFiles = <?php echo json_encode($opcache->getOption('allow_filelist')); ?>;
var debounce = function(func, wait, immediate) {
var timeout;
wait = wait || 250;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) {
func.apply(context, args);
}
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) {
func.apply(context, args);
}
};
};
function keyUp(event){
var compare = $('#frmFilter').val().toLowerCase();
$('#filelist').find('table tbody tr').each(function(index){
if ($(this).data('path').indexOf(compare) == -1) {
$(this).addClass('hide');
} else {
$(this).removeClass('hide');
}
});
$('#filelist table tbody').trigger('paint');
};
<?php if ($opcache->getOption('charts')): ?>
var Gauge = function(el, colour) {
this.canvas = $(el).get(0);
this.ctx = this.canvas.getContext('2d');
this.width = this.canvas.width;
this.height = this.canvas.height;
this.colour = colour || '#6ca6ef';
this.loop = null;
this.degrees = 0;
this.newdegs = 0;
this.text = '';
this.init = function() {
this.ctx.clearRect(0, 0, this.width, this.height);
this.ctx.beginPath();
this.ctx.strokeStyle = '#e2e2e2';
this.ctx.lineWidth = 30;
this.ctx.arc(this.width/2, this.height/2, 100, 0, Math.PI*2, false);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.strokeStyle = this.colour;
this.ctx.lineWidth = 30;
this.ctx.arc(this.width/2, this.height/2, 100, 0 - (90 * Math.PI / 180), (this.degrees * Math.PI / 180) - (90 * Math.PI / 180), false);
this.ctx.stroke();
this.ctx.fillStyle = this.colour;
this.ctx.font = '60px sans-serif';
this.text = Math.round((this.degrees/360)*100) + '%';
this.ctx.fillText(this.text, (this.width/2) - (this.ctx.measureText(this.text).width/2), (this.height/2) + 20);
};
this.draw = function() {
if (typeof this.loop != 'undefined') {
clearInterval(this.loop);
}
var self = this;
self.loop = setInterval(function(){ self.animate(); }, 1000/(this.newdegs - this.degrees));
};
this.animate = function() {
if (this.degrees == this.newdegs) {
clearInterval(this.loop);
}
if (this.degrees < this.newdegs) {
++this.degrees;
} else {
--this.degrees;
}
this.init();
};
this.setValue = function(val) {
this.newdegs = Math.round(3.6 * val);
this.draw();
};
}
<?php endif; ?>
$(function(){
<?php if ($opcache->getOption('allow_realtime')): ?>
function setCookie() {
var d = new Date();
var secure = (window.location.protocol === 'https:' ? ';secure' : '');
d.setTime(d.getTime() + (<?php echo ($opcache->getOption('cookie_ttl')); ?> * 86400000));
var expires = "expires="+d.toUTCString();
document.cookie = "<?php echo ($opcache->getOption('cookie_name')); ?>=true;" + expires + ";path=/" + secure;
};
function removeCookie() {
var secure = (window.location.protocol === 'https:' ? ';secure' : '');
document.cookie = "<?php echo ($opcache->getOption('cookie_name')); ?>=;expires=Thu, 01 Jan 1970 00:00:01 GMT;path=/" + secure;
};
function getCookie() {
var v = document.cookie.match('(^|;) ?<?php echo ($opcache->getOption('cookie_name')); ?>=([^;]*)(;|$)');
return v ? v[2] : null;
};
function updateStatus() {
$('#toggleRealtime').removeClass('pulse');
$.ajax({
url: "#",
dataType: "json",
cache: false,
success: function(data) {
$('#toggleRealtime').addClass('pulse');
opstate = data;
overviewCountsObj.setState({
data : opstate.overview
});
generalInfoObj.setState({
version : opstate.version,
start : opstate.overview.readable.start_time,
reset : opstate.overview.readable.last_restart_time
});
filesObj.setState({
data : opstate.files,
count_formatted : opstate.overview.readable.num_cached_scripts,
count : opstate.overview.num_cached_scripts
});
keyUp();
}
});
}
$('#toggleRealtime').click(function(){
if (realtime === false) {
realtime = setInterval(function(){updateStatus()}, <?php echo (int)$opcache->getOption('refresh_time') * 1000; ?>);
$(this).text('Disable real-time update');
setCookie();
} else {
clearInterval(realtime);
realtime = false;
$(this).text('Enable real-time update').removeClass('pulse');
removeCookie();
}
});
if (getCookie() == 'true') {
realtime = setInterval(function(){updateStatus()}, <?php echo (int)$opcache->getOption('refresh_time') * 1000; ?>);
$('#toggleRealtime').text('Disable real-time update');
}
<?php endif; ?>
$('nav a[data-for]').click(function(){
$('#tabs > div').hide();
$('#' + $(this).data('for')).show();
$('nav a[data-for]').removeClass('active');
$(this).addClass('active');
return false;
});
$(document).on('paint', '#filelist table tbody', function(event, params) {
var trs = $('#filelist').find('tbody tr');
trs.removeClass('alternate');
trs.filter(':not(.hide):odd').addClass('alternate');
filesObj.setState({showing: trs.filter(':not(.hide)').length});
});
$('#frmFilter').bind('keyup', debounce(keyUp, <?php echo $opcache->getOption('debounce_rate'); ?>));
});
var MemoryUsageGraph = React.createClass({
getInitialState: function () {
return {
memoryUsageGauge: null
};
},
componentDidMount: function () {
if (this.props.chart) {
this.state.memoryUsageGauge = new Gauge('#memoryUsageCanvas');
this.state.memoryUsageGauge.setValue(this.props.value);
}
},
componentDidUpdate: function () {
if (this.state.memoryUsageGauge != null) {
this.state.memoryUsageGauge.setValue(this.props.value);
}
},
render: function () {
if (this.props.chart == true) {
return React.createElement("canvas", { id: "memoryUsageCanvas", width: "250", height: "250", "data-value": this.props.value });
}
return React.createElement(
"p",
null,
React.createElement(
"span",
{ className: "large" },
this.props.value
),
React.createElement(
"span",
null,
"%"
)
);
}
});
var HitRateGraph = React.createClass({
getInitialState: function () {
return {
hitRateGauge: null
};
},
componentDidMount: function () {
if (this.props.chart) {
this.state.hitRateGauge = new Gauge('#hitRateCanvas');
this.state.hitRateGauge.setValue(this.props.value);
}
},
componentDidUpdate: function () {
if (this.state.hitRateGauge != null) {
this.state.hitRateGauge.setValue(this.props.value);
}
},
render: function () {
if (this.props.chart == true) {
return React.createElement("canvas", { id: "hitRateCanvas", width: "250", height: "250", "data-value": this.props.value });
}
return React.createElement(
"p",
null,
React.createElement(
"span",
{ className: "large" },
this.props.value
),
React.createElement(
"span",
null,
"%"
)
);
}
});
var MemoryUsagePanel = React.createClass({
render: function () {
return React.createElement(
"div",
{ className: "moreinfo" },
React.createElement(
"h3",
null,
"memory usage"
),
React.createElement(
"div",
null,
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"total memory:"
),
" ",
this.props.total
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"used memory:"
),
" ",
this.props.used
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"free memory:"
),
" ",
this.props.free
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"wasted memory:"
),
" ",
this.props.wasted,
" (",
this.props.wastedPercent,
"%)"
)
)
);
}
});
var StatisticsPanel = React.createClass({
render: function () {
return React.createElement(
"div",
{ className: "moreinfo" },
React.createElement(
"h3",
null,
"opcache statistics"
),
React.createElement(
"div",
null,
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"number of cached files:"
),
" ",
this.props.num_cached_scripts
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"number of hits:"
),
" ",
this.props.hits
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"number of misses:"
),
" ",
this.props.misses
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"blacklist misses:"
),
" ",
this.props.blacklist_miss
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"number of cached keys:"
),
" ",
this.props.num_cached_keys
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"max cached keys:"
),
" ",
this.props.max_cached_keys
)
)
);
}
});
var InternedStringsPanel = React.createClass({
render: function () {
return React.createElement(
"div",
{ className: "moreinfo" },
React.createElement(
"h3",
null,
"interned strings usage"
),
React.createElement(
"div",
null,
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"buffer size:"
),
" ",
this.props.buffer_size
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"used memory:"
),
" ",
this.props.strings_used_memory
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"free memory:"
),
" ",
this.props.strings_free_memory
),
React.createElement(
"p",
null,
React.createElement(
"b",
null,
"number of strings:"
),
" ",
this.props.number_of_strings
)
)
);
}
});
var OverviewCounts = React.createClass({
getInitialState: function () {
return {
data: opstate.overview,
chart: useCharts
};
},
render: function () {
var interned = this.state.data.readable.interned != null ? React.createElement(InternedStringsPanel, {
buffer_size: this.state.data.readable.interned.buffer_size,
strings_used_memory: this.state.data.readable.interned.strings_used_memory,
strings_free_memory: this.state.data.readable.interned.strings_free_memory,
number_of_strings: this.state.data.readable.interned.number_of_strings
}) : '';
return React.createElement(
"div",
null,
React.createElement(
"div",
null,
React.createElement(
"h3",
null,
"memory"
),
React.createElement(
"p",
null,
React.createElement(MemoryUsageGraph, { chart: this.state.chart, value: this.state.data.used_memory_percentage })
)
),
React.createElement(
"div",
null,
React.createElement(
"h3",
null,
"hit rate"
),
React.createElement(
"p",
null,
React.createElement(HitRateGraph, { chart: this.state.chart, value: this.state.data.hit_rate_percentage })
)
),
React.createElement(MemoryUsagePanel, {
total: this.state.data.readable.total_memory,
used: this.state.data.readable.used_memory,
free: this.state.data.readable.free_memory,
wasted: this.state.data.readable.wasted_memory,
wastedPercent: this.state.data.wasted_percentage
}),
React.createElement(StatisticsPanel, {
num_cached_scripts: this.state.data.readable.num_cached_scripts,
hits: this.state.data.readable.hits,
misses: this.state.data.readable.misses,
blacklist_miss: this.state.data.readable.blacklist_miss,
num_cached_keys: this.state.data.readable.num_cached_keys,
max_cached_keys: this.state.data.readable.max_cached_keys
}),
interned
);
}
});
var GeneralInfo = React.createClass({
getInitialState: function () {
return {
version: opstate.version,
start: opstate.overview.readable.start_time,
reset: opstate.overview.readable.last_restart_time
};
},
render: function () {
return React.createElement(
"table",
null,
React.createElement(
"thead",
null,
React.createElement(
"tr",
null,
React.createElement(
"th",
{ colSpan: "2" },
"General info"
)
)
),
React.createElement(
"tbody",
null,
React.createElement(
"tr",
null,
React.createElement(
"td",
null,
"Zend OPcache"
),
React.createElement(
"td",
null,
this.state.version.version
)
),
React.createElement(
"tr",
null,
React.createElement(
"td",
null,
"PHP"
),
React.createElement(
"td",
null,
this.state.version.php
)
),
React.createElement(
"tr",
null,
React.createElement(
"td",
null,
"Host"
),
React.createElement(
"td",
null,
this.state.version.host
)
),
React.createElement(
"tr",
null,
React.createElement(
"td",
null,
"Server Software"
),
React.createElement(
"td",
null,
this.state.version.server
)
),
React.createElement(
"tr",
null,
React.createElement(
"td",
null,
"Start time"
),
React.createElement(
"td",
null,
this.state.start
)
),
React.createElement(
"tr",
null,
React.createElement(
"td",
null,
"Last reset"
),
React.createElement(
"td",
null,
this.state.reset
)
)
)
);
}
});
var Directives = React.createClass({
getInitialState: function () {
return { data: opstate.directives };
},
render: function () {
var directiveNodes = this.state.data.map(function (directive) {
var map = { 'opcache.': '', '_': ' ' };
var dShow = directive.k.replace(/opcache\.|_/gi, function (matched) {
return map[matched];
});
var vShow;
if (directive.v === true || directive.v === false) {
vShow = React.createElement('i', {}, directive.v.toString());
} else if (directive.v === '') {
vShow = React.createElement('i', {}, 'no value');
} else {
vShow = directive.v;
}
return React.createElement(
"tr",
{ key: directive.k },
React.createElement(
"td",
{ title: 'View ' + directive.k + ' manual entry' },
React.createElement(
"a",
{ href: 'http://php.net/manual/en/opcache.configuration.php#ini.' + directive.k.replace(/_/g, '-'), target: "_blank" },
dShow
)
),
React.createElement(
"td",
null,
vShow
)
);
});
return React.createElement(
"table",
null,
React.createElement(
"thead",
null,
React.createElement(
"tr",
null,
React.createElement(
"th",
{ colSpan: "2" },
"Directives"
)
)
),
React.createElement(
"tbody",
null,
directiveNodes
)
);
}
});
var Files = React.createClass({
getInitialState: function () {
return {
data: opstate.files,
showing: null,
allowFiles: allowFiles
};
},
handleInvalidate: function (e) {
e.preventDefault();
if (realtime) {
$.get('#', { invalidate: e.currentTarget.getAttribute('data-file') }, function (data) {
console.log('success: ' + data.success);
}, 'json');
} else {
window.location.href = e.currentTarget.href;
}
},
render: function () {
if (this.state.allowFiles) {
var fileNodes = this.state.data.map(function (file, i) {
var invalidate, invalidated;
if (file.timestamp == 0) {
invalidated = React.createElement(
"span",
null,
React.createElement(
"i",
{ className: "invalid metainfo" },
" - has been invalidated"
)
);
}
if (canInvalidate) {
invalidate = React.createElement(
"span",
null,
", ",
React.createElement(
"a",
{ className: "metainfo", href: '?invalidate=' + file.full_path, "data-file": file.full_path, onClick: this.handleInvalidate },
"force file invalidation"
)
);
}
return React.createElement(
"tr",
{ key: file.full_path, "data-path": file.full_path.toLowerCase(), className: i % 2 ? 'alternate' : '' },
React.createElement(
"td",
null,
React.createElement(
"div",
null,
React.createElement(
"span",
{ className: "pathname" },
file.full_path
),
React.createElement("br", null),
React.createElement(FilesMeta, { data: [file.readable.hits, file.readable.memory_consumption, file.last_used] }),
invalidate,
invalidated
)
)
);
}.bind(this));
return React.createElement(
"div",
null,
React.createElement(FilesListed, { showing: this.state.showing }),
React.createElement(
"table",
null,
React.createElement(
"thead",
null,
React.createElement(
"tr",
null,
React.createElement(
"th",
null,
"Script"
)
)
),
React.createElement(
"tbody",
null,
fileNodes
)
)
);
} else {
return React.createElement("span", null);
}
}
});
var FilesMeta = React.createClass({
render: function () {
return React.createElement(
"span",
{ className: "metainfo" },
React.createElement(
"b",
null,
"hits: "
),
React.createElement(
"span",
null,
this.props.data[0],
", "
),
React.createElement(
"b",
null,
"memory: "
),
React.createElement(
"span",
null,
this.props.data[1],
", "
),
React.createElement(
"b",
null,
"last used: "
),
React.createElement(
"span",
null,
this.props.data[2]
)
);
}
});
var FilesListed = React.createClass({
getInitialState: function () {
return {
formatted: opstate.overview.readable.num_cached_scripts,
total: opstate.overview.num_cached_scripts
};
},
render: function () {
var display = this.state.formatted + ' file' + (this.state.total == 1 ? '' : 's') + ' cached';
if (this.props.showing !== null && this.props.showing != this.state.total) {
display += ', ' + this.props.showing + ' showing due to filter';
}
return React.createElement(
"h3",
null,
display
);
}
});
var overviewCountsObj = ReactDOM.render(React.createElement(OverviewCounts, null), document.getElementById('counts'));
var generalInfoObj = ReactDOM.render(React.createElement(GeneralInfo, null), document.getElementById('generalInfo'));
var filesObj = ReactDOM.render(React.createElement(Files, null), document.getElementById('filelist'));
ReactDOM.render(React.createElement(Directives, null), document.getElementById('directives'));
</script>
</body>
</html>