define('THOUSAND_SEPARATOR',true);
if (!extension_loaded('Zend OPcache')) {
echo '
require 'data-sample.php';
}
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[] = "
$k$v\n";}
continue;
}
if ($value === false) {
$value = 'false';
}
if ($value === true) {
$value = 'true';
}
$rows[] = "
$key$value\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[] = "
$key$value\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[] = '
';$rows[] = "
{$dir} ({$count} file{$file_plural}, {$m})";$rows[] = '
';}
foreach ($files as $file => $data) {
$rows[] = "
";$rows[] = "
" . $this->_format_value($data["hits"]) . "";$rows[] = "
" . $this->_size_for_humans($data["memory_consumption"]) . "";$rows[] = $count > 1 ? "
{$file}" : "{$dir}/{$file}";$rows[] = '
';}
++$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();
?>
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;
}
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');
}
}
<?php echo $dataModel->getPageTitle(); ?>Status
<?php echo $dataModel->getStatusDataRows(); ?>
Configuration
<?php echo $dataModel->getConfigDataRows(); ?>
Scripts (<?php echo $dataModel->getScriptStatusCount(); ?>)
Hits | Memory | Path |
---|
<?php echo $dataModel->getScriptStatusRows(); ?>
Visualise Partition
Memory
Keys
Hits
Restarts
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(
"
Used | <?php echo $dataModel->getHumanUsedMemory()?> |
---|
"
Free<?php echo $dataModel->getHumanFreeMemory()?>"+"
Wasted<?php echo $dataModel->getHumanWastedMemory()?>"+"
<?php echo $dataModel->getWastedMemoryPercentage()?>%");
} else if (t === "keys") {
d3.select("#stats").html(
"
Cached keys | "+format_value(dataset[t][0])+" |
---|
"
Free Keys"+format_value(dataset[t][1])+"");
} else if (t === "hits") {
d3.select("#stats").html(
"
Misses | "+format_value(dataset[t][0])+" |
---|
"
Cache Hits"+format_value(dataset[t][1])+"");
} else if (t === "restarts") {
d3.select("#stats").html(
"
Memory | "+dataset[t][0]+" |
---|
"
Manual"+dataset[t][1]+""+"
Keys"+dataset[t][2]+"");
}
}
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);
});
});