Completed
Push — master ( 8ad0e5...05ab4d )
by Alec
04:24
created

Benchmark::reset()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 2

Importance

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