Made it easier to configure a few options added a couple graphs.

This commit is contained in:
Andrew Collington 2015-05-08 17:04:40 +01:00
parent c42795acab
commit 64e6803085
2 changed files with 196 additions and 29 deletions

174
index.php
View file

@ -6,11 +6,27 @@
* A simple but effective single-file GUI for the OPcache PHP extension.
*
* @author Andrew Collington, andy@amnuts.com
* @version 2.0.2
* @version 2.1.0
* @link https://github.com/amnuts/opcache-gui
* @license MIT, http://acollington.mit-license.org/
*/
/*
* User configuration
*/
$options = [
'allow_invalidate' => true, // give a link to invalidate files
'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
];
/*
* 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');
}
@ -18,14 +34,18 @@ if (!extension_loaded('Zend OPcache')) {
class OpCacheService
{
protected $data;
protected $options = [
'allow_invalidate' => true
protected $options;
protected $defaults = [
'allow_invalidate' => true,
'size_precision' => 2,
'size_space' => false,
'charts' => true
];
private function __construct($options = [])
{
$this->options = array_merge($this->defaults, $options);
$this->data = $this->compileState();
$this->options = array_merge($this->options, $options);
}
public static function init($options = [])
@ -95,20 +115,25 @@ class OpCacheService
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',
number_format($size),
($this->getOption('size_space') ? ' ' : ''),
$val[$i]
);
}
protected function compileState()
{
$status = opcache_get_status();
$config = opcache_get_configuration();
$memsize = function($size, $precision = 3, $space = false)
{
$i = 0;
$val = array(' bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
while (($size / 1024) > 1) {
$size /= 1024;
++$i;
}
return sprintf("%.{$precision}f%s%s", $size, (($space && $i) ? ' ' : ''), $val[$i]);
};
$files = [];
if (!empty($status['scripts'])) {
@ -119,7 +144,7 @@ class OpCacheService
$file['full_path'] = str_replace('\\', '/', $file['full_path']);
$file['readable'] = [
'hits' => number_format($file['hits']),
'memory_consumption' => $memsize($file['memory_consumption'])
'memory_consumption' => $this->size($file['memory_consumption'])
];
}
$files = array_values($status['scripts']);
@ -133,10 +158,10 @@ class OpCacheService
'hit_rate_percentage' => round($status['opcache_statistics']['opcache_hit_rate']),
'wasted_percentage' => round($status['memory_usage']['current_wasted_percentage'], 2),
'readable' => [
'total_memory' => $memsize($config['directives']['opcache.memory_consumption']),
'used_memory' => $memsize($status['memory_usage']['used_memory']),
'free_memory' => $memsize($status['memory_usage']['free_memory']),
'wasted_memory' => $memsize($status['memory_usage']['wasted_memory']),
'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']),
@ -186,7 +211,7 @@ class OpCacheService
}
}
$opcache = OpCacheService::init();
$opcache = OpCacheService::init($options);
?>
<!doctype html>
@ -206,7 +231,7 @@ $opcache = OpCacheService::init();
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 { 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; }
@ -217,6 +242,7 @@ $opcache = OpCacheService::init();
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; width: 250px; height: 250px; margin: 0 auto; }
#tabs { padding: 2em; }
#tabs > div { display: none; }
#tabs > div#overview { display:block; }
@ -226,7 +252,7 @@ $opcache = OpCacheService::init();
#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; }
#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; }
#counts > div > div > p > span.large { font-size: 80pt; margin: 0; padding: 0; text-align: center; }
@ -325,6 +351,60 @@ $opcache = OpCacheService::init();
var realtime = false;
var opstate = <?php echo json_encode($opcache->getData()); ?>;
var canInvalidate = <?php echo ($opcache->canInvalidate() ? 'true' : 'false'); ?>;
var useCharts = <?php echo ($opcache->getOption('charts') ? 'true' : 'false'); ?>;
<?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(){
function updateStatus() {
@ -388,20 +468,63 @@ $opcache = OpCacheService::init();
});
});
var MemoryUsage = React.createClass({displayName: "MemoryUsage",
componentDidMount: function() {
if (this.props.chart) {
this.props.memoryUsageGauge = new Gauge('#memoryUsageCanvas');
this.props.memoryUsageGauge.setValue(this.props.value);
}
},
componentDidUpdate: function() {
if (typeof this.props.memoryUsageGauge != 'undefined') {
this.props.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",
componentDidMount: function() {
if (this.props.chart) {
this.props.hitRateGauge = new Gauge('#hitRateCanvas');
this.props.hitRateGauge.setValue(this.props.value)
}
},
componentDidUpdate: function() {
if (typeof this.props.hitRateGauge != 'undefined') {
this.props.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 };
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("span", {className: "large"}, this.state.data.used_memory_percentage), React.createElement("span", null, "%"))
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("span", {className: "large"}, this.state.data.hit_rate_percentage), React.createElement("span", null, "%"))
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),
@ -567,6 +690,7 @@ $opcache = OpCacheService::init();
var generalInfoObj = React.render(React.createElement(GeneralInfo, null), document.getElementById('generalInfo'));
var filesObj = React.render(React.createElement(Files, null), document.getElementById('filelist'));
React.render(React.createElement(Directives, null), document.getElementById('directives'));
</script>
</body>

View file

@ -1,17 +1,60 @@
var MemoryUsage = React.createClass({
componentDidMount: function() {
if (this.props.chart) {
this.props.memoryUsageGauge = new Gauge('#memoryUsageCanvas');
this.props.memoryUsageGauge.setValue(this.props.value);
}
},
componentDidUpdate: function() {
if (typeof this.props.memoryUsageGauge != 'undefined') {
this.props.memoryUsageGauge.setValue(this.props.value);
}
},
render: function() {
if (this.props.chart == true) {
return(<canvas id="memoryUsageCanvas" width="250" height="250" data-value={this.props.value} />);
}
return(<p><span className="large">{this.props.value}</span><span>%</span></p>);
}
});
var HitRate = React.createClass({
componentDidMount: function() {
if (this.props.chart) {
this.props.hitRateGauge = new Gauge('#hitRateCanvas');
this.props.hitRateGauge.setValue(this.props.value)
}
},
componentDidUpdate: function() {
if (typeof this.props.hitRateGauge != 'undefined') {
this.props.hitRateGauge.setValue(this.props.value);
}
},
render: function() {
if (this.props.chart == true) {
return(<canvas id="hitRateCanvas" width="250" height="250" data-value={this.props.value} />);
}
return(<p><span className="large">{this.props.value}</span><span>%</span></p>);
}
});
var OverviewCounts = React.createClass({
getInitialState: function() {
return { data : opstate.overview };
return {
data : opstate.overview,
chart : useCharts
};
},
render: function() {
return (
<div>
<div>
<h3>memory usage</h3>
<p><span className="large">{this.state.data.used_memory_percentage}</span><span>%</span></p>
<p><MemoryUsage chart={this.state.chart} value={this.state.data.used_memory_percentage} /></p>
</div>
<div>
<h3>hit rate</h3>
<p><span className="large">{this.state.data.hit_rate_percentage}</span><span>%</span></p>
<p><HitRate chart={this.state.chart} value={this.state.data.hit_rate_percentage} /></p>
</div>
<div id="moreinfo">
<p><b>total memory:</b> {this.state.data.readable.total_memory}</p>
@ -176,4 +219,4 @@ var FilesListed = React.createClass({
var overviewCountsObj = React.render(<OverviewCounts/>, document.getElementById('counts'));
var generalInfoObj = React.render(<GeneralInfo/>, document.getElementById('generalInfo'));
var filesObj = React.render(<Files/>, document.getElementById('filelist'));
React.render(<Directives/>, document.getElementById('directives'));
React.render(<Directives/>, document.getElementById('directives'));