Skip to content

Instantly share code, notes, and snippets.

@wimleers
Created April 14, 2015 10:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wimleers/bf06d9d38e33f87709e3 to your computer and use it in GitHub Desktop.
Save wimleers/bf06d9d38e33f87709e3 to your computer and use it in GitHub Desktop.
<?php
/**
* Environment assumptions:
* - XHProf installation directory must be hardcoded below
* - FlameGraph (https://github.com/brendangregg/FlameGraph) is checked out in a
* directory called 'FlameGraph', which is a sibling directory of
* xhprof.output_dir.
* - When you want to profile while ignoring some functions, hardcode functions
* to ignore below.
*/
// Hierarchical profiling. ?XHPROF_ENABLE
if (isset($_REQUEST['XHPROF_ENABLE'])) {
$ignored_functions = [];
if ($_REQUEST['XHPROF_ENABLE'] == 'ignore') {
$ignored_functions = [
// DB.
'Drupal\Core\Database\Connection::query',
'Drupal\Core\Database\Statement::execute',
'Drupal\Core\Database\Query\Update::execute',
'PDOStatement::execute',
// FS.
'file_exists',
// Class loader.
'Composer\Autoload\ClassLoader::findFileWithExtension',
'Composer\Autoload\ClassLoader::findFile',
'Composer\Autoload\ClassLoader::loadClass',
'spl_autoload_call',
// DIC.
'Symfony\Component\DependencyInjection\Container::get',
'Drupal\Core\DependencyInjection\Container::get',
];
}
xhprof_enable(XHPROF_FLAGS_MEMORY, ['ignored_functions' => $ignored_functions]);
// xhprof_enable(XHPROF_FLAGS_CPU | XHPROF_FLAGS_MEMORY);
register_shutdown_function(function () {
$namespace = $_SERVER['HTTP_HOST'];
$xhprof_data = xhprof_disable();
// Look at the output of
// brew info php55-xhprof | grep `brew --cellar php55-xhprof`
// to know which path.
$XHPROF_ROOT = '/usr/local/Cellar/php55-xhprof/254eb24';
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_lib.php";
include_once $XHPROF_ROOT . "/xhprof_lib/utils/xhprof_runs.php";
$xhprof_runs = new XHProfRuns_Default();
$run_id = $xhprof_runs->save_run($xhprof_data, $namespace);
echo "<div style='margin:1rem;padding:1rem;border:1px solid black;background-color: white;'>
<span style='font-size:150%'>
<a href='http://xhprof/ui/?run=$run_id&source=$namespace'>View run</a>
<tt>$run_id</tt>
<tt>$namespace</tt>
</span>
</div>";
});
}
// Sampling profiling. ?XHPROF_SAMPLE_ENABLE&NAME=<name>&SAMPLES=1000
// Optionally:
// - &OLD_NAME=<name>, to generate a diff
if (isset($_REQUEST['XHPROF_SAMPLE_ENABLE'])) {
xhprof_sample_enable();
// Validate arguments.
if (!isset($_REQUEST['NAME'])) {
echo 'Please specify a NAME parameter to name this round of sample profiling.';
exit;
}
if (!isset($_REQUEST['SAMPLES'])) {
echo 'Please specify a SAMPLES parameter to indicate the number of samples to collect.';
exit;
}
register_shutdown_function(function () {
$xhprof_data = xhprof_sample_disable();
$sample_profiling_dir = ini_get('xhprof.output_dir') . '/sample-profiling/';
$name = $_REQUEST['NAME'];
$samples_dir = $sample_profiling_dir . $name . '.samples';
// Store the collected sample profile data.
if (!file_exists($samples_dir)) {
mkdir($samples_dir, 0777);
}
$filename = $samples_dir . '/' . $_REQUEST['SAMPLES'] . '.sample_xhprof';
file_put_contents($filename, serialize($xhprof_data));
// Trigger a reload for the next sample, or if this was the last sample,
// show a message.
if ($_REQUEST['SAMPLES'] > 1) {
// Get the next sample.
$next_url = preg_replace('/SAMPLES=(\d+)/', 'SAMPLES=' . ((int)$_REQUEST['SAMPLES'] - 1), $_SERVER['REQUEST_URI']);
echo "<script>window.location = '$next_url';</script>";
}
// If this was the last sample, calculate the FlameGraphs.
else {
$file_pattern = $samples_dir . '/*.sample_xhprof';
$flamegraph_dir = dirname(ini_get('xhprof.output_dir'));
// Generate FlameGraphs.
$stacks_file = $sample_profiling_dir . $name . '.stacks';
$flamegraph_svg_file = $sample_profiling_dir . $name . '-FlameGraph.svg';
$reverse_flamegraph_svg_file = $sample_profiling_dir . $name . '-FlameGraph-reverse.svg';
file_put_contents($stacks_file, xhprof_sample_files_to_stacks(glob($file_pattern)));
shell_exec("cat $stacks_file | $flamegraph_dir/FlameGraph/flamegraph.pl --title '$name FlameGraph'> $flamegraph_svg_file");
shell_exec("cat $stacks_file | $flamegraph_dir/FlameGraph/flamegraph.pl --reverse --title '$name reverse FlameGraph'> $reverse_flamegraph_svg_file");
$files = ['file://' . $flamegraph_svg_file, 'file://' . $reverse_flamegraph_svg_file];
// If OLD_NAME is specified, then also create a diff FlameGraph.
if (isset($_REQUEST['OLD_NAME'])) {
$old_name = $_REQUEST['OLD_NAME'];
$old_stacks_file = $sample_profiling_dir . $old_name . '.stacks';
$diff_flamegraph_svg_file = $sample_profiling_dir . $name . '-FlameGraph-compared-to-old-' . $old_name . '.svg';
shell_exec("$flamegraph_dir/FlameGraph/difffolded.pl $old_stacks_file $stacks_file | $flamegraph_dir/FlameGraph/flamegraph.pl --title 'Diff FlameGraph, old = $old_name, new = $name' > $diff_flamegraph_svg_file");
$files[] = 'file://' . $diff_flamegraph_svg_file;
}
// Message.
echo "<div style='margin:1rem;padding:1rem;border:1px solid black;background-color:white;'>
<span style='font-size:150%'>
FlameGraphs: <pre>" . implode("\n", $files) . "</pre>
Samples written to: <pre>
$file_pattern
</pre>
</span>
</div>";
}
});
}
function xhprof_sample_files_to_stacks($files) {
$stacks = array();
foreach ($files as $file) {
if (!file_exists($file)) throw new RuntimeException("$file doesn't exist!");
$file = strpos($file, 'gz') === (strlen($file) - 2) ? "compress.zlib://$file" : $file;
$raw_xhprof = @unserialize(file_get_contents($file));
if ($raw_xhprof === FALSE || empty($raw_xhprof)) {
continue;
}
foreach ($raw_xhprof as $stack) {
$stack_key = implode(";", explode("==>", $stack));
if (!isset($stacks[$stack_key])) $stacks[$stack_key] = 0;
$stacks[$stack_key]++;
}
}
$output = '';
foreach ($stacks as $stack => $count) {
$output .= "$stack $count" . PHP_EOL;
}
return $output;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment