/modules/devel/webprofiler/src/Command/BenchmarkCommand.php
PHP | 311 lines | 193 code | 49 blank | 69 comment | 14 complexity | 25a2e2a71f09a78e81b7b7fb957dacbb MD5 | raw file
- <?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
- *
- * @DrupalCommand (
- * extension="webprofiler",
- * extensionType="module"
- * )
- */
- class BenchmarkCommand extends Command {
- use ContainerAwareCommandTrait;
- /**
- * {@inheritdoc}
- */
- 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'));
- }
- /**
- * {@inheritdoc}
- */
- protected function execute(InputInterface $input, OutputInterface $output) {
- $runs = $input->getOption('runs');
- $file = $input->getOption('file');
- $cache_rebuild = $input->getOption('cache-rebuild');
- // http://username:password@hostname/
- $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++;
- }
- /** @var \Drupal\Core\Http\Client $client */
- $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";
- // Enable cookies storage.
- $cookieJar = new CookieJar();
- $client->setDefaultOption('cookies', $cookieJar);
- // Retrieve a form_build_id using the DomCrawler component.
- $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');
- // Login a user.
- $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);
- }
- }
- /**
- * @param \GuzzleHttp\ClientInterface $client
- * @param $url
- *
- * @return array
- */
- private function getData(ClientInterface $client, $url) {
- /** @var \GuzzleHttp\Message\ResponseInterface $response */
- $response = $client->get($url);
- $token = $response->getHeader('X-Debug-Token');
- /** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
- $profiler = $this->container->get('profiler');
- /** @var \Symfony\Component\HttpKernel\Profiler\Profile $profile */
- $profile = $profiler->loadProfile($token);
- /** @var \Drupal\webprofiler\DataCollector\TimeDataCollector $timeDataCollector */
- $timeDataCollector = $profile->getCollector('time');
- return new BenchmarkData(
- $token,
- $timeDataCollector->getMemory(),
- $timeDataCollector->getDuration());
- }
- /**
- * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
- *
- * @return \Drupal\webprofiler\Command\BenchmarkData
- */
- 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);
- }
- /**
- * Computes percentile using The Nearest Rank method.
- *
- * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
- * @param $percentile
- *
- * @return \Drupal\webprofiler\Command\BenchmarkData
- *
- * @throws \Exception
- */
- 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());
- }
- /**
- * @return string
- */
- 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;
- }
- /**
- * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
- * @param $string
- *
- * @return array
- */
- 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;
- });
- }
- /**
- * Rebuilds Drupal cache.
- */
- 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);
- }
- /**
- * @param $gitHash
- * @param $runs
- * @param $url
- * @param \Drupal\webprofiler\Command\BenchmarkData $avg
- * @param \Drupal\webprofiler\Command\BenchmarkData $median
- * @param \Drupal\webprofiler\Command\BenchmarkData $percentile95
- *
- * @return string
- */
- 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;
- }
- /**
- * {@inheritdoc}
- */
- public function showMessage($output, $message, $type = 'info') {
- }
- }