Passed
Push — master ( 5c7bad...ea47a1 )
by Alec
03:06
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
    private $errorState = false;
34
35
    /**
36
     * Benchmark constructor.
37
     * @param int $iterations
38
     */
39 3
    public function __construct(int $iterations = 1000)
40
    {
41 3
        $this->iterations = $iterations;
42 3
        $this->rewindable =
43 3
            new Rewindable(
44
                function (int $iterations, int $i = 1): \Generator {
45 1
                    while ($i <= $iterations) {
46 1
                        yield $i++;
47
                    }
48 3
                },
49 3
                $iterations
50
            );
51 3
        $this->profiler = new Profiler();
52 3
    }
53
54
    /**
55
     * Launch benchmarking
56
     * @param bool $report
57
     */
58 1
    public function run(bool $report = false): void
59
    {
60 1
        if ($this->verbose) {
61
            $this->verboseRun();
62
        } else {
63 1
            $this->nonVerboseRun();
64
        }
65 1
        if ($report) {
66
            echo (string)$this->getReport();
67
            echo PHP_EOL;
68
        }
69 1
    }
70
71
    /**
72
     * Launch benchmarking in verbose mode
73
     */
74
    private function verboseRun(): void
75
    {
76
        echo
77
        sprintf(
78
            'Running benchmarks(%s):',
79
            $this->iterations
80
        );
81
        echo PHP_EOL;
82
        /** @var  BenchmarkFunction $f */
83
        foreach ($this->functions as $name => $f) {
84
            $function = $f->getFunction();
85
            $args = $f->getArgs();
86
            $this->prepareResult($f, $function, $args);
87
            $timer = $this->profiler->timer($name);
88
            $timer->start();
89
            if ($this->errorState) {
90
                $this->errorState = false;
91
                $timer->check();
92
                continue;
93
            }
94
            foreach ($this->rewindable as $iteration) {
95
                /** @noinspection VariableFunctionsUsageInspection */
96
                /** @noinspection DisconnectedForeachInstructionInspection */
97
                \call_user_func($function, ...$args);
98
                $timer->check($iteration);
99
                ++$this->totalIterations;
100
                if (1 === $this->totalIterations % 5000) {
101
                    echo '.';
102
                }
103
            }
104
            $this->profiler->counter()->bump();
105
        }
106
        echo PHP_EOL;
107
        echo PHP_EOL;
108
    }
109
110
    /**
111
     * @param BenchmarkFunction $f
112
     * @param callable $function
113
     * @param array $args
114
     */
115 1
    private function prepareResult(BenchmarkFunction $f, callable $function, array $args): void
116
    {
117
        try {
118
            /** @noinspection VariableFunctionsUsageInspection */
119 1
            $result = \call_user_func($function, ...$args);
120
        } catch (\Throwable $e) {
121
            $this->errorState = true;
122
            $result = brackets(typeOf($e)) . ': ' . $e->getMessage();
123
            $this->exceptionMessages[$f->getIndexedName()] = $result;
124
        }
125 1
        $f->setResult($result);
126 1
    }
127
128
    /**
129
     * Launch benchmarking in silent mode
130
     */
131 1
    private function nonVerboseRun(): void
132
    {
133
        /** @var  BenchmarkFunction $f */
134 1
        foreach ($this->functions as $name => $f) {
135 1
            $function = $f->getFunction();
136 1
            $args = $f->getArgs();
137 1
            $this->prepareResult($f, $function, $args);
138 1
            $timer = $this->profiler->timer($name);
139 1
            $timer->start();
140 1
            if ($this->errorState) {
141
                $this->errorState = false;
142
                $timer->check();
143
                continue;
144
            }
145 1
            foreach ($this->rewindable as $iteration) {
146
                /** @noinspection VariableFunctionsUsageInspection */
147
                /** @noinspection DisconnectedForeachInstructionInspection */
148 1
                \call_user_func($function, ...$args);
149 1
                $timer->check($iteration);
150 1
                ++$this->totalIterations;
151
            }
152 1
            $this->profiler->counter()->bump();
153
        }
154 1
    }
155
156
    /**
157
     * @return Benchmark
158
     */
159
    public function verbose(): self
160
    {
161
        $this->verbose = true;
162
        return $this;
163
    }
164
165
    /**
166
     * @param callable $func
167
     * @param mixed ...$args
168
     */
169 2
    public function addFunction($func, ...$args): void
170
    {
171 2
        if (!\is_callable($func, false, $name)) {
172 1
            throw new \InvalidArgumentException(
173 1
                sprintf(
174 1
                    '\'%s\' is NOT callable. Function must be callable. Type of "%s" provided instead.',
175 1
                    $name,
176 1
                    typeOf($func)
177
                )
178
            );
179
        }
180 1
        $function = new BenchmarkFunction($func, $name, $this->namingIndex++, $args, $this->comment);
181 1
        $this->comment = null;
182
183 1
        $this->functions[$function->getEnumeratedName()] = $function;
184 1
    }
185
186
    /**
187
     * @param string $name
188
     * @return Benchmark
189
     */
190 2
    public function withComment(string $name): self
191
    {
192 2
        $this->comment = $name;
193 2
        return $this;
194
    }
195
196
    /**
197
     * @return Benchmark
198
     */
199
    public function returnResults(): self
200
    {
201
        $this->withResults = true;
202
        return $this;
203
    }
204
205
    /**
206
     * @return string
207
     */
208
    public function elapsed(): string
209
    {
210
        return
211
            sprintf(
212
                'Done in: %s',
213
                $this->getProfiler()->timer()->elapsed()
214
            );
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220 2
    protected function prepareForReport(): void
221
    {
222 2
        $this->getProfiler()->getReport();
223 2
    }
224
}
225