diff --git a/.devilbox/www/config.php b/.devilbox/www/config.php index 3e9bbb2d..fe01a8b8 100644 --- a/.devilbox/www/config.php +++ b/.devilbox/www/config.php @@ -13,8 +13,8 @@ error_reporting(-1); putenv('RES_OPTIONS=retrans:1 retry:1 timeout:1 attempts:1'); -$DEVILBOX_VERSION = 'v1.4.0'; -$DEVILBOX_DATE = '2020-01-02'; +$DEVILBOX_VERSION = 'v1.5.0'; +$DEVILBOX_DATE = '2020-01-03'; $DEVILBOX_API_PAGE = 'devilbox-api/status.json'; // diff --git a/.devilbox/www/htdocs/vendor/ocp.php b/.devilbox/www/htdocs/vendor/ocp.php new file mode 100644 index 00000000..3c023d30 --- /dev/null +++ b/.devilbox/www/htdocs/vendor/ocp.php @@ -0,0 +1,391 @@ +1 || php_sapi_name()=='cli' || empty($_SERVER['REMOTE_ADDR']) ) { die; } // weak block against indirect access + +$time=time(); +define('CACHEPREFIX',function_exists('opcache_reset')?'opcache_':(function_exists('accelerator_reset')?'accelerator_':'')); + +if ( !empty($_GET['RESET']) ) { + if ( function_exists(CACHEPREFIX.'reset') ) { call_user_func(CACHEPREFIX.'reset'); } + header( 'Location: '.str_replace('?'.$_SERVER['QUERY_STRING'],'',$_SERVER['REQUEST_URI']) ); + exit; +} + +if ( !empty($_GET['RECHECK']) ) { + if ( function_exists(CACHEPREFIX.'invalidate') ) { + $recheck=trim($_GET['RECHECK']); $files=call_user_func(CACHEPREFIX.'get_status'); + if (!empty($files['scripts'])) { + foreach ($files['scripts'] as $file=>$value) { + if ( $recheck==='1' || strpos($file,$recheck)===0 ) call_user_func(CACHEPREFIX.'invalidate',$file); + } + } + header( 'Location: '.str_replace('?'.$_SERVER['QUERY_STRING'],'',$_SERVER['REQUEST_URI']) ); + } else { echo 'Sorry, this feature requires Zend Opcache newer than April 8th 2013'; } + exit; +} + +?> + + + OCP - Opcache Control Panel + + + + + + + +
+ +

Opcache Control Panel

+ +
+ Details + Files + Reset + + Recheck + + Refresh +
+ +Opcache not detected?'; die; } + +if ( !empty($_GET['FILES']) ) { echo '

files cached

'; files_display(); echo '
'; exit; } + +if ( !(isset($_REQUEST['GRAPHS']) && !$_REQUEST['GRAPHS']) && CACHEPREFIX=='opcache_') { graphs_display(); if ( !empty($_REQUEST['GRAPHS']) ) { exit; } } + +ob_start(); phpinfo(8); $phpinfo = ob_get_contents(); ob_end_clean(); // some info is only available via phpinfo? sadly buffering capture has to be used +if ( !preg_match( '/module\_Zend.(Optimizer\+|OPcache).+?(\]*\>.+?\<\/table\>).+?(\]*\>.+?\<\/table\>)/is', $phpinfo, $opcache) ) { } // todo + +if ( function_exists(CACHEPREFIX.'get_configuration') ) { echo '

general

