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