Completed
Push — master ( cf4991...5c7bad )
by Alec
03:25
created

Benchmark::elapsed()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 0
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * User: alec
4
 * Date: 29.11.18
5
 * Time: 11:04
6
 */
7
8
namespace AlecRabbit\Tools;
9
10
use AlecRabbit\Rewindable;
11
use AlecRabbit\Tools\Contracts\BenchmarkInterface;
12
use AlecRabbit\Tools\Internal\BenchmarkFunction;
13
use AlecRabbit\Tools\Reports\Contracts\ReportableInterface;
14
use AlecRabbit\Tools\Reports\Traits\Reportable;
15
use AlecRabbit\Tools\Traits\BenchmarkFields;
16
use function AlecRabbit\brackets;
17
use function AlecRabbit\typeOf;
18
19
class Benchmark implements BenchmarkInterface, ReportableInterface
20
{
21
    use BenchmarkFields, Reportable;
22
23
    /** @var int */
24
    private $namingIndex = 0;
25
    /** @var Rewindable */
26
    private $rewindable;
27
    /** @var int */
28
    private $iterations = 0;
29
    /** @var null|string */
30
    private $comment;
31
    /** @var bool */
32
    private $verbose = false;
33
34
    private $exceptionMessages = [];
35
    private $errorState = false;
36
37
    /**
38
     * Benchmark constructor.
39
     * @param int $iterations
40
     */
41 3
    public function __construct(int $iterations = 1000)
42
    {
43 3
        $this->iterations = $iterations;
44 3
        $this->rewindable =
45 3
            new Rewindable(
46
                function (int $iterations, int $i = 1): \Generator {
47 1
                    while ($i <= $iterations) {
48 1
                        yield $i++;
49
                    }
50 3
                },
51 3
                $iterations
52
            );
53 3
        $this->profiler = new Profiler();
54 3
    }
55
56
    /**
57
     * Launch benchmarking
58
     * @param bool $report
59
     */
60 1
    public function run(bool $report = false): void
61
    {
62 1
        if ($this->verbose) {
63
            $this->verboseRun();
64
        } else {
65 1
            $this->nonVerboseRun();
66
        }
67 1
        if ($report) {
68
            echo (string)$this->getReport();
69
            echo PHP_EOL;
70
        }
71 1
    }
72
73
    /**
74
     * Launch benchmarking in verbose mode
75
     */
76
    private function verboseRun(): void
77
    {
78
        echo
79
        sprintf(
80
            'Running benchmarks(%s):',
81
            $this->iterations
82
        );
83
        echo PHP_EOL;
84
        /** @var  BenchmarkFunction $f */
85
        foreach ($this->functions as $name => $f) {
86
            $function = $f->getFunction();
87
            $args = $f->getArgs();
88
            $this->prepareResult($f, $function, $args);
89
            $timer = $this->profiler->timer($name);
90
            $timer->start();
91
            if ($this->errorState) {
92
                $this->errorState = false;
93
                $timer->check();
94
                continue;
95
            }
96
            foreach ($this->rewindable as $iteration) {
97
                /** @noinspection VariableFunctionsUsageInspection */
98
                /** @noinspection DisconnectedForeachInstructionInspection */
99
                \call_user_func($function, ...$args);
100
                $timer->check($iteration);
101
                ++$this->totalIterations;
102
                if (1 === $this->totalIterations % 5000) {
103
                    echo '.';
104
                }
105
            }
106
            $this->profiler->counter()->bump();
107
        }
108
        echo PHP_EOL;
109
        echo PHP_EOL;
110
    }
111
112
    /**
113
     * @param BenchmarkFunction $f
114
     * @param callable $function
115
     * @param array $args
116
     */
117 1
    private function prepareResult(BenchmarkFunction $f, callable $function, array $args): void
118
    {
119 1
        if ($this->withResults) {
120
            try {
121
                /** @noinspection VariableFunctionsUsageInspection */
122
                $f->setResult(\call_user_func($function, ...$args));
123
            } catch (\Throwable $e) {
124
                $this->exceptionMessages[$f->getName()] = $message = $e->getMessage();
125
                $this->errorState = true;
126
                $f->setResult(brackets(typeOf($e)) . ': ' . $message);
127
            }
128
        }
129 1
    }
130
131
    /**
132
     * Launch benchmarking in verbose mode
133
     */
134 1
    private function nonVerboseRun(): void
135
    {
136
        /** @var  BenchmarkFunction $f */
137 1
        foreach ($this->functions as $name => $f) {
138 1
            $function = $f->getFunction();
139 1
            $args = $f->getArgs();
140 1
            $this->prepareResult($f, $function, $args);
141 1
            $timer = $this->profiler->timer($name);
142 1
            $timer->start();
143 1
            if ($this->errorState) {
144
                $this->errorState = false;
145
                $timer->check();
146
                continue;
147
            }
148 1
            foreach ($this->rewindable as $iteration) {
149
                /** @noinspection VariableFunctionsUsageInspection */
150
                /** @noinspection DisconnectedForeachInstructionInspection */
151 1
                \call_user_func($function, ...$args);
152 1
                $timer->check($iteration);
153 1
                ++$this->totalIterations;
154
            }
155 1
            $this->profiler->counter()->bump();
156
        }
157 1
    }
158
159
    /**
160
     * @return Benchmark
161
     */
162
    public function verbose(): self
163
    {
164
        $this->verbose = true;
165
        return $this;
166
    }
167
168
    /**
169
     * @param callable $func
170
     * @param mixed ...$args
171
     */
172 2
    public function addFunction($func, ...$args): void
173
    {
174 2
        if (!\is_callable($func, false, $name)) {
175 1
            throw new \InvalidArgumentException(
176 1
                sprintf(
177 1
                    '\'%s\' is NOT callable. Function must be callable. Type of "%s" provided instead.',
178 1
                    $name,
179 1
                    typeOf($func)
180
                )
181
            );
182
        }
183 1
        $function = new BenchmarkFunction($func, $name, $this->namingIndex++, $args, $this->comment);
184 1
        $this->comment = null;
185
186 1
        $this->functions[$function->getEnumeratedName()] = $function;
187 1
    }
188
189
    /**
190
     * @param string $name
191
     * @return Benchmark
192
     */
193 2
    public function withComment(string $name): self
194
    {
195 2
        $this->comment = $name;
196 2
        return $this;
197
    }
198
199
    /**
200
     * @return Benchmark
201
     */
202
    public function returnResults(): self
203
    {
204
        $this->withResults = true;
205
        return $this;
206
    }
207
208
    /**
209
     * @return string
210
     */
211
    public function elapsed(): string
212
    {
213
        return
214
            sprintf(
215
                'Done in: %s',
216
                $this->getProfiler()->timer()->elapsed()
217
            );
218
    }
219
220
    /**
221
     * {@inheritdoc}
222
     */
223 2
    protected function prepareForReport(): void
224
    {
225 2
        $this->getProfiler()->getReport();
226 2
    }
227
}
228