'; $configuration=call_user_func(CACHEPREFIX.'get_configuration'); } + +$host=function_exists('gethostname')?@gethostname():@php_uname('n'); if (empty($host)) { $host=empty($_SERVER['SERVER_NAME'])?$_SERVER['HOST_NAME']:$_SERVER['SERVER_NAME']; } +$version=array('Host'=>$host); +$version['PHP Version']='PHP '.(defined('PHP_VERSION')?PHP_VERSION:'???').' '.(defined('PHP_SAPI')?PHP_SAPI:'').' '.(defined('PHP_OS')?' '.PHP_OS:''); +$version['Opcache Version']=empty($configuration['version']['version'])?'???':$configuration['version'][CACHEPREFIX.'product_name'].' '.$configuration['version']['version']; +print_table($version); + +if ( !empty($opcache[2]) ) { echo preg_replace('/\\[^>]+\<\/td\>\[0-9\,\. ]+\<\/td\>\<\/tr\>/','',$opcache[2]); } + +if ( function_exists(CACHEPREFIX.'get_status') && $status=call_user_func(CACHEPREFIX.'get_status') ) { + $uptime=array(); + if ( !empty($status[CACHEPREFIX.'statistics']['start_time']) ) { + $uptime['uptime']=time_since($time,$status[CACHEPREFIX.'statistics']['start_time'],1,''); + } + if ( !empty($status[CACHEPREFIX.'statistics']['last_restart_time']) ) { + $uptime['last_restart']=time_since($time,$status[CACHEPREFIX.'statistics']['last_restart_time']); + } + if (!empty($uptime)) {print_table($uptime);} + + if ( !empty($status['cache_full']) ) { $status['memory_usage']['cache_full']=$status['cache_full']; } + + echo '

memory

'; + print_table($status['memory_usage']); + unset($status[CACHEPREFIX.'statistics']['start_time'],$status[CACHEPREFIX.'statistics']['last_restart_time']); + echo '

statistics

'; + print_table($status[CACHEPREFIX.'statistics']); +} + +if ( empty($_GET['ALL']) ) { meta_display(); exit; } + +if ( !empty($configuration['blacklist']) ) { echo '

blacklist

'; print_table($configuration['blacklist']); } + +if ( !empty($opcache[3]) ) { echo '

runtime

'; echo $opcache[3]; } + +$name='zend opcache'; $functions=get_extension_funcs($name); +if (!$functions) { $name='zend optimizer+'; $functions=get_extension_funcs($name); } +if ($functions) { echo '

functions

'; print_table($functions); } else { $name=''; } + +$level=trim(CACHEPREFIX,'_').'.optimization_level'; +if (isset($configuration['directives'][$level])) { + echo '

optimization levels

'; + $levelset=strrev(base_convert($configuration['directives'][$level], 10, 2)); + $levels=array( + 1=>'Constants subexpressions elimination (CSE) true, false, null, etc.
Optimize series of ADD_STRING / ADD_CHAR
Convert CAST(IS_BOOL,x) into BOOL(x)
Convert INIT_FCALL_BY_NAME + DO_FCALL_BY_NAME into DO_FCALL', + 2=>'Convert constant operands to expected types
Convert conditional JMP with constant operands
Optimize static BRK and CONT', + 3=>'Convert $a = $a + expr into $a += expr
Convert $a++ into ++$a
Optimize series of JMP', + 4=>'PRINT and ECHO optimization (defunct)', + 5=>'Block Optimization - most expensive pass
Performs many different optimization patterns based on control flow graph (CFG)', + 9=>'Optimize register allocation (allows re-usage of temporary variables)', + 10=>'Remove NOPs' + ); + echo ''; + foreach ($levels as $pass=>$description) { + $disabled=substr($levelset,$pass-1,1)!=='1' || $pass==4 ? ' white':''; + echo ''; + } + echo '
PassDescription
'.$pass.''.$description.'
'; +} + +if ( isset($_GET['DUMP']) ) { + if ($name) { echo '

ini

'; print_table(ini_get_all($name,true)); } + foreach ($configuration as $key=>$value) { echo '

',$key,'

