Test Failed
Push — master ( 772f18...8c594b )
by Alec
03:05
created

Benchmark::bench()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
ccs 1
cts 1
cp 1
crap 1
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\Factory;
15
use AlecRabbit\Tools\Reports\Traits\Reportable;
16
use AlecRabbit\Tools\Traits\BenchmarkFields;
17
use function AlecRabbit\brackets;
18
use function AlecRabbit\typeOf;
19
20
class Benchmark implements BenchmarkInterface, ReportableInterface
21
{
22
    protected const PG_WIDTH = 60;
23
24
    use BenchmarkFields, Reportable;
25
26
    /** @var int */
27
    private $namingIndex = 0;
28
    /** @var Rewindable */
29
    private $rewindable;
30
    /** @var int */
31
    private $iterations = 0;
32
    /** @var null|string */
33
    private $comment;
34
    /** @var bool */
35
    private $verbose = false;
36
    private $errorState = false;
37
    private $dots = 0;
38
39 3
    /**
40
     * Benchmark constructor.
41 3
     * @param int $iterations
42 3
     */
43 3
    public function __construct(int $iterations = 1000)
44
    {
45 1
        $this->iterations = $iterations;
46 1
        $this->rewindable =
47
            new Rewindable(
48 3
                function (int $iterations, int $i = 1): \Generator {
49 3
                    while ($i <= $iterations) {
50
                        yield $i++;
51 3
                    }
52 3
                },
53
                $iterations
54
            );
55
        $this->profiler = new Profiler();
56
    }
57
58 1
    /**
59
     * Launch benchmarking
60 1
     * @param bool $report
61
     */
62
    public function run(bool $report = false): void
63 1
    {
64
        if ($this->verbose) {
65 1
            echo
66
            sprintf(
67
                'Running benchmarks(%s):',
68
                $this->iterations
69 1
            );
70
            echo PHP_EOL;
71
        }
72
        $this->execute();
73
        echo PHP_EOL;
74
        echo PHP_EOL;
75
76
        if ($report) {
77
            echo (string)$this->getReport();
78
            echo PHP_EOL;
79
        }
80
    }
81
82
    /**
83
     * Launch benchmarking in verbose mode
84
     */
85
    private function execute(): void
86
    {
87
        /** @var  BenchmarkFunction $f */
88
        foreach ($this->functions as $name => $f) {
89
            $function = $f->getFunction();
90
            $args = $f->getArgs();
91
            $this->prepareResult($f, $function, $args);
92
            $timer = $this->profiler->timer($name);
93
            if ($this->errorState) {
94
                $this->errorState = false;
95
                $timer->check();
96
                continue;
97
            }
98
            foreach ($this->rewindable as $iteration) {
99
                $this->bench($timer, $function, $args, $iteration);
100
            }
101
            $this->profiler->counter()->bump();
102
        }
103
    }
104
105
    /**
106
     * @param BenchmarkFunction $f
107
     * @param callable $function
108
     * @param array $args
109
     */
110
    private function prepareResult(BenchmarkFunction $f, callable $function, array $args): void
111
    {
112
        try {
113
            $result = $function(...$args);
114
        } catch (\Throwable $e) {
115 1
            $this->errorState = true;
116
            $result = brackets(typeOf($e)) . ': ' . $e->getMessage();
117
            $this->exceptionMessages[$f->getIndexedName()] = $result;
118
        }
119 1
        $f->setResult($result);
120
    }
121
122
    /**
123
     * @param Timer $timer
124
     * @param callable $function
125 1
     * @param array $args
126 1
     * @param $iteration
127
     */
128
    private function bench(Timer $timer, callable $function, array $args, $iteration): void
129
    {
130
        $timer->start();
131 1
        $function(...$args);
132
        $timer->check($iteration);
133
        $this->progress();
134 1
    }
135 1
136 1
    private function progress(): void
137 1
    {
138 1
        if ($this->verbose && 1 === ++$this->totalIterations % 5000) {
139 1
            echo '.';
140 1
            if (++$this->dots > static::PG_WIDTH) {
141
                echo PHP_EOL;
142
                $this->dots = 0;
143
            }
144
        }
145 1
    }
146
147
    /**
148 1
     * @return Benchmark
149 1
     */
150 1
    public function verbose(): self
151
    {
152 1
        $this->verbose = true;
153
        return $this;
154 1
    }
155
156
    /**
157
     * @return Benchmark
158
     */
159
    public function color(): self
160
    {
161
        Factory::setColour(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
        $theme = Factory::getThemeObject();
211
        return
212
            sprintf(
213
                'Done in: %s',
214
                $theme->yellow($this->getProfiler()->timer()->elapsed())
215
            );
216
    }
217
218
    /**
219
     * {@inheritdoc}
220 2
     */
221
    protected function prepareForReport(): void
222 2
    {
223 2
        $this->getProfiler()->getReport();
224
    }
225
}
226