3 namespace Drupal\webprofiler\Command;
5 use GuzzleHttp\ClientInterface;
6 use GuzzleHttp\Cookie\CookieJar;
7 use Symfony\Component\Console\Helper\ProgressBar;
8 use Symfony\Component\Console\Input\InputArgument;
9 use Symfony\Component\Console\Input\InputInterface;
10 use Symfony\Component\Console\Input\InputOption;
11 use Symfony\Component\Console\Output\OutputInterface;
12 use Drupal\Console\Core\Command\Shared\ContainerAwareCommandTrait;
13 use Symfony\Component\Console\Command\Command;
14 use Symfony\Component\DomCrawler\Crawler;
15 use Symfony\Component\Process\Process;
16 use Symfony\Component\Yaml\Yaml;
17 use Drupal\Console\Annotations\DrupalCommand;
20 * Class BenchmarkCommand
23 * extension="webprofiler",
24 * extensionType="module"
27 class BenchmarkCommand extends Command {
29 use ContainerAwareCommandTrait;
34 protected function configure() {
36 ->setName('webprofiler:benchmark')
37 ->setDescription($this->trans('commands.webprofiler.benchmark.description'))
38 ->addArgument('url', InputArgument::REQUIRED, $this->trans('commands.webprofiler.benchmark.arguments.url'))
39 ->addOption('runs', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.benchmark.options.runs'), 100)
40 ->addOption('file', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.benchmark.options.file'))
41 ->addOption('cache-rebuild', 'cr', InputOption::VALUE_NONE, $this->trans('commands.webprofiler.benchmark.options.cache_rebuild'));
47 protected function execute(InputInterface $input, OutputInterface $output) {
48 $runs = $input->getOption('runs');
49 $file = $input->getOption('file');
50 $cache_rebuild = $input->getOption('cache-rebuild');
52 // http://username:password@hostname/
53 $url = $input->getArgument('url');
54 $url_components = parse_url($url);
55 $login = isset($url_components['user']) && isset($url_components['pass']);
67 /** @var \Drupal\Core\Http\Client $client */
68 $client = $this->container->get('http_client');
70 $progress = new ProgressBar($output, $runs + $steps);
71 $progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
74 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.cache_rebuild'));
75 $this->RebuildCache();
80 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.login'));
81 $login_url = "{$url_components['scheme']}://{$url_components['host']}/user/login";
83 // Enable cookies storage.
84 $cookieJar = new CookieJar();
85 $client->setDefaultOption('cookies', $cookieJar);
87 // Retrieve a form_build_id using the DomCrawler component.
88 $response = $client->get($login_url)->getBody()->getContents();
89 $crawler = new Crawler($response);
90 $form_build_id = $crawler->filter('#user-login-form input[name=form_build_id]')
92 $op = $crawler->filter('#user-login-form input[name=op]')->attr('value');
95 $response = $client->post($login_url, [
97 'name' => $url_components['user'],
98 'pass' => $url_components['pass'],
99 'form_build_id' => $form_build_id,
100 'form_id' => 'user_login_form',
104 $progress->advance();
106 if ($response->getStatusCode() != 200) {
107 throw new \Exception($this->trans('commands.webprofiler.benchmark.messages.error_login'));
112 for ($i = 0; $i < $runs; $i++) {
113 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.get'));
114 $datas[] = $this->getData($client, $url);
115 $progress->advance();
118 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_avg'));
119 $avg = $this->computeAvg($datas);
120 $progress->advance();
122 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_median'));
123 $median = $this->computePercentile($datas, 50);
124 $progress->advance();
126 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_95percentile'));
127 $percentile95 = $this->computePercentile($datas, 95);
128 $progress->advance();
130 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.git_hash'));
131 $gitHash = $this->getGitHash();
132 $progress->advance();
134 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.yaml'));
135 $yaml = $this->generateYaml($gitHash, $runs, $url, $avg, $median, $percentile95);
136 $progress->advance();
138 $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.done'));
140 $output->writeln('');
143 file_put_contents($file, $yaml);
146 $output->writeln($yaml);
152 * @param \GuzzleHttp\ClientInterface $client
157 private function getData(ClientInterface $client, $url) {
158 /** @var \GuzzleHttp\Message\ResponseInterface $response */
159 $response = $client->get($url);
161 $token = $response->getHeader('X-Debug-Token');
163 /** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
164 $profiler = $this->container->get('profiler');
166 /** @var \Symfony\Component\HttpKernel\Profiler\Profile $profile */
167 $profile = $profiler->loadProfile($token);
169 /** @var \Drupal\webprofiler\DataCollector\TimeDataCollector $timeDataCollector */
170 $timeDataCollector = $profile->getCollector('time');
172 return new BenchmarkData(
174 $timeDataCollector->getMemory(),
175 $timeDataCollector->getDuration());
179 * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
181 * @return \Drupal\webprofiler\Command\BenchmarkData
183 private function computeAvg($datas) {
184 $profiles = count($datas);
188 foreach ($datas as $data) {
189 $totalTime += $data->getTime();
190 $totalMemory += $data->getMemory();
193 return new BenchmarkData(NULL, $totalMemory / $profiles, $totalTime / $profiles);
197 * Computes percentile using The Nearest Rank method.
199 * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
202 * @return \Drupal\webprofiler\Command\BenchmarkData
206 private function computePercentile($datas, $percentile) {
207 if ($percentile < 0 || $percentile > 100) {
208 throw new \Exception('Percentile has to be between 0 and 100');
211 $profiles = count($datas);
213 $n = ceil((($percentile / 100) * $profiles));
214 $index = (int) $n - 1;
216 $orderedTime = $datas;
217 $this->getOrderedDatas($orderedTime, 'Time');
219 $orderedMemory = $datas;
220 $this->getOrderedDatas($orderedMemory, 'Memory');
222 return new BenchmarkData(NULL, $orderedMemory[$index]->getMemory(), $orderedTime[$index]->getTime());
228 private function getGitHash() {
230 $process = new Process('git rev-parse HEAD');
231 $process->setTimeout(3600);
233 $git_hash = $process->getOutput();
234 } catch (\Exception $e) {
235 $git_hash = $this->trans('commands.webprofiler.benchmark.messages.not_git');
242 * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
247 private function getOrderedDatas(&$datas, $string) {
248 usort($datas, function ($a, $b) use ($string) {
249 $method = 'get' . $string;
250 if ($a->{$method} > $b->{$method}) {
253 if ($a->{$method} < $b->{$method}) {
261 * Rebuilds Drupal cache.
263 protected function RebuildCache() {
264 require_once DRUPAL_ROOT . '/core/includes/utility.inc';
265 $kernelHelper = $this->getHelper('kernel');
266 $classLoader = $kernelHelper->getClassLoader();
267 $request = $kernelHelper->getRequest();
268 drupal_rebuild($classLoader, $request);
275 * @param \Drupal\webprofiler\Command\BenchmarkData $avg
276 * @param \Drupal\webprofiler\Command\BenchmarkData $median
277 * @param \Drupal\webprofiler\Command\BenchmarkData $percentile95
281 private function generateYaml($gitHash, $runs, $url, BenchmarkData $avg, BenchmarkData $median, BenchmarkData $percentile95) {
283 'date' => date($this->trans('commands.webprofiler.list.rows.time'), time()),
284 'git_commit' => $gitHash,
285 'number_of_runs' => $runs,
289 'time' => sprintf('%.0f ms', $avg->getTime()),
290 'memory' => sprintf('%.1f MB', $avg->getMemory() / 1024 / 1024),
293 'time' => sprintf('%.0f ms', $median->getTime()),
294 'memory' => sprintf('%.1f MB', $median->getMemory() / 1024 / 1024),
297 'time' => sprintf('%.0f ms', $percentile95->getTime()),
298 'memory' => sprintf('%.1f MB', $percentile95->getMemory() / 1024 / 1024),
308 public function showMessage($output, $message, $type = 'info') {