1
|
|
|
<?php declare(strict_types=1); |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* A page with inheritance of methods of the snapshot |
5
|
|
|
* @maintainer Timur Shagiakhmetov <[email protected]> |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
namespace Badoo\LiveProfilerUI\Pages; |
9
|
|
|
|
10
|
|
|
use Badoo\LiveProfilerUI\DataProviders\Interfaces\MethodInterface; |
11
|
|
|
use Badoo\LiveProfilerUI\DataProviders\Interfaces\MethodDataInterface; |
12
|
|
|
use Badoo\LiveProfilerUI\DataProviders\Interfaces\MethodTreeInterface; |
13
|
|
|
use Badoo\LiveProfilerUI\DataProviders\Interfaces\SnapshotInterface; |
14
|
|
|
use Badoo\LiveProfilerUI\FieldList; |
15
|
|
|
use Badoo\LiveProfilerUI\Interfaces\ViewInterface; |
16
|
|
|
|
17
|
|
|
class ProfileMethodTreePage extends BasePage |
18
|
|
|
{ |
19
|
|
|
const STAT_INTERVAL_WEEK = 7; |
20
|
|
|
const STAT_INTERVAL_MONTH = 31; |
21
|
|
|
const STAT_INTERVAL_HALF_YEAR = 182; |
22
|
|
|
|
23
|
|
|
/** @var string */ |
24
|
|
|
protected static $template_path = 'profile_method_tree'; |
25
|
|
|
/** @var SnapshotInterface */ |
26
|
|
|
protected $Snapshot; |
27
|
|
|
/** @var MethodInterface */ |
28
|
|
|
protected $Method; |
29
|
|
|
/** @var MethodTreeInterface */ |
30
|
|
|
protected $MethodTree; |
31
|
|
|
/** @var MethodDataInterface */ |
32
|
|
|
protected $MethodData; |
33
|
|
|
/** @var FieldList */ |
34
|
|
|
protected $FieldList; |
35
|
|
|
/** @var string */ |
36
|
|
|
protected $calls_count_field = ''; |
37
|
|
|
/** @var array */ |
38
|
|
|
protected static $graph_intervals = [ |
39
|
|
|
'7 days' => self::STAT_INTERVAL_WEEK, |
40
|
|
|
'1 month' => self::STAT_INTERVAL_MONTH, |
41
|
|
|
'6 months' => self::STAT_INTERVAL_HALF_YEAR, |
42
|
|
|
]; |
43
|
|
|
|
44
|
1 |
|
public function __construct( |
45
|
|
|
ViewInterface $View, |
46
|
|
|
SnapshotInterface $Snapshot, |
47
|
|
|
MethodInterface $Method, |
48
|
|
|
MethodTreeInterface $MethodTree, |
49
|
|
|
MethodDataInterface $MethodData, |
50
|
|
|
FieldList $FieldList, |
51
|
|
|
string $calls_count_field |
52
|
|
|
) { |
53
|
1 |
|
$this->View = $View; |
54
|
1 |
|
$this->Snapshot = $Snapshot; |
55
|
1 |
|
$this->Method = $Method; |
56
|
1 |
|
$this->MethodTree = $MethodTree; |
57
|
1 |
|
$this->MethodData = $MethodData; |
58
|
1 |
|
$this->FieldList = $FieldList; |
59
|
1 |
|
$this->calls_count_field = $calls_count_field; |
60
|
1 |
|
} |
61
|
|
|
|
62
|
2 |
|
protected function cleanData() : bool |
63
|
|
|
{ |
64
|
2 |
|
$this->data['app'] = isset($this->data['app']) ? trim($this->data['app']) : ''; |
65
|
2 |
|
$this->data['label'] = isset($this->data['label']) ? trim($this->data['label']) : ''; |
66
|
2 |
|
$this->data['snapshot_id'] = isset($this->data['snapshot_id']) ? (int)$this->data['snapshot_id'] : 0; |
67
|
2 |
|
$this->data['stat_interval'] = isset($this->data['stat_interval']) ? (int)$this->data['stat_interval'] : 0; |
68
|
2 |
|
$this->data['method_id'] = isset($this->data['method_id']) ? (int)$this->data['method_id'] : 0; |
69
|
|
|
|
70
|
2 |
|
if (!$this->data['snapshot_id'] && (!$this->data['app'] || !$this->data['label'])) { |
71
|
1 |
|
throw new \InvalidArgumentException('Empty snapshot_id, app and label'); |
72
|
|
|
} |
73
|
|
|
|
74
|
1 |
|
if (!\in_array($this->data['stat_interval'], self::$graph_intervals, true)) { |
75
|
1 |
|
$this->data['stat_interval'] = self::STAT_INTERVAL_WEEK; |
76
|
|
|
} |
77
|
|
|
|
78
|
1 |
|
return true; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* @return array |
83
|
|
|
* @throws \InvalidArgumentException |
84
|
|
|
*/ |
85
|
4 |
|
public function getTemplateData() : array |
86
|
|
|
{ |
87
|
4 |
|
$link_base = '/profiler/tree-view.phtml?'; |
88
|
4 |
|
$Snapshot = false; |
89
|
4 |
|
if ($this->data['snapshot_id']) { |
90
|
1 |
|
$Snapshot = $this->Snapshot->getOneById($this->data['snapshot_id']); |
91
|
|
|
$link_base .= 'snapshot_id=' . $this->data['snapshot_id']; |
92
|
3 |
|
} elseif ($this->data['app'] && $this->data['label']) { |
93
|
2 |
|
$Snapshot = $this->Snapshot->getOneByAppAndLabel($this->data['app'], $this->data['label']); |
94
|
1 |
|
$link_base .= 'app=' . urlencode($this->data['app']) . '&label=' . urlencode($this->data['label']); |
95
|
|
|
} |
96
|
|
|
|
97
|
2 |
|
if (empty($Snapshot)) { |
98
|
1 |
|
throw new \InvalidArgumentException('Can\'t get snapshot'); |
99
|
|
|
} |
100
|
|
|
|
101
|
1 |
|
if (!\in_array($this->data['stat_interval'], self::$graph_intervals, true)) { |
102
|
1 |
|
$this->data['stat_interval'] = self::STAT_INTERVAL_WEEK; |
103
|
|
|
} |
104
|
|
|
|
105
|
1 |
|
if (!$this->data['method_id']) { |
106
|
1 |
|
$this->data['method_id'] = $this->getMainMethodId(); |
107
|
|
|
} |
108
|
|
|
|
109
|
1 |
|
$dates = \Badoo\LiveProfilerUI\DateGenerator::getDatesArray( |
110
|
1 |
|
$Snapshot->getDate(), |
111
|
1 |
|
$this->data['stat_interval'], |
112
|
1 |
|
$this->data['stat_interval'] |
113
|
|
|
); |
114
|
|
|
|
115
|
1 |
|
$date_to_snapshot_map = $this->Snapshot->getSnapshotIdsByDates( |
116
|
1 |
|
$dates, |
117
|
1 |
|
$Snapshot->getApp(), |
118
|
1 |
|
$Snapshot->getLabel() |
119
|
|
|
); |
120
|
|
|
|
121
|
|
|
$view_data = [ |
122
|
1 |
|
'snapshot' => $Snapshot, |
123
|
1 |
|
'method_dates' => $dates, |
124
|
1 |
|
'stat_intervals' => $this->getIntervalsFormData($link_base), |
125
|
|
|
]; |
126
|
|
|
|
127
|
|
|
$common_block_data = [ |
128
|
1 |
|
'link_base' => $link_base, |
129
|
1 |
|
'fields' => $this->FieldList->getAllFieldsWithVariations(), |
130
|
1 |
|
'field_descriptions' => $this->FieldList->getFieldDescriptions(), |
131
|
1 |
|
'stat_interval' => $this->data['stat_interval'], |
132
|
|
|
]; |
133
|
|
|
|
134
|
1 |
|
$method_data = $this->getMethodDataWithHistory($date_to_snapshot_map, $this->data['method_id']); |
135
|
1 |
|
if ($method_data) { |
|
|
|
|
136
|
|
|
/** @var \Badoo\LiveProfilerUI\Entity\MethodData $MainMethod */ |
137
|
1 |
|
$MainMethod = current($method_data); |
138
|
1 |
|
$view_data['available_graphs'] = $this->getGraphsData($MainMethod); |
139
|
1 |
|
$view_data['method_name'] = $MainMethod->getMethodName(); |
140
|
1 |
|
$view_data['method_data'] = $this->View->fetchFile( |
141
|
1 |
|
'profiler_result_view_part', |
142
|
1 |
|
$common_block_data + ['data' => $method_data, 'hide_lines_column' => true], |
143
|
1 |
|
false |
144
|
|
|
); |
145
|
|
|
} |
146
|
|
|
|
147
|
1 |
|
$parents = $this->getMethodParentsWithHistory($date_to_snapshot_map, $this->data['method_id']); |
148
|
1 |
|
if (!empty($parents)) { |
149
|
1 |
|
$this->sortList($parents); |
150
|
1 |
|
$view_data['parents'] = $this->View->fetchFile( |
151
|
1 |
|
'profiler_result_view_part', |
152
|
1 |
|
$common_block_data + ['data' => $parents], |
153
|
1 |
|
false |
154
|
|
|
); |
155
|
|
|
} |
156
|
|
|
|
157
|
1 |
|
$children = $this->getMethodChildrenWithHistory($date_to_snapshot_map, $this->data['method_id']); |
158
|
1 |
|
if ($children) { |
|
|
|
|
159
|
1 |
|
$this->sortList($children); |
160
|
1 |
|
$view_data['children'] = $this->View->fetchFile( |
161
|
1 |
|
'profiler_result_view_part', |
162
|
1 |
|
$common_block_data + ['data' => $children, 'hide_lines_column' => true], |
163
|
1 |
|
false |
164
|
|
|
); |
165
|
|
|
} |
166
|
|
|
|
167
|
1 |
|
$view_data['js_graph_data_all'] = array_merge($method_data, $children); |
168
|
|
|
|
169
|
1 |
|
return $view_data; |
170
|
|
|
} |
171
|
|
|
|
172
|
1 |
|
protected function sortList(array &$records) |
173
|
|
|
{ |
174
|
1 |
|
$sort_field = (string)current($this->FieldList->getFields()); |
175
|
1 |
|
usort($records, function ($Element1, $Element2) use ($sort_field) : int { |
176
|
|
|
/** @var \Badoo\LiveProfilerUI\Entity\MethodData $Element1 */ |
177
|
|
|
/** @var \Badoo\LiveProfilerUI\Entity\MethodData $Element2 */ |
178
|
|
|
return $Element2->getValue($sort_field) > $Element1->getValue($sort_field) ? 1 : -1; |
179
|
1 |
|
}); |
180
|
1 |
|
} |
181
|
|
|
|
182
|
1 |
|
protected function getMainMethodId() : int |
183
|
|
|
{ |
184
|
1 |
|
$methods = $this->Method->findByName('main()', true); |
185
|
1 |
|
if (!empty($methods)) { |
186
|
1 |
|
return array_keys($methods)[0]; |
187
|
|
|
} |
188
|
|
|
return 0; |
189
|
|
|
} |
190
|
|
|
|
191
|
1 |
|
protected function getGraphsData(\Badoo\LiveProfilerUI\Entity\MethodData $MainMethod) : array |
192
|
|
|
{ |
193
|
1 |
|
$data = []; |
194
|
1 |
|
foreach (array_keys($MainMethod->getHistoryData()) as $field) { |
195
|
1 |
|
if (strpos($field, 'mem') !== false) { |
196
|
1 |
|
$type = 'memory'; |
197
|
1 |
|
} elseif (strpos($field, $this->calls_count_field) !== false) { |
198
|
1 |
|
$type = 'times'; |
199
|
|
|
} else { |
200
|
1 |
|
$type = 'time'; |
201
|
|
|
} |
202
|
1 |
|
$data[$field] = [ |
203
|
1 |
|
'type' => $type, |
204
|
1 |
|
'label' => $field, |
205
|
1 |
|
'graph_label' => $field . ' self + children calls graph' |
206
|
|
|
]; |
207
|
|
|
} |
208
|
|
|
|
209
|
1 |
|
return $data; |
210
|
|
|
} |
211
|
|
|
|
212
|
1 |
|
protected function getIntervalsFormData(string $link_base) : array |
213
|
|
|
{ |
214
|
1 |
|
$data = []; |
215
|
1 |
|
foreach (self::$graph_intervals as $name => $value) { |
216
|
1 |
|
$data[] = [ |
217
|
1 |
|
'name' => $name, |
218
|
1 |
|
'link' => $link_base . "&method_id={$this->data['method_id']}&stat_interval=$value", |
219
|
1 |
|
'selected' => $value === $this->data['stat_interval'], |
220
|
|
|
]; |
221
|
|
|
} |
222
|
1 |
|
return $data; |
223
|
|
|
} |
224
|
|
|
|
225
|
3 |
|
protected function getMethodDataWithHistory(array $dates_to_snapshots, int $method_id) : array |
226
|
|
|
{ |
227
|
3 |
|
$snapshot_ids = array_filter(array_values($dates_to_snapshots)); |
228
|
3 |
|
if (empty($snapshot_ids)) { |
229
|
1 |
|
return []; |
230
|
|
|
} |
231
|
|
|
|
232
|
2 |
|
$MethodData = $this->MethodData->getDataByMethodIdsAndSnapshotIds($snapshot_ids, [$method_id]); |
233
|
2 |
|
$MethodData = $this->Method->injectMethodNames($MethodData); |
234
|
|
|
|
235
|
2 |
|
return $this->getProfilerRecordsWithHistory($MethodData, $dates_to_snapshots); |
236
|
|
|
} |
237
|
|
|
|
238
|
3 |
|
protected function getMethodParentsWithHistory(array $dates_to_snapshots, int $method_id) : array |
239
|
|
|
{ |
240
|
3 |
|
$snapshot_ids = array_filter(array_values($dates_to_snapshots)); |
241
|
3 |
|
if (empty($snapshot_ids)) { |
242
|
1 |
|
return []; |
243
|
|
|
} |
244
|
|
|
|
245
|
2 |
|
$MethodTree = $this->MethodTree->getDataByMethodIdsAndSnapshotIds($snapshot_ids, [$method_id]); |
246
|
|
|
|
247
|
2 |
|
foreach ($MethodTree as &$Item) { |
248
|
1 |
|
$Item->setMethodId($Item->getParentId()); |
249
|
|
|
} |
250
|
2 |
|
unset($Item); |
251
|
|
|
|
252
|
2 |
|
$MethodTree = $this->Method->injectMethodNames($MethodTree); |
253
|
|
|
|
254
|
2 |
|
return $this->getProfilerRecordsWithHistory($MethodTree, $dates_to_snapshots); |
255
|
|
|
} |
256
|
|
|
|
257
|
3 |
|
protected function getMethodChildrenWithHistory(array $dates_to_snapshots, int $method_id) : array |
258
|
|
|
{ |
259
|
3 |
|
$snapshot_ids = array_filter(array_values($dates_to_snapshots)); |
260
|
3 |
|
if (empty($snapshot_ids)) { |
261
|
1 |
|
return []; |
262
|
|
|
} |
263
|
|
|
|
264
|
2 |
|
$MethodTree = $this->MethodTree->getDataByParentIdsAndSnapshotIds($snapshot_ids, [$method_id]); |
265
|
2 |
|
$MethodTree = $this->Method->injectMethodNames($MethodTree); |
266
|
|
|
|
267
|
2 |
|
return $this->getProfilerRecordsWithHistory($MethodTree, $dates_to_snapshots); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* @param \Badoo\LiveProfilerUI\Entity\MethodData[] $result |
272
|
|
|
* @param array $dates_to_snapshots |
273
|
|
|
* @return array |
274
|
|
|
*/ |
275
|
1 |
|
protected function getProfilerRecordsWithHistory(array $result, array $dates_to_snapshots) : array |
276
|
|
|
{ |
277
|
1 |
|
$last_snapshot_id = end($dates_to_snapshots); |
278
|
1 |
|
if (!$last_snapshot_id) { |
279
|
|
|
return []; |
280
|
|
|
} |
281
|
|
|
|
282
|
1 |
|
$history = []; |
283
|
1 |
|
foreach ($result as $Row) { |
284
|
1 |
|
$history[$Row->getMethodId()][$Row->getSnapshotId()] = $Row; |
285
|
|
|
} |
286
|
|
|
|
287
|
1 |
|
$all_fields = $this->FieldList->getAllFieldsWithVariations(); |
288
|
|
|
|
289
|
1 |
|
$result = []; |
290
|
1 |
|
foreach ($history as $method_rows) { |
291
|
|
|
// the method was not called in the last snapshot, so it will not displayed |
292
|
1 |
|
if (!isset($method_rows[$last_snapshot_id])) { |
293
|
|
|
continue; |
294
|
|
|
} |
295
|
|
|
|
296
|
|
|
/** @var \Badoo\LiveProfilerUI\Entity\MethodData $Row */ |
297
|
1 |
|
$Row = $method_rows[$last_snapshot_id]; |
298
|
|
|
|
299
|
1 |
|
$data = []; |
300
|
1 |
|
foreach ($all_fields as $field) { |
301
|
1 |
|
$data[$field] = []; |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
// extract data from previous snapshots |
305
|
1 |
|
foreach ($dates_to_snapshots as $snapshot_id) { |
306
|
1 |
|
if ($snapshot_id && isset($method_rows[$snapshot_id])) { |
307
|
|
|
/** @var \Badoo\LiveProfilerUI\Entity\MethodData $PreviousRow */ |
308
|
1 |
|
$PreviousRow = $method_rows[$snapshot_id]; |
309
|
1 |
|
$values = $PreviousRow->getValues(); |
310
|
|
|
|
311
|
1 |
|
foreach ($all_fields as $field) { |
312
|
1 |
|
$data[$field][] = ['val' => $values[$field]]; |
313
|
|
|
} |
314
|
|
|
} else { |
315
|
|
|
foreach ($all_fields as $field) { |
316
|
1 |
|
$data[$field][] = ['val' => 0]; |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
} |
320
|
|
|
|
321
|
1 |
|
$Row->setHistoryData($data); |
322
|
|
|
|
323
|
1 |
|
$result[] = $Row; |
324
|
|
|
} |
325
|
|
|
|
326
|
1 |
|
return $result; |
327
|
|
|
} |
328
|
|
|
} |
329
|
|
|
|
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.