Browse Source

Added files list as React object.
Added getOptions() and moved code options to within class.
Process files list before being saved to data structure.
Started some restyling.

Andrew Collington 10 years ago
parent
commit
1756e70a56
1 changed files with 212 additions and 163 deletions
  1. 212 163
      index.php

+ 212 - 163
index.php

@@ -13,22 +13,17 @@ if (!extension_loaded('Zend OPcache')) {
     die('The Zend OPcache extension does not appear to be installed');
 }
 
-$settings = array(
-    'compress_path_threshold' => 2,
-    'used_memory_percentage_high_threshold' => 80,
-    'used_memory_percentage_mid_threshold' => 60,
-    'allow_invalidate' => true
-);
-
 class OpCacheService
 {
-    protected $config;
-    protected $status;
-    protected $functions;
+    protected $data;
+    protected $options = [
+        'allow_invalidate' => true
+    ];
 
-    private function __construct()
+    private function __construct($options = [])
     {
         $this->data = $this->compileState();
+        $this->options = array_merge($this->options, $options);
     }
 
     public static function init()
@@ -47,6 +42,17 @@ class OpCacheService
         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)
     {
         if ($section === null) {
@@ -75,6 +81,7 @@ class OpCacheService
 
     protected function compileState()
     {
+        $status = opcache_get_status();
         $config = opcache_get_configuration();
         $memsize = function($size, $precision = 3, $space = false)
         {
@@ -87,12 +94,19 @@ class OpCacheService
             return sprintf("%.{$precision}f%s%s", $size, (($space && $i) ? ' ' : ''), $val[$i]);
         };
 
-        $status = opcache_get_status();
+        $files = [];
         if (!empty($status['scripts'])) {
             uasort($status['scripts'], function($a, $b) {
                 return $a['hits'] < $b['hits'];
             });
-            $this->data['files'] = $status['scripts'];
+            foreach ($status['scripts'] as &$file) {
+                $file['full_path'] = str_replace('\\', '/', $file['full_path']);
+                $file['readable'] = [
+                    'hits'               => number_format($file['hits']),
+                    'memory_consumption' => $memsize($file['memory_consumption'])
+                ];
+            }
+            $files = array_values($status['scripts']);
         }
 
         $overview = array_merge(
@@ -148,7 +162,7 @@ class OpCacheService
         return [
             'version'    => $version,
             'overview'   => $overview,
-            'files'      => $status['scripts'],
+            'files'      => $files,
             'directives' => $directives,
             'blacklist'  => $config['blacklist'],
             'functions'  => get_extension_funcs('Zend OPcache')
@@ -170,32 +184,87 @@ $opcache = OpCacheService::init();
     <style type="text/css">
         body {
             font-family:sans-serif;
-            font-size:100%;
-            padding: 2em;
+            font-size:90%;
+            padding: 0;
+            margin: 0
+        }
+
+        #tabs { padding: 2em; }
+        #tabs > div {
+            display: none;
+        }
+        #tabs > div#overview {
+            display:block;
+        }
+
+        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;
         }
-        .container{overflow:auto;width:100%;position:relative;}
-        #info{margin-right:290px;}
-        #counts{position:absolute;top:0;right:0;width:280px;}
-        #counts > div {
-            padding:0;
-            margin-bottom:1em;
-            background-color: #efefef;
+        nav > ul > li > a {
+            display: block;
+            margin: 0 10px;
+            padding: 20px 40px;
+            border: 1px solid transparent;
+            border-bottom-color: #ccc;
         }
-        #counts > div > h3 {
-            background-color: #dedede;
-            padding: 7px;
-            font-size: 100%;
+        nav > ul > li > a[data-for].active {
+            border: 1px solid #ccc;
+            border-bottom-color: #ffffff;
+            border-top: 3px solid #6ca6ef;
+        }
+        #resetCache, #toggleRealtime {
+            background-position: 15px 50%;
+            background-repeat: no-repeat;
+            background-color: transparent;
+        }
+        #resetCache {
+            background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NjBFMUMyMjI3NDlGMTFFNEE3QzNGNjQ0OEFDQzQ1MkMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NjBFMUMyMjM3NDlGMTFFNEE3QzNGNjQ0OEFDQzQ1MkMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MEUxQzIyMDc0OUYxMUU0QTdDM0Y2NDQ4QUNDNDUyQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2MEUxQzIyMTc0OUYxMUU0QTdDM0Y2NDQ4QUNDNDUyQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PplZ+ZkAAAD1SURBVHjazFPtDYJADIUJZAMZ4UbACWQENjBO4Ao6AW5AnODcADZQJwAnwJ55NbWhB/6zycsdpX39uDZNpsURtjgzwkDoCBecs5ITPGGMwCNAkIrQw+8ri36GhBHsavFdpILEo4wEpZxRigy009EhG760gr0VhFoyZfvJKPwsheIWIeGejBZRIxRVhMRFevbuUXBew/iE/lhlBduV0j8Jx+TvJEWPphq8n5li9utgaw6cW/h6NSt/JcnVBhQxotIgKTBrbNvIHo2G0x1rwlKqTDusxiAz6hHNL1zayTVqVKRKpa/LPljPH1sJh6l/oNSrZfwSYABtq3tFdZA5BAAAAABJRU5ErkJggg==');
+        }
+        #toggleRealtime {
+            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: 10px;
+            margin: 0;
+        }
+        #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;
         }
