prod_check.opcache.inc 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  1. <?php
  2. /**
  3. * Taken from https://github.com/rlerdorf/opcache-status
  4. */
  5. define('THOUSAND_SEPARATOR',true);
  6. if (!extension_loaded('Zend OPcache')) {
  7. 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>';
  8. require 'prod_check.opcache.data_sample.inc';
  9. }
  10. class OpCacheDataModel
  11. {
  12. private $_configuration;
  13. private $_status;
  14. private $_d3Scripts = array();
  15. public function __construct()
  16. {
  17. $this->_configuration = opcache_get_configuration();
  18. $this->_status = opcache_get_status();
  19. }
  20. public function getPageTitle()
  21. {
  22. return 'PHP ' . phpversion() . " with OpCache {$this->_configuration['version']['version']}";
  23. }
  24. public function getStatusDataRows()
  25. {
  26. $rows = array();
  27. foreach ($this->_status as $key => $value) {
  28. if ($key === 'scripts') {
  29. continue;
  30. }
  31. if (is_array($value)) {
  32. foreach ($value as $k => $v) {
  33. if ($v === false) {
  34. $value = 'false';
  35. }
  36. if ($v === true) {
  37. $value = 'true';
  38. }
  39. if ($k === 'used_memory' || $k === 'free_memory' || $k === 'wasted_memory') {
  40. $v = $this->_size_for_humans(
  41. $v
  42. );
  43. }
  44. if ($k === 'current_wasted_percentage' || $k === 'opcache_hit_rate') {
  45. $v = number_format(
  46. $v,
  47. 2
  48. ) . '%';
  49. }
  50. if ($k === 'blacklist_miss_ratio') {
  51. $v = number_format($v, 2) . '%';
  52. }
  53. if ($k === 'start_time' || $k === 'last_restart_time') {
  54. $v = ($v ? date(DATE_RFC822, $v) : 'never');
  55. }
  56. if (THOUSAND_SEPARATOR === true && is_int($v)) {
  57. $v = number_format($v);
  58. }
  59. $rows[] = "<tr><th>$k</th><td>$v</td></tr>\n";
  60. }
  61. continue;
  62. }
  63. if ($value === false) {
  64. $value = 'false';
  65. }
  66. if ($value === true) {
  67. $value = 'true';
  68. }
  69. $rows[] = "<tr><th>$key</th><td>$value</td></tr>\n";
  70. }
  71. return implode("\n", $rows);
  72. }
  73. public function getConfigDataRows()
  74. {
  75. $rows = array();
  76. foreach ($this->_configuration['directives'] as $key => $value) {
  77. if ($value === false) {
  78. $value = 'false';
  79. }
  80. if ($value === true) {
  81. $value = 'true';
  82. }
  83. if ($key == 'opcache.memory_consumption') {
  84. $value = $this->_size_for_humans($value);
  85. }
  86. $rows[] = "<tr><th>$key</th><td>$value</td></tr>\n";
  87. }
  88. return implode("\n", $rows);
  89. }
  90. public function getScriptStatusRows()
  91. {
  92. foreach ($this->_status['scripts'] as $key => $data) {
  93. $dirs[dirname($key)][basename($key)] = $data;
  94. $this->_arrayPset($this->_d3Scripts, $key, array(
  95. 'name' => basename($key),
  96. 'size' => $data['memory_consumption'],
  97. ));
  98. }
  99. asort($dirs);
  100. $basename = '';
  101. while (true) {
  102. if (count($this->_d3Scripts) !=1) break;
  103. $basename .= DIRECTORY_SEPARATOR . key($this->_d3Scripts);
  104. $this->_d3Scripts = reset($this->_d3Scripts);
  105. }
  106. $this->_d3Scripts = $this->_processPartition($this->_d3Scripts, $basename);
  107. $id = 1;
  108. $rows = array();
  109. foreach ($dirs as $dir => $files) {
  110. $count = count($files);
  111. $file_plural = $count > 1 ? 's' : null;
  112. $m = 0;
  113. foreach ($files as $file => $data) {
  114. $m += $data["memory_consumption"];
  115. }
  116. $m = $this->_size_for_humans($m);
  117. if ($count > 1) {
  118. $rows[] = '<tr>';
  119. $rows[] = "<th class=\"clickable\" id=\"head-{$id}\" colspan=\"3\" onclick=\"toggleVisible('#head-{$id}', '#row-{$id}')\">{$dir} ({$count} file{$file_plural}, {$m})</th>";
  120. $rows[] = '</tr>';
  121. }
  122. foreach ($files as $file => $data) {
  123. $rows[] = "<tr id=\"row-{$id}\">";
  124. $rows[] = "<td>" . $this->_format_value($data["hits"]) . "</td>";
  125. $rows[] = "<td>" . $this->_size_for_humans($data["memory_consumption"]) . "</td>";
  126. $rows[] = $count > 1 ? "<td>{$file}</td>" : "<td>{$dir}/{$file}</td>";
  127. $rows[] = '</tr>';
  128. }
  129. ++$id;
  130. }
  131. return implode("\n", $rows);
  132. }
  133. public function getScriptStatusCount()
  134. {
  135. return count($this->_status["scripts"]);
  136. }
  137. public function getGraphDataSetJson()
  138. {
  139. $dataset = array();
  140. $dataset['memory'] = array(
  141. $this->_status['memory_usage']['used_memory'],
  142. $this->_status['memory_usage']['free_memory'],
  143. $this->_status['memory_usage']['wasted_memory'],
  144. );
  145. $dataset['keys'] = array(
  146. $this->_status['opcache_statistics']['num_cached_keys'],
  147. $this->_status['opcache_statistics']['max_cached_keys'] - $this->_status['opcache_statistics']['num_cached_keys'],
  148. 0
  149. );
  150. $dataset['hits'] = array(
  151. $this->_status['opcache_statistics']['misses'],
  152. $this->_status['opcache_statistics']['hits'],
  153. 0,
  154. );
  155. $dataset['restarts'] = array(
  156. $this->_status['opcache_statistics']['oom_restarts'],
  157. $this->_status['opcache_statistics']['manual_restarts'],
  158. $this->_status['opcache_statistics']['hash_restarts'],
  159. );
  160. if (THOUSAND_SEPARATOR === true) {
  161. $dataset['TSEP'] = 1;
  162. } else {
  163. $dataset['TSEP'] = 0;
  164. }
  165. return json_encode($dataset);
  166. }
  167. public function getHumanUsedMemory()
  168. {
  169. return $this->_size_for_humans($this->getUsedMemory());
  170. }
  171. public function getHumanFreeMemory()
  172. {
  173. return $this->_size_for_humans($this->getFreeMemory());
  174. }
  175. public function getHumanWastedMemory()
  176. {
  177. return $this->_size_for_humans($this->getWastedMemory());
  178. }
  179. public function getUsedMemory()
  180. {
  181. return $this->_status['memory_usage']['used_memory'];
  182. }
  183. public function getFreeMemory()
  184. {
  185. return $this->_status['memory_usage']['free_memory'];
  186. }
  187. public function getWastedMemory()
  188. {
  189. return $this->_status['memory_usage']['wasted_memory'];
  190. }
  191. public function getWastedMemoryPercentage()
  192. {
  193. return number_format($this->_status['memory_usage']['current_wasted_percentage'], 2);
  194. }
  195. public function getD3Scripts()
  196. {
  197. return $this->_d3Scripts;
  198. }
  199. private function _processPartition($value, $name = null)
  200. {
  201. if (array_key_exists('size', $value)) {
  202. return $value;
  203. }
  204. $array = array('name' => $name,'children' => array());
  205. foreach ($value as $k => $v) {
  206. $array['children'][] = $this->_processPartition($v, $k);
  207. }
  208. return $array;
  209. }
  210. private function _format_value($value)
  211. {
  212. if (THOUSAND_SEPARATOR === true) {
  213. return number_format($value);
  214. } else {
  215. return $value;
  216. }
  217. }
  218. private function _size_for_humans($bytes)
  219. {
  220. if ($bytes > 1048576) {
  221. return sprintf('%.2f&nbsp;MB', $bytes / 1048576);
  222. } else {
  223. if ($bytes > 1024) {
  224. return sprintf('%.2f&nbsp;kB', $bytes / 1024);
  225. } else {
  226. return sprintf('%d&nbsp;bytes', $bytes);
  227. }
  228. }
  229. }
  230. // Borrowed from Laravel
  231. private function _arrayPset(&$array, $key, $value)
  232. {
  233. if (is_null($key)) return $array = $value;
  234. $keys = explode(DIRECTORY_SEPARATOR, ltrim($key, DIRECTORY_SEPARATOR));
  235. while (count($keys) > 1) {
  236. $key = array_shift($keys);
  237. if ( ! isset($array[$key]) || ! is_array($array[$key])) {
  238. $array[$key] = array();
  239. }
  240. $array =& $array[$key];
  241. }
  242. $array[array_shift($keys)] = $value;
  243. return $array;
  244. }
  245. }
  246. $dataModel = new OpCacheDataModel();
  247. ?>
  248. <!DOCTYPE html>
  249. <meta charset="utf-8">
  250. <html>
  251. <head>
  252. <style>
  253. body {
  254. font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
  255. margin: 0;
  256. padding: 0;
  257. }
  258. #container {
  259. width: 1024px;
  260. margin: auto;
  261. position: relative;
  262. }
  263. h1 {
  264. padding: 10px 0;
  265. }
  266. table {
  267. border-collapse: collapse;
  268. }
  269. tbody tr:nth-child(even) {
  270. background-color: #eee;
  271. }
  272. p.capitalize {
  273. text-transform: capitalize;
  274. }
  275. .tabs {
  276. position: relative;
  277. float: left;
  278. width: 60%;
  279. }
  280. .tab {
  281. float: left;
  282. }
  283. .tab label {
  284. background: #eee;
  285. padding: 10px 12px;
  286. border: 1px solid #ccc;
  287. margin-left: -1px;
  288. position: relative;
  289. left: 1px;
  290. }
  291. .tab [type=radio] {
  292. display: none;
  293. }
  294. .tab th, .tab td {
  295. padding: 8px 12px;
  296. }
  297. .content {
  298. position: absolute;
  299. top: 28px;
  300. left: 0;
  301. background: white;
  302. border: 1px solid #ccc;
  303. height: 450px;
  304. width: 100%;
  305. overflow: auto;
  306. }
  307. .content table {
  308. width: 100%;
  309. }
  310. .content th, .tab:nth-child(3) td {
  311. text-align: left;
  312. }
  313. .content td {
  314. text-align: right;
  315. }
  316. .clickable {
  317. cursor: pointer;
  318. }
  319. [type=radio]:checked ~ label {
  320. background: white;
  321. border-bottom: 1px solid white;
  322. z-index: 2;
  323. }
  324. [type=radio]:checked ~ label ~ .content {
  325. z-index: 1;
  326. }
  327. #graph {
  328. float: right;
  329. width: 40%;
  330. position: relative;
  331. }
  332. #graph > form {
  333. position: absolute;
  334. right: 60px;
  335. top: -20px;
  336. }
  337. #graph > svg {
  338. position: absolute;
  339. top: 0;
  340. right: 0;
  341. }
  342. #stats {
  343. position: absolute;
  344. right: 125px;
  345. top: 145px;
  346. }
  347. #stats th, #stats td {
  348. padding: 6px 10px;
  349. font-size: 0.8em;
  350. }
  351. #partition {
  352. position: absolute;
  353. width: 100%;
  354. height: 100%;
  355. z-index: 10;
  356. top: 0;
  357. left: 0;
  358. background: #ddd;
  359. display: none;
  360. }
  361. #close-partition {
  362. display: none;
  363. position: absolute;
  364. z-index: 20;
  365. right: 15px;
  366. top: 15px;
  367. background: #f9373d;
  368. color: #fff;
  369. padding: 12px 15px;
  370. }
  371. #close-partition:hover {
  372. background: #D32F33;
  373. cursor: pointer;
  374. }
  375. #partition rect {
  376. stroke: #fff;
  377. fill: #aaa;
  378. fill-opacity: 1;
  379. }
  380. #partition rect.parent {
  381. cursor: pointer;
  382. fill: steelblue;
  383. }
  384. #partition text {
  385. pointer-events: none;
  386. }
  387. label {
  388. cursor: pointer;
  389. }
  390. </style>
  391. <script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.0.1/d3.v3.min.js"></script>
  392. <script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
  393. <script>
  394. var hidden = {};
  395. function toggleVisible(head, row) {
  396. if (!hidden[row]) {
  397. d3.selectAll(row).transition().style('display', 'none');
  398. hidden[row] = true;
  399. d3.select(head).transition().style('color', '#ccc');
  400. } else {
  401. d3.selectAll(row).transition().style('display');
  402. hidden[row] = false;
  403. d3.select(head).transition().style('color', '#000');
  404. }
  405. }
  406. </script>
  407. <title><?php echo $dataModel->getPageTitle(); ?></title>
  408. </head>
  409. <body>
  410. <div id="container">
  411. <h1><?php echo $dataModel->getPageTitle(); ?></h1>
  412. <div class="tabs">
  413. <div class="tab">
  414. <input type="radio" id="tab-status" name="tab-group-1" checked>
  415. <label for="tab-status">Status</label>
  416. <div class="content">
  417. <table>
  418. <?php echo $dataModel->getStatusDataRows(); ?>
  419. </table>
  420. </div>
  421. </div>
  422. <div class="tab">
  423. <input type="radio" id="tab-config" name="tab-group-1">
  424. <label for="tab-config">Configuration</label>
  425. <div class="content">
  426. <table>
  427. <?php echo $dataModel->getConfigDataRows(); ?>
  428. </table>
  429. </div>
  430. </div>
  431. <div class="tab">
  432. <input type="radio" id="tab-scripts" name="tab-group-1">
  433. <label for="tab-scripts">Scripts (<?php echo $dataModel->getScriptStatusCount(); ?>)</label>
  434. <div class="content">
  435. <table style="font-size:0.8em;">
  436. <tr>
  437. <th width="10%">Hits</th>
  438. <th width="20%">Memory</th>
  439. <th width="70%">Path</th>
  440. </tr>
  441. <?php echo $dataModel->getScriptStatusRows(); ?>
  442. </table>
  443. </div>
  444. </div>
  445. <div class="tab">
  446. <input type="radio" id="tab-visualise" name="tab-group-1">
  447. <label for="tab-visualise">Visualise Partition</label>
  448. <div class="content"></div>
  449. </div>
  450. </div>
  451. <div id="graph">
  452. <form>
  453. <label><input type="radio" name="dataset" value="memory" checked> Memory</label>
  454. <label><input type="radio" name="dataset" value="keys"> Keys</label>
  455. <label><input type="radio" name="dataset" value="hits"> Hits</label>
  456. <label><input type="radio" name="dataset" value="restarts"> Restarts</label>
  457. </form>
  458. <div id="stats"></div>
  459. </div>
  460. </div>
  461. <div id="close-partition">&#10006; Close Visualisation</div>
  462. <div id="partition"></div>
  463. <script>
  464. var dataset = <?php echo $dataModel->getGraphDataSetJson(); ?>;
  465. var width = 400,
  466. height = 400,
  467. radius = Math.min(width, height) / 2,
  468. colours = ['#B41F1F', '#1FB437', '#ff7f0e'];
  469. d3.scale.customColours = function() {
  470. return d3.scale.ordinal().range(colours);
  471. };
  472. var colour = d3.scale.customColours();
  473. var pie = d3.layout.pie().sort(null);
  474. var arc = d3.svg.arc().innerRadius(radius - 20).outerRadius(radius - 50);
  475. var svg = d3.select("#graph").append("svg")
  476. .attr("width", width)
  477. .attr("height", height)
  478. .append("g")
  479. .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
  480. var path = svg.selectAll("path")
  481. .data(pie(dataset.memory))
  482. .enter().append("path")
  483. .attr("fill", function(d, i) { return colour(i); })
  484. .attr("d", arc)
  485. .each(function(d) { this._current = d; }); // store the initial values
  486. d3.selectAll("input").on("change", change);
  487. set_text("memory");
  488. function set_text(t) {
  489. if (t === "memory") {
  490. d3.select("#stats").html(
  491. "<table><tr><th style='background:#B41F1F;'>Used</th><td><?php echo $dataModel->getHumanUsedMemory()?></td></tr>"+
  492. "<tr><th style='background:#1FB437;'>Free</th><td><?php echo $dataModel->getHumanFreeMemory()?></td></tr>"+
  493. "<tr><th style='background:#ff7f0e;' rowspan=\"2\">Wasted</th><td><?php echo $dataModel->getHumanWastedMemory()?></td></tr>"+
  494. "<tr><td><?php echo $dataModel->getWastedMemoryPercentage()?>%</td></tr></table>"
  495. );
  496. } else if (t === "keys") {
  497. d3.select("#stats").html(
  498. "<table><tr><th style='background:#B41F1F;'>Cached keys</th><td>"+format_value(dataset[t][0])+"</td></tr>"+
  499. "<tr><th style='background:#1FB437;'>Free Keys</th><td>"+format_value(dataset[t][1])+"</td></tr></table>"
  500. );
  501. } else if (t === "hits") {
  502. d3.select("#stats").html(
  503. "<table><tr><th style='background:#B41F1F;'>Misses</th><td>"+format_value(dataset[t][0])+"</td></tr>"+
  504. "<tr><th style='background:#1FB437;'>Cache Hits</th><td>"+format_value(dataset[t][1])+"</td></tr></table>"
  505. );
  506. } else if (t === "restarts") {
  507. d3.select("#stats").html(
  508. "<table><tr><th style='background:#B41F1F;'>Memory</th><td>"+dataset[t][0]+"</td></tr>"+
  509. "<tr><th style='background:#1FB437;'>Manual</th><td>"+dataset[t][1]+"</td></tr>"+
  510. "<tr><th style='background:#ff7f0e;'>Keys</th><td>"+dataset[t][2]+"</td></tr></table>"
  511. );
  512. }
  513. }
  514. function change() {
  515. // Filter out any zero values to see if there is anything left
  516. var remove_zero_values = dataset[this.value].filter(function(value) {
  517. return value > 0;
  518. });
  519. // Skip if the value is undefined for some reason
  520. if (typeof dataset[this.value] !== 'undefined' && remove_zero_values.length > 0) {
  521. $('#graph').find('> svg').show();
  522. path = path.data(pie(dataset[this.value])); // update the data
  523. path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
  524. // Hide the graph if we can't draw it correctly, not ideal but this works
  525. } else {
  526. $('#graph').find('> svg').hide();
  527. }
  528. set_text(this.value);
  529. }
  530. function arcTween(a) {
  531. var i = d3.interpolate(this._current, a);
  532. this._current = i(0);
  533. return function(t) {
  534. return arc(i(t));
  535. };
  536. }
  537. function size_for_humans(bytes) {
  538. if (bytes > 1048576) {
  539. return (bytes/1048576).toFixed(2) + ' MB';
  540. } else if (bytes > 1024) {
  541. return (bytes/1024).toFixed(2) + ' KB';
  542. } else return bytes + ' bytes';
  543. }
  544. function format_value(value) {
  545. if (dataset["TSEP"] == 1) {
  546. return value.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
  547. } else {
  548. return value;
  549. }
  550. }
  551. var w = window.innerWidth,
  552. h = window.innerHeight,
  553. x = d3.scale.linear().range([0, w]),
  554. y = d3.scale.linear().range([0, h]);
  555. var vis = d3.select("#partition")
  556. .style("width", w + "px")
  557. .style("height", h + "px")
  558. .append("svg:svg")
  559. .attr("width", w)
  560. .attr("height", h);
  561. var partition = d3.layout.partition()
  562. .value(function(d) { return d.size; });
  563. root = JSON.parse('<?php echo json_encode($dataModel->getD3Scripts()); ?>');
  564. var g = vis.selectAll("g")
  565. .data(partition.nodes(root))
  566. .enter().append("svg:g")
  567. .attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; })
  568. .on("click", click);
  569. var kx = w / root.dx,
  570. ky = h / 1;
  571. g.append("svg:rect")
  572. .attr("width", root.dy * kx)
  573. .attr("height", function(d) { return d.dx * ky; })
  574. .attr("class", function(d) { return d.children ? "parent" : "child"; });
  575. g.append("svg:text")
  576. .attr("transform", transform)
  577. .attr("dy", ".35em")
  578. .style("opacity", function(d) { return d.dx * ky > 12 ? 1 : 0; })
  579. .text(function(d) { return d.name; })
  580. d3.select(window)
  581. .on("click", function() { click(root); })
  582. function click(d) {
  583. if (!d.children) return;
  584. kx = (d.y ? w - 40 : w) / (1 - d.y);
  585. ky = h / d.dx;
  586. x.domain([d.y, 1]).range([d.y ? 40 : 0, w]);
  587. y.domain([d.x, d.x + d.dx]);
  588. var t = g.transition()
  589. .duration(d3.event.altKey ? 7500 : 750)
  590. .attr("transform", function(d) { return "translate(" + x(d.y) + "," + y(d.x) + ")"; });
  591. t.select("rect")
  592. .attr("width", d.dy * kx)
  593. .attr("height", function(d) { return d.dx * ky; });
  594. t.select("text")
  595. .attr("transform", transform)
  596. .style("opacity", function(d) { return d.dx * ky > 12 ? 1 : 0; });
  597. d3.event.stopPropagation();
  598. }
  599. function transform(d) {
  600. return "translate(8," + d.dx * ky / 2 + ")";
  601. }
  602. $(document).ready(function() {
  603. function handleVisualisationToggle(close) {
  604. $('#partition, #close-partition').fadeToggle();
  605. // Is the visualisation being closed? If so show the status tab again
  606. if (close) {
  607. $('#tab-visualise').removeAttr('checked');
  608. $('#tab-status').trigger('click');
  609. }
  610. }
  611. $('label[for="tab-visualise"], #close-partition').on('click', function() {
  612. handleVisualisationToggle(($(this).attr('id') === 'close-partition'));
  613. });
  614. $(document).keyup(function(e) {
  615. if (e.keyCode == 27) handleVisualisationToggle(true);
  616. });
  617. });
  618. </script>
  619. </body>
  620. </html>