Elgg  Version 3.0
Profiler.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg;
4 
6 
12 class Profiler {
13 
14  public $percentage_format = "%01.2f";
15  public $duration_format = "%01.6f";
16  public $minimum_percentage = 0.2;
17 
21  private $total;
22 
29  public function buildTree(Timer $timer) {
30  $times = $timer->getTimes();
31 
32  if (!isset($times[Timer::MARKER_END])) {
33  $times[Timer::MARKER_END] = microtime(true);
34  }
35 
36  $begin = $this->findBeginTime($times);
37  $end = $this->findEndTime($times);
38  $this->total = $this->diffMicrotime($begin, $end);
39 
40  return $this->analyzePeriod('', $times);
41  }
42 
51  public function flattenTree(array &$list = [], array $tree = [], $prefix = '') {
52  $is_root = empty($list);
53 
54  if (isset($tree['periods'])) {
55  foreach ($tree['periods'] as $period) {
56  $this->flattenTree($list, $period, "{$prefix} {$period['name']}");
57  }
58  unset($tree['periods']);
59  }
60  $tree['name'] = trim($prefix);
61  $list[] = $tree;
62 
63  if ($is_root) {
64  usort($list, function ($a, $b) {
65  if ($a['duration'] == $b['duration']) {
66  return 0;
67  }
68  return ($a['duration'] > $b['duration']) ? -1 : 1;
69  });
70  }
71  }
72 
79  public function formatTree(array $tree) {
80  $tree['duration'] = sprintf($this->duration_format, $tree['duration']);
81  if (isset($tree['percentage'])) {
82  $tree['percentage'] = sprintf($this->percentage_format, $tree['percentage']);
83  }
84  if (isset($tree['periods'])) {
85  $tree['periods'] = array_map([$this, 'formatTree'], $tree['periods']);
86  }
87  return $tree;
88  }
89 
100  public static function handlePageOutput($hook, $type, $html, $params) {
101  $profiler = new self();
102  $min_percentage = _elgg_config()->profiling_minimum_percentage;
103  if ($min_percentage !== null) {
104  $profiler->minimum_percentage = $min_percentage;
105  }
106 
107  $tree = $profiler->buildTree(_elgg_services()->timer);
108  $tree = $profiler->formatTree($tree);
109  $data = [
110  'tree' => $tree,
111  'total' => $tree['duration'] . " seconds",
112  ];
113 
114  $list = [];
115  $profiler->flattenTree($list, $tree);
116 
117  $root = Paths::project();
118  $list = array_map(function ($period) use ($root) {
119  $period['name'] = str_replace("Closure $root", "Closure ", $period['name']);
120  return "{$period['percentage']}% ({$period['duration']}) {$period['name']}";
121  }, $list);
122 
123  $data['list'] = $list;
124 
125  $html .= "<script>console.log(" . json_encode($data) . ");</script>";
126 
127  return $html;
128  }
129 
138  private function analyzePeriod($name, array $times) {
139  $begin = $this->findBeginTime($times);
140  $end = $this->findEndTime($times);
141  if ($begin === false || $end === false) {
142  return false;
143  }
144  $has_own_markers = isset($times[Timer::MARKER_BEGIN]) && isset($times[Timer::MARKER_BEGIN]);
145  unset($times[Timer::MARKER_BEGIN], $times[Timer::MARKER_END]);
146 
147  $total = $this->diffMicrotime($begin, $end);
148  $ret = [
149  'name' => $name,
150  'percentage' => 100, // may be overwritten by parent
151  'duration' => $total,
152  ];
153 
154  foreach ($times as $times_key => $period) {
155  $period = $this->analyzePeriod($times_key, $period);
156  if ($period === false) {
157  continue;
158  }
159  $period['percentage'] = 100 * $period['duration'] / $this->total;
160  if ($period['percentage'] < $this->minimum_percentage) {
161  continue;
162  }
163  $ret['periods'][] = $period;
164  }
165 
166  if (isset($ret['periods'])) {
167  if (!$has_own_markers) {
168  // this is an aggregation of different non sequential timers (eg. SQL queries)
169  $ret['duration'] = 0;
170  foreach ($ret['periods'] as $period) {
171  $ret['duration'] += $period['duration'];
172  }
173  $ret['percentage'] = 100 * $ret['duration'] / $this->total;
174  }
175 
176  usort($ret['periods'], function ($a, $b) {
177  if ($a['duration'] == $b['duration']) {
178  return 0;
179  }
180  return ($a['duration'] > $b['duration']) ? -1 : 1;
181  });
182  }
183 
184  return $ret;
185  }
186 
193  private function findBeginTime(array $times) {
194  if (isset($times[Timer::MARKER_BEGIN])) {
195  return $times[Timer::MARKER_BEGIN];
196  }
197  unset($times[Timer::MARKER_BEGIN], $times[Timer::MARKER_END]);
198  $first = reset($times);
199  if (is_array($first)) {
200  return $this->findBeginTime($first);
201  }
202  return false;
203  }
204 
211  private function findEndTime(array $times) {
212  if (isset($times[Timer::MARKER_END])) {
213  return $times[Timer::MARKER_END];
214  }
215  unset($times[Timer::MARKER_BEGIN], $times[Timer::MARKER_END]);
216  $last = end($times);
217  if (is_array($last)) {
218  return $this->findEndTime($last);
219  }
220  return false;
221  }
222 
231  private function diffMicrotime($start, $end) {
232  return (float) $end - $start;
233  }
234 }
if(!$user||!$user->canDelete()) $name
Definition: delete.php:22
static handlePageOutput($hook, $type, $html, $params)
Append a SCRIPT element to the page output.
Definition: Profiler.php:100
$params
Saves global plugin settings.
Definition: save.php:13
getTimes()
Get the tree of recorded start/end times.
Definition: Timer.php:52
Analyzes duration of functions, queries, and processes.
Definition: Profiler.php:12
if(elgg_trigger_plugin_hook('usersettings:save', 'user', $hooks_params, true)) foreach($request->validation() ->all() as $item) $data
Definition: save.php:57
Capture timing info for profiling.
Definition: Timer.php:10
$type
Definition: delete.php:21
$html
Definition: section.php:10
string project
Definition: conf.py:46
Configuration exception.
$timer
Definition: pending.php:29
buildTree(Timer $timer)
Return a tree of time periods from a Timer.
Definition: Profiler.php:29
formatTree(array $tree)
Nicely format the elapsed time values.
Definition: Profiler.php:79
_elgg_config()
Get the Elgg config service.
flattenTree(array &$list=[], array $tree=[], $prefix= '')
Turn the tree of times into a sorted list.
Definition: Profiler.php:51
_elgg_services()
Get the global service provider.
Definition: elgglib.php:1292