Passed
Push — develop ( c21a1a...566459 )
by Alec
02:51 queued 10s
created

Benchmark::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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