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