xref: /PHP-8.1/sapi/fpm/tests/status.inc (revision 46bec6de)
1<?php
2
3namespace FPM;
4
5class Status
6{
7    const HTML_TITLE = 'PHP-FPM Status Page';
8
9    /**
10     * @var array
11     */
12    private $contentTypes = [
13        'plain'       => 'text/plain',
14        'html'        => 'text/html',
15        'xml'         => 'text/xml',
16        'json'        => 'application/json',
17        'openmetrics' => 'application/openmetrics-text; version=1.0.0; charset=utf-8',
18    ];
19
20    /**
21     * @var array
22     */
23    private $defaultFields = [
24        'pool'                 => '\w+',
25        'process manager'      => '(static|dynamic|ondemand)',
26        'start time'           => '\d+\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2}\s[+-]\d{4}',
27        'start since'          => '\d+',
28        'accepted conn'        => '\d+',
29        'listen queue'         => '\d+',
30        'max listen queue'     => '\d+',
31        'listen queue len'     => '\d+',
32        'idle processes'       => '\d+',
33        'active processes'     => '\d+',
34        'total processes'      => '\d+',
35        'max active processes' => '\d+',
36        'max children reached' => '\d+',
37        'slow requests'        => '\d+',
38    ];
39
40    /**
41     * Check status page.
42     *
43     * @param Response $response
44     * @param array $fields
45     * @param string $type
46     * @throws \Exception
47     */
48    public function checkStatus(Response $response, array $fields, string $type)
49    {
50        if (!isset($this->contentTypes[$type])) {
51            throw new \Exception('Invalid content type ' . $type);
52        }
53
54        $body = $response->getBody($this->contentTypes[$type]);
55        if ($body === null) {
56            return;
57        }
58        $method = "checkStatus" . ucfirst($type);
59
60        $this->$method($body, array_merge($this->defaultFields, $fields));
61    }
62
63    /**
64     * Make status check for status page.
65     *
66     * @param string $body
67     * @param array $fields
68     * @param string $rowPattern
69     * @param string $header
70     * @param string $footer
71     * @param null|callable $nameTransformer
72     * @param null|callable $valueTransformer
73     * @param bool $startTimeTimestamp
74     * @param bool $closingName
75     */
76    private function makeStatusCheck(
77        string $body,
78        array $fields,
79        string $rowPattern,
80        string $header = '',
81        string $footer = '',
82        $nameTransformer = null,
83        $valueTransformer = null,
84        bool $startTimeTimestamp = false,
85        bool $closingName = false
86    ) {
87
88        if ($startTimeTimestamp && $fields['start time'][0] === '\\') {
89            $fields['start time'] = '\d+';
90        }
91        $pattern = '(' . $header;
92        foreach ($fields as $name => $value) {
93            if ($nameTransformer) {
94                $name = call_user_func($nameTransformer, $name);
95            }
96            if ($valueTransformer) {
97                $value = call_user_func($valueTransformer, $value);
98            }
99            if ($closingName) {
100                $pattern .= sprintf($rowPattern, $name, $value, $name);
101            } else {
102                $pattern .= sprintf($rowPattern, $name, $value);
103            }
104        }
105        $pattern = rtrim($pattern, $rowPattern[strlen($rowPattern) - 1]);
106        $pattern .= $footer . ')';
107
108        if (!preg_match($pattern, $body)) {
109            echo "ERROR: Expected body does not match pattern\n";
110            echo "BODY:\n";
111            var_dump($body);
112            echo "PATTERN:\n";
113            var_dump($pattern);
114        }
115    }
116
117    /**
118     * Check plain status page.
119     *
120     * @param string $body
121     * @param array $fields
122     */
123    protected function checkStatusPlain(string $body, array $fields)
124    {
125        $this->makeStatusCheck($body, $fields, "%s:\s+%s\n");
126    }
127
128    /**
129     * Check html status page.
130     *
131     * @param string $body
132     * @param array $fields
133     */
134    protected function checkStatusHtml(string $body, array $fields)
135    {
136        $header = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " .
137            "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" .
138            "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" .
139            "<head><title>" . self::HTML_TITLE . "</title></head>\n" .
140            "<body>\n<table>\n";
141        $footer = "\n</table>\n</body></html>";
142
143        $this->makeStatusCheck(
144            $body,
145            $fields,
146            "<tr><th>%s</th><td>%s</td></tr>\n",
147            $header,
148            $footer
149        );
150    }
151
152    /**
153     * Check xml status page.
154     *
155     * @param string $body
156     * @param array $fields
157     */
158    protected function checkStatusXml(string $body, array $fields)
159    {
160        $this->makeStatusCheck(
161            $body,
162            $fields,
163            "<%s>%s</%s>\n",
164            "<\?xml version=\"1.0\" \?>\n<status>\n",
165            "\n</status>",
166            function ($name) {
167                return str_replace(' ', '-', $name);
168            },
169            null,
170            true,
171            true
172        );
173    }
174
175    /**
176     * Check json status page.
177     *
178     * @param string $body
179     * @param array $fields
180     */
181    protected function checkStatusJson(string $body, array $fields)
182    {
183        $this->makeStatusCheck(
184            $body,
185            $fields,
186            '"%s":%s,',
187            '{',
188            '}',
189            null,
190            function ($value) {
191                if (is_numeric($value) || $value === '\d+') {
192                    return $value;
193                }
194
195                return '"' . $value . '"';
196            },
197            true
198        );
199    }
200
201    /**
202     * Check openmetrics status page.
203     *
204     * @param string $body
205     * @param array $fields
206     */
207    protected function checkStatusOpenmetrics(string $body, array $fields)
208    {
209        $pattern = "(# HELP phpfpm_up Could pool " . $fields['pool'] . " using a " . $fields['process manager'] . " PM on PHP-FPM be reached\?\n" .
210            "# TYPE phpfpm_up gauge\n" .
211            "phpfpm_up 1\n" .
212            "# HELP phpfpm_start_since The number of seconds since FPM has started\.\n" .
213            "# TYPE phpfpm_start_since counter\n" .
214            "phpfpm_start_since " . $fields['start since'] . "\n" .
215            "# HELP phpfpm_accepted_connections The number of requests accepted by the pool\.\n" .
216            "# TYPE phpfpm_accepted_connections counter\n" .
217            "phpfpm_accepted_connections " . $fields['accepted conn'] . "\n" .
218            "# HELP phpfpm_listen_queue The number of requests in the queue of pending connections\.\n" .
219            "# TYPE phpfpm_listen_queue gauge\n" .
220            "phpfpm_listen_queue " . $fields['listen queue'] . "\n" .
221            "# HELP phpfpm_max_listen_queue The maximum number of requests in the queue of pending connections since FPM has started\.\n" .
222            "# TYPE phpfpm_max_listen_queue counter\n" .
223            "phpfpm_max_listen_queue " . $fields['max listen queue'] . "\n" .
224            "# TYPE phpfpm_listen_queue_length gauge\n" .
225            "# HELP phpfpm_listen_queue_length The size of the socket queue of pending connections\.\n" .
226            "phpfpm_listen_queue_length " . $fields['listen queue len'] . "\n" .
227            "# HELP phpfpm_idle_processes The number of idle processes\.\n" .
228            "# TYPE phpfpm_idle_processes gauge\n" .
229            "phpfpm_idle_processes " . $fields['idle processes'] . "\n" .
230            "# HELP phpfpm_active_processes The number of active processes\.\n" .
231            "# TYPE phpfpm_active_processes gauge\n" .
232            "phpfpm_active_processes " . $fields['active processes'] . "\n" .
233            "# HELP phpfpm_total_processes The number of idle \+ active processes\.\n" .
234            "# TYPE phpfpm_total_processes gauge\n" .
235            "phpfpm_total_processes " . $fields['total processes'] . "\n" .
236            "# HELP phpfpm_max_active_processes The maximum number of active processes since FPM has started\.\n" .
237            "# TYPE phpfpm_max_active_processes counter\n" .
238            "phpfpm_max_active_processes " . $fields['max active processes'] . "\n" .
239            "# HELP phpfpm_max_children_reached The number of times, the process limit has been reached, when pm tries to start more children \(works only for pm 'dynamic' and 'ondemand'\)\.\n" .
240            "# TYPE phpfpm_max_children_reached counter\n" .
241            "phpfpm_max_children_reached " . $fields['max children reached'] . "\n" .
242            "# HELP phpfpm_slow_requests The number of requests that exceeded your 'request_slowlog_timeout' value\.\n" .
243            "# TYPE phpfpm_slow_requests counter\n" .
244            "phpfpm_slow_requests " . $fields['slow requests'] . "\n" .
245            "# EOF)\n";
246
247        if (!preg_match($pattern, $body)) {
248            echo "ERROR: Expected body does not match pattern\n";
249            echo "BODY:\n";
250            var_dump($body);
251            echo "PATTERN:\n";
252            var_dump($pattern);
253        }
254    }
255}
256