Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
67.44% |
29 / 43 |
|
57.14% |
4 / 7 |
CRAP | |
0.00% |
0 / 1 |
HaproxyStatusParser | |
67.44% |
29 / 43 |
|
57.14% |
4 / 7 |
22.77 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
6 | |||
findServerRowIndex | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
4.13 | |||
findServerColumnValue | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getColumnValue | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getNumberOfRows | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
isQueueOverloaded | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
2 | |||
getAvailableNonQueuedConnectionSlots | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Wikispeech; |
4 | |
5 | /** |
6 | * @file |
7 | * @ingroup Extensions |
8 | * @license GPL-2.0-or-later |
9 | */ |
10 | |
11 | use MWException; |
12 | |
13 | /** |
14 | * Parses output from HAProxy stats CSV. |
15 | * |
16 | * Known columns: |
17 | * |
18 | * 0. pxname [LFBS]: proxy name |
19 | * 1. svname [LFBS]: service name |
20 | * (FRONTEND for frontend, BACKEND for backend, any name for server/listener) |
21 | * 2. qcur [..BS]: current queued requests. |
22 | * For the backend this reports the number queued without a server assigned. |
23 | * 3. qmax [..BS]: max value of qcur |
24 | * 4. scur [LFBS]: current sessions |
25 | * 5. smax [LFBS]: max sessions |
26 | * 6. slim [LFBS]: configured session limit |
27 | * 7. stot [LFBS]: cumulative number of connections |
28 | * 8. bin [LFBS]: bytes in |
29 | * 9. bout [LFBS]: bytes out |
30 | * 10. dreq [LFB.]: requests denied because of security concerns. |
31 | * - For tcp this is because of a matched tcp-request content rule. |
32 | * - For http this is because of a matched http-request or tarpit rule. |
33 | * 11. dresp [LFBS]: responses denied because of security concerns. |
34 | * - For http this is because of a matched http-request rule, or "option checkcache". |
35 | * 12. ereq [LF..]: request errors. Some of the possible causes are: |
36 | * - early termination from the client, before the request has been sent. |
37 | * - read error from the client |
38 | * - client timeout |
39 | * - client closed connection |
40 | * - various bad requests from the client. |
41 | * - request was tarpitted. |
42 | * 13. econ [..BS]: number of requests that encountered an error trying to connect to |
43 | * a backend server. The backend stat is the sum of the stat for all servers of that backend, |
44 | * plus any connection errors not associated with a particular server |
45 | * (such as the backend having no active servers). |
46 | * 14. eresp [..BS]: response errors. srv_abrt will be counted here also. |
47 | * Some other errors are: |
48 | * - write error on the client socket (won't be counted for the server stat) |
49 | * - failure applying filters to the response. |
50 | * 15. wretr [..BS]: number of times a connection to a server was retried. |
51 | * 16. wredis [..BS]: number of times a request was redispatched to another server. |
52 | * The server value counts the number of times that server was switched away from. |
53 | * 17. status [LFBS]: status (UP/DOWN/NOLB/MAINT/MAINT(via)...) |
54 | * 18. weight [..BS]: total weight (backend), server weight (server) |
55 | * 19. act [..BS]: number of active servers (backend), server is active (server) |
56 | * 20. bck [..BS]: number of backup servers (backend), server is backup (server) |
57 | * 21. chkfail [...S]: number of failed checks. (Only counts checks failed when the server is up.) |
58 | * 22. chkdown [..BS]: number of UP->DOWN transitions. The backend counter counts transitions |
59 | * to the whole backend being down, rather than the sum of the counters for each server. |
60 | * 23. lastchg [..BS]: number of seconds since the last UP<->DOWN transition |
61 | * 24. downtime [..BS]: total downtime (in seconds). The value for the backend |
62 | * is the downtime for the whole backend, not the sum of the server downtime. |
63 | * 25. qlimit [...S]: configured maxqueue for the server, |
64 | * or nothing in the value is 0 (default, meaning no limit) |
65 | * 26. pid [LFBS]: process id (0 for first instance, 1 for second, ...) |
66 | * 27. iid [LFBS]: unique proxy id |
67 | * 28. sid [L..S]: server id (unique inside a proxy) |
68 | * 29. throttle [...S]: current throttle percentage for the server, when slowstart is active, |
69 | * or no value if not in slowstart. |
70 | * 30. lbtot [..BS]: total number of times a server was selected, either for new sessions, |
71 | * or when re-dispatching. The server counter is the number of times that server was selected. |
72 | * 31. tracked [...S]: id of proxy/server if tracking is enabled. |
73 | * 32. type [LFBS]: (0=frontend, 1=backend, 2=server, 3=socket/listener) |
74 | * 33. rate [.FBS]: number of sessions per second over last elapsed second |
75 | * 34. rate_lim [.F..]: configured limit on new sessions per second |
76 | * 35. rate_max [.FBS]: max number of new sessions per second |
77 | * 36. check_status [...S]: status of last health check, one of: |
78 | * UNK -> unknown |
79 | * INI -> initializing |
80 | * SOCKERR -> socket error |
81 | * L4OK -> check passed on layer 4, no upper layers testing enabled |
82 | * L4TOUT -> layer 1-4 timeout |
83 | * L4CON -> layer 1-4 connection problem, for example |
84 | * "Connection refused" (tcp rst) or "No route to host" (icmp) |
85 | * L6OK -> check passed on layer 6 |
86 | * L6TOUT -> layer 6 (SSL) timeout |
87 | * L6RSP -> layer 6 invalid response - protocol error |
88 | * L7OK -> check passed on layer 7 |
89 | * L7OKC -> check conditionally passed on layer 7, for example 404 with |
90 | * disable-on-404 |
91 | * L7TOUT -> layer 7 (HTTP/SMTP) timeout |
92 | * L7RSP -> layer 7 invalid response - protocol error |
93 | * L7STS -> layer 7 response error, for example HTTP 5xx |
94 | * 37. check_code [...S]: layer5-7 code, if available |
95 | * 38. check_duration [...S]: time in ms took to finish last health check |
96 | * 39. hrsp_1xx [.FBS]: http responses with 1xx code |
97 | * 40. hrsp_2xx [.FBS]: http responses with 2xx code |
98 | * 41. hrsp_3xx [.FBS]: http responses with 3xx code |
99 | * 42. hrsp_4xx [.FBS]: http responses with 4xx code |
100 | * 43. hrsp_5xx [.FBS]: http responses with 5xx code |
101 | * 44. hrsp_other [.FBS]: http responses with other codes (protocol error) |
102 | * 45. hanafail [...S]: failed health checks details |
103 | * 46. req_rate [.F..]: HTTP requests per second over last elapsed second |
104 | * 47. req_rate_max [.F..]: max number of HTTP requests per second observed |
105 | * 48. req_tot [.F..]: total number of HTTP requests received |
106 | * 49. cli_abrt [..BS]: number of data transfers aborted by the client |
107 | * 50. srv_abrt [..BS]: number of data transfers aborted by the server (inc. in eresp) |
108 | * 51. comp_in [.FB.]: number of HTTP response bytes fed to the compressor |
109 | * 52. comp_out [.FB.]: number of HTTP response bytes emitted by the compressor |
110 | * 53. comp_byp [.FB.]: number of bytes that bypassed the HTTP compressor (CPU/BW limit) |
111 | * 54. comp_rsp [.FB.]: number of HTTP responses that were compressed |
112 | * 55. lastsess [..BS]: number of seconds since last session assigned to server/backend |
113 | * 56. last_chk [...S]: last health check contents or textual error |
114 | * 57. last_agt [...S]: last agent check contents or textual error |
115 | * 58. qtime [..BS]: the average queue time in ms over the 1024 last requests |
116 | * 59. ctime [..BS]: the average connect time in ms over the 1024 last requests |
117 | * 60. rtime [..BS]: the average response time in ms over the 1024 last requests (0 for TCP) |
118 | * 61. ttime [..BS]: the average total session time in ms over the 1024 last requests |
119 | * |
120 | * @since 0.1.10 |
121 | */ |
122 | class HaproxyStatusParser { |
123 | |
124 | /** |
125 | * @var array string column name => string[] rows |
126 | * Use column name rather than column index to be compatible with future changes in HAProxy. |
127 | * The array value per column name is a list of all rows values for that column, |
128 | * e.g. data is organized so they can be extracted by column and row, |
129 | * not by row and columns as in, for example, a relational database. |
130 | */ |
131 | private $valuesByColumnName; |
132 | |
133 | /** @var int */ |
134 | private $numberOfRows; |
135 | |
136 | /** |
137 | * @since 0.1.10 |
138 | * @param string $input CSV to be parsed |
139 | */ |
140 | public function __construct( string $input ) { |
141 | $values = []; |
142 | $numberOfRows = 0; |
143 | $parsedHeaders = false; |
144 | $rows = str_getcsv( $input, "\n" ); |
145 | $columns = []; |
146 | foreach ( $rows as $row ) { |
147 | $csv = str_getcsv( $row ); |
148 | if ( !$parsedHeaders ) { |
149 | if ( $csv[0] === '# pxname' ) { |
150 | $csv[0] = 'pxname'; |
151 | } |
152 | foreach ( $csv as $columnName ) { |
153 | $values[$columnName] = []; |
154 | $columns[] = $columnName; |
155 | } |
156 | $parsedHeaders = true; |
157 | } else { |
158 | foreach ( $csv as $index => $columnValue ) { |
159 | // Phan is confused by this $columns[$index] array accessor. |
160 | // @phan-suppress-next-line PhanTypeInvalidDimOffset |
161 | $values[$columns[$index]][] = $columnValue; |
162 | } |
163 | $numberOfRows++; |
164 | } |
165 | } |
166 | $this->valuesByColumnName = $values; |
167 | $this->numberOfRows = $numberOfRows; |
168 | } |
169 | |
170 | /** |
171 | * @since 0.1.10 |
172 | * @param string $pxname |
173 | * @param string $svname |
174 | * @return int |
175 | * @throws MWException If no such server in parsed data |
176 | */ |
177 | public function findServerRowIndex( |
178 | string $pxname, |
179 | string $svname |
180 | ): int { |
181 | for ( $rowIndex = 0; $rowIndex < $this->numberOfRows; $rowIndex++ ) { |
182 | if ( |
183 | $this->valuesByColumnName['pxname'][$rowIndex] === $pxname |
184 | && $this->valuesByColumnName['svname'][$rowIndex] === $svname |
185 | ) { |
186 | return $rowIndex; |
187 | } |
188 | } |
189 | throw new MWException( "No server defined with pxname '$pxname' and svname '$svname'." ); |
190 | } |
191 | |
192 | /** |
193 | * @since 0.1.10 |
194 | * @param string $pxname |
195 | * @param string $svname |
196 | * @param string $columnName |
197 | * @return string values |
198 | */ |
199 | public function findServerColumnValue( |
200 | string $pxname, |
201 | string $svname, |
202 | string $columnName |
203 | ): string { |
204 | return $this->getColumnValue( |
205 | $this->findServerRowIndex( $pxname, $svname ), |
206 | $columnName |
207 | ); |
208 | } |
209 | |
210 | /** |
211 | * @since 0.1.10 |
212 | * @param int $rowIndex |
213 | * @param string $columnName |
214 | * @return string value |
215 | */ |
216 | public function getColumnValue( |
217 | int $rowIndex, |
218 | string $columnName |
219 | ): string { |
220 | return $this->valuesByColumnName[$columnName][$rowIndex]; |
221 | } |
222 | |
223 | /** |
224 | * @since 0.1.10 |
225 | * @return int |
226 | */ |
227 | public function getNumberOfRows(): int { |
228 | return $this->numberOfRows; |
229 | } |
230 | |
231 | /** |
232 | * Queue is overloaded if there are already the maximum number of current |
233 | * connections processed by the backend at the same time as the queue |
234 | * contains more than X connections waiting for their turn, |
235 | * where X = $overloadedFactor multiplied with |
236 | * the maximum number of current connections to the backend. |
237 | * |
238 | * @since 0.1.10 |
239 | * @param string $frontendPxName |
240 | * @param string $frontendSvName |
241 | * @param string $backendPxName |
242 | * @param string $backendSvName |
243 | * @param float $overloadedFactor |
244 | * @return bool Whether or not connection queue is overloaded |
245 | */ |
246 | public function isQueueOverloaded( |
247 | string $frontendPxName, |
248 | string $frontendSvName, |
249 | string $backendPxName, |
250 | string $backendSvName, |
251 | float $overloadedFactor |
252 | ): bool { |
253 | $frontendRowIndex = $this->findServerRowIndex( $frontendPxName, $frontendSvName ); |
254 | $frontendCurrentSessions = intval( $this->getColumnValue( $frontendRowIndex, 'scur' ) ); |
255 | |
256 | $backendRowIndex = $this->findServerRowIndex( $backendPxName, $backendSvName ); |
257 | $backendSessionsLimit = $this->getColumnValue( $backendRowIndex, 'slim' ); |
258 | $backendSessionsLimit = intval( $backendSessionsLimit ); |
259 | |
260 | $maximumFrontendCurrentSessions = $backendSessionsLimit * $overloadedFactor; |
261 | |
262 | return $frontendCurrentSessions >= $maximumFrontendCurrentSessions; |
263 | } |
264 | |
265 | /** |
266 | * Counts number of requests that currently could be sent to the queue |
267 | * and immediately would be passed down to backend. |
268 | * |
269 | * If this value is greater than 0, then the next request sent via the queue |
270 | * will be immediately processed by the backend. |
271 | * |
272 | * If this value is less than 1, then the next connection will be queued, |
273 | * given that the currently processing requests will not have had time to finish by then. |
274 | * |
275 | * If this value is less than 1, then the value is the inverse size of the known queue. |
276 | * Note that the OS on the HAProxy server might be buffering connections in the TCP-stack |
277 | * and that HAProxy will not be aware of such connections. A negative number might therefor |
278 | * not represent a perfect count of current connection lined up in the queue. |
279 | * |
280 | * The idea with this function is to see if there are available resources that could |
281 | * be used for pre-synthesis of utterances during otherwise idle time. |
282 | * |
283 | * @since 0.1.10 |
284 | * @param string $frontendPxName |
285 | * @param string $frontendSvName |
286 | * @param string $backendPxName |
287 | * @param string $backendSvName |
288 | * @return int Positive number if available slots, else inverted size of queue. |
289 | */ |
290 | public function getAvailableNonQueuedConnectionSlots( |
291 | string $frontendPxName, |
292 | string $frontendSvName, |
293 | string $backendPxName, |
294 | string $backendSvName |
295 | ): int { |
296 | $frontendRowIndex = $this->findServerRowIndex( $frontendPxName, $frontendSvName ); |
297 | $frontendCurrentSessions = intval( $this->getColumnValue( $frontendRowIndex, 'scur' ) ); |
298 | |
299 | $backendRowIndex = $this->findServerRowIndex( $backendPxName, $backendSvName ); |
300 | $backendSessionsLimit = $this->getColumnValue( $backendRowIndex, 'slim' ); |
301 | $backendSessionsLimit = intval( $backendSessionsLimit ); |
302 | |
303 | return $backendSessionsLimit - $frontendCurrentSessions; |
304 | } |
305 | |
306 | } |