xref: /PHP-8.0/sapi/fpm/tests/status.inc (revision edfb3470)
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    ];
18
19    /**
20     * @var array
21     */
22    private $defaultFields = [
23        'pool'                 => '\w+',
24        'process manager'      => '(static|dynamic|ondemand)',
25        'start time'           => '\d+\/\w{3}\/\d{4}:\d{2}:\d{2}:\d{2}\s[+-]\d{4}',
26        'start since'          => '\d+',
27        'accepted conn'        => '\d+',
28        'listen queue'         => '\d+',
29        'max listen queue'     => '\d+',
30        'listen queue len'     => '\d+',
31        'idle processes'       => '\d+',
32        'active processes'     => '\d+',
33        'total processes'      => '\d+',
34        'max active processes' => '\d+',
35        'max children reached' => '\d+',
36        'slow requests'        => '\d+',
37    ];
38
39    /**
40     * Check status page.
41     *
42     * @param Response $response
43     * @param array $fields
44     * @param string $type
45     * @throws \Exception
46     */
47    public function checkStatus(Response $response, array $fields, string $type)
48    {
49        if (!isset($this->contentTypes[$type])) {
50            throw new \Exception('Invalid content type ' . $type);
51        }
52
53        $body = $response->getBody($this->contentTypes[$type]);
54        if ($body === null) {
55            return;
56        }
57        $method = "checkStatus" . ucfirst($type);
58
59        $this->$method($body, array_merge($this->defaultFields, $fields));
60    }
61
62    /**
63     * Make status check for status page.
64     *
65     * @param string $body
66     * @param array $fields
67     * @param string $rowPattern
68     * @param string $header
69     * @param string $footer
70     * @param null|callable $nameTransformer
71     * @param null|callable $valueTransformer
72     * @param bool $startTimeTimestamp
73     * @param bool $closingName
74     */
75    private function makeStatusCheck(
76        string $body,
77        array $fields,
78        string $rowPattern,
79        string $header = '',
80        string $footer = '',
81        $nameTransformer = null,
82        $valueTransformer = null,
83        bool $startTimeTimestamp = false,
84        bool $closingName = false
85    ) {
86
87        if ($startTimeTimestamp && $fields['start time'][0] === '\\') {
88            $fields['start time'] = '\d+';
89        }
90        $pattern = '(' . $header;
91        foreach ($fields as $name => $value) {
92            if ($nameTransformer) {
93                $name = call_user_func($nameTransformer, $name);
94            }
95            if ($valueTransformer) {
96                $value = call_user_func($valueTransformer, $value);
97            }
98            if ($closingName) {
99                $pattern .= sprintf($rowPattern, $name, $value, $name);
100            } else {
101                $pattern .= sprintf($rowPattern, $name, $value);
102            }
103        }
104        $pattern = rtrim($pattern, $rowPattern[strlen($rowPattern) - 1]);
105        $pattern .= $footer . ')';
106
107        if (!preg_match($pattern, $body)) {
108            echo "ERROR: Expected body does not match pattern\n";
109            echo "BODY:\n";
110            var_dump($body);
111            echo "PATTERN:\n";
112            var_dump($pattern);
113        }
114    }
115
116    /**
117     * Check plain status page.
118     *
119     * @param string $body
120     * @param array $fields
121     */
122    protected function checkStatusPlain(string $body, array $fields)
123    {
124        $this->makeStatusCheck($body, $fields, "%s:\s+%s\n");
125    }
126
127    /**
128     * Check html status page.
129     *
130     * @param string $body
131     * @param array $fields
132     */
133    protected function checkStatusHtml(string $body, array $fields)
134    {
135        $header = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" " .
136            "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n" .
137            "<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n" .
138            "<head><title>" . self::HTML_TITLE . "</title></head>\n" .
139            "<body>\n<table>\n";
140        $footer = "\n</table>\n</body></html>";
141
142        $this->makeStatusCheck(
143            $body,
144            $fields,
145            "<tr><th>%s</th><td>%s</td></tr>\n",
146            $header,
147            $footer
148        );
149    }
150
151    /**
152     * Check xml status page.
153     *
154     * @param string $body
155     * @param array $fields
156     */
157    protected function checkStatusXml(string $body, array $fields)
158    {
159        $this->makeStatusCheck(
160            $body,
161            $fields,
162            "<%s>%s</%s>\n",
163            "<\?xml version=\"1.0\" \?>\n<status>\n",
164            "\n</status>",
165            function ($name) {
166                return str_replace(' ', '-', $name);
167            },
168            null,
169            true,
170            true
171        );
172    }
173
174    /**
175     * Check json status page.
176     *
177     * @param string $body
178     * @param array $fields
179     */
180    protected function checkStatusJson(string $body, array $fields)
181    {
182        $this->makeStatusCheck(
183            $body,
184            $fields,
185            '"%s":%s,',
186            '{',
187            '}',
188            null,
189            function ($value) {
190                if (is_numeric($value) || $value === '\d+') {
191                    return $value;
192                }
193
194                return '"' . $value . '"';
195            },
196            true
197        );
198    }
199}
200