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