Skip to content

Commit

Permalink
Remove advisory rules file parser
Browse files Browse the repository at this point in the history
Uses a PHP array instead of parsing a TXT file.

Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
  • Loading branch information
MauricioFauth committed Aug 31, 2020
1 parent 5d5f149 commit 51ae4fa
Show file tree
Hide file tree
Showing 9 changed files with 973 additions and 604 deletions.
415 changes: 415 additions & 0 deletions libraries/advisory_rules_generic.php

Large diffs are not rendered by default.

402 changes: 0 additions & 402 deletions libraries/advisory_rules_generic.txt

This file was deleted.

402 changes: 402 additions & 0 deletions libraries/advisory_rules_generic_lines.php

Large diffs are not rendered by default.

70 changes: 70 additions & 0 deletions libraries/advisory_rules_mysql_before80003.php
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

// phpcs:disable Generic.Files.LineLength.TooLong
return [
// Query cache
[
'name' => 'Query cache disabled',
'formula' => 'query_cache_size',
'test' => 'value == 0 || query_cache_type == \'OFF\' || query_cache_type == \'0\'',
'issue' => 'The query cache is not enabled.',
'recommendation' => 'The query cache is known to greatly improve performance if configured correctly. Enable it by setting {query_cache_size} to a 2 digit MiB value and setting {query_cache_type} to \'ON\'. <b>Note:</b> If you are using memcached, ignore this recommendation.',
'justification' => 'query_cache_size is set to 0 or query_cache_type is set to \'OFF\'',
],
[
'name' => 'Query cache efficiency (%)',
'precondition' => 'Com_select + Qcache_hits > 0 && !fired(\'Query cache disabled\')',
'formula' => 'Qcache_hits / (Com_select + Qcache_hits) * 100',
'test' => 'value < 20',
'issue' => 'Query cache not running efficiently, it has a low hit rate.',
'recommendation' => 'Consider increasing {query_cache_limit}.',
'justification' => 'The current query cache hit rate of %s% is below 20% | round(value,1)',
],
[
'name' => 'Query Cache usage',
'precondition' => '!fired(\'Query cache disabled\')',
'formula' => '100 - Qcache_free_memory / query_cache_size * 100',
'test' => 'value < 80',
'issue' => 'Less than 80% of the query cache is being utilized.',
'recommendation' => 'This might be caused by {query_cache_limit} being too low. Flushing the query cache might help as well.',
'justification' => 'The current ratio of free query cache memory to total query cache size is %s%. It should be above 80% | round(value,1)',
],
[
'name' => 'Query cache fragmentation',
'precondition' => '!fired(\'Query cache disabled\')',
'formula' => 'Qcache_free_blocks / (Qcache_total_blocks / 2) * 100',
'test' => 'value > 20',
'issue' => 'The query cache is considerably fragmented.',
'recommendation' => 'Severe fragmentation is likely to (further) increase Qcache_lowmem_prunes. This might be caused by many Query cache low memory prunes due to {query_cache_size} being too small. For a immediate but short lived fix you can flush the query cache (might lock the query cache for a long time). Carefully adjusting {query_cache_min_res_unit} to a lower value might help too, e.g. you can set it to the average size of your queries in the cache using this formula: (query_cache_size - qcache_free_memory) / qcache_queries_in_cache',
'justification' => 'The cache is currently fragmented by %s% , with 100% fragmentation meaning that the query cache is an alternating pattern of free and used blocks. This value should be below 20%. | round(value,1)',
],
[
'name' => 'Query cache low memory prunes',
'precondition' => 'Qcache_inserts > 0 && !fired(\'Query cache disabled\')',
'formula' => 'Qcache_lowmem_prunes / Qcache_inserts * 100',
'test' => 'value > 0.1',
'issue' => 'Cached queries are removed due to low query cache memory from the query cache.',
'recommendation' => 'You might want to increase {query_cache_size}, however keep in mind that the overhead of maintaining the cache is likely to increase with its size, so do this in small increments and monitor the results.',
'justification' => 'The ratio of removed queries to inserted queries is %s%. The lower this value is, the better (This rules firing limit: 0.1%) | round(value,1)',
],
[
'name' => 'Query cache max size',
'precondition' => '!fired(\'Query cache disabled\')',
'formula' => 'query_cache_size',
'test' => 'value > 1024 * 1024 * 128',
'issue' => 'The query cache size is above 128 MiB. Big query caches may cause significant overhead that is required to maintain the cache.',
'recommendation' => 'Depending on your environment, it might be performance increasing to reduce this value.',
'justification' => 'Current query cache size: %s | ADVISOR_formatByteDown(value, 2, 2)',
],
[
'name' => 'Query cache min result size',
'precondition' => '!fired(\'Query cache disabled\')',
'formula' => 'query_cache_limit',
'test' => 'value == 1024*1024',
'issue' => 'The max size of the result set in the query cache is the default of 1 MiB.',
'recommendation' => 'Changing {query_cache_limit} (usually by increasing) may increase efficiency. This variable determines the maximum size a query result may have to be inserted into the query cache. If there are many query results above 1 MiB that are well cacheable (many reads, little writes) then increasing {query_cache_limit} will increase efficiency. Whereas in the case of many query results being above 1 MiB that are not very well cacheable (often invalidated due to table updates) increasing {query_cache_limit} might reduce efficiency.',
'justification' => 'query_cache_limit is set to 1 MiB',
],
];
57 changes: 0 additions & 57 deletions libraries/advisory_rules_mysql_before80003.txt

