Cancelled
Push — master ( 706ba7...b281b4 )
by Paul
02:38
created

Hooks   A

Complexity

Total Complexity 25

Size/Duplication

Total Lines 147
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 25
eloc 74
c 1
b 0
f 0
dl 0
loc 147
rs 10

9 Methods

Rating   Name   Duplication   Size   Complexity  
A label() 0 3 1
A info() 0 4 1
A startTimer() 0 22 4
A stopTimer() 0 6 1
A entries() 0 26 4
A callbacksForHook() 0 15 4
A totalTimeForHook() 0 7 2
A callbackFunction() 0 16 5
A sortByTime() 0 6 3
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
        if (!empty($this->hooks)) {
27
            return $this->hooks;
28
        }
29
        array_walk($this->entries, function (&$data) {
30
            $total = $this->totalTimeForHook($data);
31
            $perCall = (int) round($total / $data['count']);
32
            $data['per_call'] = $this->formatTime($perCall);
33
            $data['total'] = $total;
34
            $data['total_formatted'] = $this->formatTime($total);
35
        });
36
        $entries = $this->entries;
37
        $executionOrder = array_keys($entries);
38
        uasort($entries, [$this, 'sortByTime']);
39
        $this->hooks = array_slice($entries, 0, 50); // Keep the 50 slowest hooks
40
        $this->totalHooks = array_sum(wp_list_pluck($this->entries, 'count'));
41
        $this->totalTime = array_sum(wp_list_pluck($this->entries, 'total'));
42
        $order = array_intersect($executionOrder, array_keys($this->hooks));
43
        foreach ($order as $index => $hook) {
44
            $this->hooks[$hook]['index'] = $index;
45
        }
46
        return $this->hooks;
47
    }
48
49
    public function info(): string
50
    {
51
        $this->entries(); // calculate the totalTime
52
        return $this->formatTime($this->totalTime);
53
    }
54
55
    public function label(): string
56
    {
57
        return __('Hooks', 'blackbar');
58
    }
59
60
    public function startTimer(): void
61
    {
62
        if (class_exists('Debug_Bar_Slow_Actions')) {
63
            return;
64
        }
65
        $hook = current_filter();
66
        if (!isset($this->entries[$hook])) {
67
            $callbacks = $this->callbacksForHook($hook);
68
            if (empty($callbacks)) {
69
                return; // We skipped Blackbar callbacks
70
            }
71
            $this->entries[$hook] = [
72
                'callbacks' => $callbacks,
73
                'callbacks_count' => count(array_merge(...$callbacks)),
74
                'count' => 0,
75
                'stack' => [],
76
                'time' => [],
77
            ];
78
            add_action($hook, [$this, 'stopTimer'], 9999); // @phpstan-ignore-line
79
        }
80
        ++$this->entries[$hook]['count'];
81
        array_push($this->entries[$hook]['stack'], ['start' => (int) hrtime(true)]);
82
    }
83
84
    /**
85
     * @param mixed $filteredValue
86
     * @return mixed
87
     */
88
    public function stopTimer($filteredValue = null)
89
    {
90
        $time = array_pop($this->entries[current_filter()]['stack']);
91
        $time['stop'] = (int) hrtime(true);
92
        array_push($this->entries[current_filter()]['time'], $time);
93
        return $filteredValue; // In case this was a filter.
94
    }
95
96
    /**
97
     * @param mixed $function
98
     */
99
    protected function callbackFunction($function): string
100
    {
101
        if (is_array($function)) {
102
            list($object, $method) = $function;
103
            if (is_object($object)) {
104
                $object = get_class($object);
105
            }
106
            if (str_starts_with($object, 'GeminiLabs\BlackBar')) {
107
                return ''; // skip Blackbar callbacks
108
            }
109
            return rtrim(sprintf('%s::%s', $object, $method), ':');
110
        }
111
        if (is_object($function)) {
112
            return get_class($function);
113
        }
114
        return (string) $function;
115
    }
116
117
    protected function callbacksForHook(string $hook): array
118
    {
119
        global $wp_filter;
120
        $data = $wp_filter[$hook] ?? [];
121
        $results = [];
122
        foreach ($data as $priority => $callbacks) {
123
            $results[$priority] = $results[$priority] ?? [];
124
            foreach ($callbacks as $callback) {
125
                $function = $this->callbackFunction($callback['function']);
126
                if (!empty($function)) {
127
                    $results[$priority][] = $function;
128
                }
129
            }
130
        }
131
        return $results;
132
    }
133
134
    protected function sortByTime(array $a, array $b): int
135
    {
136
        if ($a['total'] !== $b['total']) {
137
            return ($a['total'] > $b['total']) ? -1 : 1;
138
        }
139
        return 0;
140
    }
141
142
    /**
143
     * Total elapsed time in nanoseconds.
144
     */
145
    protected function totalTimeForHook(array $data): int
146
    {
147
        $total = 0;
148
        foreach ($data['time'] as $time) {
149
            $total += ($time['stop'] - $time['start']);
150
        }
151
        return $total;
152
    }
153
}
154