View source
<?php
namespace Drupal\webprofiler\Command;
use GuzzleHttp\ClientInterface;
use GuzzleHttp\Cookie\CookieJar;
use Symfony\Component\Console\Helper\ProgressBar;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Drupal\Console\Core\Command\Shared\ContainerAwareCommandTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\Process\Process;
use Symfony\Component\Yaml\Yaml;
use Drupal\Console\Annotations\DrupalCommand;
class BenchmarkCommand extends Command {
use ContainerAwareCommandTrait;
protected function configure() {
$this
->setName('webprofiler:benchmark')
->setDescription($this
->trans('commands.webprofiler.benchmark.description'))
->addArgument('url', InputArgument::REQUIRED, $this
->trans('commands.webprofiler.benchmark.arguments.url'))
->addOption('runs', NULL, InputOption::VALUE_REQUIRED, $this
->trans('commands.webprofiler.benchmark.options.runs'), 100)
->addOption('file', NULL, InputOption::VALUE_REQUIRED, $this
->trans('commands.webprofiler.benchmark.options.file'))
->addOption('cache-rebuild', 'cr', InputOption::VALUE_NONE, $this
->trans('commands.webprofiler.benchmark.options.cache_rebuild'));
}
protected function execute(InputInterface $input, OutputInterface $output) {
$runs = $input
->getOption('runs');
$file = $input
->getOption('file');
$cache_rebuild = $input
->getOption('cache-rebuild');
$url = $input
->getArgument('url');
$url_components = parse_url($url);
$login = isset($url_components['user']) && isset($url_components['pass']);
$steps = 3;
if ($cache_rebuild) {
$steps++;
}
if ($login) {
$steps++;
}
$client = $this->container
->get('http_client');
$progress = new ProgressBar($output, $runs + $steps);
$progress
->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
if ($cache_rebuild) {
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.cache_rebuild'));
$this
->RebuildCache();
$progress
->advance();
}
if ($login) {
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.login'));
$login_url = "{$url_components['scheme']}://{$url_components['host']}/user/login";
$cookieJar = new CookieJar();
$client
->setDefaultOption('cookies', $cookieJar);
$response = $client
->get($login_url)
->getBody()
->getContents();
$crawler = new Crawler($response);
$form_build_id = $crawler
->filter('#user-login-form input[name=form_build_id]')
->attr('value');
$op = $crawler
->filter('#user-login-form input[name=op]')
->attr('value');
$response = $client
->post($login_url, [
'body' => [
'name' => $url_components['user'],
'pass' => $url_components['pass'],
'form_build_id' => $form_build_id,
'form_id' => 'user_login_form',
'op' => $op,
],
]);
$progress
->advance();
if ($response
->getStatusCode() != 200) {
throw new \Exception($this
->trans('commands.webprofiler.benchmark.messages.error_login'));
}
}
$datas = [];
for ($i = 0; $i < $runs; $i++) {
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.get'));
$datas[] = $this
->getData($client, $url);
$progress
->advance();
}
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.compute_avg'));
$avg = $this
->computeAvg($datas);
$progress
->advance();
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.compute_median'));
$median = $this
->computePercentile($datas, 50);
$progress
->advance();
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.compute_95percentile'));
$percentile95 = $this
->computePercentile($datas, 95);
$progress
->advance();
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.git_hash'));
$gitHash = $this
->getGitHash();
$progress
->advance();
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.yaml'));
$yaml = $this
->generateYaml($gitHash, $runs, $url, $avg, $median, $percentile95);
$progress
->advance();
$progress
->setMessage($this
->trans('commands.webprofiler.benchmark.progress.done'));
$progress
->finish();
$output
->writeln('');
if ($file) {
file_put_contents($file, $yaml);
}
else {
$output
->writeln($yaml);
}
}
private function getData(ClientInterface $client, $url) {
$response = $client
->get($url);
$token = $response
->getHeader('X-Debug-Token');
$profiler = $this->container
->get('profiler');
$profile = $profiler
->loadProfile($token);
$timeDataCollector = $profile
->getCollector('time');
return new BenchmarkData($token, $timeDataCollector
->getMemory(), $timeDataCollector
->getDuration());
}
private function computeAvg($datas) {
$profiles = count($datas);
$totalTime = 0;
$totalMemory = 0;
foreach ($datas as $data) {
$totalTime += $data
->getTime();
$totalMemory += $data
->getMemory();
}
return new BenchmarkData(NULL, $totalMemory / $profiles, $totalTime / $profiles);
}
private function computePercentile($datas, $percentile) {
if ($percentile < 0 || $percentile > 100) {
throw new \Exception('Percentile has to be between 0 and 100');
}
$profiles = count($datas);
$n = ceil($percentile / 100 * $profiles);
$index = (int) $n - 1;
$orderedTime = $datas;
$this
->getOrderedDatas($orderedTime, 'Time');
$orderedMemory = $datas;
$this
->getOrderedDatas($orderedMemory, 'Memory');
return new BenchmarkData(NULL, $orderedMemory[$index]
->getMemory(), $orderedTime[$index]
->getTime());
}
private function getGitHash() {
try {
$process = new Process('git rev-parse HEAD');
$process
->setTimeout(3600);
$process
->run();
$git_hash = $process
->getOutput();
} catch (\Exception $e) {
$git_hash = $this
->trans('commands.webprofiler.benchmark.messages.not_git');
}
return $git_hash;
}
private function getOrderedDatas(&$datas, $string) {
usort($datas, function ($a, $b) use ($string) {
$method = 'get' . $string;
if ($a->{$method} > $b->{$method}) {
return 1;
}
if ($a->{$method} < $b->{$method}) {
return -1;
}
return 0;
});
}
protected function RebuildCache() {
require_once DRUPAL_ROOT . '/core/includes/utility.inc';
$kernelHelper = $this
->getHelper('kernel');
$classLoader = $kernelHelper
->getClassLoader();
$request = $kernelHelper
->getRequest();
drupal_rebuild($classLoader, $request);
}
private function generateYaml($gitHash, $runs, $url, BenchmarkData $avg, BenchmarkData $median, BenchmarkData $percentile95) {
$yaml = Yaml::dump([
'date' => date($this
->trans('commands.webprofiler.list.rows.time'), time()),
'git_commit' => $gitHash,
'number_of_runs' => $runs,
'url' => $url,
'results' => [
'average' => [
'time' => sprintf('%.0f ms', $avg
->getTime()),
'memory' => sprintf('%.1f MB', $avg
->getMemory() / 1024 / 1024),
],
'median' => [
'time' => sprintf('%.0f ms', $median
->getTime()),
'memory' => sprintf('%.1f MB', $median
->getMemory() / 1024 / 1024),
],
'95_percentile' => [
'time' => sprintf('%.0f ms', $percentile95
->getTime()),
'memory' => sprintf('%.1f MB', $percentile95
->getMemory() / 1024 / 1024),
],
],
]);
return $yaml;
}
public function showMessage($output, $message, $type = 'info') {
}
}