Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
WikimediaPrometheusQueryServiceLagProvider
100.00% covered (success)
100.00%
41 / 41
100.00% covered (success)
100.00%
3 / 3
9
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 getLag
100.00% covered (success)
100.00%
33 / 33
100.00% covered (success)
100.00%
1 / 1
7
 getQuery
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare( strict_types = 1 );
4
5namespace WikidataOrg\QueryServiceLag;
6
7use MediaWiki\Http\HttpRequestFactory;
8use 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 */
17class 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}