-        #counts div.values { padding: 10px; }
-        #counts p.large {
-            font-family:'Roboto',sans-serif;
-            line-height:90%;
-            font-size:800%;
-            text-align:center;
-            margin: 20px 0;
+        #moreinfo { padding: 10px; }
+        #moreinfo > p {
+            text-align: left !important;
+            line-height: 180%;
         }
-        #counts p.large > span { font-size: 50%; }
+        #info { margin-right: 280px; }
 
         table { margin: 0 0 1em 0; border-collapse: collapse; border-color: #fff; width: 100%; }
         table caption { text-align: left; font-size: 1.5em; }
@@ -204,58 +273,8 @@ $opcache = OpCacheService::init();
         table td { padding: 4px 6px; line-height: 1.4em; vertical-align: top; border-color: #fff; }
         table tr:nth-child(odd) { background-color: #EFFEFF; }
         table tr:nth-child(even) { background-color: #E0ECEF; }
-        table tr.highlight { background-color: #61C4DF; }
-        td.pathname p { margin-bottom: 0.25em; }
-        .wsnw { white-space: nowrap; }
-        .low{color:#000000;}
-        .mid{color:#550000;}
-        .high{color:#FF0000;}
-
-        .invalid{color:#FF4545;}
-
-        span.showmore span.button {
-            display: inline-block;
-            margin-right: 5px;
-            position: relative;
-            top: -1px;
-            color: #333333;
-            background: none repeat scroll 0 0 #DDDDDD;
-            border-radius: 2px 2px 2px 2px;
-            font-size: 12px;
-            font-weight: bold;
-            height: 12px;
-            line-height: 6px;
-            padding: 0 5px;
-            vertical-align: middle;
-            cursor: pointer;
-        }
-        a.button {
-            text-decoration: none;
-            font-size: 110%;
-            color: #292929;
-            padding: 10px 26px;
-            background: -moz-linear-gradient(top, #ffffff 0%, #b4b7b8);
-            background: -webkit-gradient(linear, left top, left bottom, from(#ffffff), to(#b4b7b8));
-            -moz-border-radius: 6px;
-            -webkit-border-radius: 6px;
-            border-radius: 6px;
-            border: 1px solid #a1a1a1;
-            text-shadow: 0px -1px 0px rgba(000,000,000,0), 0px 1px 0px rgba(255,255,255,0.4);
-            margin: 0 1em;
-            white-space: nowrap;
-        }
-        span.showmore span.button:hover {
-            background-color: #CCCCCC;
-        }
+        td.pathname { width: 70%; }
 
-        @media screen and (max-width: 700px) {
-            #info{margin-right:auto;}
-            #counts{position:relative;display:block;margin-bottom:2em;width:100%;}
-        }
-        @media screen and (max-width: 550px) {
-            a.button{display:block;margin-bottom:2px;}
-            #frmFilter{width:99% !important;}
-        }
     </style>
 </head>
 
@@ -265,79 +284,32 @@ $opcache = OpCacheService::init();
     <ul>
         <li><a data-for="overview" href="#overview" class="active">Overview</a></li>
         <li><a data-for="files" href="#files">File usage</a></li>
-        <li><a data-for="reset" href="?reset=1" onclick="return confirm('Are you sure you want to reset the cache?');">Reset cache</a></li>
+        <li><a href="?reset=1" id="resetCache" onclick="return confirm('Are you sure you want to reset the cache?');">Reset cache</a></li>
+        <li><a href="#" id="toggleRealtime">Enable real-time update of stats</a></li>
     </ul>
 </nav>
 
-<div id="overview">
-    <h2>Overview</h2>
-    <div class="container">
-        <div id="counts"></div>
-        <div id="info">
-            <div id="generalInfo"></div>
-            <div id="directives"></div>
-            <div id="functions"></div>
-            <br style="clear:both;" />
+<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"></div>
+                <br style="clear:both;" />
+            </div>
         </div>
     </div>
-</div>
-
-<div id="files">
-    <h2>File usage</h2>
-    <p><label>Start typing to filter on script path<br/><input type="text" style="width:40em;" name="filter" id="frmFilter" /><label></p>
-    <div class="container">
-        <h3><?php echo $data['readable']['num_cached_scripts']; ?> file<?php echo ($data['readable']['num_cached_scripts'] == 1 ? '' : 's'); ?> cached <span id="filterShowing"></span></h3>
-        <table>
-            <thead>
-                <tr>
-                    <th>Script</th>
-                    <th>Details</th>
-                </tr>
-            </thead>
-            <tbody>
-            <?php $files = $opcache->getData('files'); ?>
-            <?php foreach ($files as $f): ?>
-                <tr>
-                    <td class="pathname">
-                        <p><?php
-                            $base  = basename($f['full_path']);
-                            $parts = array_filter(explode(DIRECTORY_SEPARATOR, dirname($f['full_path'])));
-                            if (!empty($settings['compress_path_threshold'])) {
-                                echo '<span class="showmore"><span class="button">…</span><span class="text" style="display:none;">' . DIRECTORY_SEPARATOR;
-                                echo join(DIRECTORY_SEPARATOR, array_slice($parts, 0, $settings['compress_path_threshold'])) . DIRECTORY_SEPARATOR;
-                                echo '</span>';
-                                echo join(DIRECTORY_SEPARATOR, array_slice($parts, $settings['compress_path_threshold']));
-                                if (count($parts) > $settings['compress_path_threshold']) {
-                                    echo DIRECTORY_SEPARATOR;
-                                }
-                                echo "{$base}</span>";
-                            } else {
-                                echo htmlentities($f['full_path'], ENT_COMPAT, 'UTF-8');
-                            }
-                        ?></p>
-                        <?php if ($settings['allow_invalidate'] && function_exists('opcache_invalidate')): ?>
-                            <a href="?invalidate=<?php echo urlencode($f['full_path']); ?>">Force file invalidation</a>
-                        <?php endif; ?>
-                    </td>
-                    <td>
-                        <p>
-                            hits: <?php echo $f['hits']; ?>,
-                            memory: <?php echo $f['memory_consumption']; ?><br />
-                            last used: <?php echo date_format(date_create($f['last_used']), 'Y-m-d H:i:s'); ?>
-                            <?php if ($f['timestamp'] === 0): ?>
-                                <br /><i class="invalid">has been invalidated</i>
-                            <?php endif; ?>
-                        </p>
-                    </td>
-                </tr>
-            <?php endforeach; ?>
-            </tbody>
-        </table>
+    <div id="files">
+        <p><label>Start typing to filter on script path<br/><input type="text" style="width:40em;" name="filter" id="frmFilter" /><label></p>
+        <div class="container" id="filelist"></div>
     </div>
 </div>
 
 <script type="text/jsx">
     var opstate = <?php echo json_encode($opcache->getData()); ?>;
+    var realtime = false;
 
     var OverviewCounts = React.createClass({
         getInitialState: function() {
@@ -348,13 +320,13 @@ $opcache = OpCacheService::init();
                 <div>
                     <div>
                         <h3>memory usage</h3>
-                        <p className="large">{this.state.data.used_memory_percentage}</p>
+                        <p><span className="large">{this.state.data.used_memory_percentage}</span><span>%</span></p>
                     </div>
                     <div>
                         <h3>hit rate</h3>
-                        <p className="large">{this.state.data.hit_rate_percentage}</p>
+                        <p><span className="large">{this.state.data.hit_rate_percentage}</span><span>%</span></p>
                     </div>
-                    <div>
+                    <div id="moreinfo">
                         <p><b>total memory:</b>{this.state.data.readable.total_memory}</p>
                         <p><b>used memory:</b>{this.state.data.readable.used_memory}</p>
                         <p><b>free memory:</b>{this.state.data.readable.free_memory}</p>
@@ -457,30 +429,95 @@ $opcache = OpCacheService::init();
         }
     });
 
-    React.render(<OverviewCounts/>, document.getElementById('counts'));
-    React.render(<GeneralInfo/>, document.getElementById('generalInfo'));
+    var Files = React.createClass({
+        getInitialState: function() {
+            return {
+                data : opstate.files,
+                count_formatted : opstate.overview.readable.num_cached_scripts,
+                count : opstate.overview.num_cached_scripts
+            };
+        },
+        render: function() {
+            var fileNodes = this.state.data.map(function (file) {
+                var invalidated;
+                console.log(file);
+                if (file.timestamp == 0) {
+                    invalidated = <span>
+                        <i className="invalid">has been invalidated</i>
+                    </span>;
+                }
+                return (
+                    <tr>
+                        <td className="pathname">
+                            <p>{file.full_path}</p>
+                            <?php if ($opcache->getOption('allow_invalidate') && function_exists('opcache_invalidate')): ?>
+                            <p><a href="?invalidate={file.full_path}">Force file invalidation</a></p>
+                            <?php endif; ?>
+                        </td>
+                        <td>
+                            <p>
+                                <span>hits: {file.readable.hits},</span>
+                                <span>memory: {file.readable.memory_consumption}</span>
+                                <br />
+                                <span>last used: {file.last_used}</span>
+                                {invalidated}
+                            </p>
+                        </td>
+                    </tr>
+                );
+            });
+            return (
+                <div>
+                    <h3>{this.state.count_formatted} file{this.state.count == 1 ? '' : 's'} cached
+                        <span id="filterShowing"></span>
+                    </h3>
+                    <table>
+                        <thead>
+                            <tr>
+                                <th>Script</th>
+                                <th>Details</th>
+                            </tr>
+                        </thead>
+                        <tbody>{fileNodes}</tbody>
+                    </table>
+                </div>
+            );
+        }
+    });
+
+    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(<Functions/>, document.getElementById('functions'));
-</script>
 
-<script type="text/javascript">
-    $(function(){
-        var realtime = false;
-        function ping() {
+    $(function() {
+        function updateStatus() {
             $.ajax({
                 url: "#",
                 dataType: "json",
                 cache: false,
-                success: function(data){
-                    $('.realtime').each(function(){
-                        $(this).text(data[$(this).attr('data-value')]);
+                success: function(data) {
+                    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
                     });
                 }
             });
         }
         $('#toggleRealtime').click(function(){
             if (realtime === false) {
-                realtime = setInterval(function(){ping()}, 5000);
+                realtime = setInterval(function(){updateStatus()}, 5000);
                 $(this).text('Disable real-time update of stats');
             } else {
                 clearInterval(realtime);
@@ -488,6 +525,18 @@ $opcache = OpCacheService::init();
                 $(this).text('Enable real-time update of stats');
             }
         });
+        $('nav a[data-for]').click(function(){
+            $('#tabs > div').hide();
+            $('#' + $(this).data('for')).show();
+            $('nav a[data-for]').removeClass('active');
+            $(this).addClass('active');
+            return false;
+        });
+    });
+</script>
+
+<script type="text/javascript">
+    $(function(){
         $('span.showmore span.button').click(function(){
             if ($(this).next().is(":visible")) {
                 $(this).next().hide();