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 RuntimeException; |
| 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 RuntimeException 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 RuntimeException( "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 | } |