'; print_table($configuration[$key]); } + exit; +} + +meta_display(); + +echo ''; + +exit; + +function time_since($time,$original,$extended=0,$text='ago') { + $time = $time - $original; + $day = $extended? floor($time/86400) : round($time/86400,0); + $amount=0; $unit=''; + if ( $time < 86400) { + if ( $time < 60) { $amount=$time; $unit='second'; } + elseif ( $time < 3600) { $amount=floor($time/60); $unit='minute'; } + else { $amount=floor($time/3600); $unit='hour'; } + } + elseif ( $day < 14) { $amount=$day; $unit='day'; } + elseif ( $day < 56) { $amount=floor($day/7); $unit='week'; } + elseif ( $day < 672) { $amount=floor($day/30); $unit='month'; } + else { $amount=intval(2*($day/365))/2; $unit='year'; } + + if ( $amount!=1) {$unit.='s';} + if ($extended && $time>60) { $text=' and '.time_since($time,$time<86400?($time<3600?$amount*60:$amount*3600):$day*86400,0,'').$text; } + + return $amount.' '.$unit.' '.$text; +} + +function print_table($array,$headers=false) { + if ( empty($array) || !is_array($array) ) {return;} + echo ''; + if (!empty($headers)) { + if (!is_array($headers)) {$headers=array_keys(reset($array));} + echo ''; + foreach ($headers as $value) { echo ''; } + echo ''; + } + foreach ($array as $key=>$value) { + echo ''; + if ( !is_numeric($key) ) { + $key=ucwords(str_replace('_',' ',$key)); + echo ''; + if ( is_numeric($value) ) { + if ( $value>1048576) { $value=round($value/1048576,1).'M'; } + elseif ( is_float($value) ) { $value=round($value,1); } + } + } + if ( is_array($value) ) { + foreach ($value as $column) { + echo ''; + } + echo ''; + } + else { echo ''; } + } + echo '
',$value,'
',$key,'',$column,'
',$value,'
'; +} + +function files_display() { + $status=call_user_func(CACHEPREFIX.'get_status'); + if ( empty($status['scripts']) ) {return;} + if ( isset($_GET['DUMP']) ) { print_table($status['scripts']); exit;} + $time=time(); $sort=0; + $nogroup=preg_replace('/\&?GROUP\=[\-0-9]+/','',$_SERVER['REQUEST_URI']); + $nosort=preg_replace('/\&?SORT\=[\-0-9]+/','',$_SERVER['REQUEST_URI']); + $group=empty($_GET['GROUP'])?0:intval($_GET['GROUP']); if ( $group<0 || $group>9) { $group=1;} + $groupset=array_fill(0,9,''); $groupset[$group]=' class="b" '; + + echo '
+ ungroup | + 1 | + 2 | + 3 | + 4 | + 5 +
'; + + if ( !$group ) { $files =& $status['scripts']; } + else { + $files=array(); + foreach ($status['scripts'] as $data) { + if ( preg_match('@^[/]([^/]+[/]){'.$group.'}@',$data['full_path'],$path) ) { + if ( empty($files[$path[0]])) { $files[$path[0]]=array('full_path'=>'','files'=>0,'hits'=>0,'memory_consumption'=>0,'last_used_timestamp'=>'','timestamp'=>''); } + $files[$path[0]]['full_path']=$path[0]; + $files[$path[0]]['files']++; + $files[$path[0]]['memory_consumption']+=$data['memory_consumption']; + $files[$path[0]]['hits']+=$data['hits']; + if ( $data['last_used_timestamp']>$files[$path[0]]['last_used_timestamp']) {$files[$path[0]]['last_used_timestamp']=$data['last_used_timestamp'];} + if ( $data['timestamp']>$files[$path[0]]['timestamp']) {$files[$path[0]]['timestamp']=$data['timestamp'];} + } + } + } + + if ( !empty($_GET['SORT']) ) { + $keys=array( + 'full_path'=>SORT_STRING, + 'files'=>SORT_NUMERIC, + 'memory_consumption'=>SORT_NUMERIC, + 'hits'=>SORT_NUMERIC, + 'last_used_timestamp'=>SORT_NUMERIC, + 'timestamp'=>SORT_NUMERIC + ); + $titles=array('','path',$group?'files':'','size','hits','last used','created'); + $offsets=array_keys($keys); + $key=intval($_GET['SORT']); + $direction=$key>0?1:-1; + $key=abs($key)-1; + $key=isset($offsets[$key])&&!($key==1&&empty($group))?$offsets[$key]:reset($offsets); + $sort=array_search($key,$offsets)+1; + $sortflip=range(0,7); $sortflip[$sort]=-$direction*$sort; + if ( $keys[$key]==SORT_STRING) {$direction=-$direction; } + $arrow=array_fill(0,7,''); $arrow[$sort]=$direction>0?' ▼':' ▲'; + $direction=$direction>0?SORT_DESC:SORT_ASC; + $column=array(); foreach ($files as $data) { $column[]=$data[$key]; } + array_multisort($column, $keys[$key], $direction, $files); + } + + echo ' + '; + foreach ($titles as $column=>$title) { + if ($title) echo ''; + } + echo ' '; + foreach ($files as $data) { + echo ' + ', + ($group?'':''), + '', + '', + '', + ' + '; + } + echo '
',$title,$arrow[$column],'
x',$data['full_path'],''.number_format($data['files']).'',number_format(round($data['memory_consumption']/1024)),'K',number_format($data['hits']),'',time_since($time,$data['last_used_timestamp']),'',empty($data['timestamp'])?'':time_since($time,$data['timestamp']),'
'; +} + +function graphs_display() { + $graphs=array(); + $colors=array('green','brown','red'); + $primes=array(223, 463, 983, 1979, 3907, 7963, 16229, 32531, 65407, 130987); + $configuration=call_user_func(CACHEPREFIX.'get_configuration'); + $status=call_user_func(CACHEPREFIX.'get_status'); + + $graphs['memory']['total']=$configuration['directives']['opcache.memory_consumption']; + $graphs['memory']['free']=$status['memory_usage']['free_memory']; + $graphs['memory']['used']=$status['memory_usage']['used_memory']; + $graphs['memory']['wasted']=$status['memory_usage']['wasted_memory']; + + $graphs['keys']['total']=$status[CACHEPREFIX.'statistics']['max_cached_keys']; + foreach ($primes as $prime) { if ($prime>=$graphs['keys']['total']) { $graphs['keys']['total']=$prime; break;} } + $graphs['keys']['free']=$graphs['keys']['total']-$status[CACHEPREFIX.'statistics']['num_cached_keys']; + $graphs['keys']['scripts']=$status[CACHEPREFIX.'statistics']['num_cached_scripts']; + $graphs['keys']['wasted']=$status[CACHEPREFIX.'statistics']['num_cached_keys']-$status[CACHEPREFIX.'statistics']['num_cached_scripts']; + + $graphs['hits']['total']=0; + $graphs['hits']['hits']=$status[CACHEPREFIX.'statistics']['hits']; + $graphs['hits']['misses']=$status[CACHEPREFIX.'statistics']['misses']; + $graphs['hits']['blacklist']=$status[CACHEPREFIX.'statistics']['blacklist_misses']; + $graphs['hits']['total']=array_sum($graphs['hits']); + + $graphs['restarts']['total']=0; + $graphs['restarts']['manual']=$status[CACHEPREFIX.'statistics']['manual_restarts']; + $graphs['restarts']['keys']=$status[CACHEPREFIX.'statistics']['hash_restarts']; + $graphs['restarts']['memory']=$status[CACHEPREFIX.'statistics']['oom_restarts']; + $graphs['restarts']['total']=array_sum($graphs['restarts']); + + foreach ( $graphs as $caption=>$graph) { + echo '
',$caption,'
'; + foreach ($graph as $label=>$value) { + if ($label=='total') { $key=0; $total=$value; $totaldisplay=''; continue;} + $percent=$total?floor($value*100/$total):''; $percent=!$percent||$percent>99?'':$percent.'%'; + echo '',$totaldisplay,''; + $key++; $totaldisplay=''; + } + echo '
'.($total>999999?round($total/1024/1024).'M':($total>9999?round($total/1024).'K':$total)).'
', ($value>999999?round($value/1024/1024).'M':($value>9999?round($value/1024).'K':$value)),'',$percent,'',$label,'
',"\n"; + } +} + +function meta_display() { +?> + + 'Opcache GUI', 'path' => '/opcache.php' + ), + array( + 'name' => 'Opcache Control Panel', + 'path' => '/vendor/ocp.php' ) ) ) diff --git a/.tests/Makefile b/.tests/Makefile index 9fea0ef6..a5df37b6 100644 --- a/.tests/Makefile +++ b/.tests/Makefile @@ -107,6 +107,7 @@ test-smoke-vendors: $(PWD)/tests/vendor-phpmyadmin.sh $(PWD)/tests/vendor-phppgadmin.sh $(PWD)/tests/vendor-phpredmin.sh + $(PWD)/tests/vendor-ocp.sh ### diff --git a/.tests/tests/vendor-ocp.sh b/.tests/tests/vendor-ocp.sh new file mode 100755 index 00000000..fd517041 --- /dev/null +++ b/.tests/tests/vendor-ocp.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# NOTE: Parsing curl to tac to circumnvent "failed writing body" +# https://stackoverflow.com/questions/16703647/why-curl-return-and-error-23-failed-writing-body + +set -e +set -u +set -o pipefail + +SCRIPT_PATH="$( cd "$(dirname "$0")" && pwd -P )" +DVLBOX_PATH="$( cd "${SCRIPT_PATH}/../.." && pwd -P )" +# shellcheck disable=SC1090 +. "${SCRIPT_PATH}/../scripts/.lib.sh" + +RETRIES=10 +DISABLED_VERSIONS=() + + +echo +echo "# --------------------------------------------------------------------------------------------------" +echo "# [Vendor] Opcache Control Panel" +echo "# --------------------------------------------------------------------------------------------------" +echo + + +# ------------------------------------------------------------------------------------------------- +# Pre-check +# ------------------------------------------------------------------------------------------------- + +PHP_VERSION="$( get_php_version "${DVLBOX_PATH}" )" +if [[ ${DISABLED_VERSIONS[*]} =~ ${PHP_VERSION} ]]; then + printf "[SKIP] Skipping all checks for PHP %s\\n" "${PHP_VERSION}" + exit 0 +fi + + +# ------------------------------------------------------------------------------------------------- +# ENTRYPOINT +# ------------------------------------------------------------------------------------------------- + +### +### Get required env values +### +HOST_PORT_HTTPD="$( "${SCRIPT_PATH}/../scripts/env-getvar.sh" "HOST_PORT_HTTPD" )" + + +### +### Ensure Opcache Control Panel works +### +URL="/vendor/ocp.php" +printf "[TEST] Fetch %s" "${URL}" +if [ "$( run "\ + curl -sS --fail 'http://localhost:${HOST_PORT_HTTPD}${URL}' \ + | tac \ + | tac \ + | grep -Ec 'Used Memory'" \ + "${RETRIES}" "" "0" )" != "1" ]; then + printf "\\r[FAIL] Fetch %s\\n" "${URL}" + run "curl -sS 'http://localhost:${HOST_PORT_HTTPD}${URL}' || true" + run "curl -sS -I 'http://localhost:${HOST_PORT_HTTPD}${URL}' || true" + exit 1 +else + printf "\\r[OK] Fetch %s\\n" "${URL}" +fi + + +URL="/vendor/ocp.php?FILES=1&GROUP=2&SORT=3" +printf "[TEST] Fetch %s" "${URL}" +if [ "$( run "\ + curl -sS --fail 'http://localhost:${HOST_PORT_HTTPD}${URL}' \ + | tac \ + | tac \ + | grep -Ec 'files cached'" \ + "${RETRIES}" "" "0" )" != "1" ]; then + printf "\\r[FAIL] Fetch %s\\n" "${URL}" + run "curl -sS 'http://localhost:${HOST_PORT_HTTPD}${URL}' || true" + run "curl -sS -I 'http://localhost:${HOST_PORT_HTTPD}${URL}' || true" + exit 1 +else + printf "\\r[OK] Fetch %s\\n" "${URL}" +fi diff --git a/CHANGELOG.md b/CHANGELOG.md index b6181e5c..99f610cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ major versions. ## Unreleased +## Release v1.5.0 (2020-01-03) + +#### Added +- [#654](https://github.com/cytopia/devilbox/issues/654) Added Opcache Control Panel + + ## Release v1.4.0 (2020-01-02) #### Fixed