@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
fork

Configure Feed

Select the types of activity you want to include in your feed.

Skeleton for "Multimeter", a performance sampling application

Summary:
Ref T6930. This application collects and displays performance samples -- roughly, things Phabricator spent some kind of resource on. It will collect samples on different types of resources and events:

- Wall time (queries, service calls, pages)
- Bytes In / Bytes Out (requests)
- Implicit requests to CSS/JS (static resources)

I've started with the simplest case (static resources), since this can be used in an immediate, straghtforward way to improve packaging (look at which individual files have the most requests recently).

There's no aggregation yet and a lot of the data isn't collected properly. Future diffs will add more dimension data (controllers, users), more event and resource types (queries, service calls, wall time), and more display options (aggregation, sorting).

Test Plan: {F389344}

Reviewers: btrahan

Reviewed By: btrahan

Subscribers: epriestley

Maniphest Tasks: T6930

Differential Revision: https://secure.phabricator.com/D12623

+641
+14
resources/sql/autopatches/20150430.multimeter.1.sql
··· 1 + CREATE TABLE {$NAMESPACE}_multimeter.multimeter_event ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + eventType INT UNSIGNED NOT NULL, 4 + eventLabelID INT UNSIGNED NOT NULL, 5 + resourceCost BIGINT NOT NULL, 6 + sampleRate INT UNSIGNED NOT NULL, 7 + eventContextID INT UNSIGNED NOT NULL, 8 + eventHostID INT UNSIGNED NOT NULL, 9 + eventViewerID INT UNSIGNED NOT NULL, 10 + epoch INT UNSIGNED NOT NULL, 11 + requestKey BINARY(12) NOT NULL, 12 + KEY `key_request` (requestKey), 13 + KEY `key_type` (eventType, epoch) 14 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+6
resources/sql/autopatches/20150430.multimeter.2.host.sql
··· 1 + CREATE TABLE {$NAMESPACE}_multimeter.multimeter_host ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + name LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 4 + nameHash BINARY(12) NOT NULL, 5 + UNIQUE KEY `key_hash` (nameHash) 6 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+6
resources/sql/autopatches/20150430.multimeter.3.viewer.sql
··· 1 + CREATE TABLE {$NAMESPACE}_multimeter.multimeter_viewer ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + name LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 4 + nameHash BINARY(12) NOT NULL, 5 + UNIQUE KEY `key_hash` (nameHash) 6 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+6
resources/sql/autopatches/20150430.multimeter.4.context.sql
··· 1 + CREATE TABLE {$NAMESPACE}_multimeter.multimeter_context ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + name LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 4 + nameHash BINARY(12) NOT NULL, 5 + UNIQUE KEY `key_hash` (nameHash) 6 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+6
resources/sql/autopatches/20150430.multimeter.5.label.sql
··· 1 + CREATE TABLE {$NAMESPACE}_multimeter.multimeter_label ( 2 + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, 3 + name LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, 4 + nameHash BINARY(12) NOT NULL, 5 + UNIQUE KEY `key_hash` (nameHash) 6 + ) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+23
src/__phutil_library_map__.php
··· 1080 1080 'MetaMTAMailSentGarbageCollector' => 'applications/metamta/garbagecollector/MetaMTAMailSentGarbageCollector.php', 1081 1081 'MetaMTANotificationType' => 'applications/metamta/constants/MetaMTANotificationType.php', 1082 1082 'MetaMTAReceivedMailStatus' => 'applications/metamta/constants/MetaMTAReceivedMailStatus.php', 1083 + 'MultimeterContext' => 'applications/multimeter/storage/MultimeterContext.php', 1084 + 'MultimeterControl' => 'applications/multimeter/data/MultimeterControl.php', 1085 + 'MultimeterController' => 'applications/multimeter/controller/MultimeterController.php', 1086 + 'MultimeterDAO' => 'applications/multimeter/storage/MultimeterDAO.php', 1087 + 'MultimeterDimension' => 'applications/multimeter/storage/MultimeterDimension.php', 1088 + 'MultimeterEvent' => 'applications/multimeter/storage/MultimeterEvent.php', 1089 + 'MultimeterEventGarbageCollector' => 'applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php', 1090 + 'MultimeterHost' => 'applications/multimeter/storage/MultimeterHost.php', 1091 + 'MultimeterLabel' => 'applications/multimeter/storage/MultimeterLabel.php', 1092 + 'MultimeterSampleController' => 'applications/multimeter/controller/MultimeterSampleController.php', 1093 + 'MultimeterViewer' => 'applications/multimeter/storage/MultimeterViewer.php', 1083 1094 'NuanceConduitAPIMethod' => 'applications/nuance/conduit/NuanceConduitAPIMethod.php', 1084 1095 'NuanceController' => 'applications/nuance/controller/NuanceController.php', 1085 1096 'NuanceCreateItemConduitAPIMethod' => 'applications/nuance/conduit/NuanceCreateItemConduitAPIMethod.php', ··· 2062 2073 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 2063 2074 'PhabricatorMultiColumnUIExample' => 'applications/uiexample/examples/PhabricatorMultiColumnUIExample.php', 2064 2075 'PhabricatorMultiFactorSettingsPanel' => 'applications/settings/panel/PhabricatorMultiFactorSettingsPanel.php', 2076 + 'PhabricatorMultimeterApplication' => 'applications/multimeter/application/PhabricatorMultimeterApplication.php', 2065 2077 'PhabricatorMustVerifyEmailController' => 'applications/auth/controller/PhabricatorMustVerifyEmailController.php', 2066 2078 'PhabricatorMySQLConfigOptions' => 'applications/config/option/PhabricatorMySQLConfigOptions.php', 2067 2079 'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/PhabricatorMySQLFileStorageEngine.php', ··· 4381 4393 'MetaMTAMailSentGarbageCollector' => 'PhabricatorGarbageCollector', 4382 4394 'MetaMTANotificationType' => 'MetaMTAConstants', 4383 4395 'MetaMTAReceivedMailStatus' => 'MetaMTAConstants', 4396 + 'MultimeterContext' => 'MultimeterDimension', 4397 + 'MultimeterController' => 'PhabricatorController', 4398 + 'MultimeterDAO' => 'PhabricatorLiskDAO', 4399 + 'MultimeterDimension' => 'MultimeterDAO', 4400 + 'MultimeterEvent' => 'MultimeterDAO', 4401 + 'MultimeterEventGarbageCollector' => 'PhabricatorGarbageCollector', 4402 + 'MultimeterHost' => 'MultimeterDimension', 4403 + 'MultimeterLabel' => 'MultimeterDimension', 4404 + 'MultimeterSampleController' => 'MultimeterController', 4405 + 'MultimeterViewer' => 'MultimeterDimension', 4384 4406 'NuanceConduitAPIMethod' => 'ConduitAPIMethod', 4385 4407 'NuanceController' => 'PhabricatorController', 4386 4408 'NuanceCreateItemConduitAPIMethod' => 'NuanceConduitAPIMethod', ··· 5443 5465 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 5444 5466 'PhabricatorMultiColumnUIExample' => 'PhabricatorUIExample', 5445 5467 'PhabricatorMultiFactorSettingsPanel' => 'PhabricatorSettingsPanel', 5468 + 'PhabricatorMultimeterApplication' => 'PhabricatorApplication', 5446 5469 'PhabricatorMustVerifyEmailController' => 'PhabricatorAuthController', 5447 5470 'PhabricatorMySQLConfigOptions' => 'PhabricatorApplicationConfigOptions', 5448 5471 'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
+9
src/aphront/configuration/AphrontApplicationConfiguration.php
··· 58 58 * @phutil-external-symbol class PhabricatorStartup 59 59 */ 60 60 public static function runHTTPRequest(AphrontHTTPSink $sink) { 61 + $multimeter = MultimeterControl::newInstance(); 62 + $multimeter->setEventContext('<http-init>'); 63 + $multimeter->setEventViewer('<none>'); 64 + 61 65 PhabricatorEnv::initializeWebEnvironment(); 66 + 67 + $multimeter->setSampleRate( 68 + PhabricatorEnv::getEnvConfig('debug.sample-rate')); 62 69 63 70 $debug_time_limit = PhabricatorEnv::getEnvConfig('debug.time-limit'); 64 71 if ($debug_time_limit) { ··· 134 141 )); 135 142 136 143 $access_log->write(); 144 + 145 + $multimeter->saveEvents(); 137 146 138 147 DarkConsoleXHProfPluginAPI::saveProfilerSample($access_log); 139 148
+3
src/applications/celerity/CelerityStaticResourceResponse.php
··· 144 144 $uri = $this->getURI($map, $name); 145 145 $type = $map->getResourceTypeForName($name); 146 146 147 + $event_type = MultimeterEvent::TYPE_STATIC_RESOURCE; 148 + MultimeterControl::getInstance()->newEvent($event_type, 'rsrc.'.$name, 1); 149 + 147 150 switch ($type) { 148 151 case 'css': 149 152 return phutil_tag(
+21
src/applications/config/option/PhabricatorDeveloperConfigOptions.php
··· 118 118 "data to look at eventually). In development, it may be useful to ". 119 119 "set it to 1 in order to debug performance problems.\n\n". 120 120 "NOTE: You must install XHProf for profiling to work.")), 121 + $this->newOption('debug.sample-rate', 'int', 1000) 122 + ->setLocked(true) 123 + ->addExample(0, pht('No performance sampling.')) 124 + ->addExample(1, pht('Sample every request (slow).')) 125 + ->addExample(1000, pht('Sample 0.1%% of requests.')) 126 + ->setSummary(pht('Automatically sample some fraction of requests.')) 127 + ->setDescription( 128 + pht( 129 + "The Multimeter application collects performance samples. You ". 130 + "can use this data to help you understand what Phabricator is ". 131 + "spending time and resources doing, and to identify problematic ". 132 + "access patterns.". 133 + "\n\n". 134 + "This option controls how frequently sampling activates. Set it ". 135 + "to some positive integer N to sample every 1 / N pages.". 136 + "\n\n". 137 + "For most installs, the default value (1 sample per 1000 pages) ". 138 + "should collect enough data to be useful without requiring much ". 139 + "storage or meaningfully impacting performance. If you're ". 140 + "investigating performance issues, you can adjust the rate ". 141 + "in order to collect more data.")), 121 142 $this->newOption('phabricator.developer-mode', 'bool', false) 122 143 ->setBoolOptions( 123 144 array(
+46
src/applications/multimeter/application/PhabricatorMultimeterApplication.php
··· 1 + <?php 2 + 3 + final class PhabricatorMultimeterApplication 4 + extends PhabricatorApplication { 5 + 6 + public function getName() { 7 + return pht('Multimeter'); 8 + } 9 + 10 + public function getBaseURI() { 11 + return '/multimeter/'; 12 + } 13 + 14 + public function getFontIcon() { 15 + return 'fa-motorcycle'; 16 + } 17 + 18 + public function isPrototype() { 19 + return true; 20 + } 21 + 22 + public function getTitleGlyph() { 23 + return "\xE2\x8F\xB3"; 24 + } 25 + 26 + public function getApplicationGroup() { 27 + return self::GROUP_DEVELOPER; 28 + } 29 + 30 + public function getShortDescription() { 31 + return pht('Performance Sampler'); 32 + } 33 + 34 + public function getRemarkupRules() { 35 + return array(); 36 + } 37 + 38 + public function getRoutes() { 39 + return array( 40 + '/multimeter/' => array( 41 + '' => 'MultimeterSampleController', 42 + ), 43 + ); 44 + } 45 + 46 + }
+70
src/applications/multimeter/controller/MultimeterController.php
··· 1 + <?php 2 + 3 + abstract class MultimeterController extends PhabricatorController { 4 + 5 + private $dimensions = array(); 6 + 7 + protected function loadDimensions(array $rows) { 8 + if (!$rows) { 9 + return; 10 + } 11 + 12 + $map = array( 13 + 'eventLabelID' => new MultimeterLabel(), 14 + 'eventViewerID' => new MultimeterViewer(), 15 + 'eventHostID' => new MultimeterHost(), 16 + 'eventContextID' => new MultimeterContext(), 17 + ); 18 + 19 + $ids = array(); 20 + foreach ($map as $key => $object) { 21 + foreach ($rows as $row) { 22 + $ids[$key][] = $row[$key]; 23 + } 24 + } 25 + 26 + foreach ($ids as $key => $list) { 27 + $object = $map[$key]; 28 + if (empty($this->dimensions[$key])) { 29 + $this->dimensions[$key] = array(); 30 + } 31 + $this->dimensions[$key] += $object->loadAllWhere( 32 + 'id IN (%Ld)', 33 + $list); 34 + } 35 + } 36 + 37 + protected function getLabelDimension($id) { 38 + if (empty($this->dimensions['eventLabelID'][$id])) { 39 + return $this->newMissingDimension(new MultimeterLabel(), $id); 40 + } 41 + return $this->dimensions['eventLabelID'][$id]; 42 + } 43 + 44 + protected function getViewerDimension($id) { 45 + if (empty($this->dimensions['eventViewerID'][$id])) { 46 + return $this->newMissingDimension(new MultimeterViewer(), $id); 47 + } 48 + return $this->dimensions['eventViewerID'][$id]; 49 + } 50 + 51 + protected function getHostDimension($id) { 52 + if (empty($this->dimensions['eventHostID'][$id])) { 53 + return $this->newMissingDimension(new MultimeterHost(), $id); 54 + } 55 + return $this->dimensions['eventHostID'][$id]; 56 + } 57 + 58 + protected function getContextDimension($id) { 59 + if (empty($this->dimensions['eventContextID'][$id])) { 60 + return $this->newMissingDimension(new MultimeterContext(), $id); 61 + } 62 + return $this->dimensions['eventContextID'][$id]; 63 + } 64 + 65 + private function newMissingDimension(MultimeterDimension $dim, $id) { 66 + $dim->setName('<missing:'.$id.'>'); 67 + return $dim; 68 + } 69 + 70 + }
+85
src/applications/multimeter/controller/MultimeterSampleController.php
··· 1 + <?php 2 + 3 + final class MultimeterSampleController extends MultimeterController { 4 + 5 + public function shouldAllowPublic() { 6 + return true; 7 + } 8 + 9 + public function handleRequest(AphrontRequest $request) { 10 + $viewer = $this->getViewer(); 11 + 12 + $table = new MultimeterEvent(); 13 + $conn = $table->establishConnection('r'); 14 + $data = queryfx_all( 15 + $conn, 16 + 'SELECT * FROM %T ORDER BY id DESC LIMIT 100', 17 + $table->getTableName()); 18 + 19 + $this->loadDimensions($data); 20 + 21 + $rows = array(); 22 + foreach ($data as $row) { 23 + $rows[] = array( 24 + $row['id'], 25 + $row['requestKey'], 26 + $this->getViewerDimension($row['eventViewerID'])->getName(), 27 + $this->getContextDimension($row['eventContextID'])->getName(), 28 + $this->getHostDimension($row['eventHostID'])->getName(), 29 + MultimeterEvent::getEventTypeName($row['eventType']), 30 + $this->getLabelDimension($row['eventLabelID'])->getName(), 31 + MultimeterEvent::formatResourceCost( 32 + $viewer, 33 + $row['eventType'], 34 + $row['resourceCost']), 35 + $row['sampleRate'], 36 + phabricator_datetime($row['epoch'], $viewer), 37 + ); 38 + } 39 + 40 + $table = id(new AphrontTableView($rows)) 41 + ->setHeaders( 42 + array( 43 + pht('ID'), 44 + pht('Request'), 45 + pht('Viewer'), 46 + pht('Context'), 47 + pht('Host'), 48 + pht('Type'), 49 + pht('Label'), 50 + pht('Cost'), 51 + pht('Rate'), 52 + pht('Epoch'), 53 + )) 54 + ->setColumnClasses( 55 + array( 56 + null, 57 + null, 58 + null, 59 + null, 60 + null, 61 + null, 62 + 'wide', 63 + 'n', 64 + 'n', 65 + null, 66 + )); 67 + 68 + $box = id(new PHUIObjectBoxView()) 69 + ->setHeaderText(pht('Samples')) 70 + ->appendChild($table); 71 + 72 + $crumbs = $this->buildApplicationCrumbs(); 73 + $crumbs->addTextCrumb(pht('Samples')); 74 + 75 + return $this->buildApplicationPage( 76 + array( 77 + $crumbs, 78 + $box, 79 + ), 80 + array( 81 + 'title' => pht('Samples'), 82 + )); 83 + } 84 + 85 + }
+203
src/applications/multimeter/data/MultimeterControl.php
··· 1 + <?php 2 + 3 + final class MultimeterControl { 4 + 5 + private static $instance; 6 + 7 + private $events = array(); 8 + private $sampleRate; 9 + private $pauseDepth; 10 + 11 + private $eventViewer; 12 + private $eventContext; 13 + 14 + private function __construct() { 15 + // Private. 16 + } 17 + 18 + public static function newInstance() { 19 + $instance = new MultimeterControl(); 20 + 21 + // NOTE: We don't set the sample rate yet. This allows the multimeter to 22 + // be initialized and begin recording events, then make a decision about 23 + // whether the page will be sampled or not later on (once we've loaded 24 + // enough configuration). 25 + 26 + self::$instance = $instance; 27 + return self::getInstance(); 28 + } 29 + 30 + public static function getInstance() { 31 + return self::$instance; 32 + } 33 + 34 + public function isActive() { 35 + return ($this->sampleRate !== 0) && ($this->pauseDepth == 0); 36 + } 37 + 38 + public function setSampleRate($rate) { 39 + if ($rate && (mt_rand(1, $rate) == $rate)) { 40 + $sample_rate = $rate; 41 + } else { 42 + $sample_rate = 0; 43 + } 44 + 45 + $this->sampleRate = $sample_rate; 46 + 47 + return; 48 + } 49 + 50 + public function pauseMultimeter() { 51 + $this->pauseDepth++; 52 + return $this; 53 + } 54 + 55 + public function unpauseMultimeter() { 56 + if (!$this->pauseDepth) { 57 + throw new Exception(pht('Trying to unpause an active multimeter!')); 58 + } 59 + $this->pauseDepth--; 60 + return $this; 61 + } 62 + 63 + 64 + public function newEvent($type, $label, $cost) { 65 + if (!$this->isActive()) { 66 + return null; 67 + } 68 + 69 + $event = id(new MultimeterEvent()) 70 + ->setEventType($type) 71 + ->setEventLabel($label) 72 + ->setResourceCost($cost) 73 + ->setEpoch(PhabricatorTime::getNow()); 74 + 75 + $this->events[] = $event; 76 + 77 + return $event; 78 + } 79 + 80 + public function saveEvents() { 81 + if (!$this->isActive()) { 82 + return; 83 + } 84 + 85 + $events = $this->events; 86 + if (!$events) { 87 + return; 88 + } 89 + 90 + if ($this->sampleRate === null) { 91 + throw new Exception(pht('Call setSampleRate() before saving events!')); 92 + } 93 + 94 + // Don't sample any of this stuff. 95 + $this->pauseMultimeter(); 96 + 97 + $use_scope = AphrontWriteGuard::isGuardActive(); 98 + if ($use_scope) { 99 + $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); 100 + } else { 101 + AphrontWriteGuard::allowDangerousUnguardedWrites(true); 102 + } 103 + 104 + $caught = null; 105 + try { 106 + $this->writeEvents(); 107 + } catch (Exception $ex) { 108 + $caught = $ex; 109 + } 110 + 111 + if ($use_scope) { 112 + unset($unguarded); 113 + } else { 114 + AphrontWriteGuard::allowDangerousUnguardedWrites(false); 115 + } 116 + 117 + $this->unpauseMultimeter(); 118 + 119 + if ($caught) { 120 + throw $caught; 121 + } 122 + } 123 + 124 + private function writeEvents() { 125 + $events = $this->events; 126 + 127 + $random = Filesystem::readRandomBytes(32); 128 + $request_key = PhabricatorHash::digestForIndex($random); 129 + 130 + $host_id = $this->loadHostID(php_uname('n')); 131 + $context_id = $this->loadEventContextID($this->eventContext); 132 + $viewer_id = $this->loadEventViewerID($this->eventViewer); 133 + $label_map = $this->loadEventLabelIDs(mpull($events, 'getEventLabel')); 134 + 135 + foreach ($events as $event) { 136 + $event 137 + ->setRequestKey($request_key) 138 + ->setSampleRate($this->sampleRate) 139 + ->setEventHostID($host_id) 140 + ->setEventContextID($context_id) 141 + ->setEventViewerID($viewer_id) 142 + ->setEventLabelID($label_map[$event->getEventLabel()]) 143 + ->save(); 144 + } 145 + } 146 + 147 + public function setEventContext($event_context) { 148 + $this->eventContext = $event_context; 149 + return $this; 150 + } 151 + 152 + public function setEventViewer($viewer) { 153 + $this->eventViewer = $viewer; 154 + return $this; 155 + } 156 + 157 + private function loadHostID($host) { 158 + $map = $this->loadDimensionMap(new MultimeterHost(), array($host)); 159 + return idx($map, $host); 160 + } 161 + 162 + private function loadEventViewerID($viewer) { 163 + $map = $this->loadDimensionMap(new MultimeterViewer(), array($viewer)); 164 + return idx($map, $viewer); 165 + } 166 + 167 + private function loadEventContextID($context) { 168 + $map = $this->loadDimensionMap(new MultimeterContext(), array($context)); 169 + return idx($map, $context); 170 + } 171 + 172 + private function loadEventLabelIDs(array $labels) { 173 + return $this->loadDimensionMap(new MultimeterLabel(), $labels); 174 + } 175 + 176 + private function loadDimensionMap(MultimeterDimension $table, array $names) { 177 + $hashes = array(); 178 + foreach ($names as $name) { 179 + $hashes[] = PhabricatorHash::digestForIndex($name); 180 + } 181 + 182 + $objects = $table->loadAllWhere('nameHash IN (%Ls)', $hashes); 183 + $map = mpull($objects, 'getID', 'getName'); 184 + 185 + $need = array(); 186 + foreach ($names as $name) { 187 + if (isset($map[$name])) { 188 + continue; 189 + } 190 + $need[] = $name; 191 + } 192 + 193 + foreach ($need as $name) { 194 + $object = id(clone $table) 195 + ->setName($name) 196 + ->save(); 197 + $map[$name] = $object->getID(); 198 + } 199 + 200 + return $map; 201 + } 202 + 203 + }
+21
src/applications/multimeter/garbagecollector/MultimeterEventGarbageCollector.php
··· 1 + <?php 2 + 3 + final class MultimeterEventGarbageCollector 4 + extends PhabricatorGarbageCollector { 5 + 6 + public function collectGarbage() { 7 + $ttl = phutil_units('90 days in seconds'); 8 + 9 + $table = new MultimeterEvent(); 10 + $conn_w = $table->establishConnection('w'); 11 + 12 + queryfx( 13 + $conn_w, 14 + 'DELETE FROM %T WHERE epoch < %d LIMIT 100', 15 + $table->getTableName(), 16 + PhabricatorTime::getNow() - $ttl); 17 + 18 + return ($conn_w->getAffectedRows() == 100); 19 + } 20 + 21 + }
+3
src/applications/multimeter/storage/MultimeterContext.php
··· 1 + <?php 2 + 3 + final class MultimeterContext extends MultimeterDimension {}
+9
src/applications/multimeter/storage/MultimeterDAO.php
··· 1 + <?php 2 + 3 + abstract class MultimeterDAO extends PhabricatorLiskDAO { 4 + 5 + public function getApplicationName() { 6 + return 'multimeter'; 7 + } 8 + 9 + }
+29
src/applications/multimeter/storage/MultimeterDimension.php
··· 1 + <?php 2 + 3 + abstract class MultimeterDimension extends MultimeterDAO { 4 + 5 + protected $name; 6 + protected $nameHash; 7 + 8 + public function setName($name) { 9 + $this->nameHash = PhabricatorHash::digestForIndex($name); 10 + return parent::setName($name); 11 + } 12 + 13 + protected function getConfiguration() { 14 + return array( 15 + self::CONFIG_TIMESTAMPS => false, 16 + self::CONFIG_COLUMN_SCHEMA => array( 17 + 'name' => 'text', 18 + 'nameHash' => 'bytes12', 19 + ), 20 + self::CONFIG_KEY_SCHEMA => array( 21 + 'key_hash' => array( 22 + 'columns' => array('nameHash'), 23 + 'unique' => true, 24 + ), 25 + ), 26 + ) + parent::getConfiguration(); 27 + } 28 + 29 + }
+71
src/applications/multimeter/storage/MultimeterEvent.php
··· 1 + <?php 2 + 3 + final class MultimeterEvent extends MultimeterDAO { 4 + 5 + const TYPE_STATIC_RESOURCE = 0; 6 + 7 + protected $eventType; 8 + protected $eventLabelID; 9 + protected $resourceCost; 10 + protected $sampleRate; 11 + protected $eventContextID; 12 + protected $eventHostID; 13 + protected $eventViewerID; 14 + protected $epoch; 15 + protected $requestKey; 16 + 17 + private $eventLabel; 18 + 19 + public function setEventLabel($event_label) { 20 + $this->eventLabel = $event_label; 21 + return $this; 22 + } 23 + 24 + public function getEventLabel() { 25 + return $this->eventLabel; 26 + } 27 + 28 + public static function getEventTypeName($type) { 29 + switch ($type) { 30 + case self::TYPE_STATIC_RESOURCE: 31 + return pht('Static Resource'); 32 + } 33 + 34 + return pht('Unknown ("%s")', $type); 35 + } 36 + 37 + public static function formatResourceCost( 38 + PhabricatorUser $viewer, 39 + $type, 40 + $cost) { 41 + 42 + switch ($type) { 43 + case self::TYPE_STATIC_RESOURCE: 44 + return pht('%s Req', new PhutilNumber($cost)); 45 + } 46 + 47 + return pht('%s Unit(s)', new PhutilNumber($cost)); 48 + } 49 + 50 + 51 + protected function getConfiguration() { 52 + return array( 53 + self::CONFIG_TIMESTAMPS => false, 54 + self::CONFIG_COLUMN_SCHEMA => array( 55 + 'eventType' => 'uint32', 56 + 'resourceCost' => 'sint64', 57 + 'sampleRate' => 'uint32', 58 + 'requestKey' => 'bytes12', 59 + ), 60 + self::CONFIG_KEY_SCHEMA => array( 61 + 'key_request' => array( 62 + 'columns' => array('requestKey'), 63 + ), 64 + 'key_type' => array( 65 + 'columns' => array('eventType', 'epoch'), 66 + ), 67 + ), 68 + ) + parent::getConfiguration(); 69 + } 70 + 71 + }
+3
src/applications/multimeter/storage/MultimeterHost.php
··· 1 + <?php 2 + 3 + final class MultimeterHost extends MultimeterDimension {}
+3
src/applications/multimeter/storage/MultimeterLabel.php
··· 1 + <?php 2 + 3 + final class MultimeterLabel extends MultimeterDimension {}
+3
src/applications/multimeter/storage/MultimeterViewer.php
··· 1 + <?php 2 + 3 + final class MultimeterViewer extends MultimeterDimension {}
+1
src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
··· 104 104 'db.system' => array(), 105 105 'db.fund' => array(), 106 106 'db.almanac' => array(), 107 + 'db.multimeter' => array(), 107 108 '0000.legacy.sql' => array( 108 109 'legacy' => 0, 109 110 ),