This file was deleted.

68 changes: 68 additions & 0 deletions libraries/advisory_rules_mysql_before80003_lines.php
@@ -0,0 +1,68 @@
<?php

declare(strict_types=1);

return [
[
'name' => 10,
'formula' => 11,
'test' => 12,
'issue' => 13,
'recommendation' => 14,
'justification' => 15,
],
[
'name' => 17,
'precondition' => 17,
'formula' => 18,
'test' => 19,
'issue' => 20,
'recommendation' => 21,
'justification' => 22,
],
[
'name' => 24,
'precondition' => 24,
'formula' => 25,
'test' => 26,
'issue' => 27,
'recommendation' => 28,
'justification' => 29,
],
[
'name' => 31,
'precondition' => 31,
'formula' => 32,
'test' => 33,
'issue' => 34,
'recommendation' => 35,
'justification' => 36,
],
[
'name' => 38,
'precondition' => 38,
'formula' => 39,
'test' => 40,
'issue' => 41,
'recommendation' => 42,
'justification' => 43,
],
[
'name' => 45,
'precondition' => 45,
'formula' => 46,
'test' => 47,
'issue' => 48,
'recommendation' => 49,
'justification' => 50,
],
[
'name' => 52,
'precondition' => 52,
'formula' => 53,
'test' => 54,
'issue' => 55,
'recommendation' => 56,
'justification' => 57,
],
];
137 changes: 15 additions & 122 deletions libraries/classes/Advisor.php
Expand Up @@ -12,21 +12,17 @@
use PhpMyAdmin\Server\SysInfo\SysInfo;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Throwable;
use const FILE_IGNORE_NEW_LINES;
use function array_merge;
use function array_merge_recursive;
use function count;
use function file;
use function htmlspecialchars;
use function implode;
use function mb_substr;
use function pow;
use function preg_match;
use function preg_replace;
use function preg_replace_callback;
use function preg_split;
use function round;
use function rtrim;
use function sprintf;
use function strpos;
use function substr;
Expand All @@ -37,8 +33,8 @@
*/
class Advisor
{
public const GENERIC_RULES_FILE = 'libraries/advisory_rules_generic.txt';
public const BEFORE_MYSQL80003_RULES_FILE = 'libraries/advisory_rules_mysql_before80003.txt';
public const GENERIC_RULES_FILE = 'libraries/advisory_rules_generic.php';
public const BEFORE_MYSQL80003_RULES_FILE = 'libraries/advisory_rules_mysql_before80003.php';

/** @var DatabaseInterface */
protected $dbi;
Expand Down Expand Up @@ -299,10 +295,7 @@ public function run(): array
// $runResult
$this->runRules();

return [
'parse' => ['errors' => $this->parseResult['errors']],
'run' => $this->runResult,
];
return ['run' => $this->runResult];
}

