Path: blob/master/src/infrastructure/util/PhabricatorMetronome.php
12241 views
<?php12/**3* Tick at a given frequency with a specifiable offset.4*5* One use case for this is to flatten out load spikes caused by periodic6* service calls. Give each host a metronome that ticks at the same frequency,7* but with different offsets. Then, have hosts make service calls only after8* their metronome ticks. This spreads service calls out evenly more quickly9* and more predictably than adding random jitter.10*/11final class PhabricatorMetronome12extends Phobject {1314private $offset = 0;15private $frequency;1617public function setOffset($offset) {18if (!is_int($offset)) {19throw new Exception(pht('Metronome offset must be an integer.'));20}2122if ($offset < 0) {23throw new Exception(pht('Metronome offset must be 0 or more.'));24}2526// We're not requiring that the offset be smaller than the frequency. If27// the offset is larger, we'll just clamp it to the frequency before we28// use it. This allows the offset to be configured before the frequency29// is configured, which is useful for using a hostname as an offset seed.3031$this->offset = $offset;3233return $this;34}3536public function setFrequency($frequency) {37if (!is_int($frequency)) {38throw new Exception(pht('Metronome frequency must be an integer.'));39}4041if ($frequency < 1) {42throw new Exception(pht('Metronome frequency must be 1 or more.'));43}4445$this->frequency = $frequency;4647return $this;48}4950public function setOffsetFromSeed($seed) {51$offset = PhabricatorHash::digestToRange($seed, 0, 0x7FFFFFFF);52return $this->setOffset($offset);53}5455public function getFrequency() {56if ($this->frequency === null) {57throw new PhutilInvalidStateException('setFrequency');58}59return $this->frequency;60}6162public function getOffset() {63$frequency = $this->getFrequency();64return ($this->offset % $frequency);65}6667public function getNextTickAfter($epoch) {68$frequency = $this->getFrequency();69$offset = $this->getOffset();7071$remainder = ($epoch % $frequency);7273if ($remainder < $offset) {74return ($epoch - $remainder) + $offset;75} else {76return ($epoch - $remainder) + $frequency + $offset;77}78}7980public function didTickBetween($min, $max) {81if ($max < $min) {82throw new Exception(83pht(84'Maximum tick window must not be smaller than minimum tick window.'));85}8687$next = $this->getNextTickAfter($min);88return ($next <= $max);89}9091}929394