Mercurial > hg > cmmr2012-drupal-site
comparison vendor/psy/psysh/src/Command/TimeitCommand.php @ 0:c75dbcec494b
Initial commit from drush-created site
author | Chris Cannam |
---|---|
date | Thu, 05 Jul 2018 14:24:15 +0000 |
parents | |
children | a9cd425dd02b |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:c75dbcec494b |
---|---|
1 <?php | |
2 | |
3 /* | |
4 * This file is part of Psy Shell. | |
5 * | |
6 * (c) 2012-2018 Justin Hileman | |
7 * | |
8 * For the full copyright and license information, please view the LICENSE | |
9 * file that was distributed with this source code. | |
10 */ | |
11 | |
12 namespace Psy\Command; | |
13 | |
14 use PhpParser\NodeTraverser; | |
15 use PhpParser\PrettyPrinter\Standard as Printer; | |
16 use Psy\Command\TimeitCommand\TimeitVisitor; | |
17 use Psy\Input\CodeArgument; | |
18 use Psy\ParserFactory; | |
19 use Psy\Shell; | |
20 use Symfony\Component\Console\Input\InputInterface; | |
21 use Symfony\Component\Console\Input\InputOption; | |
22 use Symfony\Component\Console\Output\OutputInterface; | |
23 | |
24 /** | |
25 * Class TimeitCommand. | |
26 */ | |
27 class TimeitCommand extends Command | |
28 { | |
29 const RESULT_MSG = '<info>Command took %.6f seconds 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 } | |
54 | |
55 /** | |
56 * {@inheritdoc} | |
57 */ | |
58 protected function configure() | |
59 { | |
60 $this | |
61 ->setName('timeit') | |
62 ->setDefinition([ | |
63 new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Number of iterations.'), | |
64 new CodeArgument('code', CodeArgument::REQUIRED, 'Code to execute.'), | |
65 ]) | |
66 ->setDescription('Profiles with a timer.') | |
67 ->setHelp( | |
68 <<<'HELP' | |
69 Time profiling for functions and commands. | |
70 | |
71 e.g. | |
72 <return>>>> timeit sleep(1)</return> | |
73 <return>>>> timeit -n1000 $closure()</return> | |
74 HELP | |
75 ); | |
76 } | |
77 | |
78 /** | |
79 * {@inheritdoc} | |
80 */ | |
81 protected function execute(InputInterface $input, OutputInterface $output) | |
82 { | |
83 $code = $input->getArgument('code'); | |
84 $num = $input->getOption('num') ?: 1; | |
85 $shell = $this->getApplication(); | |
86 | |
87 $instrumentedCode = $this->instrumentCode($code); | |
88 | |
89 self::$times = []; | |
90 | |
91 for ($i = 0; $i < $num; $i++) { | |
92 $_ = $shell->execute($instrumentedCode); | |
93 $this->ensureEndMarked(); | |
94 } | |
95 | |
96 $shell->writeReturnValue($_); | |
97 | |
98 $times = self::$times; | |
99 self::$times = []; | |
100 | |
101 if ($num === 1) { | |
102 $output->writeln(sprintf(self::RESULT_MSG, $times[0])); | |
103 } else { | |
104 $total = array_sum($times); | |
105 rsort($times); | |
106 $median = $times[round($num / 2)]; | |
107 | |
108 $output->writeln(sprintf(self::AVG_RESULT_MSG, $total / $num, $median, $total)); | |
109 } | |
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 } | |
196 } |