/**
Expand All @@ -326,14 +319,12 @@ public function storeError(string $description, Throwable $exception): void
*/
public function runRules(): bool
{
$this->setRunResult(
[
'fired' => [],
'notfired' => [],
'unchecked' => [],
'errors' => [],
]
);
$this->setRunResult([
'fired' => [],
'notfired' => [],
'unchecked' => [],
'errors' => [],
]);

foreach ($this->parseResult['rules'] as $rule) {
$this->variables['value'] = 0;
Expand Down Expand Up @@ -577,118 +568,20 @@ public function ruleExprEvaluate(string $expr)
*/
public static function parseRulesFile(string $filename): array
{
$file = file($filename, FILE_IGNORE_NEW_LINES);

$errors = [];
$rules = [];
$lines = [];

if ($file === false) {
$errors[] = sprintf(
__('Error in reading file: The file \'%s\' does not exist or is not readable!'),
$filename
);

return [
'rules' => $rules,
'lines' => $lines,
'errors' => $errors,
];
}

$ruleSyntax = [
'name',
'formula',
'test',
'issue',
'recommendation',
'justification',
];
$numRules = count($ruleSyntax);
$numLines = count($file);
$ruleNo = -1;
$ruleLine = -1;

for ($i = 0; $i < $numLines; $i++) {
$line = $file[$i];
if ($line == '' || $line[0] === '#') {
continue;
}

// Reading new rule
if (substr($line, 0, 4) === 'rule') {
if ($ruleLine > 0) {
$errors[] = sprintf(
__(
'Invalid rule declaration on line %1$s, expected line '
. '%2$s of previous rule.'
),
$i + 1,
$ruleSyntax[$ruleLine++]
);
continue;
}
if (preg_match("/rule\s'(.*)'( \[(.*)\])?$/", $line, $match)) {
$ruleLine = 1;
$ruleNo++;
$rules[$ruleNo] = ['name' => $match[1]];
$lines[$ruleNo] = ['name' => $i + 1];
if (isset($match[3])) {
$rules[$ruleNo]['precondition'] = $match[3];
$lines[$ruleNo]['precondition'] = $i + 1;
}
} else {
$errors[] = sprintf(
__('Invalid rule declaration on line %s.'),
$i + 1
);
}
continue;
}

if ($ruleLine == -1) {
$errors[] = sprintf(
__('Unexpected characters on line %s.'),
$i + 1
);
}

// Reading rule lines
if ($ruleLine > 0) {
if (! isset($line[0])) {
continue; // Empty lines are ok
}
// Non tabbed lines are not
if ($line[0] != "\t") {
$errors[] = sprintf(
__(
'Unexpected character on line %1$s. Expected tab, but '
. 'found "%2$s".'
),
$i + 1,
$line[0]
);
continue;
}
$rules[$ruleNo][$ruleSyntax[$ruleLine]] = rtrim(
mb_substr($line, 1)
);
$lines[$ruleNo][$ruleSyntax[$ruleLine]] = $i + 1;
++$ruleLine;
}

// Rule complete
if ($ruleLine != $numRules) {
continue;
}

$ruleLine = -1;
if ($filename === self::GENERIC_RULES_FILE) {
$rules = include ROOT_PATH . 'libraries/advisory_rules_generic.php';
$lines = include ROOT_PATH . 'libraries/advisory_rules_generic_lines.php';
} elseif ($filename === self::BEFORE_MYSQL80003_RULES_FILE) {
$rules = include ROOT_PATH . 'libraries/advisory_rules_mysql_before80003.php';
$lines = include ROOT_PATH . 'libraries/advisory_rules_mysql_before80003_lines.php';
}

return [
'rules' => $rules,
'lines' => $lines,
'errors' => $errors,
];
}

Expand Down

0 comments on commit 51ae4fa

Please sign in to comment.