MediaWiki REL1_40
WRStatsReader.php
Go to the documentation of this file.
1<?php
2
3namespace Wikimedia\WRStats;
4
13 private $store;
15 private $metricSpecs;
17 private $prefixComponents;
19 private $now;
21 private $queuedKeys = [];
23 private $cachedValues = [];
24
32 public function __construct( StatsStore $store, $specs, $prefix ) {
33 $this->store = $store;
34 $this->metricSpecs = [];
35 foreach ( $specs as $name => $spec ) {
36 $this->metricSpecs[$name] = new MetricSpec( $spec );
37 }
38 $this->prefixComponents = is_array( $prefix ) ? $prefix : [ $prefix ];
39 if ( !count( $this->prefixComponents ) ) {
40 throw new WRStatsError( __METHOD__ .
41 ': there must be at least one prefix component' );
42 }
43 }
44
53 public function latest( $numSeconds ) {
54 $now = $this->now();
55 return new TimeRange( $now - $numSeconds, $now );
56 }
57
65 public function timeRange( $start, $end ) {
66 return new TimeRange( $start, $end );
67 }
68
77 public function getRate( $metricName, ?EntityKey $entity, TimeRange $range ) {
78 $metricSpec = $this->metricSpecs[$metricName] ?? null;
79 if ( $metricSpec === null ) {
80 throw new WRStatsError( __METHOD__ . ": Unrecognised metric \"$metricName\"" );
81 }
82 $entity ??= new LocalEntityKey;
83 $now = $this->now();
84 $seqSpec = null;
85 foreach ( $metricSpec->sequences as $seqSpec ) {
86 $seqStart = $now - $seqSpec->softExpiry;
87 if ( $seqStart <= $range->start ) {
88 break;
89 }
90 }
91 $timeStep = $seqSpec->timeStep;
92 $firstBucket = (int)( $range->start / $timeStep );
93 $lastBucket = (int)ceil( $range->end / $timeStep );
94 for ( $bucket = $firstBucket; $bucket <= $lastBucket; $bucket++ ) {
95 $key = $this->store->makeKey(
96 $this->prefixComponents,
97 [ $metricName, $seqSpec->name, $bucket ],
98 $entity
99 );
100 if ( !isset( $this->cachedValues[$key] ) ) {
101 $this->queuedKeys[$key] = true;
102 }
103 }
104 return new RatePromise( $this, $metricName, $entity, $metricSpec, $seqSpec, $range );
105 }
106
116 public function getRates( $metricNames, ?EntityKey $entity, TimeRange $range ) {
117 $rates = [];
118 foreach ( $metricNames as $name ) {
119 $rates[$name] = $this->getRate( $name, $entity, $range );
120 }
121 return $rates;
122 }
123
127 public function fetch() {
128 if ( !$this->queuedKeys ) {
129 return;
130 }
131 $this->cachedValues += $this->store->query( array_keys( $this->queuedKeys ) );
132 $this->queuedKeys = [];
133 }
134
140 public function setCurrentTime( $now ) {
141 $this->now = $now;
142 }
143
148 public function resetCurrentTime() {
149 $this->now = null;
150 }
151
155 private function now() {
156 $this->now ??= microtime( true );
157 return $this->now;
158 }
159
170 public function internalGetCount(
171 $metricName,
172 EntityKey $entity,
173 MetricSpec $metricSpec,
174 SequenceSpec $seqSpec,
175 TimeRange $range
176 ) {
177 $this->fetch();
178 $timeStep = $seqSpec->timeStep;
179 $firstBucket = (int)( $range->start / $timeStep );
180 $lastBucket = (int)( $range->end / $timeStep );
181 $now = $this->now();
182 $total = 0;
183 for ( $bucket = $firstBucket; $bucket <= $lastBucket; $bucket++ ) {
184 $key = $this->store->makeKey(
185 $this->prefixComponents,
186 [ $metricName, $seqSpec->name, $bucket ],
187 $entity
188 );
189 $value = $this->cachedValues[$key] ?? 0;
190 if ( !$value ) {
191 continue;
192 } elseif ( $bucket === $firstBucket ) {
193 if ( $bucket === $lastBucket ) {
194 // It can be assumed that there are zero events in the future
195 $bucketStartTime = $bucket * $timeStep;
196 $rateInterpolationEndTime = min( $bucketStartTime + $timeStep, $now );
197 $interpolationDuration = $rateInterpolationEndTime - $bucketStartTime;
198 if ( $interpolationDuration > 0 ) {
199 $total += $value * $range->getDuration() / $interpolationDuration;
200 }
201 } else {
202 $overlapDuration = max( ( $bucket + 1 ) * $timeStep - $range->start, 0 );
203 $total += $value * $overlapDuration / $timeStep;
204 }
205 } elseif ( $bucket === $lastBucket ) {
206 // It can be assumed that there are zero events in the future
207 $bucketStartTime = $bucket * $timeStep;
208 $rateInterpolationEndTime = min( $bucketStartTime + $timeStep, $now );
209 $overlapDuration = max( $range->end - $bucketStartTime, 0 );
210 $interpolationDuration = $rateInterpolationEndTime - $bucketStartTime;
211 if ( $overlapDuration === $interpolationDuration ) {
212 // Special case for 0/0 -- current time exactly on boundary.
213 $total += $value;
214 } elseif ( $interpolationDuration > 0 ) {
215 $total += $value * $overlapDuration / $interpolationDuration;
216 }
217 } else {
218 $total += $value;
219 }
220 }
221 // Round to nearest resolution step for nicer display
222 $rounded = round( $total ) * $metricSpec->resolution;
223 // Convert to integer if integer is expected
224 if ( is_int( $metricSpec->resolution ) ) {
225 $rounded = (int)$rounded;
226 }
227 return $rounded;
228 }
229
237 public function total( $rates ) {
238 $result = [];
239 foreach ( $rates as $key => $rate ) {
240 $result[$key] = $rate->total();
241 }
242 return $result;
243 }
244
251 public function perSecond( $rates ) {
252 $result = [];
253 foreach ( $rates as $key => $rate ) {
254 $result[$key] = $rate->perSecond();
255 }
256 return $result;
257 }
258
265 public function perMinute( $rates ) {
266 $result = [];
267 foreach ( $rates as $key => $rate ) {
268 $result[$key] = $rate->perMinute();
269 }
270 return $result;
271 }
272
279 public function perHour( $rates ) {
280 $result = [];
281 foreach ( $rates as $key => $rate ) {
282 $result[$key] = $rate->perHour();
283 }
284 return $result;
285 }
286
293 public function perDay( $rates ) {
294 $result = [];
295 foreach ( $rates as $key => $rate ) {
296 $result[$key] = $rate->perDay();
297 }
298 return $result;
299 }
300}
Base class for entity keys.
Definition EntityKey.php:13
Entity key with global=false.
Class representation of normalized metric specifications.
A WRStats query result promise.
Class representation of normalized sequence specifications.
getDuration()
Get the duration of the time range in seconds.
Definition TimeRange.php:32
Exception class for errors thrown by the WRStats library.
Readers gather a batch of read operations, returning promises.
fetch()
Perform any queued fetch operations.
latest( $numSeconds)
Get a TimeRange for some period ending at the current time.
internalGetCount( $metricName, EntityKey $entity, MetricSpec $metricSpec, SequenceSpec $seqSpec, TimeRange $range)
perMinute( $rates)
Resolve a batch of RatePromise objects, returning their per-minute rates.
resetCurrentTime()
Clear the current time so that it will be filled with the real current time on the next call.
__construct(StatsStore $store, $specs, $prefix)
perDay( $rates)
Resolve a batch of RatePromise objects, returning their per-day rates.
timeRange( $start, $end)
Get a specified time range.
getRate( $metricName, ?EntityKey $entity, TimeRange $range)
Queue a fetch operation.
perHour( $rates)
Resolve a batch of RatePromise objects, returning their per-hour rates.
getRates( $metricNames, ?EntityKey $entity, TimeRange $range)
Queue a batch of fetch operations for different metrics with the same time range.
perSecond( $rates)
Resolve a batch of RatePromise objects, returning their per-second rates.
total( $rates)
Resolve a batch of RatePromise objects, returning their counter totals, indexed as in the input array...
setCurrentTime( $now)
Set the current time to be used in latest() etc.
The narrow interface WRStats needs into a memcached-like key-value store.