Elgg  Version master
Profiler.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\Debug;
4 
6 use Elgg\Timer;
7 
13 class Profiler {
14 
15  protected $percentage_format = '%01.2f';
16 
17  protected $duration_format = '%01.6f';
18 
19  protected $minimum_percentage = 0.2;
20 
24  protected $total;
25 
33  public function __invoke(\Elgg\Event $event) {
34  if (!_elgg_services()->config->enable_profiling) {
35  return;
36  }
37 
38  $profiler = new self();
39  $min_percentage = _elgg_services()->config->profiling_minimum_percentage;
40  if ($min_percentage !== null) {
41  $profiler->minimum_percentage = $min_percentage;
42  }
43 
44  $tree = $profiler->buildTree(_elgg_services()->timer);
45  $tree = $profiler->formatTree($tree);
46  $data = [
47  'tree' => $tree,
48  'total' => $tree['duration'] . ' seconds',
49  ];
50 
51  $list = [];
52  $profiler->flattenTree($list, $tree);
53 
54  $root = Paths::project();
55  $list = array_map(function ($period) use ($root) {
56  $period['name'] = str_replace("Closure {$root}", 'Closure ', $period['name']);
57  return "{$period['percentage']}% ({$period['duration']}) {$period['name']}";
58  }, $list);
59 
60  $data['list'] = $list;
61 
62  $html = $event->getValue();
63  $html .= '<script>console.log(' . json_encode($data) . ');</script>';
64 
65  return $html;
66  }
67 
75  protected function buildTree(Timer $timer) {
76  $times = $timer->getTimes();
77 
78  if (!isset($times[Timer::MARKER_END])) {
79  $times[Timer::MARKER_END] = microtime(true);
80  }
81 
82  $begin = $this->findBeginTime($times);
83  $end = $this->findEndTime($times);
84  $this->total = $this->diffMicrotime($begin, $end);
85 
86  return $this->analyzePeriod('', $times);
87  }
88 
98  protected function flattenTree(array &$list = [], array $tree = [], $prefix = '') {
99  $is_root = empty($list);
100 
101  if (isset($tree['periods'])) {
102  foreach ($tree['periods'] as $period) {
103  $this->flattenTree($list, $period, "{$prefix} {$period['name']}");
104  }
105 
106  unset($tree['periods']);
107  }
108 
109  $tree['name'] = trim($prefix);
110  $list[] = $tree;
111 
112  if ($is_root) {
113  usort($list, function ($a, $b) {
114  if ($a['duration'] == $b['duration']) {
115  return 0;
116  }
117 
118  return ($a['duration'] > $b['duration']) ? -1 : 1;
119  });
120  }
121  }
122 
130  protected function formatTree(array $tree) {
131  $tree['duration'] = sprintf($this->duration_format, $tree['duration']);
132  if (isset($tree['percentage'])) {
133  $tree['percentage'] = sprintf($this->percentage_format, $tree['percentage']);
134  }
135 
136  if (isset($tree['periods'])) {
137  $tree['periods'] = array_map([$this, 'formatTree'], $tree['periods']);
138  }
139 
140  return $tree;
141  }
142 
151  protected function analyzePeriod($name, array $times) {
152  $begin = $this->findBeginTime($times);
153  $end = $this->findEndTime($times);
154  if ($begin === false || $end === false) {
155  return false;
156  }
157 
158  $has_own_markers = isset($times[Timer::MARKER_BEGIN], $times[Timer::MARKER_BEGIN]);
159  unset($times[Timer::MARKER_BEGIN], $times[Timer::MARKER_END]);
160 
161  $total = $this->diffMicrotime($begin, $end);
162  $ret = [
163  'name' => $name,
164  'percentage' => 100, // may be overwritten by parent
165  'duration' => $total,
166  ];
167 
168  foreach ($times as $times_key => $period) {
169  $period = $this->analyzePeriod($times_key, $period);
170  if ($period === false) {
171  continue;
172  }
173 
174  $period['percentage'] = 100 * $period['duration'] / $this->total;
175  if ($period['percentage'] < $this->minimum_percentage) {
176  continue;
177  }
178 
179  $ret['periods'][] = $period;
180  }
181 
182  if (isset($ret['periods'])) {
183  if (!$has_own_markers) {
184  // this is an aggregation of different non sequential timers (eg. SQL queries)
185  $ret['duration'] = 0;
186  foreach ($ret['periods'] as $period) {
187  $ret['duration'] += $period['duration'];
188  }
189 
190  $ret['percentage'] = 100 * $ret['duration'] / $this->total;
191  }
192 
193  usort($ret['periods'], function ($a, $b) {
194  if ($a['duration'] == $b['duration']) {
195  return 0;
196  }
197 
198  return ($a['duration'] > $b['duration']) ? -1 : 1;
199  });
200  }
201 
202  return $ret;
203  }
204 
212  protected function findBeginTime(array $times) {
213  if (isset($times[Timer::MARKER_BEGIN])) {
214  return $times[Timer::MARKER_BEGIN];
215  }
216 
217  unset($times[Timer::MARKER_BEGIN], $times[Timer::MARKER_END]);
218  $first = reset($times);
219  if (is_array($first)) {
220  return $this->findBeginTime($first);
221  }
222 
223  return false;
224  }
225 
233  protected function findEndTime(array $times) {
234  if (isset($times[Timer::MARKER_END])) {
235  return $times[Timer::MARKER_END];
236  }
237 
238  unset($times[Timer::MARKER_BEGIN], $times[Timer::MARKER_END]);
239  $last = end($times);
240  if (is_array($last)) {
241  return $this->findEndTime($last);
242  }
243 
244  return false;
245  }
246 
255  protected function diffMicrotime($start, $end) {
256  return (float) $end - $start;
257  }
258 }
__invoke(\Elgg\Event $event)
Append a SCRIPT element to the page output.
Definition: Profiler.php:33
static project()
Get the project root (where composer is installed) path with "/".
Definition: Paths.php:25
getTimes()
Get the tree of recorded start/end times.
Definition: Timer.php:52
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
Capture timing info for profiling.
Definition: Timer.php:10
analyzePeriod($name, array $times)
Analyze a time period.
Definition: Profiler.php:151
$html
A wrapper to render a section of the page shell.
Definition: section.php:9
findBeginTime(array $times)
Get the microtime start time.
Definition: Profiler.php:212
const MARKER_BEGIN
Definition: Timer.php:11
foreach($notification_settings as $purpose=> $prefered_methods) if((bool) elgg_get_config('enable_delayed_email')) $start
if(!$entity instanceof\ElggUser) $data
Definition: attributes.php:13
formatTree(array $tree)
Nicely format the elapsed time values.
Definition: Profiler.php:130
findEndTime(array $times)
Get the microtime end time.
Definition: Profiler.php:233
Analyzes duration of functions, queries, and processes.
Definition: Profiler.php:13
$timer
Definition: pending.php:28
flattenTree(array &$list=[], array $tree=[], $prefix= '')
Turn the tree of times into a sorted list.
Definition: Profiler.php:98
diffMicrotime($start, $end)
Calculate a precise time difference.
Definition: Profiler.php:255
_elgg_services()
Get the global service provider.
Definition: elgglib.php:351
const MARKER_END
Definition: Timer.php:12
$end
buildTree(Timer $timer)
Return a tree of time periods from a Timer.
Definition: Profiler.php:75
Models an event passed to event handlers.
Definition: Event.php:11