PluginProbe
404 Solution / trunk
404 Solution vtrunk
4.3.5 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 4.2.0 4.1.19 4.1.18 4.1.17 4.1.16 4.1.15 4.1.13 4.1.12 4.1.11 4.1.10 4.1.9 4.1.8 4.1.7 4.1.6 4.1.5 4.1.4 4.1.3 trunk 2.30.0 All 109 releases
404-solution / includes / diagnostics / ShutdownEnvironmentInventory.php

ShutdownEnvironmentInventory.php in 404 Solution trunk, at includes/diagnostics/ShutdownEnvironmentInventory.php

117 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 if (!defined('ABSPATH')) {
4 exit;
5 }
6
7 /**
8 * Describes the process's shutdown-time environment for the AJAX trace's
9 * once-per-rotation inventory record: the WordPress $wp_filter['shutdown']
10 * callback roster and the loaded PHP extension list.
11 *
12 * This is the shutdown-time analog of ABJ_404_Solution_RequestEnvironmentFingerprint
13 * (which describes the request-start environment): pure runtime introspection
14 * that turns "something in shutdown was slow" into a list of named candidates.
15 * It holds no request state, so it is safe to call from any diagnostic that
16 * needs to know what runs at shutdown on this host, not only the AJAX trace.
17 *
18 * Everything it returns is a callback name, a hook priority, or an extension
19 * name -- it carries no value from the site's data.
20 */
21 final class ABJ_404_Solution_ShutdownEnvironmentInventory {
22
23 /** Enumerated extensions are bounded; a truncation is stated, never silent. */
24 const MAX_EXTENSIONS = 200;
25
26 /**
27 * The full shutdown-time environment as two serializable lists.
28 *
29 * @return array{callbacks: array<int, string>, extensions: array<int, string>}
30 */
31 public static function capture(): array {
32 return array(
33 'callbacks' => self::describeShutdownCallbacks(),
34 'extensions' => self::describeLoadedExtensions(),
35 );
36 }
37
38 /**
39 * The $wp_filter['shutdown'] callback roster, as `priority:callable`
40 * strings. This names WordPress shutdown-ACTION callbacks only; raw
41 * register_shutdown_function callbacks, destructors, and extension
42 * request-shutdown handlers do not appear here (PHP exposes no API for
43 * them), which is exactly why describeLoadedExtensions() exists alongside it.
44 *
45 * @return array<int, string>
46 */
47 private static function describeShutdownCallbacks(): array {
48 $wpFilter = $GLOBALS['wp_filter'] ?? null;
49 $hook = is_array($wpFilter) ? ($wpFilter['shutdown'] ?? null) : null;
50 $callbacks = null;
51 if (is_object($hook) && isset($hook->callbacks) && is_array($hook->callbacks)) {
52 $callbacks = $hook->callbacks;
53 } elseif (is_array($hook)) {
54 $callbacks = $hook;
55 }
56 if ($callbacks === null) {
57 return array('unavailable');
58 }
59 $described = array();
60 foreach ($callbacks as $priority => $priorityCallbacks) {
61 if (!is_array($priorityCallbacks)) {
62 continue;
63 }
64 foreach ($priorityCallbacks as $entry) {
65 $function = is_array($entry) ? ($entry['function'] ?? null) : null;
66 $described[] = $priority . ':' . self::describeCallable($function);
67 }
68 }
69 return $described;
70 }
71
72 /** @param mixed $function */
73 private static function describeCallable($function): string {
74 if (is_string($function)) {
75 return $function;
76 }
77 if (is_array($function) && count($function) === 2) {
78 $target = $function[0];
79 $targetName = is_object($target) ? get_class($target) : (is_string($target) ? $target : 'unknown');
80 return $targetName . '::' . (is_string($function[1]) ? $function[1] : 'unknown');
81 }
82 if ($function instanceof Closure) {
83 return 'Closure';
84 }
85 if (is_object($function)) {
86 return get_class($function);
87 }
88 return 'unknown';
89 }
90
91 /**
92 * Loaded PHP extensions, sorted. An extension's request-shutdown handler
93 * is one of the named cause-class-G suspects and cannot be timed or
94 * enumerated from userland at all; listing what is loaded at least lets a
95 * reader name the candidates (Redis object cache, ionCube, Monarx, APM
96 * agents) behind an unexplained non_wp_shutdown_ms.
97 *
98 * @return array<int, string>
99 */
100 private static function describeLoadedExtensions(): array {
101 if (!function_exists('get_loaded_extensions')) {
102 return array('unavailable');
103 }
104 $extensions = get_loaded_extensions();
105 sort($extensions, SORT_STRING);
106 $described = array();
107 foreach ($extensions as $name) {
108 if (count($described) >= self::MAX_EXTENSIONS) {
109 $described[] = 'truncated:' . (count($extensions) - self::MAX_EXTENSIONS) . '-more';
110 break;
111 }
112 $described[] = substr((string)$name, 0, 64);
113 }
114 return $described;
115 }
116 }
117