Passed
Push — develop ( 4c7784...9c7d33 )
by Alec
03:41 queued 10s
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\typeOf;
12
13
class Benchmark implements BenchmarkInterface, ReportableInterface
14
{
15
    protected const ADDED = 'added';
16
    protected const BENCHMARKED = 'benchmarked';
17
18
    use BenchmarkFields, Reportable;
19
20
    /** @var int */
21
    private $functionIndex = 1;
22
    /** @var Rewindable */
23
    private $rewindable;
24
    /** @var int */
25
    private $iterations;
26
    /** @var null|string */
27
    private $comment;
28
    /** @var array */
29
    private $names;
30
    /** @var string|null */
31
    private $humanReadableName;
32
    /** @var int */
33
    private $totalIterations;
34
    /** @var null|callable */
35
    private $onStart;
36
    /** @var null|callable */
37
    private $onAdvance;
38
    /** @var null|callable */
39
    private $onFinish;
40
    /** @var int */
41
    private $advanceStep;
42
43
    /**
44
     * Benchmark constructor.
45
     * @param int $iterations
46
     */
47 4
    public function __construct(int $iterations = 1000)
48
    {
49 4
        $this->iterations = $iterations;
50 4
        $this->timer = new Timer();
51 4
        $this->reset();
52 4
    }
53
54
    /**
55
     * Resets Benchmark object clear
56
     */
57 4
    public function reset(): void
58
    {
59 4
        $this->names = [];
60 4
        $this->humanReadableName = null;
61 4
        $this->rewindable =
62 4
            new Rewindable(
63
                function (int $iterations, int $i = 1): \Generator {
64 2
                    while ($i <= $iterations) {
65 2
                        yield $i++;
66
                    }
67 4
                },
68 4
                $this->iterations
69
            );
70 4
        $this->resetFields();
71 4
    }
72
73
    /**
74
     * Launch benchmarking
75
     */
76 2
    public function run(): void
77
    {
78 2
        if ($this->onStart) {
79
            ($this->onStart)();
80
        }
81 2
        $this->execute();
82 2
        if ($this->onFinish) {
83
            ($this->onFinish)();
84
        }
85 2
    }
86
87
    /**
88
     * Benchmarking
89
     */
90 2
    private function execute(): void
91
    {
92
        /** @var  BenchmarkFunction $f */
93 2
        foreach ($this->functions as $f) {
94 2
            if (!$f->execute()) {
95 1
                $this->totalIterations -= $this->iterations;
96 1
                continue;
97
            }
98 2
            $this->advanceStep = (int)($this->totalIterations / 100);
99 2
            $this->bench($f);
100 2
            $this->profiler->counter(self::BENCHMARKED)->bump();
101
        }
102 2
    }
103
104
    /**
105
     * @param BenchmarkFunction $f
106
     */
107 2
    private function bench(BenchmarkFunction $f): void
108
    {
109 2
        $timer = $f->getTimer();
110 2
        $function = $f->getCallable();
111 2
        $args = $f->getArgs();
112 2
        foreach ($this->rewindable as $iteration) {
113 2
            $start = microtime(true);
114
            /** @noinspection DisconnectedForeachInstructionInspection */
115 2
            $function(...$args);
116 2
            $stop = microtime(true);
117 2
            $timer->bounds($start, $stop, $iteration);
118
            /** @noinspection DisconnectedForeachInstructionInspection */
119 2
            $this->progress();
120
        }
121 2
    }
122
123 2
    private function progress(): void
124
    {
125 2
        if ($this->onAdvance && 0 === ++$this->doneIterations % $this->advanceStep) {
126
            ($this->onAdvance)();
127
        }
128 2
    }
129
130
    /**
131
     * @param callable|null $onStart
132
     * @param callable|null $onAdvance
133
     * @param callable|null $onFinish
134
     * @return Benchmark
135
     */
136
    public function progressBar(
137
        callable $onStart = null,
138
        callable $onAdvance = null,
139
        callable $onFinish = null
140
    ): Benchmark {
141
        $this->onStart = $onStart;
142
        $this->onAdvance = $onAdvance;
143
        $this->onFinish = $onFinish;
144
        return $this;
145
    }
146
147
    /**
148
     * @param callable $func
149
     * @param mixed ...$args
150
     */
151 3
    public function addFunction($func, ...$args): void
152
    {
153 3
        if (!\is_callable($func, false, $name)) {
154 1
            throw new \InvalidArgumentException(
155 1
                sprintf(
156 1
                    '\'%s\' is NOT callable. Function must be callable. Type of "%s" provided instead.',
157 1
                    $name,
158 1
                    typeOf($func)
159
                )
160
            );
161
        }
162
        $function =
163 2
            new BenchmarkFunction(
164 2
                $func,
165 2
                $this->refineName($func, $name),
166 2
                $this->functionIndex++,
167 2
                $args,
168 2
                $this->comment,
169 2
                $this->humanReadableName
170
            );
171 2
        $this->functions[$function->enumeratedName()] = $function;
172 2
        $this->humanReadableName = null;
173 2
        $this->comment = null;
174 2
        $this->profiler->counter(self::ADDED)->bump();
175 2
        $this->totalIterations += $this->iterations;
176 2
    }
177
178
    /**
179
     * @param callable $func
180
     * @param string $name
181
     * @return string
182
     */
183 2
    private function refineName($func, $name): string
184
    {
185 2
        if ($func instanceof \Closure) {
186 2
            $name = 'λ';
187
        }
188 2
        return $name;
189
    }
190
191
    /**
192
     * @param string $comment
193
     * @return Benchmark
194
     */
195 2
    public function withComment(string $comment): self
196
    {
197 2
        $this->comment = $comment;
198 2
        return $this;
199
    }
200
201
    /**
202
     * @param string $name
203
     * @return Benchmark
204
     */
205
    public function useName(string $name): self
206
    {
207
        if (in_array($name, $this->names, true)) {
208
            throw new \InvalidArgumentException(sprintf('Name "%s" is not unique', $name));
209
        }
210
        $this->names[] = $name;
211
        $this->humanReadableName = $name;
212
        return $this;
213
    }
214
215
    /**
216
     * @return string
217
     */
218 1
    public function elapsed(): string
219
    {
220
        return
221 1
            sprintf(
222 1
                'Done in: %s',
223 1
                $this->getTimer()->elapsed()
224
            );
225
    }
226
227
    /**
228
     * {@inheritdoc}
229
     */
230 3
    protected function prepareForReport(): void
231
    {
232 3
        $this->getProfiler()->getReport();
233 3
    }
234
}
235