Page MenuHomestyx hydra

No OneTemporary

diff --git a/src/applications/multimeter/data/MultimeterControl.php b/src/applications/multimeter/data/MultimeterControl.php
index 9d948609a6..2bc849c185 100644
--- a/src/applications/multimeter/data/MultimeterControl.php
+++ b/src/applications/multimeter/data/MultimeterControl.php
@@ -1,207 +1,292 @@
<?php
final class MultimeterControl {
private static $instance;
private $events = array();
private $sampleRate;
private $pauseDepth;
private $eventViewer;
private $eventContext;
private function __construct() {
// Private.
}
public static function newInstance() {
$instance = new MultimeterControl();
// NOTE: We don't set the sample rate yet. This allows the multimeter to
// be initialized and begin recording events, then make a decision about
// whether the page will be sampled or not later on (once we've loaded
// enough configuration).
self::$instance = $instance;
return self::getInstance();
}
public static function getInstance() {
return self::$instance;
}
public function isActive() {
return ($this->sampleRate !== 0) && ($this->pauseDepth == 0);
}
public function setSampleRate($rate) {
if ($rate && (mt_rand(1, $rate) == $rate)) {
$sample_rate = $rate;
} else {
$sample_rate = 0;
}
$this->sampleRate = $sample_rate;
return;
}
public function pauseMultimeter() {
$this->pauseDepth++;
return $this;
}
public function unpauseMultimeter() {
if (!$this->pauseDepth) {
throw new Exception(pht('Trying to unpause an active multimeter!'));
}
$this->pauseDepth--;
return $this;
}
public function newEvent($type, $label, $cost) {
if (!$this->isActive()) {
return null;
}
$event = id(new MultimeterEvent())
->setEventType($type)
->setEventLabel($label)
->setResourceCost($cost)
->setEpoch(PhabricatorTime::getNow());
$this->events[] = $event;
return $event;
}
public function saveEvents() {
if (!$this->isActive()) {
return;
}
$events = $this->events;
if (!$events) {
return;
}
if ($this->sampleRate === null) {
throw new Exception(pht('Call setSampleRate() before saving events!'));
}
+ $this->addServiceEvents();
+
// Don't sample any of this stuff.
$this->pauseMultimeter();
$use_scope = AphrontWriteGuard::isGuardActive();
if ($use_scope) {
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
} else {
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
}
$caught = null;
try {
$this->writeEvents();
} catch (Exception $ex) {
$caught = $ex;
}
if ($use_scope) {
unset($unguarded);
} else {
AphrontWriteGuard::allowDangerousUnguardedWrites(false);
}
$this->unpauseMultimeter();
if ($caught) {
throw $caught;
}
}
private function writeEvents() {
$events = $this->events;
$random = Filesystem::readRandomBytes(32);
$request_key = PhabricatorHash::digestForIndex($random);
$host_id = $this->loadHostID(php_uname('n'));
$context_id = $this->loadEventContextID($this->eventContext);
$viewer_id = $this->loadEventViewerID($this->eventViewer);
$label_map = $this->loadEventLabelIDs(mpull($events, 'getEventLabel'));
foreach ($events as $event) {
$event
->setRequestKey($request_key)
->setSampleRate($this->sampleRate)
->setEventHostID($host_id)
->setEventContextID($context_id)
->setEventViewerID($viewer_id)
->setEventLabelID($label_map[$event->getEventLabel()])
->save();
}
}
public function setEventContext($event_context) {
$this->eventContext = $event_context;
return $this;
}
public function getEventContext() {
return $this->eventContext;
}
public function setEventViewer($viewer) {
$this->eventViewer = $viewer;
return $this;
}
private function loadHostID($host) {
$map = $this->loadDimensionMap(new MultimeterHost(), array($host));
return idx($map, $host);
}
private function loadEventViewerID($viewer) {
$map = $this->loadDimensionMap(new MultimeterViewer(), array($viewer));
return idx($map, $viewer);
}
private function loadEventContextID($context) {
$map = $this->loadDimensionMap(new MultimeterContext(), array($context));
return idx($map, $context);
}
private function loadEventLabelIDs(array $labels) {
return $this->loadDimensionMap(new MultimeterLabel(), $labels);
}
private function loadDimensionMap(MultimeterDimension $table, array $names) {
$hashes = array();
foreach ($names as $name) {
$hashes[] = PhabricatorHash::digestForIndex($name);
}
$objects = $table->loadAllWhere('nameHash IN (%Ls)', $hashes);
$map = mpull($objects, 'getID', 'getName');
$need = array();
foreach ($names as $name) {
if (isset($map[$name])) {
continue;
}
- $need[] = $name;
+ $need[$name] = $name;
}
foreach ($need as $name) {
$object = id(clone $table)
->setName($name)
->save();
$map[$name] = $object->getID();
}
return $map;
}
+ private function addServiceEvents() {
+ $events = PhutilServiceProfiler::getInstance()->getServiceCallLog();
+ foreach ($events as $event) {
+ $type = idx($event, 'type');
+ switch ($type) {
+ case 'exec':
+ $this->newEvent(
+ MultimeterEvent::TYPE_EXEC_TIME,
+ $label = $this->getLabelForCommandEvent($event['command']),
+ (1000000 * $event['duration']));
+ break;
+ }
+ }
+ }
+
+ private function getLabelForCommandEvent($command) {
+ $argv = preg_split('/\s+/', $command);
+
+ $bin = array_shift($argv);
+ $bin = basename($bin);
+ $bin = trim($bin, '"\'');
+
+ // It's important to avoid leaking details about command parameters,
+ // because some may be sensitive. Given this, it's not trivial to
+ // determine which parts of a command are arguments and which parts are
+ // flags.
+
+ // Rather than try too hard for now, just whitelist some workflows that we
+ // know about and record everything else generically. Overall, this will
+ // produce labels like "pygmentize" or "git log", discarding all flags and
+ // arguments.
+
+ $workflows = array(
+ 'git' => array(
+ 'log' => true,
+ 'for-each-ref' => true,
+ 'pull' => true,
+ 'clone' => true,
+ 'fetch' => true,
+ 'cat-file' => true,
+ 'init' => true,
+ 'config' => true,
+ 'remote' => true,
+ 'rev-parse' => true,
+ 'diff' => true,
+ 'ls-tree' => true,
+ ),
+ 'svn' => array(
+ 'log' => true,
+ 'diff' => true,
+ ),
+ 'hg' => array(
+ 'log' => true,
+ 'locate' => true,
+ 'pull' => true,
+ 'clone' => true,
+ 'init' => true,
+ 'diff' => true,
+ 'cat' => true,
+ ),
+ 'svnadmin' => array(
+ 'create' => true,
+ ),
+ );
+
+ $workflow = null;
+ $candidates = idx($workflows, $bin);
+ if ($candidates) {
+ foreach ($argv as $arg) {
+ if (isset($candidates[$arg])) {
+ $workflow = $arg;
+ break;
+ }
+ }
+ }
+
+ if ($workflow) {
+ return 'bin.'.$bin.' '.$workflow;
+ } else {
+ return 'bin.'.$bin;
+ }
+ }
+
}
diff --git a/src/applications/multimeter/storage/MultimeterEvent.php b/src/applications/multimeter/storage/MultimeterEvent.php
index 23263a6e8d..1585640fdf 100644
--- a/src/applications/multimeter/storage/MultimeterEvent.php
+++ b/src/applications/multimeter/storage/MultimeterEvent.php
@@ -1,76 +1,80 @@
<?php
final class MultimeterEvent extends MultimeterDAO {
const TYPE_STATIC_RESOURCE = 0;
const TYPE_REQUEST_TIME = 1;
+ const TYPE_EXEC_TIME = 2;
protected $eventType;
protected $eventLabelID;
protected $resourceCost;
protected $sampleRate;
protected $eventContextID;
protected $eventHostID;
protected $eventViewerID;
protected $epoch;
protected $requestKey;
private $eventLabel;
public function setEventLabel($event_label) {
$this->eventLabel = $event_label;
return $this;
}
public function getEventLabel() {
return $this->eventLabel;
}
public static function getEventTypeName($type) {
switch ($type) {
case self::TYPE_STATIC_RESOURCE:
return pht('Static Resource');
case self::TYPE_REQUEST_TIME:
return pht('Web Request');
+ case self::TYPE_EXEC_TIME:
+ return pht('Subprocesses');
}
return pht('Unknown ("%s")', $type);
}
public static function formatResourceCost(
PhabricatorUser $viewer,
$type,
$cost) {
switch ($type) {
case self::TYPE_STATIC_RESOURCE:
return pht('%s Req', new PhutilNumber($cost));
case self::TYPE_REQUEST_TIME:
+ case self::TYPE_EXEC_TIME:
return pht('%s us', new PhutilNumber($cost));
}
return pht('%s Unit(s)', new PhutilNumber($cost));
}
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'eventType' => 'uint32',
'resourceCost' => 'sint64',
'sampleRate' => 'uint32',
'requestKey' => 'bytes12',
),
self::CONFIG_KEY_SCHEMA => array(
'key_request' => array(
'columns' => array('requestKey'),
),
'key_type' => array(
'columns' => array('eventType', 'epoch'),
),
),
) + parent::getConfiguration();
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 24, 1:50 AM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
182485
Default Alt Text
(9 KB)

Event Timeline