mirror of
https://github.com/moodle/moodle.git
synced 2025-03-19 23:20:09 +01:00
MDL-79285 xhprof: Enable reducing runs data for quicker use
Here we are reducing the xhprof runs data by removing the __Mustache==>__Mustache calls and all the orphaned data. To save N iterations what we are doing is: 0. The information is "topologically" sorted, so we ensure that all the parents in the data are processed before the children. (this will help a lot when cleaning orphaned data, see below). 1. First pass, all the candidate (by regexp) calls are removed from the run data. 2. Second pass, all the orphaned information (calls that have ended losing his parent) are also removed, so data is consistent. Note that, normally we would need N passes to remove all the orphaned data (because each pass creates new orphan candidates), but, as far as we have ensured that the information is topologically sorted (see point 0 above), all this can be done in one unique pass. TODO: - Add unit tests. - Enable some system to be able to decide which utilities we want to get the data reduced and which ones will continue using the complete data. Right now the reduction is being applied to all the utilities (both table and graph views). - Document the change and, if implemented, the way to select between complete/reduced data. - Consider adding some caching to speed-up the reduction process (some TODOs have been left in the code pointing to the critical points).
This commit is contained in:
parent
b660e6979a
commit
f82ca155ac
@ -888,7 +888,11 @@ class moodle_xhprofrun implements iXHProfRuns {
|
||||
if (@gzuncompress(base64_decode($rec->data)) === false) {
|
||||
return unserialize(base64_decode($rec->data));
|
||||
} else {
|
||||
return unserialize(gzuncompress(base64_decode($rec->data)));
|
||||
$info = unserialize(gzuncompress(base64_decode($rec->data)));
|
||||
|
||||
// We want to apply some transformations here, in order to reduce
|
||||
// the information for some complex (too many levels) cases.
|
||||
return $this->reduce_run_data($info);
|
||||
}
|
||||
}
|
||||
|
||||
@ -963,11 +967,142 @@ class moodle_xhprofrun implements iXHProfRuns {
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
// Private API starts here
|
||||
// Private API starts here.
|
||||
|
||||
protected function sum_calls($sum, $data) {
|
||||
return $sum + $data['ct'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the run data to a more manageable size.
|
||||
*
|
||||
* This removes from the run data all the entries that
|
||||
* are matching a group of regular expressions.
|
||||
*
|
||||
* The main use is to remove all the calls between "__Mustache"
|
||||
* functions, which don't provide any useful information and
|
||||
* make the call-graph too complex to be handled.
|
||||
*
|
||||
* @param array $info The xhprof run data, original array.
|
||||
* @return array The xhprof run data, reduced array.
|
||||
*/
|
||||
protected function reduce_run_data(array $info): array {
|
||||
// Define which (regular expressions) we want to remove. Already escaped if needed to, please.
|
||||
$toremove = [
|
||||
'__Mustache.*==>__Mustache.*', // All __Mustache to __Mustache calls.
|
||||
];
|
||||
// Build the regular expression to be used.
|
||||
$regexp = '/^(' . implode('|', $toremove) . ')$/';
|
||||
|
||||
// Given that the keys of the array have the format "parent==>child"
|
||||
// we want to rebuild the array with the same structure but
|
||||
// topologically sorted (parents always before children).
|
||||
// Note that we do this exclusively to guarantee that the
|
||||
// second pass (see below) works properly in all cases because,
|
||||
// without it, we may need to perform N (while loop) second passes.
|
||||
$sorted = $this->xhprof_topo_sort($info);
|
||||
|
||||
// To keep track of removed and remaining (child-parent) pairs.
|
||||
$removed = [];
|
||||
$remaining = [];
|
||||
|
||||
// First pass, we are going to remove all the elements which
|
||||
// both parent and child are __Mustache function calls.
|
||||
foreach ($sorted as $key => $value) {
|
||||
if (!str_contains($key, '==>')) {
|
||||
$parent = 'NULL';
|
||||
$child = $key;
|
||||
} else {
|
||||
list ($parent, $child) = explode('==>', $key); // TODO: Consider caching this in a property.
|
||||
}
|
||||
|
||||
if (preg_match($regexp, $key)) {
|
||||
unset($sorted[$key]);
|
||||
$removed[$child][$parent] = true;
|
||||
} else {
|
||||
$remaining[$child][$parent] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass, we are going to remove all the elements which
|
||||
// parent was removed by first pass and doesn't appear anymore
|
||||
// as a child of anything (aka, they have become orphaned).
|
||||
// Note, that thanks to the topological sorting, we can be sure
|
||||
// one unique pass is enough. Without it, we may need to perform
|
||||
// N (while loop) second passes.
|
||||
foreach ($sorted as $key => $value) {
|
||||
if (!str_contains($key, '==>')) {
|
||||
$parent = 'NULL';
|
||||
$child = $key;
|
||||
} else {
|
||||
list ($parent, $child) = explode('==>', $key); // TODO: Consider caching this in a property.
|
||||
}
|
||||
|
||||
if (isset($removed[$parent]) && !isset($remaining[$parent])) {
|
||||
unset($sorted[$key]);
|
||||
$removed[$child][$parent] = true;
|
||||
unset($remaining[$child][$parent]);
|
||||
// If this was the last parent of this child, remove it completely from the remaining array.
|
||||
if (empty($remaining[$child])) {
|
||||
unset($remaining[$child]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We are done, let's return the reduced array.
|
||||
return $sorted;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sort the xhprof run pseudo-topologically, so all parents are always before their children.
|
||||
*
|
||||
* Note that this is not a proper, complex, recursive topological sorting algorithm, returning
|
||||
* nodes that later have to be converted back to xhprof "pairs" but, instead, does the specific
|
||||
* work to get those parent==>child (2 levels only) "pairs" sorted (parents always before children).
|
||||
*
|
||||
* @param array $info The xhprof run data, original array.
|
||||
*
|
||||
* @return array The xhprof run data, sorted array.
|
||||
*/
|
||||
protected function xhprof_topo_sort(array $info): array {
|
||||
$sorted = [];
|
||||
$visited = [];
|
||||
$remaining = $info;
|
||||
do {
|
||||
$newremaining = [];
|
||||
foreach ($remaining as $key => $value) {
|
||||
// If we already have visited this element, we can skip it.
|
||||
if (isset($visited[$key])) {
|
||||
continue;
|
||||
}
|
||||
if (!str_contains($key, '==>')) {
|
||||
// It's a root element, we can add it to the sorted array.
|
||||
$sorted[$key] = $info[$key];
|
||||
$visited[$key] = true;
|
||||
} else {
|
||||
list ($parent, $child) = explode('==>', $key); // TODO: Consider caching this in a property.
|
||||
if (isset($visited[$parent])) {
|
||||
// Parent already visited, we can add any children to the sorted array.
|
||||
$sorted[$key] = $info[$key];
|
||||
$visited[$child] = true;
|
||||
} else {
|
||||
// Cannot add this yet, we need to wait for the parent.
|
||||
$newremaining[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Protection against infinite loops.
|
||||
if (count($remaining) === count($newremaining)) {
|
||||
$remaining = []; // So we exit the do...while loop.
|
||||
} else {
|
||||
$remaining = $newremaining; // There is still work to do.
|
||||
}
|
||||
} while (count($remaining) > 0);
|
||||
|
||||
// We are done, let's return the sorted array.
|
||||
return $sorted;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user