Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/console/plugin/DarkConsoleServicesPlugin.php
13402 views
1
<?php
2
3
final class DarkConsoleServicesPlugin extends DarkConsolePlugin {
4
5
protected $observations;
6
7
public function getName() {
8
return pht('Services');
9
}
10
11
public function getDescription() {
12
return pht('Information about services.');
13
}
14
15
public static function getQueryAnalyzerHeader() {
16
return 'X-Phabricator-QueryAnalyzer';
17
}
18
19
public static function isQueryAnalyzerRequested() {
20
if (!empty($_REQUEST['__analyze__'])) {
21
return true;
22
}
23
24
$header = AphrontRequest::getHTTPHeader(self::getQueryAnalyzerHeader());
25
if ($header) {
26
return true;
27
}
28
29
return false;
30
}
31
32
public function didStartup() {
33
$should_analyze = self::isQueryAnalyzerRequested();
34
35
if ($should_analyze) {
36
PhutilServiceProfiler::getInstance()
37
->setCollectStackTraces(true);
38
}
39
40
return null;
41
}
42
43
44
/**
45
* @phutil-external-symbol class PhabricatorStartup
46
*/
47
public function generateData() {
48
$should_analyze = self::isQueryAnalyzerRequested();
49
50
$log = PhutilServiceProfiler::getInstance()->getServiceCallLog();
51
foreach ($log as $key => $entry) {
52
$config = idx($entry, 'config', array());
53
unset($log[$key]['config']);
54
55
if (!$should_analyze) {
56
$log[$key]['explain'] = array(
57
'sev' => 7,
58
'size' => null,
59
'reason' => pht('Disabled'),
60
);
61
// Query analysis is disabled for this request, so don't do any of it.
62
continue;
63
}
64
65
if ($entry['type'] != 'query') {
66
continue;
67
}
68
69
// For each SELECT query, go issue an EXPLAIN on it so we can flag stuff
70
// causing table scans, etc.
71
if (preg_match('/^\s*SELECT\b/i', $entry['query'])) {
72
$conn = PhabricatorDatabaseRef::newRawConnection($entry['config']);
73
try {
74
$explain = queryfx_all(
75
$conn,
76
'EXPLAIN %Q',
77
$entry['query']);
78
79
$badness = 0;
80
$size = 1;
81
$reason = null;
82
83
foreach ($explain as $table) {
84
$size *= (int)$table['rows'];
85
86
switch ($table['type']) {
87
case 'index':
88
$cur_badness = 1;
89
$cur_reason = 'Index';
90
break;
91
case 'const':
92
$cur_badness = 1;
93
$cur_reason = 'Const';
94
break;
95
case 'eq_ref';
96
$cur_badness = 2;
97
$cur_reason = 'EqRef';
98
break;
99
case 'range':
100
$cur_badness = 3;
101
$cur_reason = 'Range';
102
break;
103
case 'ref':
104
$cur_badness = 3;
105
$cur_reason = 'Ref';
106
break;
107
case 'fulltext':
108
$cur_badness = 3;
109
$cur_reason = 'Fulltext';
110
break;
111
case 'ALL':
112
if (preg_match('/Using where/', $table['Extra'])) {
113
if ($table['rows'] < 256 && !empty($table['possible_keys'])) {
114
$cur_badness = 2;
115
$cur_reason = pht('Small Table Scan');
116
} else {
117
$cur_badness = 6;
118
$cur_reason = pht('TABLE SCAN!');
119
}
120
} else {
121
$cur_badness = 3;
122
$cur_reason = pht('Whole Table');
123
}
124
break;
125
default:
126
if (preg_match('/No tables used/i', $table['Extra'])) {
127
$cur_badness = 1;
128
$cur_reason = pht('No Tables');
129
} else if (preg_match('/Impossible/i', $table['Extra'])) {
130
$cur_badness = 1;
131
$cur_reason = pht('Empty');
132
} else {
133
$cur_badness = 4;
134
$cur_reason = pht("Can't Analyze");
135
}
136
break;
137
}
138
139
if ($cur_badness > $badness) {
140
$badness = $cur_badness;
141
$reason = $cur_reason;
142
}
143
}
144
145
$log[$key]['explain'] = array(
146
'sev' => $badness,
147
'size' => $size,
148
'reason' => $reason,
149
);
150
} catch (Exception $ex) {
151
$log[$key]['explain'] = array(
152
'sev' => 5,
153
'size' => null,
154
'reason' => $ex->getMessage(),
155
);
156
}
157
}
158
}
159
160
return array(
161
'start' => PhabricatorStartup::getStartTime(),
162
'end' => microtime(true),
163
'log' => $log,
164
'analyzeURI' => (string)$this
165
->getRequestURI()
166
->alter('__analyze__', true),
167
'didAnalyze' => $should_analyze,
168
);
169
}
170
171
public function renderPanel() {
172
$data = $this->getData();
173
174
$log = $data['log'];
175
$results = array();
176
177
$results[] = phutil_tag(
178
'div',
179
array('class' => 'dark-console-panel-header'),
180
array(
181
phutil_tag(
182
'a',
183
array(
184
'href' => $data['analyzeURI'],
185
'class' => $data['didAnalyze'] ?
186
'disabled button' : 'button button-green',
187
),
188
pht('Analyze Query Plans')),
189
phutil_tag('h1', array(), pht('Calls to External Services')),
190
phutil_tag('div', array('style' => 'clear: both;')),
191
));
192
193
$page_total = $data['end'] - $data['start'];
194
$totals = array();
195
$counts = array();
196
197
foreach ($log as $row) {
198
$totals[$row['type']] = idx($totals, $row['type'], 0) + $row['duration'];
199
$counts[$row['type']] = idx($counts, $row['type'], 0) + 1;
200
}
201
$totals['All Services'] = array_sum($totals);
202
$counts['All Services'] = array_sum($counts);
203
204
$totals['Entire Page'] = $page_total;
205
$counts['Entire Page'] = 0;
206
207
$summary = array();
208
foreach ($totals as $type => $total) {
209
$summary[] = array(
210
$type,
211
number_format($counts[$type]),
212
pht('%s us', new PhutilNumber((int)(1000000 * $totals[$type]))),
213
sprintf('%.1f%%', 100 * $totals[$type] / $page_total),
214
);
215
}
216
$summary_table = new AphrontTableView($summary);
217
$summary_table->setColumnClasses(
218
array(
219
'',
220
'n',
221
'n',
222
'wide',
223
));
224
$summary_table->setHeaders(
225
array(
226
pht('Type'),
227
pht('Count'),
228
pht('Total Cost'),
229
pht('Page Weight'),
230
));
231
232
$results[] = $summary_table->render();
233
234
$rows = array();
235
foreach ($log as $row) {
236
237
$analysis = null;
238
239
switch ($row['type']) {
240
case 'query':
241
$info = $row['query'];
242
$info = wordwrap($info, 128, "\n", true);
243
244
if (!empty($row['explain'])) {
245
$analysis = phutil_tag(
246
'span',
247
array(
248
'class' => 'explain-sev-'.$row['explain']['sev'],
249
),
250
$row['explain']['reason']);
251
}
252
253
break;
254
case 'connect':
255
$info = $row['host'].':'.$row['database'];
256
break;
257
case 'exec':
258
$info = $row['command'];
259
break;
260
case 's3':
261
case 'conduit':
262
$info = $row['method'];
263
break;
264
case 'http':
265
$info = $row['uri'];
266
break;
267
default:
268
$info = '-';
269
break;
270
}
271
272
$offset = ($row['begin'] - $data['start']);
273
274
$rows[] = array(
275
$row['type'],
276
pht('+%s ms', new PhutilNumber(1000 * $offset)),
277
pht('%s us', new PhutilNumber(1000000 * $row['duration'])),
278
$info,
279
$analysis,
280
);
281
282
if (isset($row['trace'])) {
283
$rows[] = array(
284
null,
285
null,
286
null,
287
$row['trace'],
288
null,
289
);
290
}
291
}
292
293
$table = new AphrontTableView($rows);
294
$table->setColumnClasses(
295
array(
296
null,
297
'n',
298
'n',
299
'wide prewrap',
300
'',
301
));
302
$table->setHeaders(
303
array(
304
pht('Event'),
305
pht('Start'),
306
pht('Duration'),
307
pht('Details'),
308
pht('Analysis'),
309
));
310
311
$results[] = $table->render();
312
313
return phutil_implode_html("\n", $results);
314
}
315
}
316
317