From 64e6803085e0ab33316e25a972aab4126dd47b30 Mon Sep 17 00:00:00 2001 From: Andrew Collington Date: Fri, 8 May 2015 17:04:40 +0100 Subject: [PATCH] Made it easier to configure a few options added a couple graphs. --- index.php | 174 ++++++++++++++++++++++++++++++++++++++++++------- src/status.jsx | 51 +++++++++++++-- 2 files changed, 196 insertions(+), 29 deletions(-) diff --git a/index.php b/index.php index 9ae5d5c..4ae7b0c 100644 --- a/index.php +++ b/index.php @@ -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); ?> @@ -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 = getData()); ?>; var canInvalidate = canInvalidate() ? 'true' : 'false'); ?>; + var useCharts = getOption('charts') ? 'true' : 'false'); ?>; + +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(); + }; + } + $(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')); + diff --git a/src/status.jsx b/src/status.jsx index 39f397f..20fc43f 100644 --- a/src/status.jsx +++ b/src/status.jsx @@ -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(); + } + return(

{this.props.value}%

); + } +}); + +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(); + } + return(

{this.props.value}%

); + } +}); + var OverviewCounts = React.createClass({ getInitialState: function() { - return { data : opstate.overview }; + return { + data : opstate.overview, + chart : useCharts + }; }, render: function() { return (

memory usage

-

{this.state.data.used_memory_percentage}%

+

hit rate

-

{this.state.data.hit_rate_percentage}%

+

total memory: {this.state.data.readable.total_memory}

@@ -176,4 +219,4 @@ var FilesListed = React.createClass({ var overviewCountsObj = React.render(, document.getElementById('counts')); var generalInfoObj = React.render(, document.getElementById('generalInfo')); var filesObj = React.render(, document.getElementById('filelist')); -React.render(, document.getElementById('directives')); \ No newline at end of file +React.render(, document.getElementById('directives'));