Completed
Push — master ( 6a7da5...1827cf )
by Alec
08:13 queued 02:32
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\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
    /** @var int */
19
    private $functionIndex = 1;
20
    /** @var Rewindable */
21
    private $rewindable;
22
    /** @var int */
23
    private $iterations;
24
    /** @var null|string */
25
    private $comment;
26
    /** @var array */
27
    private $names;
28
    /** @var string|null */
29
    private $humanReadableName;
30
    /** @var int */
31
    private $totalIterations = 0;
32
    /** @var null|callable */
33
    private $onStart;
34
    /** @var null|callable */
35
    private $onAdvance;
36
    /** @var null|callable */
37
    private $onFinish;
38
    /** @var int */
39
    private $advanceStep = 0;
40
    /** @var int */
41
    protected $advanceSteps = 100;
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(): Benchmark
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
        return $this;
99
    }
100
101
    /**
102
     * Benchmarking
103
     */
104 2
    private function execute(): void
105
    {
106
        /** @var  BenchmarkFunction $f */
107 2
        foreach ($this->functions as $f) {
108 2
            if (!$f->execute()) {
109 1
                $this->totalIterations -= $this->iterations;
110 1
                continue;
111
            }
112 2
            $this->advanceStep = (int)($this->totalIterations / $this->advanceSteps);
113 2
            $this->bench($f);
114 2
            $this->profiler->counter(self::BENCHMARKED)->bump();
115
        }
116 2
    }
117
118
    /**
119
     * @param BenchmarkFunction $f
120
     */
121 2
    private function bench(BenchmarkFunction $f): void
122
    {
123 2
        $timer = $f->getTimer();
124 2
        $function = $f->getCallable();
125 2
        $args = $f->getArgs();
126 2
        foreach ($this->rewindable as $iteration) {
127 2
            $start = microtime(true);
128
            /** @noinspection DisconnectedForeachInstructionInspection */
129 2
            $function(...$args);
130 2
            $stop = microtime(true);
131 2
            $timer->bounds($start, $stop, $iteration);
132
            /** @noinspection DisconnectedForeachInstructionInspection */
133 2
            $this->progress();
134
        }
135 2
    }
136
137 2
    private function progress(): void
138
    {
139 2
        if ($this->onAdvance && 0 === ++$this->doneIterations % $this->advanceStep) {
140
            ($this->onAdvance)();
141
        }
142 2
    }
143
144
    /**
145
     * @param callable|null $onStart
146
     * @param callable|null $onAdvance
147
     * @param callable|null $onFinish
148
     * @return Benchmark
149
     */
150
    public function progressBar(
151
        callable $onStart = null,
152
        callable $onAdvance = null,
153
        callable $onFinish = null
154
    ): Benchmark {
155
        $this->onStart = $onStart;
156
        $this->onAdvance = $onAdvance;
157
        $this->onFinish = $onFinish;
158
        return $this;
159
    }
160
161
    /**
162
     * @param mixed $func
163
     * @param mixed ...$args
164
     */
165 3
    public function addFunction($func, ...$args): void
166
    {
167 3
        if (!\is_callable($func, false, $name)) {
168 1
            throw new \InvalidArgumentException(
169 1
                sprintf(
170 1
                    '\'%s\' is NOT callable. Function must be callable. Type of "%s" provided instead.',
171 1
                    $name,
172 1
                    typeOf($func)
173
                )
174
            );
175
        }
176
        $function =
177 2
            new BenchmarkFunction(
178 2
                $func,
179 2
                $this->refineName($func, $name),
180 2
                $this->functionIndex++,
181 2
                $args,
182 2
                $this->comment,
183 2
                $this->humanReadableName
184
            );
185 2
        $this->functions[$function->enumeratedName()] = $function;
186 2
        $this->humanReadableName = null;
187 2
        $this->comment = null;
188 2
        $this->profiler->counter(self::ADDED)->bump();
189 2
        $this->totalIterations += $this->iterations;
190 2
    }
191
192
    /**
193
     * @param callable $func
194
     * @param string $name
195
     * @return string
196
     */
197 2
    private function refineName($func, $name): string
198
    {
199 2
        if ($func instanceof \Closure) {
200 2
            $name = 'λ';
201
        }
202 2
        return $name;
203
    }
204
205
    /**
206
     * @param string $comment
207
     * @return Benchmark
208
     */
209 2
    public function withComment(string $comment): self
210
    {
211 2
        $this->comment = $comment;
212 2
        return $this;
213
    }
214
215
    /**
216
     * @param string $name
217
     * @return Benchmark
218
     */
219
    public function useName(string $name): self
220
    {
221
        if (in_array($name, $this->names, true)) {
222
            throw new \InvalidArgumentException(sprintf('Name "%s" is not unique', $name));
223
        }
224
        $this->names[] = $name;
225
        $this->humanReadableName = $name;
226
        return $this;
227
    }
228
229
    /**
230
     * @return string
231
     */
232 1
    public function elapsed(): string
233
    {
234
        return
235 1
            sprintf(
236 1
                'Done in: %s',
237 1
                $this->getTimer()->elapsed()
238
            );
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244 3
    protected function prepareForReport(): void
245
    {
246 3
        $this->getProfiler()->getReport();
247 3
    }
248
}
249