diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/vendor/psy/psysh/src/Command/TimeitCommand.php	Thu Jul 05 14:24:15 2018 +0000
@@ -0,0 +1,196 @@
+<?php
+
+/*
+ * This file is part of Psy Shell.
+ *
+ * (c) 2012-2018 Justin Hileman
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Psy\Command;
+
+use PhpParser\NodeTraverser;
+use PhpParser\PrettyPrinter\Standard as Printer;
+use Psy\Command\TimeitCommand\TimeitVisitor;
+use Psy\Input\CodeArgument;
+use Psy\ParserFactory;
+use Psy\Shell;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+/**
+ * Class TimeitCommand.
+ */
+class TimeitCommand extends Command
+{
+    const RESULT_MSG     = '<info>Command took %.6f seconds to complete.</info>';
+    const AVG_RESULT_MSG = '<info>Command took %.6f seconds on average (%.6f median; %.6f total) to complete.</info>';
+
+    private static $start = null;
+    private static $times = [];
+
+    private $parser;
+    private $traverser;
+    private $printer;
+
+    /**
+     * {@inheritdoc}
+     */
+    public function __construct($name = null)
+    {
+        $parserFactory = new ParserFactory();
+        $this->parser = $parserFactory->createParser();
+
+        $this->traverser = new NodeTraverser();
+        $this->traverser->addVisitor(new TimeitVisitor());
+
+        $this->printer = new Printer();
+
+        parent::__construct($name);
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this
+            ->setName('timeit')
+            ->setDefinition([
+                new InputOption('num', 'n', InputOption::VALUE_REQUIRED, 'Number of iterations.'),
+                new CodeArgument('code', CodeArgument::REQUIRED, 'Code to execute.'),
+            ])
+            ->setDescription('Profiles with a timer.')
+            ->setHelp(
+                <<<'HELP'
+Time profiling for functions and commands.
+
+e.g.
+<return>>>> timeit sleep(1)</return>
+<return>>>> timeit -n1000 $closure()</return>
+HELP
+            );
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $code = $input->getArgument('code');
+        $num = $input->getOption('num') ?: 1;
+        $shell = $this->getApplication();
+
+        $instrumentedCode = $this->instrumentCode($code);
+
+        self::$times = [];
+
+        for ($i = 0; $i < $num; $i++) {
+            $_ = $shell->execute($instrumentedCode);
+            $this->ensureEndMarked();
+        }
+
+        $shell->writeReturnValue($_);
+
+        $times = self::$times;
+        self::$times = [];
+
+        if ($num === 1) {
+            $output->writeln(sprintf(self::RESULT_MSG, $times[0]));
+        } else {
+            $total = array_sum($times);
+            rsort($times);
+            $median = $times[round($num / 2)];
+
+            $output->writeln(sprintf(self::AVG_RESULT_MSG, $total / $num, $median, $total));
+        }
+    }
+
+    /**
+     * Internal method for marking the start of timeit execution.
+     *
+     * A static call to this method will be injected at the start of the timeit
+     * input code to instrument the call. We will use the saved start time to
+     * more accurately calculate time elapsed during execution.
+     */
+    public static function markStart()
+    {
+        self::$start = microtime(true);
+    }
+
+    /**
+     * Internal method for marking the end of timeit execution.
+     *
+     * A static call to this method is injected by TimeitVisitor at the end
+     * of the timeit input code to instrument the call.
+     *
+     * Note that this accepts an optional $ret parameter, which is used to pass
+     * the return value of the last statement back out of timeit. This saves us
+     * a bunch of code rewriting shenanigans.
+     *
+     * @param mixed $ret
+     *
+     * @return mixed it just passes $ret right back
+     */
+    public static function markEnd($ret = null)
+    {
+        self::$times[] = microtime(true) - self::$start;
+        self::$start = null;
+
+        return $ret;
+    }
+
+    /**
+     * Ensure that the end of code execution was marked.
+     *
+     * The end *should* be marked in the instrumented code, but just in case
+     * we'll add a fallback here.
+     */
+    private function ensureEndMarked()
+    {
+        if (self::$start !== null) {
+            self::markEnd();
+        }
+    }
+
+    /**
+     * Instrument code for timeit execution.
+     *
+     * This inserts `markStart` and `markEnd` calls to ensure that (reasonably)
+     * accurate times are recorded for just the code being executed.
+     *
+     * @param string $code
+     *
+     * @return string
+     */
+    private function instrumentCode($code)
+    {
+        return $this->printer->prettyPrint($this->traverser->traverse($this->parse($code)));
+    }
+
+    /**
+     * Lex and parse a string of code into statements.
+     *
+     * @param string $code
+     *
+     * @return array Statements
+     */
+    private function parse($code)
+    {
+        $code = '<?php ' . $code;
+
+        try {
+            return $this->parser->parse($code);
+        } catch (\PhpParser\Error $e) {
+            if (strpos($e->getMessage(), 'unexpected EOF') === false) {
+                throw $e;
+            }
+
+            // If we got an unexpected EOF, let's try it again with a semicolon.
+            return $this->parser->parse($code . ';');
+        }
+    }
+}