Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
41 / 41 |
|
100.00% |
3 / 3 |
CRAP | |
100.00% |
1 / 1 |
WikimediaPrometheusQueryServiceLagProvider | |
100.00% |
41 / 41 |
|
100.00% |
3 / 3 |
9 | |
100.00% |
1 / 1 |
__construct | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
getLag | |
100.00% |
33 / 33 |
|
100.00% |
1 / 1 |
7 | |||
getQuery | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | declare( strict_types = 1 ); |
4 | |
5 | namespace WikidataOrg\QueryServiceLag; |
6 | |
7 | use MediaWiki\Http\HttpRequestFactory; |
8 | use Psr\Log\LoggerInterface; |
9 | |
10 | /** |
11 | * Looks up lag of query service in Prometheous backend. |
12 | * |
13 | * @license GPL-2.0-or-later |
14 | * @author Marius Hoch |
15 | * @author Alaa Sarhan |
16 | */ |
17 | class WikimediaPrometheusQueryServiceLagProvider { |
18 | |
19 | /** |
20 | * @var HttpRequestFactory |
21 | */ |
22 | private $httpRequestFactory; |
23 | |
24 | /** |
25 | * @var LoggerInterface |
26 | */ |
27 | private $logger; |
28 | |
29 | /** |
30 | * @var string[] |
31 | */ |
32 | private $prometheusUrls; |
33 | |
34 | /** |
35 | * @var float |
36 | */ |
37 | private float $pooledServerMinQueryRate; |
38 | |
39 | /** |
40 | * @param HttpRequestFactory $httpRequestFactory |
41 | * @param LoggerInterface $logger |
42 | * @param string[] $prometheusUrls Prometheus query endpoint URLs (.../query) |
43 | * @param float $pooledMinServerQueryRate Minimal query rate expected to be served from a pooled server |
44 | */ |
45 | public function __construct( |
46 | HttpRequestFactory $httpRequestFactory, |
47 | LoggerInterface $logger, |
48 | array $prometheusUrls, |
49 | float $pooledMinServerQueryRate |
50 | ) { |
51 | $this->prometheusUrls = $prometheusUrls; |
52 | $this->httpRequestFactory = $httpRequestFactory; |
53 | $this->logger = $logger; |
54 | $this->pooledServerMinQueryRate = $pooledMinServerQueryRate; |
55 | } |
56 | |
57 | /** |
58 | * @return array|null Array with keys 'lag' and 'host' or null |
59 | */ |
60 | public function getLag(): ?array { |
61 | $mostLagged = null; |
62 | foreach ( $this->prometheusUrls as $prometheusUrl ) { |
63 | $fullUrl = $prometheusUrl . '?query=' . rawurlencode( $this->getQuery() ); |
64 | |
65 | // XXX: Custom timeout? |
66 | $request = $this->httpRequestFactory->create( $fullUrl, [], __METHOD__ ); |
67 | $requestStatus = $request->execute(); |
68 | |
69 | if ( !$requestStatus->isOK() ) { |
70 | $this->logger->warning( |
71 | __METHOD__ . ': Request to Prometheus API {fullUrl} failed with {error}', |
72 | [ |
73 | 'fullUrl' => $fullUrl, |
74 | 'error' => $requestStatus->getMessage()->inContentLanguage()->text(), |
75 | ] |
76 | ); |
77 | continue; |
78 | } |
79 | |
80 | $response = json_decode( $request->getContent(), true ); |
81 | $result = $response['data']['result'][0] ?? []; |
82 | |
83 | if ( |
84 | isset( $result['value'][1] ) && |
85 | isset( $result['metric']['host'] ) |
86 | ) { |
87 | $maxLag = intval( round( floatval( $result['value'][1] ) ) ); |
88 | $host = $result['metric']['host']; |
89 | |
90 | if ( $mostLagged === null || $mostLagged['lag'] < $maxLag ) { |
91 | $mostLagged = [ |
92 | 'lag' => $maxLag, |
93 | 'host' => $host |
94 | ]; |
95 | } |
96 | } else { |
97 | $this->logger->warning( |
98 | __METHOD__ . ': unexpected result from Prometheus API {fullUrl}: {response}', |
99 | [ |
100 | 'fullUrl' => $fullUrl, |
101 | 'response' => $request->getContent() |
102 | ] |
103 | ); |
104 | } |
105 | |
106 | } |
107 | |
108 | return $mostLagged; |
109 | } |
110 | |
111 | private function getQuery(): string { |
112 | return 'topk(1, time() - label_replace(blazegraph_lastupdated, "host", "$1", "instance", "^([^:]+):.*")' . |
113 | 'and on(host) label_replace(rate(' . |
114 | 'org_wikidata_query_rdf_blazegraph_filters_QueryEventSenderFilter_event_sender_filter_StartedQueries{}' . |
115 | '[5m]) > ' . round( $this->pooledServerMinQueryRate, 3 ) . ', "host", "$1", "instance", "^([^:]+):.*"))'; |
116 | } |
117 | |
118 | } |