Passed
Push — master ( 4bff04...8ad0e5 )
by Alec
03:06
created

Benchmark::returnResults()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 3
cts 3
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * User: alec
4
 * Date: 29.11.18
5
 * Time: 11:04
6
 */
7
8
namespace AlecRabbit\Tools;
9
10
use AlecRabbit\Rewindable;
11
use AlecRabbit\Tools\Contracts\BenchmarkInterface;
12
use AlecRabbit\Tools\Internal\BenchmarkFunction;
13
use AlecRabbit\Tools\Reports\Contracts\ReportableInterface;
14
use AlecRabbit\Tools\Reports\Factory;
15
use AlecRabbit\Tools\Reports\Traits\Reportable;
16
use AlecRabbit\Tools\Traits\BenchmarkFields;
17
use function AlecRabbit\brackets;
18
use function AlecRabbit\typeOf;
19
20
class Benchmark implements BenchmarkInterface, ReportableInterface
21
{
22
    protected const PG_WIDTH = 60;
23
24
    use BenchmarkFields, Reportable;
25
26
    /** @var int */
27
    private $namingIndex = 0;
28
    /** @var Rewindable */
29
    private $rewindable;
30
    /** @var int */
31
    private $iterations = 0;
32
    /** @var null|string */
33
    private $comment;
34
    /** @var bool */
35
    private $verbose = false;
36
    /** @var bool */
37
    private $errorState = false;
38
    /** @var int */
39
    private $dots = 0;
40
41
    /**
42
     * Benchmark constructor.
43
     * @param int $iterations
44
     */
45 5
    public function __construct(int $iterations = 1000)
46
    {
47 5
        $this->iterations = $iterations;
48 5
        $this->rewindable =
49 5
            new Rewindable(
50
                function (int $iterations, int $i = 1): \Generator {
51 2
                    while ($i <= $iterations) {
52 2
                        yield $i++;
53
                    }
54 5
                },
55 5
                $iterations
56
            );
57 5
        $this->profiler = new Profiler();
58 5
    }
59
60
    /**
61
     * Launch benchmarking
62
     * @param bool $report
63
     */
64 2
    public function run(bool $report = false): void
65
    {
66 2
        if ($this->verbose) {
67
            echo
68 1
            sprintf(
69 1
                'Running benchmarks(%s):',
70 1
                $this->iterations
71
            );
72 1
            echo PHP_EOL;
73
        }
74 2
        $this->execute();
75 2
        echo PHP_EOL;
76 2
        echo PHP_EOL;
77
78 2
        if ($report) {
79 1
            echo (string)$this->getReport();
80 1
            echo PHP_EOL;
81
        }
82 2
    }
83
84
    /**
85
     * Launch benchmarking in verbose mode
86
     */
87 2
    private function execute(): void
88
    {
89
        /** @var  BenchmarkFunction $f */
90 2
        foreach ($this->functions as $name => $f) {
91 2
            $function = $f->getFunction();
92 2
            $args = $f->getArgs();
93 2
            $this->prepareResult($f, $function, $args);
94 2
            $timer = $this->profiler->timer($name);
95 2
            if ($this->errorState) {
96 1
                $this->errorState = false;
97 1
                $timer->check();
98 1
                continue;
99
            }
100 2
            foreach ($this->rewindable as $iteration) {
101 2
                $this->bench($timer, $function, $args, $iteration);
102
            }
103 2
            $this->profiler->counter()->bump();
104
        }
105 2
    }
106
107
    /**
108
     * @param BenchmarkFunction $f
109
     * @param callable $function
110
     * @param array $args
111
     */
112 2
    private function prepareResult(BenchmarkFunction $f, callable $function, array $args): void
113
    {
114
        try {
115 2
            $result = $function(...$args);
116 1
        } catch (\Throwable $e) {
117 1
            $this->errorState = true;
118 1
            $result = brackets(typeOf($e)) . ': ' . $e->getMessage();
119 1
            $this->exceptionMessages[$f->getIndexedName()] = $result;
120
        }
121 2
        $f->setResult($result);
122 2
    }
123
124
    /**
125
     * @param Timer $timer
126
     * @param callable $function
127
     * @param array $args
128
     * @param int $iteration
129
     */
130 2
    private function bench(Timer $timer, callable $function, array $args, int $iteration): void
131
    {
132 2
        $timer->start();
133 2
        $function(...$args);
134 2
        $timer->check($iteration);
135 2
        $this->progress();
136 2
    }
137
138 2
    private function progress(): void
139
    {
140 2
        if ($this->verbose && 1 === ++$this->totalIterations % 5000) {
141 1
            echo '.';
142 1
            if (++$this->dots > static::PG_WIDTH) {
143 1
                echo PHP_EOL;
144 1
                $this->dots = 0;
145
            }
146
        }
147 2
    }
148
149
    /**
150
     * @return Benchmark
151
     */
152 1
    public function verbose(): self
153
    {
154 1
        $this->verbose = true;
155 1
        return $this;
156
    }
157
158
    /**
159
     * @return Benchmark
160
     */
161 2
    public function color(): self
162
    {
163 2
        Factory::setColour(true);
164 2
        return $this;
165
    }
166
167
    /**
168
     * @param callable $func
169
     * @param mixed ...$args
170
     */
171 3
    public function addFunction($func, ...$args): void
172
    {
173 3
        if (!\is_callable($func, false, $name)) {
174 1
            throw new \InvalidArgumentException(
175 1
                sprintf(
176 1
                    '\'%s\' is NOT callable. Function must be callable. Type of "%s" provided instead.',
177 1
                    $name,
178 1
                    typeOf($func)
179
                )
180
            );
181
        }
182 2
        $function = new BenchmarkFunction($func, $name, $this->namingIndex++, $args, $this->comment);
183 2
        $this->comment = null;
184
185 2
        $this->functions[$function->getEnumeratedName()] = $function;
186 2
    }
187
188
    /**
189
     * @param string $name
190
     * @return Benchmark
191
     */
192 2
    public function withComment(string $name): self
193
    {
194 2
        $this->comment = $name;
195 2
        return $this;
196
    }
197
198
    /**
199
     * @return Benchmark
200
     */
201 1
    public function returnResults(): self
202
    {
203 1
        $this->withResults = true;
204 1
        return $this;
205
    }
206
207
    /**
208
     * @return string
209
     * @throws \Throwable
210
     */
211 1
    public function elapsed(): string
212
    {
213 1
        $theme = Factory::getThemeObject();
214
        return
215 1
            sprintf(
216 1
                'Done in: %s',
217 1
                $theme->yellow($this->getProfiler()->timer()->elapsed())
218
            );
219
    }
220
221
    /**
222
     * {@inheritdoc}
223
     */
224 3
    protected function prepareForReport(): void
225
    {
226 3
        $this->getProfiler()->getReport();
227 3
    }
228
}
229