c33905b8b29b20bc37a6bcd269537a50e1726631
[yaffs-website] / web / modules / contrib / devel / webprofiler / src / Command / BenchmarkCommand.php
1 <?php
2
3 namespace Drupal\webprofiler\Command;
4
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;
18
19 /**
20  * Class BenchmarkCommand
21  *
22  * @DrupalCommand (
23  *     extension="webprofiler",
24  *     extensionType="module"
25  * )
26  */
27 class BenchmarkCommand extends Command {
28
29   use ContainerAwareCommandTrait;
30
31   /**
32    * {@inheritdoc}
33    */
34   protected function configure() {
35     $this
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'));
42   }
43
44   /**
45    * {@inheritdoc}
46    */
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');
51
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']);
56
57     $steps = 3;
58
59     if ($cache_rebuild) {
60       $steps++;
61     }
62
63     if ($login) {
64       $steps++;
65     }
66
67     /** @var \Drupal\Core\Http\Client $client */
68     $client = $this->container->get('http_client');
69
70     $progress = new ProgressBar($output, $runs + $steps);
71     $progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
72
73     if ($cache_rebuild) {
74       $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.cache_rebuild'));
75       $this->RebuildCache();
76       $progress->advance();
77     }
78
79     if ($login) {
80       $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.login'));
81       $login_url = "{$url_components['scheme']}://{$url_components['host']}/user/login";
82
83       // Enable cookies storage.
84       $cookieJar = new CookieJar();
85       $client->setDefaultOption('cookies', $cookieJar);
86
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]')
91         ->attr('value');
92       $op = $crawler->filter('#user-login-form input[name=op]')->attr('value');
93
94       // Login a user.
95       $response = $client->post($login_url, [
96         'body' => [
97           'name' => $url_components['user'],
98           'pass' => $url_components['pass'],
99           'form_build_id' => $form_build_id,
100           'form_id' => 'user_login_form',
101           'op' => $op,
102         ]
103       ]);
104       $progress->advance();
105
106       if ($response->getStatusCode() != 200) {
107         throw new \Exception($this->trans('commands.webprofiler.benchmark.messages.error_login'));
108       }
109     }
110
111     $datas = [];
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();
116     }
117
118     $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_avg'));
119     $avg = $this->computeAvg($datas);
120     $progress->advance();
121
122     $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_median'));
123     $median = $this->computePercentile($datas, 50);
124     $progress->advance();
125
126     $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_95percentile'));
127     $percentile95 = $this->computePercentile($datas, 95);
128     $progress->advance();
129
130     $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.git_hash'));
131     $gitHash = $this->getGitHash();
132     $progress->advance();
133
134     $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.yaml'));
135     $yaml = $this->generateYaml($gitHash, $runs, $url, $avg, $median, $percentile95);
136     $progress->advance();
137
138     $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.done'));
139     $progress->finish();
140     $output->writeln('');
141
142     if ($file) {
143       file_put_contents($file, $yaml);
144     }
145     else {
146       $output->writeln($yaml);
147     }
148
149   }
150
151   /**
152    * @param \GuzzleHttp\ClientInterface $client
153    * @param $url
154    *
155    * @return array
156    */
157   private function getData(ClientInterface $client, $url) {
158     /** @var \GuzzleHttp\Message\ResponseInterface $response */
159     $response = $client->get($url);
160
161     $token = $response->getHeader('X-Debug-Token');
162
163     /** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
164     $profiler = $this->container->get('profiler');
165
166     /** @var \Symfony\Component\HttpKernel\Profiler\Profile $profile */
167     $profile = $profiler->loadProfile($token);
168
169     /** @var \Drupal\webprofiler\DataCollector\TimeDataCollector $timeDataCollector */
170     $timeDataCollector = $profile->getCollector('time');
171
172     return new BenchmarkData(
173       $token,
174       $timeDataCollector->getMemory(),
175       $timeDataCollector->getDuration());
176   }
177
178   /**
179    * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
180    *
181    * @return \Drupal\webprofiler\Command\BenchmarkData
182    */
183   private function computeAvg($datas) {
184     $profiles = count($datas);
185     $totalTime = 0;
186     $totalMemory = 0;
187
188     foreach ($datas as $data) {
189       $totalTime += $data->getTime();
190       $totalMemory += $data->getMemory();
191     }
192
193     return new BenchmarkData(NULL, $totalMemory / $profiles, $totalTime / $profiles);
194   }
195
196   /**
197    * Computes percentile using The Nearest Rank method.
198    *
199    * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
200    * @param $percentile
201    *
202    * @return \Drupal\webprofiler\Command\BenchmarkData
203    *
204    * @throws \Exception
205    */
206   private function computePercentile($datas, $percentile) {
207     if ($percentile < 0 || $percentile > 100) {
208       throw new \Exception('Percentile has to be between 0 and 100');
209     }
210
211     $profiles = count($datas);
212
213     $n = ceil((($percentile / 100) * $profiles));
214     $index = (int) $n - 1;
215
216     $orderedTime = $datas;
217     $this->getOrderedDatas($orderedTime, 'Time');
218
219     $orderedMemory = $datas;
220     $this->getOrderedDatas($orderedMemory, 'Memory');
221
222     return new BenchmarkData(NULL, $orderedMemory[$index]->getMemory(), $orderedTime[$index]->getTime());
223   }
224
225   /**
226    * @return string
227    */
228   private function getGitHash() {
229     try {
230       $process = new Process('git rev-parse HEAD');
231       $process->setTimeout(3600);
232       $process->run();
233       $git_hash = $process->getOutput();
234     } catch (\Exception $e) {
235       $git_hash = $this->trans('commands.webprofiler.benchmark.messages.not_git');
236     }
237
238     return $git_hash;
239   }
240
241   /**
242    * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
243    * @param $string
244    *
245    * @return array
246    */
247   private function getOrderedDatas(&$datas, $string) {
248     usort($datas, function ($a, $b) use ($string) {
249       $method = 'get' . $string;
250       if ($a->{$method} > $b->{$method}) {
251         return 1;
252       }
253       if ($a->{$method} < $b->{$method}) {
254         return -1;
255       }
256       return 0;
257     });
258   }
259
260   /**
261    * Rebuilds Drupal cache.
262    */
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);
269   }
270
271   /**
272    * @param $gitHash
273    * @param $runs
274    * @param $url
275    * @param \Drupal\webprofiler\Command\BenchmarkData $avg
276    * @param \Drupal\webprofiler\Command\BenchmarkData $median
277    * @param \Drupal\webprofiler\Command\BenchmarkData $percentile95
278    *
279    * @return string
280    */
281   private function generateYaml($gitHash, $runs, $url, BenchmarkData $avg, BenchmarkData $median, BenchmarkData $percentile95) {
282     $yaml = Yaml::dump([
283       'date' => date($this->trans('commands.webprofiler.list.rows.time'), time()),
284       'git_commit' => $gitHash,
285       'number_of_runs' => $runs,
286       'url' => $url,
287       'results' => [
288         'average' => [
289           'time' => sprintf('%.0f ms', $avg->getTime()),
290           'memory' => sprintf('%.1f MB', $avg->getMemory() / 1024 / 1024),
291         ],
292         'median' => [
293           'time' => sprintf('%.0f ms', $median->getTime()),
294           'memory' => sprintf('%.1f MB', $median->getMemory() / 1024 / 1024),
295         ],
296         '95_percentile' => [
297           'time' => sprintf('%.0f ms', $percentile95->getTime()),
298           'memory' => sprintf('%.1f MB', $percentile95->getMemory() / 1024 / 1024),
299         ],
300       ],
301     ]);
302     return $yaml;
303   }
304
305   /**
306    * {@inheritdoc}
307    */
308   public function showMessage($output, $message, $type = 'info') {
309   }
310
311 }