forked from llvm/phabricator
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathPhabricatorMetronome.php
92 lines (72 loc) · 2.45 KB
/
PhabricatorMetronome.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<?php
/**
* Tick at a given frequency with a specifiable offset.
*
* One use case for this is to flatten out load spikes caused by periodic
* service calls. Give each host a metronome that ticks at the same frequency,
* but with different offsets. Then, have hosts make service calls only after
* their metronome ticks. This spreads service calls out evenly more quickly
* and more predictably than adding random jitter.
*/
final class PhabricatorMetronome
extends Phobject {
private $offset = 0;
private $frequency;
public function setOffset($offset) {
if (!is_int($offset)) {
throw new Exception(pht('Metronome offset must be an integer.'));
}
if ($offset < 0) {
throw new Exception(pht('Metronome offset must be 0 or more.'));
}
// We're not requiring that the offset be smaller than the frequency. If
// the offset is larger, we'll just clamp it to the frequency before we
// use it. This allows the offset to be configured before the frequency
// is configured, which is useful for using a hostname as an offset seed.
$this->offset = $offset;
return $this;
}
public function setFrequency($frequency) {
if (!is_int($frequency)) {
throw new Exception(pht('Metronome frequency must be an integer.'));
}
if ($frequency < 1) {
throw new Exception(pht('Metronome frequency must be 1 or more.'));
}
$this->frequency = $frequency;
return $this;
}
public function setOffsetFromSeed($seed) {
$offset = PhabricatorHash::digestToRange($seed, 0, PHP_INT_MAX);
return $this->setOffset($offset);
}
public function getFrequency() {
if ($this->frequency === null) {
throw new PhutilInvalidStateException('setFrequency');
}
return $this->frequency;
}
public function getOffset() {
$frequency = $this->getFrequency();
return ($this->offset % $frequency);
}
public function getNextTickAfter($epoch) {
$frequency = $this->getFrequency();
$offset = $this->getOffset();
$remainder = ($epoch % $frequency);
if ($remainder < $offset) {
return ($epoch - $remainder) + $offset;
} else {
return ($epoch - $remainder) + $frequency + $offset;
}
}
public function didTickBetween($min, $max) {
if ($max < $min) {
throw new Exception(
pht(
'Maximum tick window must not be smaller than minimum tick window.'));
}
$next = $this->getNextTickAfter($min);
return ($next <= $max);
}
}