Browse Source

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

Andrew Collington 10 years ago
parent
commit
64e6803085
2 changed files with 196 additions and 29 deletions
  1. 149 25
      index.php
  2. 47 4
      src/status.jsx

+ 149 - 25
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);
 
 ?>
 <!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>

+ 47 - 4
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(<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'));