123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748 |
- <?php
- /**
- * Taken from https://github.com/rlerdorf/opcache-status
- */
- define('THOUSAND_SEPARATOR',true);
- if (!extension_loaded('Zend OPcache')) {
- echo '<div style="background-color: #F2DEDE; color: #B94A48; padding: 1em;">You do not have the Zend OPcache extension loaded, sample data is being shown instead.</div>';
- require 'prod_check.opcache.data_sample.inc';
- }
- class OpCacheDataModel
- {
- private $_configuration;
- private $_status;
- private $_d3Scripts = array();
- public function __construct()
- {
- $this->_configuration = opcache_get_configuration();
- $this->_status = opcache_get_status();
- }
- public function getPageTitle()
- {
- return 'PHP ' . phpversion() . " with OpCache {$this->_configuration['version']['version']}";
- }
- public function getStatusDataRows()
- {
- $rows = array();
- foreach ($this->_status as $key => $value) {
- if ($key === 'scripts') {
- continue;
- }
- if (is_array($value)) {
- foreach ($value as $k => $v) {
- if ($v === false) {
- $value = 'false';
- }
- if ($v === true) {
- $value = 'true';
- }
- if ($k === 'used_memory' || $k === 'free_memory' || $k === 'wasted_memory') {
- $v = $this->_size_for_humans(
- $v
- );
- }
- if ($k === 'current_wasted_percentage' || $k === 'opcache_hit_rate') {
- $v = number_format(
- $v,
- 2
- ) . '%';
- }
- if ($k === 'blacklist_miss_ratio') {
- $v = number_format($v, 2) . '%';
- }
- if ($k === 'start_time' || $k === 'last_restart_time') {
- $v = ($v ? date(DATE_RFC822, $v) : 'never');
- }
- if (THOUSAND_SEPARATOR === true && is_int($v)) {
- $v = number_format($v);
- }
- $rows[] = "<tr><th>$k</th><td>$v</td></tr>\n";
- }
- continue;
- }
- if ($value === false) {
- $value = 'false';
- }
- if ($value === true) {
- $value = 'true';
- }
- $rows[] = "<tr><th>$key</th><td>$value</td></tr>\n";
- }
- return implode("\n", $rows);
- }
- public function getConfigDataRows()
- {
- $rows = array();
- foreach ($this->_configuration['directives'] as $key => $value) {
- if ($value === false) {
- $value = 'false';
- }
- if ($value === true) {
- $value = 'true';
- }
- if ($key == 'opcache.memory_consumption') {
- $value = $this->_size_for_humans($value);
- }
- $rows[] = "<tr><th>$key</th><td>$value</td></tr>\n";
- }
- return implode("\n", $rows);
- }
- public function getScriptStatusRows()
- {
- foreach ($this->_status['scripts'] as $key => $data) {
- $dirs[dirname($key)][basename($key)] = $data;
- $this->_arrayPset($this->_d3Scripts, $key, array(
- 'name' => basename($key),
- 'size' => $data['memory_consumption'],
- ));
- }
- asort($dirs);
- $basename = '';
- while (true) {
- if (count($this->_d3Scripts) !=1) break;
- $basename .= DIRECTORY_SEPARATOR . key($this->_d3Scripts);
- $this->_d3Scripts = reset($this->_d3Scripts);
- }
- $this->_d3Scripts = $this->_processPartition($this->_d3Scripts, $basename);
- $id = 1;
- $rows = array();
- foreach ($dirs as $dir => $files) {
- $count = count($files);
- $file_plural = $count > 1 ? 's' : null;
- $m = 0;
- foreach ($files as $file => $data) {
- $m += $data["memory_consumption"];
- }
- $m = $this->_size_for_humans($m);
- if ($count > 1) {
- $rows[] = '<tr>';
- $rows[] = "<th class=\"clickable\" id=\"head-{$id}\" colspan=\"3\" onclick=\"toggleVisible('#head-{$id}', '#row-{$id}')\">{$dir} ({$count} file{$file_plural}, {$m})</th>";
- $rows[] = '</tr>';
- }
- foreach ($files as $file => $data) {
- $rows[] = "<tr id=\"row-{$id}\">";
- $rows[] = "<td>" . $this->_format_value($data["hits"]) . "</td>";
- $rows[] = "<td>" . $this->_size_for_humans($data["memory_consumption"]) . "</td>";
- $rows[] = $count > 1 ? "<td>{$file}</td>" : "<td>{$dir}/{$file}</td>";
- $rows[] = '</tr>';
- }
- ++$id;
- }
- return implode("\n", $rows);
- }
- public function getScriptStatusCount()
- {
- return count($this->_status["scripts"]);
- }
- public function getGraphDataSetJson()
- {
- $dataset = array();
- $dataset['memory'] = array(
- $this->_status['memory_usage']['used_memory'],
- $this->_status['memory_usage']['free_memory'],
- $this->_status['memory_usage']['wasted_memory'],
- );
- $dataset['keys'] = array(
- $this->_status['opcache_statistics']['num_cached_keys'],
- $this->_status['opcache_statistics']['max_cached_keys'] - $this->_status['opcache_statistics']['num_cached_keys'],
- 0
- );
- $dataset['hits'] = array(
- $this->_status['opcache_statistics']['misses'],
- $this->_status['opcache_statistics']['hits'],
- 0,
- );
- $dataset['restarts'] = array(
- $this->_status['opcache_statistics']['oom_restarts'],
- $this->_status['opcache_statistics']['manual_restarts'],
- $this->_status['opcache_statistics']['hash_restarts'],
- );
- if (THOUSAND_SEPARATOR === true) {
- $dataset['TSEP'] = 1;
- } else {
- $dataset['TSEP'] = 0;
- }
- return json_encode($dataset);
- }
- public function getHumanUsedMemory()
- {
- return $this->_size_for_humans($this->getUsedMemory());
- }
- public function getHumanFreeMemory()
- {
- return $this->_size_for_humans($this->getFreeMemory());
- }
- public function getHumanWastedMemory()
- {
- return $this->_size_for_humans($this->getWastedMemory());
- }
- public function getUsedMemory()
- {
- return $this->_status['memory_usage']['used_memory'];
- }
- public function getFreeMemory()
- {
- return $this->_status['memory_usage']['free_memory'];
- }
- public function getWastedMemory()
- {
- return $this->_status['memory_usage']['wasted_memory'];
- }
- public function getWastedMemoryPercentage()
- {
- return number_format($this->_status['memory_usage']['current_wasted_percentage'], 2);
- }
- public function getD3Scripts()
- {
- return $this->_d3Scripts;
- }
- private function _processPartition($value, $name = null)
- {
- if (array_key_exists('size', $value)) {
- return $value;
- }
- $array = array('name' => $name,'children' => array());
- foreach ($value as $k => $v) {
- $array['children'][] = $this->_processPartition($v, $k);
- }
- return $array;
- }
- private function _format_value($value)
- {
- if (THOUSAND_SEPARATOR === true) {
- return number_format($value);
- } else {
- return $value;
- }
- }
- private function _size_for_humans($bytes)
- {
- if ($bytes > 1048576) {
- return sprintf('%.2f MB', $bytes / 1048576);
- } else {
- if ($bytes > 1024) {
- return sprintf('%.2f kB', $bytes / 1024);
- } else {
- return sprintf('%d bytes', $bytes);
- }
- }
- }
- // Borrowed from Laravel
- private function _arrayPset(&$array, $key, $value)
- {
- if (is_null($key)) return $array = $value;
- $keys = explode(DIRECTORY_SEPARATOR, ltrim($key, DIRECTORY_SEPARATOR));
- while (count($keys) > 1) {
- $key = array_shift($keys);
- if ( ! isset($array[$key]) || ! is_array($array[$key])) {
- $array[$key] = array();
- }
- $array =& $array[$key];
- }
- $array[array_shift($keys)] = $value;
- return $array;
- }
- }
- $dataModel = new OpCacheDataModel();
- ?>
- <!DOCTYPE html>
- <meta charset="utf-8">
- <html>
- <head>
- <style>
- body {
- font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
- margin: 0;
- padding: 0;
- }
- #container {
- width: 1024px;
- margin: auto;
- position: relative;
- }
- h1 {
- padding: 10px 0;
- }
- table {
- border-collapse: collapse;
- }
- tbody tr:nth-child(even) {
- background-color: #eee;
- }
- p.capitalize {
- text-transform: capitalize;
- }
- .tabs {
- position: relative;
- float: left;
- width: 60%;
- }
- .tab {
- float: left;
- }
- .tab label {
- background: #eee;
- padding: 10px 12px;
- border: 1px solid #ccc;
- margin-left: -1px;
- position: relative;
- left: 1px;
- }
- .tab [type=radio] {
- display: none;
- }
- .tab th, .tab td {
- padding: 8px 12px;
- }
- .content {
- position: absolute;
- top: 28px;
- left: 0;
- background: white;
- border: 1px solid #ccc;
- height: 450px;
- width: 100%;
- overflow: auto;
- }
- .content table {
- width: 100%;
- }
- .content th, .tab:nth-child(3) td {
- text-align: left;
- }
- .content td {
- text-align: right;
- }
- .clickable {
- cursor: pointer;
- }
- [type=radio]:checked ~ label {
- background: white;
- border-bottom: 1px solid white;
- z-index: 2;
- }
- [type=radio]:checked ~ label ~ .content {
- z-index: 1;
- }
- #graph {
- float: right;
- width: 40%;
- position: relative;
- }
- #graph > form {
- position: absolute;
- right: 60px;
- top: -20px;
- }
- #graph > svg {
- position: absolute;
- top: 0;
- right: 0;
- }
- #stats {
- position: absolute;
- right: 125px;
- top: 145px;
- }
- #stats th, #stats td {
- padding: 6px 10px;
- font-size: 0.8em;
- }
- #partition {
- position: absolute;
- width: 100%;
- height: 100%;
- z-index: 10;
- top: 0;
- left: 0;
- background: #ddd;
- display: none;
- }
- #close-partition {
- display: none;
- position: absolute;
- z-index: 20;
- right: 15px;
- top: 15px;
- background: #f9373d;
- color: #fff;
- padding: 12px 15px;
- }
- #close-partition:hover {
- background: #D32F33;
- cursor: pointer;
- }
- #partition rect {
- stroke: #fff;
- fill: #aaa;
- fill-opacity: 1;
- }
- #partition rect.parent {
- cursor: pointer;
- fill: steelblue;
- }
- #partition text {
- pointer-events: none;
- }
- label {
- cursor: pointer;
- }
- </style>
- <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.0.1/d3.v3.min.js"></script>
- <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
- <script>
- var hidden = {};
- function toggleVisible(head, row) {
- if (!hidden[row]) {
- d3.selectAll(row).transition().style('display', 'none');
- hidden[row] = true;
- d3.select(head).transition().style('color', '#ccc');
- } else {
- d3.selectAll(row).transition().style('display');
- hidden[row] = false;
- d3.select(head).transition().style('color', '#000');
- }
- }
- </script>
- <title><?php echo $dataModel->getPageTitle(); ?></title>
- </head>
- <body>
- <div id="container">
- <h1><?php echo $dataModel->getPageTitle(); ?></h1>
- <div class="tabs">
- <div class="tab">
- <input type="radio" id="tab-status" name="tab-group-1" checked>
- <label for="tab-status">Status</label>
- <div class="content">
- <table>
- <?php echo $dataModel->getStatusDataRows(); ?>
- </table>
- </div>
- </div>
- <div class="tab">
- <input type="radio" id="tab-config" name="tab-group-1">
- <label for="tab-config">Configuration</label>
- <div class="content">
- <table>
- <?php echo $dataModel->getConfigDataRows(); ?>
- </table>
- </div>
- </div>
- <div class="tab">
- <input type="radio" id="tab-scripts" name="tab-group-1">
- <label for="tab-scripts">Scripts (<?php echo $dataModel->getScriptStatusCount(); ?>)</label>
- <div class="content">
- <table style="font-size:0.8em;">
- <tr>
- <th width="10%">Hits</th>
- <th width="20%">Memory</th>
- <th width="70%">Path</th>
- </tr>
- <?php echo $dataModel->getScriptStatusRows(); ?>
- </table>
- </div>
- </div>
- <div class="tab">
- <input type="radio" id="tab-visualise" name="tab-group-1">
- <label for="tab-visualise">Visualise Partition</label>
- <div class="content"></div>
- </div>
- </div>
- <div id="graph">
- <form>
- <label><input type="radio" name="dataset" value="memory" checked> Memory</label>
- <label><input type="radio" name="dataset" value="keys"> Keys</label>
- <label><input type="radio" name="dataset" value="hits"> Hits</label>
- <label><input type="radio" name="dataset" value="restarts"> Restarts</label>
- </form>
- <div id="stats"></div>
- </div>
- </div>
- <div id="close-partition">✖ Close Visualisation</div>
- <div id="partition"></div>
- <script>
- var dataset = <?php echo $dataModel->getGraphDataSetJson(); ?>;
- var width = 400,
- height = 400,
- radius = Math.min(width, height) / 2,
- colours = ['#B41F1F', '#1FB437', '#ff7f0e'];
- d3.scale.customColours = function() {
- return d3.scale.ordinal().range(colours);
- };
- var colour = d3.scale.customColours();
- var pie = d3.layout.pie().sort(null);
- var arc = d3.svg.arc().innerRadius(radius - 20).outerRadius(radius - 50);
- var svg = d3.select("#graph").append("svg")
- .attr("width", width)
- .attr("height", height)
- .append("g")
- .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
- var path = svg.selectAll("path")
- .data(pie(dataset.memory))
- .enter().append("path")
- .attr("fill", function(d, i) { return colour(i); })
- .attr("d", arc)
- .each(function(d) { this._current = d; }); // store the initial values
- d3.selectAll("input").on("change", change);
- set_text("memory");
- function set_text(t) {
- if (t === "memory") {
- d3.select("#stats").html(
- "<table><tr><th style='background:#B41F1F;'>Used</th><td><?php echo $dataModel->getHumanUsedMemory()?></td></tr>"+
- "<tr><th style='background:#1FB437;'>Free</th><td><?php echo $dataModel->getHumanFreeMemory()?></td></tr>"+
- "<tr><th style='background:#ff7f0e;' rowspan=\"2\">Wasted</th><td><?php echo $dataModel->getHumanWastedMemory()?></td></tr>"+
- "<tr><td><?php echo $dataModel->getWastedMemoryPercentage()?>%</td></tr></table>"
- );
- } else if (t === "keys") {
- d3.select("#stats").html(
- "<table><tr><th style='background:#B41F1F;'>Cached keys</th><td>"+format_value(dataset[t][0])+"</td></tr>"+
- "<tr><th style='background:#1FB437;'>Free Keys</th><td>"+format_value(dataset[t][1])+"</td></tr></table>"
- );
- } else if (t === "hits") {
- d3.select("#stats").html(
- "<table><tr><th style='background:#B41F1F;'>Misses</th><td>"+format_value(dataset[t][0])+"</td></tr>"+
- "<tr><th style='background:#1FB437;'>Cache Hits</th><td>"+format_value(dataset[t][1])+"</td></tr></table>"
- );
- } else if (t === "restarts") {
- d3.select("#stats").html(
- "<table><tr><th style='background:#B41F1F;'>Memory</th><td>"+dataset[t][0]+"</td></tr>"+
- "<tr><th style='background:#1FB437;'>Manual</th><td>"+dataset[t][1]+"</td></tr>"+
- "<tr><th style='background:#ff7f0e;'>Keys</th><td>"+dataset[t][2]+"</td></tr></table>"
- );
- }
- }
- function change() {
- // Filter out any zero values to see if there is anything left
- var remove_zero_values = dataset[this.value].filter(function(value) {
- return value > 0;
- });
- // Skip if the value is undefined for some reason
- if (typeof dataset[this.value] !== 'undefined' && remove_zero_values.length > 0) {
- $('#graph').find('> svg').show();
- path = path.data(pie(dataset[this.value])); // update the data
- path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
- // Hide the graph if we can't draw it correctly, not ideal but this works
- } else {
- $('#graph').find('> svg').hide();
- }
- set_text(this.value);
- }
- function arcTween(a) {
- var i = d3.interpolate(this._current, a);
- this._current = i(0);
- return function(t) {
- return arc(i(t));
- };
- }
- function size_for_humans(bytes) {
- if (bytes > 1048576) {
- return (bytes/1048576).toFixed(2) + ' MB';
- } else if (bytes > 1024) {
- return (bytes/1024).toFixed(2) + ' KB';
- } else return bytes + ' bytes';
- }
- function format_value(value) {
- if (dataset["TSEP"] == 1) {
- return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
- } else {
- return value;
- }
- }
- var w = window.innerWidth,
- h = window.innerHeight,
- x = d3.scale.linear().range([0, w]),
- y = d3.scale.linear().range([0, h]);
- var vis = d3.select("#partition")
- .style("width", w + "px")
- .style("height", h + "px")
- .append("svg:svg")
- .attr("width", w)
- .attr("height", h);
- var partition = d3.layout.partition()
- .value(function(d) { return d.size; });
- root = JSON.parse('<?php echo json_encode($dataModel->getD3Scripts()); ?>');
- var g = vis.selectAll("g")
- .data(partition.nodes(root))
- .enter().append("svg:g")
- .attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; })
- .on("click", click);
- var kx = w / root.dx,
- ky = h / 1;
- g.append("svg:rect")
- .attr("width", root.dy * kx)
- .attr("height", function(d) { return d.dx * ky; })
- .attr("class", function(d) { return d.children ? "parent" : "child"; });
- g.append("svg:text")
- .attr("transform", transform)
- .attr("dy", ".35em")
- .style("opacity", function(d) { return d.dx * ky > 12 ? 1 : 0; })
- .text(function(d) { return d.name; })
- d3.select(window)
- .on("click", function() { click(root); })
- function click(d) {
- if (!d.children) return;
- kx = (d.y ? w - 40 : w) / (1 - d.y);
- ky = h / d.dx;
- x.domain([d.y, 1]).range([d.y ? 40 : 0, w]);
- y.domain([d.x, d.x + d.dx]);
- var t = g.transition()
- .duration(d3.event.altKey ? 7500 : 750)
- .attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; });
- t.select("rect")
- .attr("width", d.dy * kx)
- .attr("height", function(d) { return d.dx * ky; });
- t.select("text")
- .attr("transform", transform)
- .style("opacity", function(d) { return d.dx * ky > 12 ? 1 : 0; });
- d3.event.stopPropagation();
- }
- function transform(d) {
- return "translate(8," + d.dx * ky / 2 + ")";
- }
- $(document).ready(function() {
- function handleVisualisationToggle(close) {
- $('#partition, #close-partition').fadeToggle();
- // Is the visualisation being closed? If so show the status tab again
- if (close) {
- $('#tab-visualise').removeAttr('checked');
- $('#tab-status').trigger('click');
- }
- }
- $('label[for="tab-visualise"], #close-partition').on('click', function() {
- handleVisualisationToggle(($(this).attr('id') === 'close-partition'));
- });
- $(document).keyup(function(e) {
- if (e.keyCode == 27) handleVisualisationToggle(true);
- });
- });
- </script>
- </body>
- </html>
|