Test Failed
Push — develop ( f741b1...4c7784 )
by Alec
05:40
created

Benchmark::prepareResult()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 3
dl 0
loc 10
ccs 3
cts 3
cp 1
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace AlecRabbit\Tools;
4
5
use AlecRabbit\Rewindable;
6
use AlecRabbit\Tools\Contracts\BenchmarkInterface;
7
use AlecRabbit\Tools\Internal\BenchmarkFunction;
8
use AlecRabbit\Tools\Reports\Contracts\ReportableInterface;
9
use AlecRabbit\Tools\Reports\Traits\Reportable;
10
use AlecRabbit\Tools\Traits\BenchmarkFields;
11
use function AlecRabbit\brackets;
12
use function AlecRabbit\typeOf;
13
14
class Benchmark implements BenchmarkInterface, ReportableInterface
15
{
16
    protected const PG_WIDTH = 60;
17
    protected const ADDED = 'added';
18
    protected const BENCHMARKED = 'benchmarked';
19
    protected const ADVANCE_STEP = 5000;
20
21
    use BenchmarkFields, Reportable;
22
23
    /** @var int */
24
    private $functionIndex = 1;
25
    /** @var Rewindable */
26
    private $rewindable;
27
    /** @var int */
28
    private $iterations;
29
    /** @var null|string */
30
    private $comment;
31
    /** @var array */
32
    private $names;
33
    /** @var string|null */
34
    private $humanReadableName;
35
    /** @var int */
36
    private $totalIterations;
37
    /** @var null|callable */
38
    private $onStart;
39
    /** @var null|callable */
40
    private $onAdvance;
41
    /** @var null|callable */
42
    private $onFinish;
43
    /** @var int */
44
    private $advanceStep;
45
46
    /**
47
     * Benchmark constructor.
48 5
     * @param int $iterations
49
     */
50 5
    public function __construct(int $iterations = 1000)
51 5
    {
52 5
        $this->iterations = $iterations;
53
        $this->timer = new Timer();
54
        $this->reset();
55
    }
56
57 5
    /**
58
     * Resets Benchmark object clear
59 5
     */
60 5
    public function reset(): void
61 5
    {
62 5
        $this->names = [];
63 5
        $this->humanReadableName = null;
64 5
        $this->rewindable =
65
            new Rewindable(
66 2
                function (int $iterations, int $i = 1): \Generator {
67 2
                    while ($i <= $iterations) {
68
                        yield $i++;
69 5
                    }
70 5
                },
71
                $this->iterations
72 5
            );
73 5
        $this->resetFields();
74 5
    }
75
76
//    private function expectedTotalIterations(): int
77
//    {
78
//        return count($this->functions) * $this->iterations;
79
//    }
80
//
81 2
    /**
82
     * Launch benchmarking
83 2
     */
84
    public function run(): void
85 1
    {
86 1
        if ($this->onStart) {
87 1
            ($this->onStart)();
88 1
        }
89
        $this->execute();
90 1
        if ($this->onFinish) {
91
            ($this->onFinish)();
92 2
        }
93
    }
94 2
95 1
    /**
96 1
     * Benchmarking
97 1
     */
98
    private function execute(): void
99
    {
100 2
        /** @var  BenchmarkFunction $f */
101 1
        foreach ($this->functions as $name => $f) {
102 1
            $function = $f->getCallable();
103 1
            $args = $f->getArgs();
104
            $this->prepareResult($f, $function, $args);
105 2
            $timer = $f->getTimer();
106
            if ($f->getException()) {
107
                $timer->check();
108
                $this->totalIterations -= $this->iterations;
109
                continue;
110 2
            }
111
            $this->advanceStep = (int)($this->totalIterations / 100);
112
            foreach ($this->rewindable as $iteration) {
113 2
                $this->bench($timer, $function, $args, $iteration);
114 2
            }
115 2
            $this->profiler->counter(self::BENCHMARKED)->bump();
116 2
        }
117 2
    }
118 2
119 1
    /**
120 1
     * @param BenchmarkFunction $f
121 1
     * @param callable $function
122
     * @param array $args
123 2
     */
124 2
    private function prepareResult(BenchmarkFunction $f, callable $function, array $args): void
125
    {
126 2
        try {
127
            $result = $function(...$args);
128 2
        } catch (\Throwable $e) {
129
            $this->exceptionMessages[$f->getIndexedName()] = $result = brackets(typeOf($e)) . ': ' . $e->getMessage();
130
            $this->exceptions[$f->getIndexedName()] = $e;
131
            $f->setException($e);
132
        }
133
        $f->setResult($result);
134
    }
135 2
136
    /**
137
     * @param Timer $timer
138 2
     * @param callable $function
139 1
     * @param array $args
140 1
     * @param int $iteration
141 1
     */
142 1
    private function bench(Timer $timer, callable $function, array $args, int $iteration): void
143
    {
144 2
        $timer->start();
145 2
        $function(...$args);
146
        $timer->check($iteration);
147
        $this->progress();
148
    }
149
150
    private function progress(): void
151
    {
152
        if ($this->onAdvance && 1 === ++$this->doneIterations % $this->advanceStep) {
153 2
            ($this->onAdvance)();
154
        }
155 2
    }
156 2
157 2
    public function progressBar(
158 2
        callable $onStart = null,
159 2
        callable $onAdvance = null,
160
        callable $onFinish = null
161 2
    ): Benchmark {
162
        $this->onStart = $onStart;
163 2
        $this->onAdvance = $onAdvance;
164 1
        $this->onFinish = $onFinish;
165 1
        return $this;
166
    }
167 1
168 1
    /**
169 1
     * @param callable $func
170 1
     * @param mixed ...$args
171 1
     */
172
    public function addFunction($func, ...$args): void
173 1
    {
174 1
        if (!\is_callable($func, false, $name)) {
175
            throw new \InvalidArgumentException(
176
                sprintf(
177
                    '\'%s\' is NOT callable. Function must be callable. Type of "%s" provided instead.',
178
                    $name,
179 2
                    typeOf($func)
180
                )
181 1
            );
182
        }
183 1
        $function =
184 1
            new BenchmarkFunction(
185
                $func,
186
                $this->refineName($func, $name),
187
                $this->functionIndex++,
188
                $args,
189 1
                $this->comment,
190
                $this->humanReadableName
191 1
            );
192 1
        $this->functions[$function->enumeratedName()] = $function;
193
        $this->humanReadableName = null;
194
        $this->comment = null;
195
        $this->profiler->counter(self::ADDED)->bump();
196
        $this->totalIterations += $this->iterations;
197
    }
198 2
199
    /**
200 2
     * @param callable $func
201 2
     * @param string $name
202
     * @return string
203
     */
204
    private function refineName($func, $name): string
205
    {
206
        if ($func instanceof \Closure) {
207
            $name = 'λ';
208 3
        }
209
        return $name;
210 3
    }
211 1
212 1
    /**
213 1
     * @param string $comment
214 1
     * @return Benchmark
215 1
     */
216
    public function withComment(string $comment): self
217
    {
218
        $this->comment = $comment;
219
        return $this;
220 2
    }
221 2
222 2
    /**
223 2
     * @param string $name
224 2
     * @return Benchmark
225 2
     */
226 2
    public function useName(string $name): self
227
    {
228 2
        if (in_array($name, $this->names, true)) {
229 2
            throw new \InvalidArgumentException(sprintf('Name "%s" is not unique', $name));
230 2
        }
231 2
        $this->names[] = $name;
232 2
        $this->humanReadableName = $name;
233 2
        return $this;
234
    }
235
236
    /**
237
     * @return Benchmark
238
     */
239
    public function returnResults(): self
240 2
    {
241
        $this->withResults = true;
242 2
        return $this;
243 2
    }
244
245 2
    /**
246
     * @return string
247
     */
248
    public function elapsed(): string
249
    {
250
        return
251
            sprintf(
252 2
                'Done in: %s',
253
                $this->getTimer()->elapsed()
254 2
            );
255 2
    }
256
257
    /**
258
     * {@inheritdoc}
259
     */
260
    protected function prepareForReport(): void
261
    {
262
        $this->getProfiler()->getReport();
263
    }
264
}
265