Passed
Push — master ( ae2541...2d70d9 )
by Alexander
02:38
created

Profiler::getTargets()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 10
cc 4
nc 2
nop 0
crap 4
1
<?php
2
namespace Yiisoft\Profiler;
3
4
use yii\helpers\Yii;
0 ignored issues
show
Bug introduced by
The type yii\helpers\Yii was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
5
6
/**
7
 * Profiler provides profiling support. It stores profiling messages in the memory and sends them to different targets
8
 * according to [[targets]].
9
 *
10
 * A Profiler instance can be accessed via `Yii::getProfiler()`.
11
 *
12
 * For convenience, a set of shortcut methods are provided for profiling via the [[Yii]] class:
13
 *
14
 * - [[Yii::beginProfile()]]
15
 * - [[Yii::endProfile()]]
16
 *
17
 * For more details and usage information on Profiler, see the [guide article on profiling](guide:runtime-profiling)
18
 */
19
class Profiler implements ProfilerInterface
20
{
21
    /**
22
     * @var bool whether to profiler is enabled. Defaults to true.
23
     * You may use this field to disable writing of the profiling messages and thus save the memory usage.
24
     */
25
    public $enabled = true;
26
    /**
27
     * @var array[] complete profiling messages.
28
     * Each message has a following keys:
29
     *
30
     * - token: string, profiling token.
31
     * - category: string, message category.
32
     * - nestedLevel: int, profiling message nested level.
33
     * - beginTime: float, profiling begin timestamp obtained by microtime(true).
34
     * - endTime: float, profiling end timestamp obtained by microtime(true).
35
     * - duration: float, profiling block duration in milliseconds.
36
     * - beginMemory: int, memory usage at the beginning of profile block in bytes, obtained by `memory_get_usage()`.
37
     * - endMemory: int, memory usage at the end of profile block in bytes, obtained by `memory_get_usage()`.
38
     * - memoryDiff: int, a diff between 'endMemory' and 'beginMemory'.
39
     */
40
    public $messages = [];
41
42
    /**
43
     * @var array pending profiling messages, e.g. the ones which have begun but not ended yet.
44
     */
45
    private $_pendingMessages = [];
46
    /**
47
     * @var int current profiling messages nested level.
48
     */
49
    private $_nestedLevel = 0;
50
    /**
51
     * @var array|Target[] the profiling targets. Each array element represents a single [[Target|profiling target]] instance
52
     * or the configuration for creating the profiling target instance.
53
     */
54
    private $_targets = [];
55
    /**
56
     * @var bool whether [[targets]] have been initialized, e.g. ensured to be objects.
57
     */
58
    private $_isTargetsInitialized = false;
59
60
61
    /**
62
     * Initializes the profiler by registering [[flush()]] as a shutdown function.
63
     */
64 5
    public function __construct()
65
    {
66 5
        register_shutdown_function([$this, 'flush']);
67
    }
68
69
    /**
70
     * @return Target[] the profiling targets. Each array element represents a single [[Target|profiling target]] instance.
71
     */
72 1
    public function getTargets(): array
73
    {
74 1
        if (!$this->_isTargetsInitialized) {
75 1
            foreach ($this->_targets as $name => $target) {
76 1
                if (!$target instanceof Target) {
77 1
                    $this->_targets[$name] = Yii::createObject($target);
78
                }
79
            }
80 1
            $this->_isTargetsInitialized = true;
81
        }
82 1
        return $this->_targets;
83
    }
84
85
    /**
86
     * @param array|Target[] $targets the profiling targets. Each array element represents a single [[Target|profiling target]] instance
87
     * or the configuration for creating the profiling target instance.
88
     */
89 1
    public function setTargets(array $targets): void
90
    {
91 1
        $this->_targets = $targets;
92 1
        $this->_isTargetsInitialized = false;
93
    }
94
95
    /**
96
     * Adds extra target to [[targets]].
97
     * @param Target|array $target the log target instance or its DI compatible configuration.
98
     * @param string|null $name array key to be used to store target, if `null` is given target will be append
99
     * to the end of the array by natural integer key.
100
     */
101
    public function addTarget($target, ?string $name = null): void
102
    {
103
        if (!$target instanceof Target) {
104
            $this->_isTargetsInitialized = false;
105
        }
106
        if ($name === null) {
107
            $this->_targets[] = $target;
108
        } else {
109
            $this->_targets[$name] = $target;
110
        }
111
    }
112
113
    /**
114
     * {@inheritdoc}
115
     */
116 3
    public function begin(string $token, array $context = []): void
117
    {
118 3
        if (!$this->enabled) {
119 1
            return;
120
        }
121
122 3
        $category = isset($context['category']) ?: 'application';
123
124 3
        $message = array_merge($context, [
125 3
            'token' => $token,
126 3
            'category' => $category,
127 3
            'nestedLevel' => $this->_nestedLevel,
128 3
            'beginTime' => microtime(true),
129 3
            'beginMemory' => memory_get_usage(),
130
        ]);
131
132 3
        $this->_pendingMessages[$category][$token][] = $message;
133 3
        $this->_nestedLevel++;
134
    }
135
136
    /**
137
     * {@inheritdoc}
138
     */
139 3
    public function end(string $token, array $context = []): void
140
    {
141 3
        if (!$this->enabled) {
142 1
            return;
143
        }
144
145 3
        $category = isset($context['category']) ?: 'application';
146
147 3
        if (empty($this->_pendingMessages[$category][$token])) {
148
            throw new InvalidArgumentException('Unexpected ' . get_called_class() . '::end() call for category "' . $category . '" token "' . $token . '". A matching begin() is not found.');
0 ignored issues
show
Bug introduced by
Are you sure $category of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

148
            throw new InvalidArgumentException('Unexpected ' . get_called_class() . '::end() call for category "' . /** @scrutinizer ignore-type */ $category . '" token "' . $token . '". A matching begin() is not found.');
Loading history...
Bug introduced by
The type Yiisoft\Profiler\InvalidArgumentException was not found. Did you mean InvalidArgumentException? If so, make sure to prefix the type with \.
Loading history...
149
        }
150
151 3
        $message = array_pop($this->_pendingMessages[$category][$token]);
152 3
        if (empty($this->_pendingMessages[$category][$token])) {
153 3
            unset($this->_pendingMessages[$category][$token]);
154 3
            if (empty($this->_pendingMessages[$category])) {
155 3
                unset($this->_pendingMessages[$category]);
156
            }
157
        }
158
159 3
        $message = array_merge(
160 3
            $message,
161
            $context,
162
            [
163 3
                'endTime' => microtime(true),
164 3
                'endMemory' => memory_get_usage(),
165
            ]
166
        );
167
168 3
        $message['duration'] = $message['endTime'] - $message['beginTime'];
169 3
        $message['memoryDiff'] = $message['endMemory'] - $message['beginMemory'];
170
171 3
        $this->messages[] = $message;
172 3
        $this->_nestedLevel--;
173
    }
174
175
    /**
176
     * {@inheritdoc}
177
     */
178 1
    public function flush(): void
179
    {
180 1
        foreach ($this->_pendingMessages as $category => $categoryMessages) {
181
            foreach ($categoryMessages as $token => $messages) {
182
                if (!empty($messages)) {
183
                    Yii::warning('Unclosed profiling entry detected: category "' . $category . '" token "' . $token . '"', __METHOD__);
184
                }
185
            }
186
        }
187 1
        $this->_pendingMessages = [];
188 1
        $this->_nestedLevel = 0;
189
190 1
        if (empty($this->messages)) {
191
            return;
192
        }
193
194 1
        $messages = $this->messages;
195
        // new messages could appear while the existing ones are being handled by targets
196 1
        $this->messages = [];
197
198 1
        $this->dispatch($messages);
199
    }
200
201
    /**
202
     * Dispatches the profiling messages to [[targets]].
203
     * @param array $messages the profiling messages.
204
     */
205
    protected function dispatch(array $messages): void
206
    {
207
        foreach ($this->getTargets() as $target) {
208
            $target->collect($messages);
209
        }
210
    }
211
}
212