View source
<?php
use Drupal\Core\Datetime\Entity\DateFormat;
define('PERFMON_VERSION', '8.x-1.0');
function perfmon_run(array $testlist = NULL, $log = FALSE) {
if (!$testlist) {
$testlist = _perfmon_performance_tests();
}
$results = _perfmon_run($testlist, $log);
return $results;
}
function _perfmon_run($testlist, $log = FALSE) {
$results = array();
foreach ($testlist as $test_name => $arguments) {
$test_result = _perfmon_run_test($test_name, $arguments, $log);
if (!empty($test_result)) {
$results[$test_name] = $test_result;
}
}
return $results;
}
function _perfmon_run_test($test_name, $test, $log, $store = FALSE) {
$last_test = array();
if ($store) {
$last_test = perfmon_get_last_test($test_name);
}
$test_result = array();
$return = array(
'result' => NULL,
);
$function = $test['callback'];
if (function_exists($function)) {
$return = call_user_func($function, $last_test);
}
$test_result = array_merge($test, $return);
$test_result['lastrun'] = REQUEST_TIME;
if ($log && !is_null($return['result'])) {
$variables = array(
'@name' => $test_result['title'],
);
_perfmon_log($test_name, '@name tested', $variables, 'notice');
}
return $test_result;
}
function _perfmon_performance_tests() {
$tests = array();
$tests['config'] = array(
'bold' => TRUE,
'title' => t('Performance score'),
'callback' => 'perfmon_test_server_configuration',
'description' => t('Drupal installation files and directories (except required) are not writable by the server.'),
);
$tests['cpu'] = array(
'title' => t('CPU (ops per second)'),
'callback' => 'perfmon_test_cpu',
'description' => t('Untrusted users are not allowed to input dangerous HTML tags.'),
);
$tests['files'] = array(
'title' => t('Files operations (ops per second)'),
'callback' => 'perfmon_test_filesop',
'description' => t('Dangerous tags were not found in any submitted content (fields).'),
);
$tests['db_read'] = array(
'title' => t('DB read operations (ops per second)'),
'callback' => 'perfmon_test_db_read',
'description' => t('Error reporting set to log only.'),
);
$tests['db_write'] = array(
'title' => t('DB write operations (ops per second)'),
'callback' => 'perfmon_test_db_write',
'description' => t('Private files directory is outside the web server root.'),
);
$tests['db_update'] = array(
'title' => t('DB update operations (ops per second)'),
'callback' => 'perfmon_test_db_update',
'description' => t('Only safe extensions are allowed for uploaded files and images.'),
);
return $tests;
}
function perfmon_test_server_configuration() {
global $_SESSION;
global $base_url;
$domain = $_SERVER['SERVER_NAME'];
$port = $_SERVER['SERVER_PORT'];
$request_uri = '/perfmon.test.php';
$url = $base_url . $request_uri;
$executeCount = 50;
$executeTime = 0;
$count = 10;
$curl_arr = array();
$master = curl_multi_init();
$cookiestr = "";
foreach ($_COOKIE as $key => $value) {
$cookiestr = $cookiestr . $key . "=" . $value . "; ";
}
for ($k = 0; $k < $executeCount; $k++) {
for ($i = 0; $i < $count; $i++) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
curl_setopt($ch, CURLOPT_COOKIE, $cookiestr);
curl_setopt($ch, CURLOPT_PORT, $port);
curl_setopt($ch, CURLOPT_MAXREDIRS, 0);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$curl_arr[$i] = $ch;
curl_multi_add_handle($master, $curl_arr[$i]);
}
do {
curl_multi_exec($master, $running);
} while ($running > 0);
for ($i = 0; $i < $count; $i++) {
$info = curl_getinfo($curl_arr[$i]);
$executeTime += $info['starttransfer_time'] - $info['pretransfer_time'];
curl_multi_remove_handle($master, $curl_arr[$i]);
curl_close($curl_arr[$i]);
}
sleep(2);
}
return array(
'result' => round(1 / ($executeTime / $executeCount / $count), 0),
'value' => '0',
);
}
function perfmon_test_cpu() {
$executeTime = 0;
$count = 100000;
for ($i = 0; $i < $count; $i++) {
$starttime = microtime(TRUE);
$test = 0;
$test++;
$test--;
$test = 2 / 5;
$test = 2 * 5;
$endtime = microtime(TRUE);
$executeTime += $endtime - $starttime;
}
return array(
'result' => round(1 / ($executeTime / $count), 0),
'value' => '0',
);
}
function perfmon_test_filesop() {
$tempDir = file_directory_temp();
$count = 1000;
$executeTime = microtime(TRUE);
for ($i = 0; $i < $count; $i++) {
$fileContent = "<?php phpinfo(); print(''" . $i . "'')";
$filenName = $tempDir . "/phptest" . $i . ".php";
$file = fopen($filename, "w");
fwrite($file, $fileContent);
unlink($filenName);
}
$executeTime = microtime(TRUE) - $executeTime;
return array(
'result' => round(1 / ($executeTime / $count), 0),
'value' => '0',
);
}
function perfmon_test_db_prepare($testname, $count) {
for ($i = 0; $i < $count; $i++) {
\Drupal::database()
->insert('perfmon_test')
->fields([
'id' => $i,
'testname' => 'dbtest',
'data' => 'aaaaaaaaaaaaaaaaaaaaa',
])
->execute();
}
}
function perfmon_test_db_read() {
$executeTime = 0;
$count = 1000;
\Drupal::database()
->truncate('perfmon_test')
->execute();
perfmon_test_db_prepare('test_db_read', $count);
$executeTime = microtime(TRUE);
for ($i = 0; $i < $count; $i++) {
\Drupal::database()
->select('perfmon_test', 'pt')
->fields('pt', [
'id',
'testname',
'data',
])
->condition('pt.id', $i, '=')
->execute();
}
$executeTime = microtime(TRUE) - $executeTime;
return array(
'result' => round(1 / ($executeTime / $count), 0),
'value' => '0',
);
}
function perfmon_test_db_write() {
$executeTime = 0;
$count = 1000;
\Drupal::database()
->truncate('perfmon_test')
->execute();
$executeTime = microtime(TRUE);
perfmon_test_db_prepare('test_db_write', $count);
$executeTime = microtime(TRUE) - $executeTime;
return array(
'result' => round(1 / ($executeTime / $count), 0),
'value' => '0',
);
}
function perfmon_test_db_update() {
$executeTime = 0;
$count = 1000;
\Drupal::database()
->truncate('perfmon_test')
->execute();
perfmon_test_db_prepare('test_db_update', $count);
$executeTime = microtime(TRUE);
for ($i = 0; $i < $count; $i++) {
\Drupal::database()
->update('perfmon_test')
->fields([
'data' => 'bbbbbbbbbbbbbbbbbbbbb',
])
->condition('id', $i)
->execute();
}
$executeTime = microtime(TRUE) - $executeTime;
return array(
'result' => round(1 / ($executeTime / $count), 0),
'value' => '0',
);
}
function perfmon_get_testlist() {
$tests = _perfmon_performance_tests();
return $tests;
}
function perfmon_get_mysqlvariables() {
$mysqlvariables = \Drupal::database()
->query("SHOW GLOBAL VARIABLES")
->fetchAll();
$result = perfmon_convert2array($mysqlvariables);
return $result;
}
function perfmon_get_mysqlstatus() {
$mysqlstatus = \Drupal::database()
->query("SHOW GLOBAL STATUS")
->fetchAll();
$result = perfmon_convert2array($mysqlstatus);
return $result;
}
function perfmon_convert2array($variables) {
$result = array();
foreach ($variables as $parameter) {
$result[$parameter->Variable_name] = $parameter->Value;
}
return $result;
}
function _perfmon_seconds_to_time($seconds) {
$dtF = new \DateTime('@0');
$dtT = new \DateTime("@{$seconds}");
return $dtF
->diff($dtT)
->format(t('%a days, %h hours, %i minutes and %s seconds'));
}
function _perfmon_format_bytes($bytes, $precision = 2) {
$units = array(
'B',
'KB',
'MB',
'GB',
'TB',
);
$bytes = max($bytes, 0);
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
$pow = min($pow, count($units) - 1);
$bytes /= pow(1024, $pow);
return round($bytes, $precision) . ' ' . $units[$pow];
}
function perfmon_get_mysql_performance_variables() {
$variables = perfmon_get_mysqlvariables();
$status = perfmon_get_mysqlstatus();
$performance_variables = array();
$uptime = $status['Uptime'];
$performance_variables['uptime'] = array(
'value' => $uptime,
'title' => t('MySQL Uptime'),
'display_value' => _perfmon_seconds_to_time($uptime),
'message' => t('MySQL Server Uptime'),
'class' => 'ok',
);
if ($uptime < 86400) {
$performance_variables['uptime']['message'] = t('MySQL started within last 24 hours - recommendations may be inaccurate');
$performance_variables['uptime']['class'] = 'error';
}
$memory_global = $variables['key_buffer_size'] + $variables['tmp_table_size'] + $variables['innodb_buffer_pool_size'] + $variables['query_cache_size'] + $variables['innodb_additional_mem_pool_size'] + $variables['innodb_log_buffer_size'];
$performance_variables['memory_global'] = array(
'value' => $memory_global,
'title' => t('Global Memory'),
'display_value' => _perfmon_format_bytes($memory_global),
'message' => t('Global memory size (key_buffer_size + tmp_table_size + innodb_buffer_pool_size + innodb_additional_mem_pool_size + innodb_log_buffer_size + query_cache_size)'),
'class' => 'ok',
);
$memory_per_connection = $variables['read_buffer_size'] + $variables['read_rnd_buffer_size'] + $variables['sort_buffer_size'] + $variables['join_buffer_size'];
$performance_variables['memory_per_connection'] = array(
'value' => $memory_per_connection,
'title' => t('Memory per connection'),
'display_value' => _perfmon_format_bytes($memory_per_connection),
'message' => t('Memory per connection (read_buffer_size + read_rnd_buffer_size + sort_buffer_size + thread_stack + join_buffer_size)'),
'class' => 'ok',
);
$performance_variables['max_connections'] = array(
'value' => $variables['max_connections'],
'title' => 'Max connections',
'display_value' => $variables['max_connections'],
'message' => t('Max user connections ( max_connections )'),
'class' => 'ok',
);
$max_memory = $performance_variables['memory_global']['value'] + $performance_variables['max_connections']['value'] * $performance_variables['memory_per_connection']['value'];
$performance_variables['max_memory'] = array(
'value' => $max_memory,
'title' => 'Max. Memory',
'display_value' => _perfmon_format_bytes($max_memory),
'message' => t('Max Mysql memory (Global memory + Memory per connection * Max connections). Check that this variable lower than 85% of server RAM.'),
'class' => 'ok',
);
$performance_variables['query_cache_size'] = array(
'value' => $variables['query_cache_size'],
'title' => t('Query cache size'),
'display_value' => _perfmon_format_bytes($variables['query_cache_size']),
'message' => t('Query cache size ( query_cache_size ). Recommended 128M'),
'class' => 'ok',
);
if ($variables['query_cache_size'] != 1024 * 1024 * 128) {
$performance_variables['query_cache_size']['message'] = t('Query cache size query_cache_size. Recommended 128M. Increasing the query_cache size over 128M may reduce performance.');
$performance_variables['query_cache_size']['class'] = 'error';
}
$query_cache_efficiency = $status['Qcache_hits'] / ($status['Com_select'] + $status['Qcache_hits']);
$performance_variables['query_cache_efficiency'] = array(
'value' => $query_cache_efficiency,
'title' => t('Query cache efficiency'),
'display_value' => round($query_cache_efficiency * 100, 2) . '%',
'message' => t('Query cache efficiency'),
'class' => 'ok',
);
if ($query_cache_efficiency < 0.2) {
$performance_variables['query_cache_efficiency']['message'] = t('Query cache efficiency. This parameter is low try to increase query_cache_limit.');
$performance_variables['query_cache_efficiency']['class'] = 'error';
}
$query_cache_prunes_per_day = $status['Qcache_lowmem_prunes'] / ($status['Uptime'] / 86400);
$performance_variables['query_cache_prunes_per_day'] = array(
'value' => $query_cache_prunes_per_day,
'title' => t('Query cache prunes per day'),
'display_value' => round($query_cache_prunes_per_day, 2),
'message' => t('Query cache prunes per day. If value grows rapidly you need to increase query_cache_size.'),
'class' => 'ok',
);
$total_sorts = $status['Sort_scan'] + $status['Sort_range'];
$performance_variables['total_sorts'] = array(
'value' => $total_sorts,
'title' => t('Total sorts'),
'display_value' => $total_sorts,
'message' => t('Total sorts count.'),
'class' => 'ok',
);
$total_sorts_temp = $status['Sort_merge_passes'] / $performance_variables['total_sorts']['value'];
$performance_variables['total_sorts_temp'] = array(
'value' => $total_sorts_temp,
'title' => t('Total sorts using disk'),
'display_value' => round($total_sorts_temp * 100, 2) . '%',
'message' => t('Sorts requiring temporary tables. If value grater than 10% then you need to increase sort_buffer_size and read_rnd_buffer_size.'),
'class' => 'ok',
);
if ($total_sorts_temp > 0.1) {
$performance_variables['total_sorts_temp']['class'] = 'error';
}
$created_tmp_tables_dsk = $status['Created_tmp_disk_tables'] / $status['Created_tmp_tables'];
$performance_variables['created_tmp_tables_dsk'] = array(
'value' => $created_tmp_tables_dsk,
'title' => t('Temporary tables created on disk'),
'display_value' => round($created_tmp_tables_dsk * 100, 2) . '%',
'message' => t('Temporary tables created on disk. If value grater than 30% then you need to increase tmp_table_size and tmp_table_size. When making adjustments, make tmp_table_size/max_heap_table_size equal.'),
'class' => 'ok',
);
if ($created_tmp_tables_dsk > 0.3) {
$performance_variables['created_tmp_tables_dsk']['class'] = 'error';
}
$open_files = $status['Open_files'] / $variables['open_files_limit'];
$performance_variables['open_files'] = array(
'value' => $open_files,
'title' => t('Open files'),
'display_value' => round($open_files * 100, 2) . '%',
'message' => t('Open file limit used. If value grater than 85% then you need to increase open_files_limit.'),
'class' => 'ok',
);
if ($open_files > 0.85) {
$performance_variables['open_files']['class'] = 'error';
}
$innodb_buffer_efficiency = 1 - $status['Innodb_buffer_pool_reads'] / $status['Innodb_buffer_pool_read_requests'];
$performance_variables['innodb_buffer_efficiency'] = array(
'value' => $innodb_buffer_efficiency,
'title' => t('Innodb buffer pool efficiency'),
'display_value' => round($innodb_buffer_efficiency * 100, 2) . '%',
'message' => t('InnoDB Buffer pool read cache effiency. If value lower than 95% then you need to increase innodb_buffer_pool_size.'),
'class' => 'ok',
);
if ($innodb_buffer_efficiency < 0.95) {
$performance_variables['innodb_buffer_efficiency']['class'] = 'error';
}
$performance_variables['innodb_flush_log_at_trx_commit'] = array(
'value' => $variables['innodb_flush_log_at_trx_commit'],
'title' => t('innodb_flush_log_at_trx_commit'),
'display_value' => $variables['innodb_flush_log_at_trx_commit'],
'message' => t('innodb_flush_log_at_trx_commit. Recommended value is 2.'),
'class' => 'ok',
);
if ($variables['innodb_flush_log_at_trx_commit'] != 2) {
$performance_variables['innodb_flush_log_at_trx_commit']['class'] = 'error';
}
return $performance_variables;
}
function _perfmon_log($check_name, $message, $variables = [], $type = 'error') {
switch ($type) {
case 'notice':
\Drupal::logger('perfmon:' . $check_name)
->notice($message, $variables);
break;
case 'error':
\Drupal::logger('perfmon:' . $check_name)
->error($message, $variables);
break;
}
\Drupal::moduleHandler()
->invokeAll('perfmon_log', [
$check_name,
$message,
$variables,
$type,
]);
}
function perfmon_get_stored_results() {
$connection = \Drupal::database();
$query = $connection
->query("SELECT testname, result, lastrun FROM {perfmon}");
$result = $query
->fetchAll();
$tests = [];
foreach ($result as $record) {
$tests[] = array(
'testname' => $record->testname,
'result' => $record->result,
'lastrun' => $record->lastrun,
);
}
return $tests;
}
function _perfmon_batch_op($testname, $test, $log, &$context) {
$context['message'] = $test['title'];
$test_result = _perfmon_run_test($testname, $test, $log);
if (!empty($test_result)) {
$context['results'][$testname] = $test_result;
}
}
function perfmon_get_last_test($testname) {
$query = \Drupal::database()
->select('perfmon', 'pm');
$query
->fields('pm', [
'testname',
'result',
'lastrun',
]);
$query
->condition('pm.testname', $testname);
$result = $query
->fetchAssoc();
if (!empty($result)) {
return $result;
}
return FALSE;
}
function perfmon_run_store($tests) {
$results = _perfmon_run($tests);
\Drupal::configFactory()
->getEditable('perfmon.settings')
->set('last_run', time())
->save();
return perfmon_store_results($results);
}
function perfmon_store_results($results) {
$saved = $to_save = 0;
foreach ($results as $testname => $test) {
\Drupal::database()
->delete('perfmon')
->condition('testname', $testname)
->execute();
$to_save++;
$record = [
'testname' => $testname,
'result' => $test['result'],
'lastrun' => $test['lastrun'] ? $test['lastrun'] : time(),
];
$result = \Drupal::database()
->insert('perfmon')
->fields($record)
->execute();
_perfmon_log($testname, 'test :@perfmon_reuslt:', [
'@perfmon_reuslt' => $result,
]);
if ($result) {
$saved++;
}
else {
_perfmon_log($testname, 'Unable to store test @testname', [
'@testname' => $testname,
]);
}
}
return $to_save == $saved ? TRUE : FALSE;
}
function perfmon_run_tests($testlist, $log = NULL) {
if (is_null($log)) {
$log = \Drupal::configFactory()
->get('perfmon.settings');
}
$results = _perfmon_run_tests($testlist, $log);
return $results;
}
function _perfmon_batch_finished($success, $results, $operations) {
\Drupal::configFactory()
->getEditable('perfmon.settings')
->set('last_run', time())
->save();
if ($success) {
if (!empty($results)) {
perfmon_store_results($results);
}
\Drupal::messenger()
->addMessage(t('Review complete'));
}
else {
$error_operation = reset($operations);
$message = 'An error occurred while processing ' . $error_operation[0] . ' with arguments :' . print_r($error_operation[0], TRUE);
_perfmon_log('Operation ' . $error_operation[0], $message);
\Drupal::messenger()
->addMessage(t('The review did not store all results, please run again or check the logs for details.'));
}
}
function perfmon_reviewed($testlist, $tests) {
$items = array();
$last_run = \Drupal::config('perfmon.settings')
->get('last_run');
$date_formatter = \Drupal::service('date.formatter');
$date = !empty($last_run) ? $date_formatter
->format($last_run, 'long') : '';
$header = t('Review results from last run @date', [
'@date' => $date,
]);
$desc = t("Here you can review the results from the last run of the testlist. You can run the testlist again by expanding the fieldset above.");
foreach ($tests as $test) {
if (!isset($testlist[$test['testname']])) {
continue;
}
$message = $testlist[$test['testname']]['title'];
$title = t('OK');
$class = 'ok';
$bold = FALSE;
if ($test['testname'] == 'config') {
$bold = TRUE;
}
$items[] = array(
'title' => $title,
'bold' => $bold,
'value' => $test['result'],
'class' => $class,
'message' => $message,
'help_link' => 'DeleteLINK',
);
}
$output = [
'#theme' => 'perfmon_reviewed',
'#items' => $items,
'#header' => $header,
'#description' => $desc,
];
return $output;
}
function perfmon_theme($existing, $type, $theme, $path) {
return array(
'perfmon_reviewed' => array(
'variables' => array(
'items' => array(),
'header' => '',
'description' => '',
),
),
'perfmon_mysql_reviewed' => array(
'variables' => array(
'items' => array(),
'header' => '',
'description' => '',
),
),
);
}