index.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772
  1. <?php
  2. namespace OpcacheGui;
  3. /**
  4. * OPcache GUI
  5. *
  6. * A simple but effective single-file GUI for the OPcache PHP extension.
  7. *
  8. * @author Andrew Collington, andy@amnuts.com
  9. * @version 2.2.3
  10. * @link https://github.com/amnuts/opcache-gui
  11. * @license MIT, http://acollington.mit-license.org/
  12. */
  13. /*
  14. * User configuration
  15. */
  16. $options = [
  17. 'allow_filelist' => true, // show/hide the files tab
  18. 'allow_invalidate' => true, // give a link to invalidate files
  19. 'allow_reset' => true, // give option to reset the whole cache
  20. 'allow_realtime' => true, // give option to enable/disable real-time updates
  21. 'refresh_time' => 5, // how often the data will refresh, in seconds
  22. 'size_precision' => 2, // Digits after decimal point
  23. 'size_space' => false, // have '1MB' or '1 MB' when showing sizes
  24. 'charts' => true, // show gauge chart or just big numbers
  25. 'debounce_rate' => 250 // milliseconds after key press to send keyup event when filtering
  26. ];
  27. /*
  28. * Shouldn't need to alter anything else below here
  29. */
  30. if (!extension_loaded('Zend OPcache')) {
  31. die('The Zend OPcache extension does not appear to be installed');
  32. }
  33. if (empty(ini_get('opcache.enable'))) {
  34. die('The Zend OPcache extension is installed but not turned on');
  35. }
  36. class OpCacheService
  37. {
  38. protected $data;
  39. protected $options;
  40. protected $defaults = [
  41. 'allow_filelist' => true,
  42. 'allow_invalidate' => true,
  43. 'allow_reset' => true,
  44. 'allow_realtime' => true,
  45. 'refresh_time' => 5,
  46. 'size_precision' => 2,
  47. 'size_space' => false,
  48. 'charts' => true,
  49. 'debounce_rate' => 250
  50. ];
  51. private function __construct($options = [])
  52. {
  53. $this->options = array_merge($this->defaults, $options);
  54. $this->data = $this->compileState();
  55. }
  56. public static function init($options = [])
  57. {
  58. $self = new self($options);
  59. if (!empty($_SERVER['HTTP_X_REQUESTED_WITH'])
  60. && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest'
  61. ) {
  62. if (isset($_GET['reset']) && $self->getOption('allow_reset')) {
  63. echo '{ "success": "' . ($self->resetCache() ? 'yes' : 'no') . '" }';
  64. } else if (isset($_GET['invalidate']) && $self->getOption('allow_invalidate')) {
  65. echo '{ "success": "' . ($self->resetCache($_GET['invalidate']) ? 'yes' : 'no') . '" }';
  66. } else {
  67. echo json_encode($self->getData((empty($_GET['section']) ? null : $_GET['section'])));
  68. }
  69. exit;
  70. } else if (isset($_GET['reset']) && $self->getOption('allow_reset')) {
  71. $self->resetCache();
  72. header('Location: ?');
  73. exit;
  74. } else if (isset($_GET['invalidate']) && $self->getOption('allow_invalidate')) {
  75. $self->resetCache($_GET['invalidate']);
  76. header('Location: ?');
  77. exit;
  78. }
  79. return $self;
  80. }
  81. public function getOption($name = null)
  82. {
  83. if ($name === null) {
  84. return $this->options;
  85. }
  86. return (isset($this->options[$name])
  87. ? $this->options[$name]
  88. : null
  89. );
  90. }
  91. public function getData($section = null, $property = null)
  92. {
  93. if ($section === null) {
  94. return $this->data;
  95. }
  96. $section = strtolower($section);
  97. if (isset($this->data[$section])) {
  98. if ($property === null || !isset($this->data[$section][$property])) {
  99. return $this->data[$section];
  100. }
  101. return $this->data[$section][$property];
  102. }
  103. return null;
  104. }
  105. public function canInvalidate()
  106. {
  107. return ($this->getOption('allow_invalidate') && function_exists('opcache_invalidate'));
  108. }
  109. public function resetCache($file = null)
  110. {
  111. $success = false;
  112. if ($file === null) {
  113. $success = opcache_reset();
  114. } else if (function_exists('opcache_invalidate')) {
  115. $success = opcache_invalidate(urldecode($file), true);
  116. }
  117. if ($success) {
  118. $this->compileState();
  119. }
  120. return $success;
  121. }
  122. protected function size($size)
  123. {
  124. $i = 0;
  125. $val = array('b', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB');
  126. while (($size / 1024) > 1) {
  127. $size /= 1024;
  128. ++$i;
  129. }
  130. return sprintf('%.'.$this->getOption('size_precision').'f%s%s',
  131. $size, ($this->getOption('size_space') ? ' ' : ''), $val[$i]
  132. );
  133. }
  134. protected function compileState()
  135. {
  136. $status = opcache_get_status();
  137. $config = opcache_get_configuration();
  138. $files = [];
  139. if (!empty($status['scripts']) && $this->getOption('allow_filelist')) {
  140. uasort($status['scripts'], function($a, $b) {
  141. return $a['hits'] < $b['hits'];
  142. });
  143. foreach ($status['scripts'] as &$file) {
  144. $file['full_path'] = str_replace('\\', '/', $file['full_path']);
  145. $file['readable'] = [
  146. 'hits' => number_format($file['hits']),
  147. 'memory_consumption' => $this->size($file['memory_consumption'])
  148. ];
  149. }
  150. $files = array_values($status['scripts']);
  151. }
  152. $overview = array_merge(
  153. $status['memory_usage'], $status['opcache_statistics'], [
  154. 'used_memory_percentage' => round(100 * (
  155. ($status['memory_usage']['used_memory'] + $status['memory_usage']['wasted_memory'])
  156. / $config['directives']['opcache.memory_consumption'])),
  157. 'hit_rate_percentage' => round($status['opcache_statistics']['opcache_hit_rate']),
  158. 'wasted_percentage' => round($status['memory_usage']['current_wasted_percentage'], 2),
  159. 'readable' => [
  160. 'total_memory' => $this->size($config['directives']['opcache.memory_consumption']),
  161. 'used_memory' => $this->size($status['memory_usage']['used_memory']),
  162. 'free_memory' => $this->size($status['memory_usage']['free_memory']),
  163. 'wasted_memory' => $this->size($status['memory_usage']['wasted_memory']),
  164. 'num_cached_scripts' => number_format($status['opcache_statistics']['num_cached_scripts']),
  165. 'hits' => number_format($status['opcache_statistics']['hits']),
  166. 'misses' => number_format($status['opcache_statistics']['misses']),
  167. 'blacklist_miss' => number_format($status['opcache_statistics']['blacklist_misses']),
  168. 'num_cached_keys' => number_format($status['opcache_statistics']['num_cached_keys']),
  169. 'max_cached_keys' => number_format($status['opcache_statistics']['max_cached_keys']),
  170. 'start_time' => date('Y-m-d H:i:s', $status['opcache_statistics']['start_time']),
  171. 'last_restart_time' => ($status['opcache_statistics']['last_restart_time'] == 0
  172. ? 'never'
  173. : date('Y-m-d H:i:s', $status['opcache_statistics']['last_restart_time'])
  174. )
  175. ]
  176. ]
  177. );
  178. $directives = [];
  179. ksort($config['directives']);
  180. foreach ($config['directives'] as $k => $v) {
  181. $directives[] = ['k' => $k, 'v' => $v];
  182. }
  183. $version = array_merge(
  184. $config['version'],
  185. [
  186. 'php' => phpversion(),
  187. 'server' => $_SERVER['SERVER_SOFTWARE'],
  188. 'host' => (function_exists('gethostname')
  189. ? gethostname()
  190. : (php_uname('n')
  191. ?: (empty($_SERVER['SERVER_NAME'])
  192. ? $_SERVER['HOST_NAME']
  193. : $_SERVER['SERVER_NAME']
  194. )
  195. )
  196. )
  197. ]
  198. );
  199. return [
  200. 'version' => $version,
  201. 'overview' => $overview,
  202. 'files' => $files,
  203. 'directives' => $directives,
  204. 'blacklist' => $config['blacklist'],
  205. 'functions' => get_extension_funcs('Zend OPcache')
  206. ];
  207. }
  208. }
  209. $opcache = OpCacheService::init($options);
  210. ?>
  211. <!DOCTYPE html>
  212. <html>
  213. <head>
  214. <meta charset="UTF-8">
  215. <meta name="viewport" content="width=device-width,initial-scale=1.0">
  216. <title>OPcache statistics on <?php echo $opcache->getData('version', 'host'); ?></title>
  217. <script src="//cdn.jsdelivr.net/react/15.4.2/react.min.js"></script>
  218. <script src="//cdn.jsdelivr.net/react/15.4.2/react-dom.min.js"></script>
  219. <script src="//cdn.jsdelivr.net/jquery/3.1.1/jquery.min.js"></script>
  220. <style type="text/css">
  221. body { font-family:sans-serif; font-size:90%; padding: 0; margin: 0 }
  222. nav { padding-top: 20px; }
  223. nav > ul { list-style-type: none; padding-left: 8px; margin: 0; border-bottom: 1px solid #ccc; }
  224. nav > ul > li { display: inline-block; padding: 0; margin: 0 0 -1px 0; }
  225. nav > ul > li > a { display: block; margin: 0 10px; padding: 15px 30px; border: 1px solid transparent; border-bottom-color: #ccc; text-decoration: none; }
  226. nav > ul > li > a:hover { background-color: #f4f4f4; text-decoration: underline; }
  227. nav > ul > li > a.active:hover { background-color: initial; }
  228. nav > ul > li > a[data-for].active { border: 1px solid #ccc; border-bottom-color: #ffffff; border-top: 3px solid #6ca6ef; }
  229. table { margin: 0 0 1em 0; border-collapse: collapse; border-color: #fff; width: 100%; table-layout: fixed; }
  230. table caption { text-align: left; font-size: 1.5em; }
  231. table tr { background-color: #99D0DF; border-color: #fff; }
  232. table th { text-align: left; padding: 6px; background-color: #6ca6ef; color: #fff; border-color: #fff; font-weight: normal; }
  233. table td { padding: 4px 6px; line-height: 1.4em; vertical-align: top; border-color: #fff; overflow: hidden; overflow-wrap: break-word; text-overflow: ellipsis;}
  234. table tr:nth-child(odd) { background-color: #EFFEFF; }
  235. table tr:nth-child(even) { background-color: #E0ECEF; }
  236. #filelist table tr { background-color: #EFFEFF; }
  237. #filelist table tr.alternate { background-color: #E0ECEF; }
  238. td.pathname { width: 70%; }
  239. footer { border-top: 1px solid #ccc; padding: 1em 2em; }
  240. footer a { padding: 2em; text-decoration: none; opacity: 0.7; }
  241. footer a:hover { opacity: 1; }
  242. canvas { display: block; max-width: 100%; height: auto; margin: 0 auto; }
  243. #tabs { padding: 2em; }
  244. #tabs > div { display: none; }
  245. #tabs > div#overview { display:block; }
  246. #resetCache, #toggleRealtime, footer > a { background-position: 5px 50%; background-repeat: no-repeat; background-color: transparent; }
  247. footer > a { background-position: 0 50%; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAQCAYAAAAbBi9cAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyBpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNSBXaW5kb3dzIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjE2MENCRkExNzVBQjExRTQ5NDBGRTUzMzQyMDVDNzFFIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjE2MENCRkEyNzVBQjExRTQ5NDBGRTUzMzQyMDVDNzFFIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MTYwQ0JGOUY3NUFCMTFFNDk0MEZFNTMzNDIwNUM3MUUiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MTYwQ0JGQTA3NUFCMTFFNDk0MEZFNTMzNDIwNUM3MUUiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7HtUU1AAABN0lEQVR42qyUvWoCQRSF77hCLLKC+FOlCKTyIbYQUuhbWPkSFnZ2NpabUvANLGyz5CkkYGMlFtFAUmiSM8lZOVkWsgm58K079+fMnTusZl92BXbgDrTtZ2szd8fas/XBOzmBKaiCEFyTkL4pc9L8vgpNJJDyWtDna61EoXpO+xcFfXUVqtrf7Vx7m9Pub/EatvgHoYXD4ylztC14BBVwydvydgDPHPgNaErN3jLKIxAUmEvAXK21I18SJpXBGAxyBAaMlblOWOs1bMXFkMGeBFsi0pJNe/QNuV7563+gs8LfhrRfE6GaHLuRqfnUiKi6lJ034B44EXL0baTTJWujNGkG3kBX5uRyZuRkPl3WzDTBtzjnxxiDDq83yNxUk7GYuXM53jeLuMNavvAXkv4zrJkTaeGHAAMAIal3icPMsyQAAAAASUVORK5CYII='); font-size: 80%; }
  248. #resetCache { background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NjBFMUMyMjI3NDlGMTFFNEE3QzNGNjQ0OEFDQzQ1MkMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NjBFMUMyMjM3NDlGMTFFNEE3QzNGNjQ0OEFDQzQ1MkMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo2MEUxQzIyMDc0OUYxMUU0QTdDM0Y2NDQ4QUNDNDUyQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo2MEUxQzIyMTc0OUYxMUU0QTdDM0Y2NDQ4QUNDNDUyQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PplZ+ZkAAAD1SURBVHjazFPtDYJADIUJZAMZ4UbACWQENjBO4Ao6AW5AnODcADZQJwAnwJ55NbWhB/6zycsdpX39uDZNpsURtjgzwkDoCBecs5ITPGGMwCNAkIrQw+8ri36GhBHsavFdpILEo4wEpZxRigy009EhG760gr0VhFoyZfvJKPwsheIWIeGejBZRIxRVhMRFevbuUXBew/iE/lhlBduV0j8Jx+TvJEWPphq8n5li9utgaw6cW/h6NSt/JcnVBhQxotIgKTBrbNvIHo2G0x1rwlKqTDusxiAz6hHNL1zayTVqVKRKpa/LPljPH1sJh6l/oNSrZfwSYABtq3tFdZA5BAAAAABJRU5ErkJggg=='); }
  249. #toggleRealtime { position: relative; background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAAUCAYAAACAl21KAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODE5RUU4NUE3NDlGMTFFNDkyMzA4QzY1RjRBQkIzQjUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODE5RUU4NUI3NDlGMTFFNDkyMzA4QzY1RjRBQkIzQjUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4MTlFRTg1ODc0OUYxMUU0OTIzMDhDNjVGNEFCQjNCNSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4MTlFRTg1OTc0OUYxMUU0OTIzMDhDNjVGNEFCQjNCNSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PpXjpvMAAAD2SURBVHjarFQBEcMgDKR3E1AJldA5wMEqAQmTgINqmILdFCChdUAdMAeMcukuSwnQbbnLlZLwJPkQIcrSiT/IGNQHNb8CGQDyRw+2QWUBqC+luzo4OKQZIAVrB+ssyKp3Bkijf0+ijzIh4wQppoBauMSjyDZfMSCDxYZMsfHF120T36AqWZMkgyguQ3GOfottJ5TKnHC+wfeRsC2oDVayPgr3bbN2tHBH3tWuJCPa0JUgKtFzMQrcZH3FNHAc0yOp1cCASALyngoN6lhDopkJWxdifwY9A3u7l29ImpxOFSWIOVsGwHKENIWxss2eBVKdOeeXAAMAk/Z9h4QhXmUAAAAASUVORK5CYII='); }
  250. #counts { width: 270px; float: right; }
  251. #counts > div > div { background-color: #ededed; margin-bottom: 10px; }
  252. #counts > div > div > h3 { background-color: #cdcdcd; padding: 4px 6px; margin: 0; text-align: center; }
  253. #counts > div > div > p { margin: 0; text-align: center; }
  254. #counts > div > div > p span.large + span { font-size: 20pt; margin: 0; color: #6ca6ef; }
  255. #counts > div > div > p span.large { color: #6ca6ef; font-size: 80pt; margin: 0; padding: 0; text-align: center; }
  256. #info { margin-right: 280px; }
  257. #frmFilter { width: 520px; }
  258. #moreinfo { padding: 10px; }
  259. #moreinfo > p { text-align: left !important; line-height: 180%; }
  260. .metainfo { font-size: 80%; }
  261. .hide { display: none; }
  262. #toggleRealtime.pulse::before {
  263. content: ""; position: absolute;
  264. top: 13px; left: 3px; width: 18px; height: 18px;
  265. z-index: 10; opacity: 0; background-color: transparent;
  266. border: 2px solid rgb(255, 116, 0); border-radius: 100%;
  267. -webkit-animation: pulse 1s linear 2;
  268. -moz-animation: pulse 1s linear 2;
  269. animation: pulse 1s linear 2;
  270. }
  271. @media screen and (max-width: 750px) {
  272. #info { margin-right:auto; clear:both; }
  273. nav > ul { border-bottom: 0; }
  274. nav > ul > li { display: block; margin: 0; }
  275. nav > ul > li > a { display: block; margin: 0 10px; padding: 10px 0 10px 30px; border: 0; }
  276. nav > ul > li > a[data-for].active { border-bottom-color: #ccc; }
  277. #counts { position:relative; display:block; width:100%; }
  278. #toggleRealtime.pulse::before { top: 8px; }
  279. }
  280. @media screen and (max-width: 550px) {
  281. #frmFilter { width: 100%; }
  282. }
  283. @keyframes pulse {
  284. 0% {transform: scale(1); opacity: 0;}
  285. 50% {transform: scale(1.3); opacity: 0.7;}
  286. 100% {transform: scale(1.6); opacity: 1;}
  287. }
  288. @-webkit-keyframes pulse {
  289. 0% {-webkit-transform: scale(1); opacity: 0;}
  290. 50% {-webkit-transform: scale(1.3); opacity: 0.7;}
  291. 100% {-webkit-transform: scale(1.6); opacity: 0;}
  292. }
  293. @-moz-keyframes pulse {
  294. 0% {-moz-transform: scale(1); opacity: 0;}
  295. 50% {-moz-transform: scale(1.3); opacity: 0.7;}
  296. 100% {-moz-transform: scale(1.6); opacity: 0;}
  297. }
  298. </style>
  299. </head>
  300. <body>
  301. <header>
  302. <nav>
  303. <ul>
  304. <li><a data-for="overview" href="#overview" class="active">Overview</a></li>
  305. <?php if ($opcache->getOption('allow_filelist')): ?>
  306. <li><a data-for="files" href="#files">File usage</a></li>
  307. <?php endif; ?>
  308. <?php if ($opcache->getOption('allow_reset')): ?>
  309. <li><a href="?reset=1" id="resetCache" onclick="return confirm('Are you sure you want to reset the cache?');">Reset cache</a></li>
  310. <?php endif; ?>
  311. <?php if ($opcache->getOption('allow_realtime')): ?>
  312. <li><a href="#" id="toggleRealtime">Enable real-time update</a></li>
  313. <?php endif; ?>
  314. </ul>
  315. </nav>
  316. </header>
  317. <div id="tabs">
  318. <div id="overview">
  319. <div class="container">
  320. <div id="counts"></div>
  321. <div id="info">
  322. <div id="generalInfo"></div>
  323. <div id="directives"></div>
  324. <div id="functions">
  325. <table>
  326. <thead>
  327. <tr><th>Available functions</th></tr>
  328. </thead>
  329. <tbody>
  330. <?php foreach ($opcache->getData('functions') as $func): ?>
  331. <tr><td><a href="http://php.net/<?php echo $func; ?>" title="View manual page" target="_blank"><?php echo $func; ?></a></td></tr>
  332. <?php endforeach; ?>
  333. </tbody>
  334. </table>
  335. </div>
  336. <br style="clear:both;" />
  337. </div>
  338. </div>
  339. </div>
  340. <div id="files">
  341. <?php if ($opcache->getOption('allow_filelist')): ?>
  342. <form action="#">
  343. <label for="frmFilter">Start typing to filter on script path</label><br>
  344. <input type="text" name="filter" id="frmFilter">
  345. </form>
  346. <?php endif; ?>
  347. <div class="container" id="filelist"></div>
  348. </div>
  349. </div>
  350. <footer>
  351. <a href="https://github.com/amnuts/opcache-gui" target="_blank">https://github.com/amnuts/opcache-gui</a>
  352. </footer>
  353. <script type="text/javascript">
  354. var realtime = false;
  355. var opstate = <?php echo json_encode($opcache->getData()); ?>;
  356. var canInvalidate = <?php echo json_encode($opcache->canInvalidate()); ?>;
  357. var useCharts = <?php echo json_encode($opcache->getOption('charts')); ?>;
  358. var allowFiles = <?php echo json_encode($opcache->getOption('allow_filelist')); ?>;
  359. var debounce = function(func, wait, immediate) {
  360. var timeout;
  361. wait = wait || 250;
  362. return function() {
  363. var context = this, args = arguments;
  364. var later = function() {
  365. timeout = null;
  366. if (!immediate) {
  367. func.apply(context, args);
  368. }
  369. };
  370. var callNow = immediate && !timeout;
  371. clearTimeout(timeout);
  372. timeout = setTimeout(later, wait);
  373. if (callNow) {
  374. func.apply(context, args);
  375. }
  376. };
  377. };
  378. function keyUp(event){
  379. var compare = $('#frmFilter').val().toLowerCase();
  380. $('#filelist').find('table tbody tr').each(function(index){
  381. if ($(this).data('path').indexOf(compare) == -1) {
  382. $(this).addClass('hide');
  383. } else {
  384. $(this).removeClass('hide');
  385. }
  386. });
  387. $('#filelist table tbody').trigger('paint');
  388. };
  389. <?php if ($opcache->getOption('charts')): ?>
  390. var Gauge = function(el, colour) {
  391. this.canvas = $(el).get(0);
  392. this.ctx = this.canvas.getContext('2d');
  393. this.width = this.canvas.width;
  394. this.height = this.canvas.height;
  395. this.colour = colour || '#6ca6ef';
  396. this.loop = null;
  397. this.degrees = 0;
  398. this.newdegs = 0;
  399. this.text = '';
  400. this.init = function() {
  401. this.ctx.clearRect(0, 0, this.width, this.height);
  402. this.ctx.beginPath();
  403. this.ctx.strokeStyle = '#e2e2e2';
  404. this.ctx.lineWidth = 30;
  405. this.ctx.arc(this.width/2, this.height/2, 100, 0, Math.PI*2, false);
  406. this.ctx.stroke();
  407. this.ctx.beginPath();
  408. this.ctx.strokeStyle = this.colour;
  409. this.ctx.lineWidth = 30;
  410. 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);
  411. this.ctx.stroke();
  412. this.ctx.fillStyle = this.colour;
  413. this.ctx.font = '60px sans-serif';
  414. this.text = Math.round((this.degrees/360)*100) + '%';
  415. this.ctx.fillText(this.text, (this.width/2) - (this.ctx.measureText(this.text).width/2), (this.height/2) + 20);
  416. };
  417. this.draw = function() {
  418. if (typeof this.loop != 'undefined') {
  419. clearInterval(this.loop);
  420. }
  421. var self = this;
  422. self.loop = setInterval(function(){ self.animate(); }, 1000/(this.newdegs - this.degrees));
  423. };
  424. this.animate = function() {
  425. if (this.degrees == this.newdegs) {
  426. clearInterval(this.loop);
  427. }
  428. if (this.degrees < this.newdegs) {
  429. ++this.degrees;
  430. } else {
  431. --this.degrees;
  432. }
  433. this.init();
  434. };
  435. this.setValue = function(val) {
  436. this.newdegs = Math.round(3.6 * val);
  437. this.draw();
  438. };
  439. }
  440. <?php endif; ?>
  441. $(function(){
  442. <?php if ($opcache->getOption('allow_realtime')): ?>
  443. function updateStatus() {
  444. $('#toggleRealtime').removeClass('pulse');
  445. $.ajax({
  446. url: "#",
  447. dataType: "json",
  448. cache: false,
  449. success: function(data) {
  450. $('#toggleRealtime').addClass('pulse');
  451. opstate = data;
  452. overviewCountsObj.setState({
  453. data : opstate.overview
  454. });
  455. generalInfoObj.setState({
  456. version : opstate.version,
  457. start : opstate.overview.readable.start_time,
  458. reset : opstate.overview.readable.last_restart_time
  459. });
  460. filesObj.setState({
  461. data : opstate.files,
  462. count_formatted : opstate.overview.readable.num_cached_scripts,
  463. count : opstate.overview.num_cached_scripts
  464. });
  465. keyUp();
  466. }
  467. });
  468. }
  469. $('#toggleRealtime').click(function(){
  470. if (realtime === false) {
  471. realtime = setInterval(function(){updateStatus()}, <?php echo (int)$opcache->getOption('refresh_time') * 1000; ?>);
  472. $(this).text('Disable real-time update');
  473. } else {
  474. clearInterval(realtime);
  475. realtime = false;
  476. $(this).text('Enable real-time update').removeClass('pulse');
  477. }
  478. });
  479. <?php endif; ?>
  480. $('nav a[data-for]').click(function(){
  481. $('#tabs > div').hide();
  482. $('#' + $(this).data('for')).show();
  483. $('nav a[data-for]').removeClass('active');
  484. $(this).addClass('active');
  485. return false;
  486. });
  487. $(document).on('paint', '#filelist table tbody', function(event, params) {
  488. var trs = $('#filelist').find('tbody tr');
  489. trs.removeClass('alternate');
  490. trs.filter(':not(.hide):odd').addClass('alternate');
  491. filesObj.setState({showing: trs.filter(':not(.hide)').length});
  492. });
  493. $('#frmFilter').bind('keyup', debounce(keyUp, <?php echo $opcache->getOption('debounce_rate'); ?>));
  494. });
  495. var MemoryUsage = React.createClass({displayName: "MemoryUsage",
  496. getInitialState: function() {
  497. return {
  498. memoryUsageGauge : null
  499. };
  500. },
  501. componentDidMount: function() {
  502. if (this.props.chart) {
  503. this.state.memoryUsageGauge = new Gauge('#memoryUsageCanvas');
  504. this.state.memoryUsageGauge.setValue(this.props.value);
  505. }
  506. },
  507. componentDidUpdate: function() {
  508. if (this.state.memoryUsageGauge != null) {
  509. this.state.memoryUsageGauge.setValue(this.props.value);
  510. }
  511. },
  512. render: function() {
  513. if (this.props.chart == true) {
  514. return(React.createElement("canvas", {id: "memoryUsageCanvas", width: "250", height: "250", "data-value": this.props.value}));
  515. }
  516. return(React.createElement("p", null, React.createElement("span", {className: "large"}, this.props.value), React.createElement("span", null, "%")));
  517. }
  518. });
  519. var HitRate = React.createClass({displayName: "HitRate",
  520. getInitialState: function() {
  521. return {
  522. hitRateGauge : null
  523. };
  524. },
  525. componentDidMount: function() {
  526. if (this.props.chart) {
  527. this.state.hitRateGauge = new Gauge('#hitRateCanvas');
  528. this.state.hitRateGauge.setValue(this.props.value)
  529. }
  530. },
  531. componentDidUpdate: function() {
  532. if (this.state.hitRateGauge != null) {
  533. this.state.hitRateGauge.setValue(this.props.value);
  534. }
  535. },
  536. render: function() {
  537. if (this.props.chart == true) {
  538. return(React.createElement("canvas", {id: "hitRateCanvas", width: "250", height: "250", "data-value": this.props.value}));
  539. }
  540. return(React.createElement("p", null, React.createElement("span", {className: "large"}, this.props.value), React.createElement("span", null, "%")));
  541. }
  542. });
  543. var OverviewCounts = React.createClass({displayName: "OverviewCounts",
  544. getInitialState: function() {
  545. return {
  546. data : opstate.overview,
  547. chart : useCharts
  548. };
  549. },
  550. render: function() {
  551. return (
  552. React.createElement("div", null,
  553. React.createElement("div", null,
  554. React.createElement("h3", null, "memory usage"),
  555. React.createElement("p", null, React.createElement(MemoryUsage, {chart: this.state.chart, value: this.state.data.used_memory_percentage}))
  556. ),
  557. React.createElement("div", null,
  558. React.createElement("h3", null, "hit rate"),
  559. React.createElement("p", null, React.createElement(HitRate, {chart: this.state.chart, value: this.state.data.hit_rate_percentage}))
  560. ),
  561. React.createElement("div", {id: "moreinfo"},
  562. React.createElement("p", null, React.createElement("b", null, "total memory:"), " ", this.state.data.readable.total_memory),
  563. React.createElement("p", null, React.createElement("b", null, "used memory:"), " ", this.state.data.readable.used_memory),
  564. React.createElement("p", null, React.createElement("b", null, "free memory:"), " ", this.state.data.readable.free_memory),
  565. React.createElement("p", null, React.createElement("b", null, "wasted memory:"), " ", this.state.data.readable.wasted_memory, " (", this.state.data.wasted_percentage, "%)"),
  566. React.createElement("p", null, React.createElement("b", null, "number of cached files:"), " ", this.state.data.readable.num_cached_scripts),
  567. React.createElement("p", null, React.createElement("b", null, "number of hits:"), " ", this.state.data.readable.hits),
  568. React.createElement("p", null, React.createElement("b", null, "number of misses:"), " ", this.state.data.readable.misses),
  569. React.createElement("p", null, React.createElement("b", null, "blacklist misses:"), " ", this.state.data.readable.blacklist_miss),
  570. React.createElement("p", null, React.createElement("b", null, "number of cached keys:"), " ", this.state.data.readable.num_cached_keys),
  571. React.createElement("p", null, React.createElement("b", null, "max cached keys:"), " ", this.state.data.readable.max_cached_keys)
  572. )
  573. )
  574. );
  575. }
  576. });
  577. var GeneralInfo = React.createClass({displayName: "GeneralInfo",
  578. getInitialState: function() {
  579. return {
  580. version : opstate.version,
  581. start : opstate.overview.readable.start_time,
  582. reset : opstate.overview.readable.last_restart_time
  583. };
  584. },
  585. render: function() {
  586. return (
  587. React.createElement("table", null,
  588. React.createElement("thead", null,
  589. React.createElement("tr", null, React.createElement("th", {colSpan: "2"}, "General info"))
  590. ),
  591. React.createElement("tbody", null,
  592. React.createElement("tr", null, React.createElement("td", null, "Zend OPcache"), React.createElement("td", null, this.state.version.version)),
  593. React.createElement("tr", null, React.createElement("td", null, "PHP"), React.createElement("td", null, this.state.version.php)),
  594. React.createElement("tr", null, React.createElement("td", null, "Host"), React.createElement("td", null, this.state.version.host)),
  595. React.createElement("tr", null, React.createElement("td", null, "Server Software"), React.createElement("td", null, this.state.version.server)),
  596. React.createElement("tr", null, React.createElement("td", null, "Start time"), React.createElement("td", null, this.state.start)),
  597. React.createElement("tr", null, React.createElement("td", null, "Last reset"), React.createElement("td", null, this.state.reset))
  598. )
  599. )
  600. );
  601. }
  602. });
  603. var Directives = React.createClass({displayName: "Directives",
  604. getInitialState: function() {
  605. return { data : opstate.directives };
  606. },
  607. render: function() {
  608. var directiveNodes = this.state.data.map(function(directive) {
  609. var map = { 'opcache.':'', '_':' ' };
  610. var dShow = directive.k.replace(/opcache\.|_/gi, function(matched){
  611. return map[matched];
  612. });
  613. var vShow;
  614. if (directive.v === true || directive.v === false) {
  615. vShow = React.createElement('i', {}, directive.v.toString());
  616. } else if (directive.v === '') {
  617. vShow = React.createElement('i', {}, 'no value');
  618. } else {
  619. vShow = directive.v;
  620. }
  621. return (
  622. React.createElement("tr", {key: directive.k},
  623. React.createElement("td", {title: 'View ' + directive.k + ' manual entry'}, React.createElement("a", {href: 'http://php.net/manual/en/opcache.configuration.php#ini.'
  624. + (directive.k).replace(/_/g,'-'), target: "_blank"}, dShow)),
  625. React.createElement("td", null, vShow)
  626. )
  627. );
  628. });
  629. return (
  630. React.createElement("table", null,
  631. React.createElement("thead", null,
  632. React.createElement("tr", null, React.createElement("th", {colSpan: "2"}, "Directives"))
  633. ),
  634. React.createElement("tbody", null, directiveNodes)
  635. )
  636. );
  637. }
  638. });
  639. var Files = React.createClass({displayName: "Files",
  640. getInitialState: function() {
  641. return {
  642. data : opstate.files,
  643. showing: null,
  644. allowFiles: allowFiles
  645. };
  646. },
  647. handleInvalidate: function(e) {
  648. e.preventDefault();
  649. if (realtime) {
  650. $.get('#', { invalidate: e.currentTarget.getAttribute('data-file') }, function(data) {
  651. console.log('success: ' + data.success);
  652. }, 'json');
  653. } else {
  654. window.location.href = e.currentTarget.href;
  655. }
  656. },
  657. render: function() {
  658. if (this.state.allowFiles) {
  659. var fileNodes = this.state.data.map(function(file, i) {
  660. var invalidate, invalidated;
  661. if (file.timestamp == 0) {
  662. invalidated = React.createElement("span", null, React.createElement("i", {className: "invalid metainfo"}, " - has been invalidated"));
  663. }
  664. if (canInvalidate) {
  665. invalidate = React.createElement("span", null, ", ", React.createElement("a", {className: "metainfo", href: '?invalidate='
  666. + file.full_path, "data-file": file.full_path, onClick: this.handleInvalidate}, "force file invalidation"));
  667. }
  668. return (
  669. React.createElement("tr", {key: file.full_path, "data-path": file.full_path.toLowerCase(), className: i%2?'alternate':''},
  670. React.createElement("td", null,
  671. React.createElement("div", null,
  672. React.createElement("span", {className: "pathname"}, file.full_path), React.createElement("br", null),
  673. React.createElement(FilesMeta, {data: [file.readable.hits, file.readable.memory_consumption, file.last_used]}),
  674. invalidate,
  675. invalidated
  676. )
  677. )
  678. )
  679. );
  680. }.bind(this));
  681. return (
  682. React.createElement("div", null,
  683. React.createElement(FilesListed, {showing: this.state.showing}),
  684. React.createElement("table", null,
  685. React.createElement("thead", null,
  686. React.createElement("tr", null,
  687. React.createElement("th", null, "Script")
  688. )
  689. ),
  690. React.createElement("tbody", null, fileNodes)
  691. )
  692. )
  693. );
  694. } else {
  695. return React.createElement("span", null);
  696. }
  697. }
  698. });
  699. var FilesMeta = React.createClass({displayName: "FilesMeta",
  700. render: function() {
  701. return (
  702. React.createElement("span", {className: "metainfo"},
  703. React.createElement("b", null, "hits: "), React.createElement("span", null, this.props.data[0], ", "),
  704. React.createElement("b", null, "memory: "), React.createElement("span", null, this.props.data[1], ", "),
  705. React.createElement("b", null, "last used: "), React.createElement("span", null, this.props.data[2])
  706. )
  707. );
  708. }
  709. });
  710. var FilesListed = React.createClass({displayName: "FilesListed",
  711. getInitialState: function() {
  712. return {
  713. formatted : opstate.overview.readable.num_cached_scripts,
  714. total : opstate.overview.num_cached_scripts
  715. };
  716. },
  717. render: function() {
  718. var display = this.state.formatted + ' file' + (this.state.total == 1 ? '' : 's') + ' cached';
  719. if (this.props.showing !== null && this.props.showing != this.state.total) {
  720. display += ', ' + this.props.showing + ' showing due to filter';
  721. }
  722. return (React.createElement("h3", null, display));
  723. }
  724. });
  725. var overviewCountsObj = ReactDOM.render(React.createElement(OverviewCounts, null), document.getElementById('counts'));
  726. var generalInfoObj = ReactDOM.render(React.createElement(GeneralInfo, null), document.getElementById('generalInfo'));
  727. var filesObj = ReactDOM.render(React.createElement(Files, null), document.getElementById('filelist'));
  728. ReactDOM.render(React.createElement(Directives, null), document.getElementById('directives'));
  729. </script>
  730. </body>
  731. </html>