PluginProbe
Black Bar / trunk
Black Bar vtrunk
trunk 1.0.0 1.1.0 1.2.0 2.0.0 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.2.0 2.2.1 3.0.0 3.1.0 4.0.0 4.0.1 4.0.2 4.0.3 4.1.0 4.1.1 4.1.2 4.1.3 4.1.4 4.2.0
blackbar / plugin / Modules / Hooks.php

Hooks.php in Black Bar trunk, at plugin/Modules/Hooks.php

221 lines 6.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace GeminiLabs\BlackBar\Modules;
4
5 class Hooks extends Module
6 {
7 /**
8 * @var array
9 */
10 protected $hooks = [];
11 /**
12 * @var int
13 */
14 protected $totalHooks = 0;
15 /**
16 * Total elapsed time in nanoseconds.
17 * @var int
18 */
19 protected $totalTime = 0;
20
21 public function entries(): array
22 {
23 if (!$this->hasEntries()) {
24 return [];
25 }
26 wp_raise_memory_limit('admin');
27 array_walk($this->entries, function (&$data) {
28 $total = $this->totalTimeForHook($data);
29 $perCall = (int) round($total / $data['count']);
30 $data['per_call'] = $this->formatTime($perCall);
31 $data['total'] = $total;
32 $data['total_formatted'] = $this->formatTime($total);
33 });
34 $entries = $this->entries;
35 $executionOrder = array_keys($entries);
36 uasort($entries, [$this, 'sortByTime']);
37 $hooks = $entries;
38 if (!apply_filters('blackbar/hooks/all', false)) {
39 $hooks = array_slice($entries, 0, 50); // Keep the 50 slowest hooks
40 }
41 $this->totalHooks = array_sum(wp_list_pluck($this->entries, 'count'));
42 $this->totalTime = array_sum(wp_list_pluck($this->entries, 'total'));
43 $order = array_intersect($executionOrder, array_keys($hooks));
44 foreach ($order as $index => $hook) {
45 $hooks[$hook]['index'] = $index;
46 }
47 return $hooks;
48 }
49
50 public function highlighted(): array
51 {
52 return [
53 'admin_bar_init',
54 'admin_bar_menu',
55 'admin_enqueue_scripts',
56 'admin_footer',
57 'admin_head',
58 'admin_init',
59 'admin_menu',
60 'admin_menu',
61 'admin_notices',
62 'admin_print_footer_scripts',
63 'admin_print_scripts',
64 'admin_print_styles',
65 'after_setup_theme',
66 'all_admin_notices',
67 'current_screen',
68 'get_header',
69 'init',
70 'load_textdomain',
71 'muplugins_loaded',
72 'plugin_loaded',
73 'plugins_loaded',
74 'pre_get_posts',
75 'setup_theme',
76 'wp',
77 'wp_default_scripts',
78 'wp_default_styles',
79 'wp_enqueue_scripts',
80 'wp_footer',
81 'wp_head',
82 'wp_loaded',
83 'wp_print_footer_scripts',
84 'wp_print_scripts',
85 'wp_print_styles',
86 'wp_print_scripts',
87 ];
88 }
89
90 public function icon(): string
91 {
92 return 'dashicons-clock';
93 }
94
95 public function info(): string
96 {
97 $this->entries(); // calculate the totalTime
98 return $this->formatTime($this->totalTime);
99 }
100
101 public function label(): string
102 {
103 return __('Hooks', 'blackbar');
104 }
105
106 public function startTimer(): void
107 {
108 if (class_exists('Debug_Bar_Slow_Actions')) {
109 return;
110 }
111 $hook = current_filter();
112 if (!isset($this->entries[$hook])) {
113 $callbacks = $this->callbacksForHook($hook);
114 if (empty($callbacks)) {
115 return; // We skipped Blackbar callbacks
116 }
117 $cb = array_values($callbacks);
118 $num = count($cb);
119 $total = 0;
120 for ($i = 0; $i < $num; $i++) {
121 $total += count($cb[$i]);
122 }
123 $this->entries[$hook] = [
124 'callbacks' => $callbacks,
125 'callbacks_count' => $total,
126 'count' => 0,
127 'stack' => [],
128 'time' => [],
129 ];
130 add_action($hook, [$this, 'stopTimer'], 9999); // @phpstan-ignore-line
131 }
132 ++$this->entries[$hook]['count'];
133 array_push($this->entries[$hook]['stack'], ['start' => (int) hrtime(true)]);
134 }
135
136 /**
137 * @param mixed $filteredValue
138 * @return mixed
139 */
140 public function stopTimer($filteredValue = null)
141 {
142 $time = array_pop($this->entries[current_filter()]['stack']);
143 $time['stop'] = (int) hrtime(true);
144 array_push($this->entries[current_filter()]['time'], $time);
145 return $filteredValue; // In case this was a filter.
146 }
147
148 /**
149 * @param mixed $function
150 */
151 protected function callbackFunction($function): string
152 {
153 if (is_array($function)) {
154 list($object, $method) = $function;
155 if (is_object($object)) {
156 $object = get_class($object);
157 }
158 if (str_starts_with($object, 'GeminiLabs\BlackBar')) {
159 return ''; // skip Blackbar callbacks
160 }
161 return rtrim(sprintf('%s::%s', $object, $method), ':');
162 }
163 if (is_a($function, 'Closure')) {
164 $ref = new \ReflectionFunction($function);
165 $vars = $ref->getStaticVariables();
166 if (isset($vars['callback'])
167 && is_array($vars['callback'])
168 && 2 === count($vars['callback'])
169 && is_string($vars['callback'][1])
170 ) {
171 list($object, $method) = $vars['callback'];
172 if (is_object($object)) {
173 $object = get_class($object);
174 }
175 return rtrim(sprintf('%s::%s', $object, $method), ':');
176 }
177 }
178 if (is_object($function)) {
179 return get_class($function);
180 }
181 return (string) $function;
182 }
183
184 protected function callbacksForHook(string $hook): array
185 {
186 global $wp_filter;
187 $data = $wp_filter[$hook] ?? [];
188 $results = [];
189 foreach ($data as $priority => $callbacks) {
190 $results[$priority] = $results[$priority] ?? [];
191 foreach ($callbacks as $callback) {
192 $function = $this->callbackFunction($callback['function']);
193 if (!empty($function)) {
194 $results[$priority][] = $function;
195 }
196 }
197 }
198 return $results;
199 }
200
201 protected function sortByTime(array $a, array $b): int
202 {
203 if ($a['total'] !== $b['total']) {
204 return ($a['total'] > $b['total']) ? -1 : 1;
205 }
206 return 0;
207 }
208
209 /**
210 * Total elapsed time in nanoseconds.
211 */
212 protected function totalTimeForHook(array $data): int
213 {
214 $total = 0;
215 foreach ($data['time'] as $time) {
216 $total += ($time['stop'] - $time['start']);
217 }
218 return $total;
219 }
220 }
221