Changeset View
Changeset View
Standalone View
Standalone View
src/infrastructure/cluster/PhabricatorDatabaseHealthRecord.php
- This file was added.
<?php | |||||
final class PhabricatorDatabaseHealthRecord | |||||
extends Phobject { | |||||
private $ref; | |||||
private $shouldCheck; | |||||
private $isHealthy; | |||||
private $upEventCount; | |||||
private $downEventCount; | |||||
public function __construct(PhabricatorDatabaseRef $ref) { | |||||
$this->ref = $ref; | |||||
$this->readState(); | |||||
} | |||||
/** | |||||
* Is the database currently healthy? | |||||
*/ | |||||
public function getIsHealthy() { | |||||
return $this->isHealthy; | |||||
} | |||||
/** | |||||
* Should this request check database health? | |||||
*/ | |||||
public function getShouldCheck() { | |||||
return $this->shouldCheck; | |||||
} | |||||
/** | |||||
* How many recent health checks were successful? | |||||
*/ | |||||
public function getUpEventCount() { | |||||
return $this->upEventCount; | |||||
} | |||||
/** | |||||
* How many recent health checks failed? | |||||
*/ | |||||
public function getDownEventCount() { | |||||
return $this->downEventCount; | |||||
} | |||||
/** | |||||
* Number of failures or successes we need to see in a row before we change | |||||
* the state. | |||||
*/ | |||||
public function getRequiredEventCount() { | |||||
return 5; | |||||
} | |||||
/** | |||||
* Seconds to wait between health checks. | |||||
*/ | |||||
public function getHealthCheckFrequency() { | |||||
return 3; | |||||
} | |||||
public function didHealthCheck($result) { | |||||
$now = microtime(true); | |||||
$check_frequency = $this->getHealthCheckFrequency(); | |||||
$event_count = $this->getRequiredEventCount(); | |||||
$record = $this->readHealthRecord(); | |||||
$log = $record['log']; | |||||
foreach ($log as $key => $event) { | |||||
$when = idx($event, 'timestamp'); | |||||
// If the log already has another nearby event, just ignore this one. | |||||
// We raced with another process and our result can just be thrown away. | |||||
if (($now - $when) <= $check_frequency) { | |||||
return $this; | |||||
} | |||||
} | |||||
$log[] = array( | |||||
'timestamp' => $now, | |||||
'up' => $result, | |||||
); | |||||
// Throw away older events which are now obsolete. | |||||
$log = array_slice($log, -$event_count); | |||||
$count_up = 0; | |||||
$count_down = 0; | |||||
foreach ($log as $event) { | |||||
if ($event['up']) { | |||||
$count_up++; | |||||
} else { | |||||
$count_down++; | |||||
} | |||||
} | |||||
// If all of the events are the same, change the state. | |||||
if ($count_up == $event_count) { | |||||
$record['up'] = true; | |||||
} else if ($count_down == $event_count) { | |||||
$record['up'] = false; | |||||
} | |||||
$record['log'] = $log; | |||||
$this->writeHealthRecord($record); | |||||
$this->isHealthy = $record['up']; | |||||
$this->shouldCheck = false; | |||||
$this->updateStatistics($record); | |||||
return $this; | |||||
} | |||||
private function readState() { | |||||
$now = microtime(true); | |||||
$check_frequency = $this->getHealthCheckFrequency(); | |||||
$record = $this->readHealthRecord(); | |||||
$last_check = $record['lastCheck']; | |||||
if (($now - $last_check) >= $check_frequency) { | |||||
$record['lastCheck'] = $now; | |||||
$this->writeHealthRecord($record); | |||||
$this->shouldCheck = true; | |||||
} else { | |||||
$this->shouldCheck = false; | |||||
} | |||||
$this->isHealthy = $record['up']; | |||||
$this->updateStatistics($record); | |||||
} | |||||
private function updateStatistics(array $record) { | |||||
$this->upEventCount = 0; | |||||
$this->downEventCount = 0; | |||||
foreach ($record['log'] as $event) { | |||||
if ($event['up']) { | |||||
$this->upEventCount++; | |||||
} else { | |||||
$this->downEventCount++; | |||||
} | |||||
} | |||||
} | |||||
private function getHealthRecordCacheKey() { | |||||
$ref = $this->ref; | |||||
$host = $ref->getHost(); | |||||
$port = $ref->getPort(); | |||||
return "cluster.db.health({$host}, {$port})"; | |||||
} | |||||
private function readHealthRecord() { | |||||
$cache = PhabricatorCaches::getSetupCache(); | |||||
$cache_key = $this->getHealthRecordCacheKey(); | |||||
$health_record = $cache->getKey($cache_key); | |||||
if (!is_array($health_record)) { | |||||
$health_record = array( | |||||
'up' => true, | |||||
'lastCheck' => 0, | |||||
'log' => array(), | |||||
); | |||||
} | |||||
return $health_record; | |||||
} | |||||
private function writeHealthRecord(array $record) { | |||||
$cache = PhabricatorCaches::getSetupCache(); | |||||
$cache_key = $this->getHealthRecordCacheKey(); | |||||
$cache->setKey($cache_key, $record); | |||||
} | |||||
} |