Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/multimeter/data/MultimeterControl.php
12256 views
1
<?php
2
3
final class MultimeterControl extends Phobject {
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 PhutilInvalidStateException('setSampleRate');
92
}
93
94
$this->addServiceEvents();
95
96
// Don't sample any of this stuff.
97
$this->pauseMultimeter();
98
99
$use_scope = AphrontWriteGuard::isGuardActive();
100
if ($use_scope) {
101
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
102
} else {
103
AphrontWriteGuard::allowDangerousUnguardedWrites(true);
104
}
105
106
$caught = null;
107
try {
108
$this->writeEvents();
109
} catch (Exception $ex) {
110
$caught = $ex;
111
}
112
113
if ($use_scope) {
114
unset($unguarded);
115
} else {
116
AphrontWriteGuard::allowDangerousUnguardedWrites(false);
117
}
118
119
$this->unpauseMultimeter();
120
121
if ($caught) {
122
throw $caught;
123
}
124
}
125
126
private function writeEvents() {
127
if (PhabricatorEnv::isReadOnly()) {
128
return;
129
}
130
131
$events = $this->events;
132
133
$random = Filesystem::readRandomBytes(32);
134
$request_key = PhabricatorHash::digestForIndex($random);
135
136
$host_id = $this->loadHostID(php_uname('n'));
137
$context_id = $this->loadEventContextID($this->eventContext);
138
$viewer_id = $this->loadEventViewerID($this->eventViewer);
139
$label_map = $this->loadEventLabelIDs(mpull($events, 'getEventLabel'));
140
141
foreach ($events as $event) {
142
$event
143
->setRequestKey($request_key)
144
->setSampleRate($this->sampleRate)
145
->setEventHostID($host_id)
146
->setEventContextID($context_id)
147
->setEventViewerID($viewer_id)
148
->setEventLabelID($label_map[$event->getEventLabel()])
149
->save();
150
}
151
}
152
153
public function setEventContext($event_context) {
154
$this->eventContext = $event_context;
155
return $this;
156
}
157
158
public function getEventContext() {
159
return $this->eventContext;
160
}
161
162
public function setEventViewer($viewer) {
163
$this->eventViewer = $viewer;
164
return $this;
165
}
166
167
private function loadHostID($host) {
168
$map = $this->loadDimensionMap(new MultimeterHost(), array($host));
169
return idx($map, $host);
170
}
171
172
private function loadEventViewerID($viewer) {
173
$map = $this->loadDimensionMap(new MultimeterViewer(), array($viewer));
174
return idx($map, $viewer);
175
}
176
177
private function loadEventContextID($context) {
178
$map = $this->loadDimensionMap(new MultimeterContext(), array($context));
179
return idx($map, $context);
180
}
181
182
private function loadEventLabelIDs(array $labels) {
183
return $this->loadDimensionMap(new MultimeterLabel(), $labels);
184
}
185
186
private function loadDimensionMap(MultimeterDimension $table, array $names) {
187
$hashes = array();
188
foreach ($names as $name) {
189
$hashes[] = PhabricatorHash::digestForIndex($name);
190
}
191
192
$objects = $table->loadAllWhere('nameHash IN (%Ls)', $hashes);
193
$map = mpull($objects, 'getID', 'getName');
194
195
$need = array();
196
foreach ($names as $name) {
197
if (isset($map[$name])) {
198
continue;
199
}
200
$need[$name] = $name;
201
}
202
203
foreach ($need as $name) {
204
$object = id(clone $table)
205
->setName($name)
206
->save();
207
$map[$name] = $object->getID();
208
}
209
210
return $map;
211
}
212
213
private function addServiceEvents() {
214
$events = PhutilServiceProfiler::getInstance()->getServiceCallLog();
215
foreach ($events as $event) {
216
$type = idx($event, 'type');
217
switch ($type) {
218
case 'exec':
219
$this->newEvent(
220
MultimeterEvent::TYPE_EXEC_TIME,
221
$label = $this->getLabelForCommandEvent($event['command']),
222
(1000000 * $event['duration']));
223
break;
224
}
225
}
226
}
227
228
private function getLabelForCommandEvent($command) {
229
$argv = preg_split('/\s+/', $command);
230
231
$bin = array_shift($argv);
232
$bin = basename($bin);
233
$bin = trim($bin, '"\'');
234
235
// It's important to avoid leaking details about command parameters,
236
// because some may be sensitive. Given this, it's not trivial to
237
// determine which parts of a command are arguments and which parts are
238
// flags.
239
240
// Rather than try too hard for now, just whitelist some workflows that we
241
// know about and record everything else generically. Overall, this will
242
// produce labels like "pygmentize" or "git log", discarding all flags and
243
// arguments.
244
245
$workflows = array(
246
'git' => array(
247
'log' => true,
248
'for-each-ref' => true,
249
'pull' => true,
250
'clone' => true,
251
'fetch' => true,
252
'cat-file' => true,
253
'init' => true,
254
'config' => true,
255
'remote' => true,
256
'rev-parse' => true,
257
'diff' => true,
258
'ls-tree' => true,
259
),
260
'svn' => array(
261
'log' => true,
262
'diff' => true,
263
),
264
'hg' => array(
265
'log' => true,
266
'locate' => true,
267
'pull' => true,
268
'clone' => true,
269
'init' => true,
270
'diff' => true,
271
'cat' => true,
272
'files' => true,
273
),
274
'svnadmin' => array(
275
'create' => true,
276
),
277
);
278
279
$workflow = null;
280
$candidates = idx($workflows, $bin);
281
if ($candidates) {
282
foreach ($argv as $arg) {
283
if (isset($candidates[$arg])) {
284
$workflow = $arg;
285
break;
286
}
287
}
288
}
289
290
if ($workflow) {
291
return 'bin.'.$bin.' '.$workflow;
292
} else {
293
return 'bin.'.$bin;
294
}
295
}
296
297
}
298
299