PageRenderTime 26ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/web/modules/contrib/devel/webprofiler/src/Command/BenchmarkCommand.php

https://gitlab.com/andecode/theme-spark
PHP | 312 lines | 194 code | 49 blank | 69 comment | 14 complexity | 8b6054e1ab167edfaee481105d98ece9 MD5 | raw file
  1. <?php
  2. namespace Drupal\webprofiler\Command;
  3. use Drupal\Console\Annotations\DrupalCommand;
  4. use GuzzleHttp\ClientInterface;
  5. use GuzzleHttp\Cookie\CookieJar;
  6. use Symfony\Component\Console\Helper\ProgressBar;
  7. use Symfony\Component\Console\Input\InputArgument;
  8. use Symfony\Component\Console\Input\InputInterface;
  9. use Symfony\Component\Console\Input\InputOption;
  10. use Symfony\Component\Console\Output\OutputInterface;
  11. use Drupal\Console\Core\Command\Shared\ContainerAwareCommandTrait;
  12. use Symfony\Component\Console\Command\Command;
  13. use Symfony\Component\DomCrawler\Crawler;
  14. use Symfony\Component\Process\Process;
  15. use Symfony\Component\Yaml\Yaml;
  16. /**
  17. * Class BenchmarkCommand.
  18. *
  19. * @DrupalCommand (
  20. * extension="webprofiler",
  21. * extensionType="module"
  22. * )
  23. */
  24. class BenchmarkCommand extends Command {
  25. use ContainerAwareCommandTrait;
  26. /**
  27. * {@inheritdoc}
  28. */
  29. protected function configure() {
  30. $this
  31. ->setName('webprofiler:benchmark')
  32. ->setDescription($this->trans('commands.webprofiler.benchmark.description'))
  33. ->addArgument('url', InputArgument::REQUIRED, $this->trans('commands.webprofiler.benchmark.arguments.url'))
  34. ->addOption('runs', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.benchmark.options.runs'), 100)
  35. ->addOption('file', NULL, InputOption::VALUE_REQUIRED, $this->trans('commands.webprofiler.benchmark.options.file'))
  36. ->addOption('cache-rebuild', 'cr', InputOption::VALUE_NONE, $this->trans('commands.webprofiler.benchmark.options.cache_rebuild'));
  37. }
  38. /**
  39. * {@inheritdoc}
  40. */
  41. protected function execute(InputInterface $input, OutputInterface $output) {
  42. $runs = $input->getOption('runs');
  43. $file = $input->getOption('file');
  44. $cache_rebuild = $input->getOption('cache-rebuild');
  45. // http://username:password@hostname/
  46. $url = $input->getArgument('url');
  47. $url_components = parse_url($url);
  48. $login = isset($url_components['user']) && isset($url_components['pass']);
  49. $steps = 3;
  50. if ($cache_rebuild) {
  51. $steps++;
  52. }
  53. if ($login) {
  54. $steps++;
  55. }
  56. /** @var \Drupal\Core\Http\Client $client */
  57. $client = $this->container->get('http_client');
  58. $progress = new ProgressBar($output, $runs + $steps);
  59. $progress->setFormat(' %current%/%max% [%bar%] %percent:3s%% %message%');
  60. if ($cache_rebuild) {
  61. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.cache_rebuild'));
  62. $this->RebuildCache();
  63. $progress->advance();
  64. }
  65. if ($login) {
  66. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.login'));
  67. $login_url = "{$url_components['scheme']}://{$url_components['host']}/user/login";
  68. // Enable cookies storage.
  69. $cookieJar = new CookieJar();
  70. $client->setDefaultOption('cookies', $cookieJar);
  71. // Retrieve a form_build_id using the DomCrawler component.
  72. $response = $client->get($login_url)->getBody()->getContents();
  73. $crawler = new Crawler($response);
  74. $form_build_id = $crawler->filter('#user-login-form input[name=form_build_id]')
  75. ->attr('value');
  76. $op = $crawler->filter('#user-login-form input[name=op]')->attr('value');
  77. // Login a user.
  78. $response = $client->post($login_url, [
  79. 'body' => [
  80. 'name' => $url_components['user'],
  81. 'pass' => $url_components['pass'],
  82. 'form_build_id' => $form_build_id,
  83. 'form_id' => 'user_login_form',
  84. 'op' => $op,
  85. ],
  86. ]);
  87. $progress->advance();
  88. if ($response->getStatusCode() != 200) {
  89. throw new \Exception($this->trans('commands.webprofiler.benchmark.messages.error_login'));
  90. }
  91. }
  92. $datas = [];
  93. for ($i = 0; $i < $runs; $i++) {
  94. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.get'));
  95. $datas[] = $this->getData($client, $url);
  96. $progress->advance();
  97. }
  98. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_avg'));
  99. $avg = $this->computeAvg($datas);
  100. $progress->advance();
  101. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_median'));
  102. $median = $this->computePercentile($datas, 50);
  103. $progress->advance();
  104. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.compute_95percentile'));
  105. $percentile95 = $this->computePercentile($datas, 95);
  106. $progress->advance();
  107. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.git_hash'));
  108. $gitHash = $this->getGitHash();
  109. $progress->advance();
  110. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.yaml'));
  111. $yaml = $this->generateYaml($gitHash, $runs, $url, $avg, $median, $percentile95);
  112. $progress->advance();
  113. $progress->setMessage($this->trans('commands.webprofiler.benchmark.progress.done'));
  114. $progress->finish();
  115. $output->writeln('');
  116. if ($file) {
  117. file_put_contents($file, $yaml);
  118. }
  119. else {
  120. $output->writeln($yaml);
  121. }
  122. }
  123. /**
  124. * @param \GuzzleHttp\ClientInterface $client
  125. * @param $url
  126. *
  127. * @return array
  128. */
  129. private function getData(ClientInterface $client, $url) {
  130. /** @var \GuzzleHttp\Message\ResponseInterface $response */
  131. $response = $client->get($url);
  132. $token = $response->getHeader('X-Debug-Token');
  133. /** @var \Drupal\webprofiler\Profiler\Profiler $profiler */
  134. $profiler = $this->container->get('profiler');
  135. /** @var \Symfony\Component\HttpKernel\Profiler\Profile $profile */
  136. $profile = $profiler->loadProfile($token);
  137. /** @var \Drupal\webprofiler\DataCollector\TimeDataCollector $timeDataCollector */
  138. $timeDataCollector = $profile->getCollector('time');
  139. return new BenchmarkData(
  140. $token,
  141. $timeDataCollector->getMemory(),
  142. $timeDataCollector->getDuration());
  143. }
  144. /**
  145. * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
  146. *
  147. * @return \Drupal\webprofiler\Command\BenchmarkData
  148. */
  149. private function computeAvg($datas) {
  150. $profiles = count($datas);
  151. $totalTime = 0;
  152. $totalMemory = 0;
  153. foreach ($datas as $data) {
  154. $totalTime += $data->getTime();
  155. $totalMemory += $data->getMemory();
  156. }
  157. return new BenchmarkData(NULL, $totalMemory / $profiles, $totalTime / $profiles);
  158. }
  159. /**
  160. * Computes percentile using The Nearest Rank method.
  161. *
  162. * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
  163. * @param $percentile
  164. *
  165. * @return \Drupal\webprofiler\Command\BenchmarkData
  166. *
  167. * @throws \Exception
  168. */
  169. private function computePercentile($datas, $percentile) {
  170. if ($percentile < 0 || $percentile > 100) {
  171. throw new \Exception('Percentile has to be between 0 and 100');
  172. }
  173. $profiles = count($datas);
  174. $n = ceil((($percentile / 100) * $profiles));
  175. $index = (int) $n - 1;
  176. $orderedTime = $datas;
  177. $this->getOrderedDatas($orderedTime, 'Time');
  178. $orderedMemory = $datas;
  179. $this->getOrderedDatas($orderedMemory, 'Memory');
  180. return new BenchmarkData(NULL, $orderedMemory[$index]->getMemory(), $orderedTime[$index]->getTime());
  181. }
  182. /**
  183. * @return string
  184. */
  185. private function getGitHash() {
  186. try {
  187. $process = new Process('git rev-parse HEAD');
  188. $process->setTimeout(3600);
  189. $process->run();
  190. $git_hash = $process->getOutput();
  191. }
  192. catch (\Exception $e) {
  193. $git_hash = $this->trans('commands.webprofiler.benchmark.messages.not_git');
  194. }
  195. return $git_hash;
  196. }
  197. /**
  198. * @param \Drupal\webprofiler\Command\BenchmarkData[] $datas
  199. * @param $string
  200. *
  201. * @return array
  202. */
  203. private function getOrderedDatas(&$datas, $string) {
  204. usort($datas, function ($a, $b) use ($string) {
  205. $method = 'get' . $string;
  206. if ($a->{$method} > $b->{$method}) {
  207. return 1;
  208. }
  209. if ($a->{$method} < $b->{$method}) {
  210. return -1;
  211. }
  212. return 0;
  213. });
  214. }
  215. /**
  216. * Rebuilds Drupal cache.
  217. */
  218. protected function RebuildCache() {
  219. require_once DRUPAL_ROOT . '/core/includes/utility.inc';
  220. $kernelHelper = $this->getHelper('kernel');
  221. $classLoader = $kernelHelper->getClassLoader();
  222. $request = $kernelHelper->getRequest();
  223. drupal_rebuild($classLoader, $request);
  224. }
  225. /**
  226. * @param $gitHash
  227. * @param $runs
  228. * @param $url
  229. * @param \Drupal\webprofiler\Command\BenchmarkData $avg
  230. * @param \Drupal\webprofiler\Command\BenchmarkData $median
  231. * @param \Drupal\webprofiler\Command\BenchmarkData $percentile95
  232. *
  233. * @return string
  234. */
  235. private function generateYaml($gitHash, $runs, $url, BenchmarkData $avg, BenchmarkData $median, BenchmarkData $percentile95) {
  236. $yaml = Yaml::dump([
  237. 'date' => date($this->trans('commands.webprofiler.list.rows.time'), time()),
  238. 'git_commit' => $gitHash,
  239. 'number_of_runs' => $runs,
  240. 'url' => $url,
  241. 'results' => [
  242. 'average' => [
  243. 'time' => sprintf('%.0f ms', $avg->getTime()),
  244. 'memory' => sprintf('%.1f MB', $avg->getMemory() / 1024 / 1024),
  245. ],
  246. 'median' => [
  247. 'time' => sprintf('%.0f ms', $median->getTime()),
  248. 'memory' => sprintf('%.1f MB', $median->getMemory() / 1024 / 1024),
  249. ],
  250. '95_percentile' => [
  251. 'time' => sprintf('%.0f ms', $percentile95->getTime()),
  252. 'memory' => sprintf('%.1f MB', $percentile95->getMemory() / 1024 / 1024),
  253. ],
  254. ],
  255. ]);
  256. return $yaml;
  257. }
  258. /**
  259. * {@inheritdoc}
  260. */
  261. public function showMessage($output, $message, $type = 'info') {
  262. }
  263. }