annotate vendor/psy/psysh/src/Command/TimeitCommand.php @ 16:c2387f117808

Routine composer update
author Chris Cannam
date Tue, 10 Jul 2018 15:07:59 +0100
parents 5fb285c0d0e3
children 129ea1e6d783
rev   line source
Chris@13 1 <?php
Chris@13 2
Chris@13 3 /*
Chris@13 4 * This file is part of Psy Shell.
Chris@13 5 *
Chris@13 6 * (c) 2012-2018 Justin Hileman
Chris@13 7 *
Chris@13 8 * For the full copyright and license information, please view the LICENSE
Chris@13 9 * file that was distributed with this source code.
Chris@13 10 */
Chris@13 11
Chris@13 12 namespace Psy\Command;
Chris@13 13
Chris@16 14 use PhpParser\NodeTraverser;
Chris@16 15 use PhpParser\PrettyPrinter\Standard as Printer;
Chris@16 16 use Psy\Command\TimeitCommand\TimeitVisitor;
Chris@13 17 use Psy\Input\CodeArgument;
Chris@16 18 use Psy\ParserFactory;
Chris@13 19 use Psy\Shell;
Chris@13 20 use Symfony\Component\Console\Input\InputInterface;
Chris@13 21 use Symfony\Component\Console\Input\InputOption;
Chris@13 22 use Symfony\Component\Console\Output\OutputInterface;
Chris@13 23
Chris@13 24 /**
Chris@13 25 * Class TimeitCommand.
Chris@13 26 */
Chris@13 27 class TimeitCommand extends Command
Chris@13 28 {
Chris@13 29 const RESULT_MSG = '<info>Command took %.6f seconds to complete.</info>';
Chris@13 30 const AVG_RESULT_MSG = '<info>Command took %.6f seconds on average (%.6f median; %.6f total) to complete.</info>';
Chris@13 31
Chris@16 32 private static $start = null;
Chris@16 33 private static $times = [];
Chris@16 34
Chris@16 35 private $parser;
Chris@16 36 private $traverser;
Chris@16 37 private $printer;
Chris@16 38
Chris@16 39 /**
Chris@16 40 * {@inheritdoc}
Chris@16 41 */
Chris@16 42 public function __construct($name = null)
Chris@16 43 {
Chris@16 44 $parserFactory = new ParserFactory();
Chris@16 45 $this->parser = $parserFactory->createParser();
Chris@16 46
Chris@16 47 $this->traverser = new NodeTraverser();
Chris@16 48 $this->traverser->addVisitor(new TimeitVisitor());
Chris@16 49
Chris@16 50 $this->printer = new Printer();
Chris@16 51
Chris@16 52 parent::__construct($name);
Chris@16 53 }
Chris@16 54
Chris@13 55 /**
Chris@13 56 * {@inheritdoc}
Chris@13 57 */
Chris@13 58 protected function configure()
Chris@13 59 {
Chris@13 60 $this
Chris@13 61 ->setName('timeit')
Chris@13 62 ->setDefinition([
Chris@13 63 new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Number of iterations.'),
Chris@13 64 new CodeArgument('code', CodeArgument::REQUIRED, 'Code to execute.'),
Chris@13 65 ])
Chris@13 66 ->setDescription('Profiles with a timer.')
Chris@13 67 ->setHelp(
Chris@13 68 <<<'HELP'
Chris@13 69 Time profiling for functions and commands.
Chris@13 70
Chris@13 71 e.g.
Chris@13 72 <return>>>> timeit sleep(1)</return>
Chris@13 73 <return>>>> timeit -n1000 $closure()</return>
Chris@13 74 HELP
Chris@13 75 );
Chris@13 76 }
Chris@13 77
Chris@13 78 /**
Chris@13 79 * {@inheritdoc}
Chris@13 80 */
Chris@13 81 protected function execute(InputInterface $input, OutputInterface $output)
Chris@13 82 {
Chris@13 83 $code = $input->getArgument('code');
Chris@13 84 $num = $input->getOption('num') ?: 1;
Chris@13 85 $shell = $this->getApplication();
Chris@13 86
Chris@16 87 $instrumentedCode = $this->instrumentCode($code);
Chris@16 88
Chris@16 89 self::$times = [];
Chris@16 90
Chris@13 91 for ($i = 0; $i < $num; $i++) {
Chris@16 92 $_ = $shell->execute($instrumentedCode);
Chris@16 93 $this->ensureEndMarked();
Chris@13 94 }
Chris@13 95
Chris@13 96 $shell->writeReturnValue($_);
Chris@13 97
Chris@16 98 $times = self::$times;
Chris@16 99 self::$times = [];
Chris@16 100
Chris@13 101 if ($num === 1) {
Chris@13 102 $output->writeln(sprintf(self::RESULT_MSG, $times[0]));
Chris@13 103 } else {
Chris@13 104 $total = array_sum($times);
Chris@13 105 rsort($times);
Chris@13 106 $median = $times[round($num / 2)];
Chris@13 107
Chris@13 108 $output->writeln(sprintf(self::AVG_RESULT_MSG, $total / $num, $median, $total));
Chris@13 109 }
Chris@13 110 }
Chris@16 111
Chris@16 112 /**
Chris@16 113 * Internal method for marking the start of timeit execution.
Chris@16 114 *
Chris@16 115 * A static call to this method will be injected at the start of the timeit
Chris@16 116 * input code to instrument the call. We will use the saved start time to
Chris@16 117 * more accurately calculate time elapsed during execution.
Chris@16 118 */
Chris@16 119 public static function markStart()
Chris@16 120 {
Chris@16 121 self::$start = microtime(true);
Chris@16 122 }
Chris@16 123
Chris@16 124 /**
Chris@16 125 * Internal method for marking the end of timeit execution.
Chris@16 126 *
Chris@16 127 * A static call to this method is injected by TimeitVisitor at the end
Chris@16 128 * of the timeit input code to instrument the call.
Chris@16 129 *
Chris@16 130 * Note that this accepts an optional $ret parameter, which is used to pass
Chris@16 131 * the return value of the last statement back out of timeit. This saves us
Chris@16 132 * a bunch of code rewriting shenanigans.
Chris@16 133 *
Chris@16 134 * @param mixed $ret
Chris@16 135 *
Chris@16 136 * @return mixed it just passes $ret right back
Chris@16 137 */
Chris@16 138 public static function markEnd($ret = null)
Chris@16 139 {
Chris@16 140 self::$times[] = microtime(true) - self::$start;
Chris@16 141 self::$start = null;
Chris@16 142
Chris@16 143 return $ret;
Chris@16 144 }
Chris@16 145
Chris@16 146 /**
Chris@16 147 * Ensure that the end of code execution was marked.
Chris@16 148 *
Chris@16 149 * The end *should* be marked in the instrumented code, but just in case
Chris@16 150 * we'll add a fallback here.
Chris@16 151 */
Chris@16 152 private function ensureEndMarked()
Chris@16 153 {
Chris@16 154 if (self::$start !== null) {
Chris@16 155 self::markEnd();
Chris@16 156 }
Chris@16 157 }
Chris@16 158
Chris@16 159 /**
Chris@16 160 * Instrument code for timeit execution.
Chris@16 161 *
Chris@16 162 * This inserts `markStart` and `markEnd` calls to ensure that (reasonably)
Chris@16 163 * accurate times are recorded for just the code being executed.
Chris@16 164 *
Chris@16 165 * @param string $code
Chris@16 166 *
Chris@16 167 * @return string
Chris@16 168 */
Chris@16 169 private function instrumentCode($code)
Chris@16 170 {
Chris@16 171 return $this->printer->prettyPrint($this->traverser->traverse($this->parse($code)));
Chris@16 172 }
Chris@16 173
Chris@16 174 /**
Chris@16 175 * Lex and parse a string of code into statements.
Chris@16 176 *
Chris@16 177 * @param string $code
Chris@16 178 *
Chris@16 179 * @return array Statements
Chris@16 180 */
Chris@16 181 private function parse($code)
Chris@16 182 {
Chris@16 183 $code = '<?php ' . $code;
Chris@16 184
Chris@16 185 try {
Chris@16 186 return $this->parser->parse($code);
Chris@16 187 } catch (\PhpParser\Error $e) {
Chris@16 188 if (strpos($e->getMessage(), 'unexpected EOF') === false) {
Chris@16 189 throw $e;
Chris@16 190 }
Chris@16 191
Chris@16 192 // If we got an unexpected EOF, let's try it again with a semicolon.
Chris@16 193 return $this->parser->parse($code . ';');
Chris@16 194 }
Chris@16 195 }
Chris@13 196 }