opcache-gui/index.php

773 lines
39 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.2.3
* @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
];
/*
* 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
];
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']),
'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'])
)
]
]
);
$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 { padding: 10px; }
#moreinfo > p { text-align: left !important; line-height: 180%; }
.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 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');
} else {
clearInterval(realtime);
realtime = false;
$(this).text('Enable real-time update').removeClass('pulse');
}
});
<?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 MemoryUsage = React.createClass({displayName: "MemoryUsage",
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 HitRate = React.createClass({displayName: "HitRate",
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 OverviewCounts = React.createClass({displayName: "OverviewCounts",
getInitialState: function() {
return {
data : opstate.overview,
chart : useCharts
};
},
render: function() {
return (
React.createElement("div", null,
React.createElement("div", null,
React.createElement("h3", null, "memory usage"),
React.createElement("p", null, React.createElement(MemoryUsage, {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(HitRate, {chart: this.state.chart, value: this.state.data.hit_rate_percentage}))
),
React.createElement("div", {id: "moreinfo"},
React.createElement("p", null, React.createElement("b", null, "total memory:"), " ", this.state.data.readable.total_memory),
React.createElement("p", null, React.createElement("b", null, "used memory:"), " ", this.state.data.readable.used_memory),
React.createElement("p", null, React.createElement("b", null, "free memory:"), " ", this.state.data.readable.free_memory),
React.createElement("p", null, React.createElement("b", null, "wasted memory:"), " ", this.state.data.readable.wasted_memory, " (", this.state.data.wasted_percentage, "%)"),
React.createElement("p", null, React.createElement("b", null, "number of cached files:"), " ", this.state.data.readable.num_cached_scripts),
React.createElement("p", null, React.createElement("b", null, "number of hits:"), " ", this.state.data.readable.hits),
React.createElement("p", null, React.createElement("b", null, "number of misses:"), " ", this.state.data.readable.misses),
React.createElement("p", null, React.createElement("b", null, "blacklist misses:"), " ", this.state.data.readable.blacklist_miss),
React.createElement("p", null, React.createElement("b", null, "number of cached keys:"), " ", this.state.data.readable.num_cached_keys),
React.createElement("p", null, React.createElement("b", null, "max cached keys:"), " ", this.state.data.readable.max_cached_keys)
)
)
);
}
});
var GeneralInfo = React.createClass({displayName: "GeneralInfo",
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({displayName: "Directives",
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({displayName: "Files",
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({displayName: "FilesMeta",
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({displayName: "FilesListed",
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>