Passed
Push — develop ( d84c4e...84aa32 )
by Alec
05:02 queued 01:51
created

Benchmark::reset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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