OLD | NEW |
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 library cpu_profile_table_element; | 5 library cpu_profile_table_element; |
6 | 6 |
7 import 'dart:async'; | 7 import 'dart:async'; |
8 import 'dart:html'; | 8 import 'dart:html'; |
9 import 'observatory_element.dart'; | |
10 import 'sample_buffer_control.dart'; | |
11 import 'stack_trace_tree_config.dart'; | |
12 import 'cpu_profile/virtual_tree.dart'; | |
13 import 'package:observatory/service.dart'; | |
14 import 'package:observatory/app.dart'; | |
15 import 'package:observatory/cpu_profile.dart'; | |
16 import 'package:observatory/elements.dart'; | |
17 import 'package:observatory/models.dart' as M; | 9 import 'package:observatory/models.dart' as M; |
18 import 'package:observatory/repositories.dart'; | 10 import 'package:observatory/src/elements/containers/virtual_collection.dart'; |
19 import 'package:polymer/polymer.dart'; | 11 import 'package:observatory/src/elements/cpu_profile/virtual_tree.dart'; |
20 | 12 import 'package:observatory/src/elements/function_ref.dart'; |
21 List<String> sorted(Set<String> attributes) { | 13 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
22 var list = attributes.toList(); | 14 import 'package:observatory/src/elements/helpers/tag.dart'; |
23 list.sort(); | 15 import 'package:observatory/src/elements/helpers/uris.dart'; |
24 return list; | 16 import 'package:observatory/src/elements/nav/bar.dart'; |
| 17 import 'package:observatory/src/elements/nav/isolate_menu.dart'; |
| 18 import 'package:observatory/src/elements/nav/menu.dart'; |
| 19 import 'package:observatory/src/elements/nav/notify.dart'; |
| 20 import 'package:observatory/src/elements/nav/refresh.dart'; |
| 21 import 'package:observatory/src/elements/nav/top_menu.dart'; |
| 22 import 'package:observatory/src/elements/nav/vm_menu.dart'; |
| 23 import 'package:observatory/src/elements/sample_buffer_control.dart'; |
| 24 import 'package:observatory/src/elements/stack_trace_tree_config.dart'; |
| 25 import 'package:observatory/utils.dart'; |
| 26 |
| 27 enum _Table { |
| 28 functions, |
| 29 caller, |
| 30 callee |
25 } | 31 } |
26 | 32 |
27 abstract class ProfileTreeRow<T> extends TableTreeRow { | 33 enum _SortingField { |
28 final CpuProfile profile; | 34 exclusive, |
29 final T node; | 35 inclusive, |
30 final String selfPercent; | 36 caller, |
31 final String percent; | 37 callee, |
32 bool _infoBoxShown = false; | 38 method |
33 HtmlElement infoBox; | 39 } |
34 HtmlElement infoButton; | 40 |
35 | 41 enum _SortingDirection { |
36 ProfileTreeRow(TableTree tree, TableTreeRow parent, | 42 ascending, |
37 this.profile, this.node, double selfPercent, double percent) | 43 descending |
38 : super(tree, parent), | 44 } |
39 selfPercent = Utils.formatPercentNormalized(selfPercent), | 45 |
40 percent = Utils.formatPercentNormalized(percent); | 46 class CpuProfileTableElement extends HtmlElement implements Renderable { |
41 | 47 static const tag = const Tag<CpuProfileTableElement>('cpu-profile-table', |
42 static _addToMemberList(DivElement memberList, Map<String, String> items) { | 48 dependencies: const [ |
43 items.forEach((k, v) { | 49 FunctionRefElement.tag, |
44 var item = new DivElement(); | 50 NavBarElement.tag, |
45 item.classes.add('memberItem'); | 51 NavTopMenuElement.tag, |
46 var name = new DivElement(); | 52 NavVMMenuElement.tag, |
47 name.classes.add('memberName'); | 53 NavIsolateMenuElement.tag, |
48 name.text = k; | 54 NavMenuElement.tag, |
49 var value = new DivElement(); | 55 NavRefreshElement.tag, |
50 value.classes.add('memberValue'); | 56 NavNotifyElement.tag, |
51 value.text = v; | 57 SampleBufferControlElement.tag, |
52 item.children.add(name); | 58 StackTraceTreeConfigElement.tag, |
53 item.children.add(value); | 59 CpuProfileVirtualTreeElement.tag, |
54 memberList.children.add(item); | 60 VirtualCollectionElement.tag |
55 }); | 61 ]); |
56 } | 62 |
57 | 63 RenderingScheduler<CpuProfileTableElement> _r; |
58 makeInfoBox() { | 64 |
59 if (infoBox != null) { | 65 Stream<RenderedEvent<CpuProfileTableElement>> get onRendered => _r.onRendered; |
60 return; | 66 |
61 } | 67 M.VM _vm; |
62 infoBox = new DivElement(); | 68 M.IsolateRef _isolate; |
63 infoBox.classes.add('infoBox'); | 69 M.EventRepository _events; |
64 infoBox.classes.add('shadow'); | 70 M.NotificationRepository _notifications; |
65 infoBox.style.display = 'none'; | 71 M.IsolateSampleProfileRepository _profiles; |
66 listeners.add(infoBox.onClick.listen((e) => e.stopPropagation())); | 72 Stream<M.SampleProfileLoadingProgressEvent> _progressStream; |
67 } | 73 M.SampleProfileLoadingProgress _progress; |
68 | 74 final _sortingField = <_Table, _SortingField>{ |
69 makeInfoButton() { | 75 _Table.functions : _SortingField.exclusive, |
70 infoButton = new SpanElement(); | 76 _Table.caller : _SortingField.caller, |
71 infoButton.style.marginLeft = 'auto'; | 77 _Table.callee : _SortingField.callee, |
72 infoButton.style.marginRight = '1em'; | |
73 infoButton.children.add(new Element.tag('icon-info-outline')); | |
74 listeners.add(infoButton.onClick.listen((event) { | |
75 event.stopPropagation(); | |
76 toggleInfoBox(); | |
77 })); | |
78 } | |
79 | |
80 static const attributes = const { | |
81 'optimized' : const ['O', null, 'Optimized'], | |
82 'unoptimized' : const ['U', null, 'Unoptimized'], | |
83 'inlined' : const ['I', null, 'Inlined'], | |
84 'intrinsic' : const ['It', null, 'Intrinsic'], | |
85 'ffi' : const ['F', null, 'FFI'], | |
86 'dart' : const ['D', null, 'Dart'], | |
87 'tag' : const ['T', null, 'Tag'], | |
88 'native' : const ['N', null, 'Native'], | |
89 'stub': const ['S', null, 'Stub'], | |
90 'synthetic' : const ['?', null, 'Synthetic'], | |
91 }; | 78 }; |
92 | 79 final _sortingDirection = <_Table, _SortingDirection>{ |
93 HtmlElement newAttributeBox(String attribute) { | 80 _Table.functions : _SortingDirection.descending, |
94 List attributeDetails = attributes[attribute]; | 81 _Table.caller : _SortingDirection.descending, |
95 if (attributeDetails == null) { | 82 _Table.callee : _SortingDirection.descending, |
96 print('could not find attribute $attribute'); | 83 }; |
97 return null; | 84 String _filter = ''; |
98 } | 85 |
99 var element = new SpanElement(); | 86 |
100 element.style.border = 'solid 2px #ECECEC'; | 87 M.IsolateRef get isolate => _isolate; |
101 element.style.height = '100%'; | 88 M.NotificationRepository get notifications => _notifications; |
102 element.style.display = 'inline-block'; | 89 M.IsolateSampleProfileRepository get profiles => _profiles; |
103 element.style.textAlign = 'center'; | 90 M.VMRef get vm => _vm; |
104 element.style.minWidth = '1.5em'; | 91 |
105 element.style.fontWeight = 'bold'; | 92 factory CpuProfileTableElement(M.VM vm, M.IsolateRef isolate, |
106 if (attributeDetails[1] != null) { | 93 M.EventRepository events, |
107 element.style.backgroundColor = attributeDetails[1]; | 94 M.NotificationRepository notifications, |
108 } | 95 M.IsolateSampleProfileRepository profiles, |
109 element.text = attributeDetails[0]; | 96 {RenderingQueue queue}) { |
110 element.title = attributeDetails[2]; | 97 assert(vm != null); |
111 return element; | 98 assert(isolate != null); |
112 } | 99 assert(events != null); |
113 | 100 assert(notifications != null); |
114 onHide() { | 101 assert(profiles != null); |
115 super.onHide(); | 102 CpuProfileTableElement e = document.createElement(tag.name); |
116 infoBox = null; | 103 e._r = new RenderingScheduler(e, queue: queue); |
117 infoButton = null; | 104 e._vm = vm; |
118 } | 105 e._isolate = isolate; |
119 | 106 e._events = events; |
120 showInfoBox() { | 107 e._notifications = notifications; |
121 if ((infoButton == null) || (infoBox == null)) { | 108 e._profiles = profiles; |
122 return; | 109 return e; |
123 } | 110 } |
124 _infoBoxShown = true; | 111 |
125 infoBox.style.display = 'block'; | 112 CpuProfileTableElement.created() : super.created(); |
126 infoButton.children.clear(); | 113 |
127 infoButton.children.add(new Element.tag('icon-info')); | |
128 } | |
129 | |
130 hideInfoBox() { | |
131 _infoBoxShown = false; | |
132 if ((infoButton == null) || (infoBox == null)) { | |
133 return; | |
134 } | |
135 infoBox.style.display = 'none'; | |
136 infoButton.children.clear(); | |
137 infoButton.children.add(new Element.tag('icon-info-outline')); | |
138 } | |
139 | |
140 toggleInfoBox() { | |
141 if (_infoBoxShown) { | |
142 hideInfoBox(); | |
143 } else { | |
144 showInfoBox(); | |
145 } | |
146 } | |
147 | |
148 hideAllInfoBoxes() { | |
149 final List<ProfileTreeRow> rows = tree.rows; | |
150 for (var row in rows) { | |
151 row.hideInfoBox(); | |
152 } | |
153 } | |
154 | |
155 onClick(MouseEvent e) { | |
156 e.stopPropagation(); | |
157 if (e.altKey) { | |
158 bool show = !_infoBoxShown; | |
159 hideAllInfoBoxes(); | |
160 if (show) { | |
161 showInfoBox(); | |
162 } | |
163 return; | |
164 } | |
165 super.onClick(e); | |
166 } | |
167 | |
168 HtmlElement newCodeRef(ProfileCode code) { | |
169 var codeRef = new Element.tag('code-ref'); | |
170 codeRef.ref = code.code; | |
171 return codeRef; | |
172 } | |
173 | |
174 HtmlElement newFunctionRef(ProfileFunction function) { | |
175 var ref = new Element.tag('function-ref'); | |
176 ref.ref = function.function; | |
177 return ref; | |
178 } | |
179 | |
180 HtmlElement hr() { | |
181 var element = new HRElement(); | |
182 return element; | |
183 } | |
184 | |
185 HtmlElement div(String text) { | |
186 var element = new DivElement(); | |
187 element.text = text; | |
188 return element; | |
189 } | |
190 | |
191 HtmlElement br() { | |
192 return new BRElement(); | |
193 } | |
194 | |
195 HtmlElement span(String text) { | |
196 var element = new SpanElement(); | |
197 element.style.minWidth = '1em'; | |
198 element.text = text; | |
199 return element; | |
200 } | |
201 } | |
202 | |
203 class CodeProfileTreeRow extends ProfileTreeRow<CodeCallTreeNode> { | |
204 CodeProfileTreeRow(TableTree tree, CodeProfileTreeRow parent, | |
205 CpuProfile profile, CodeCallTreeNode node) | |
206 : super(tree, parent, profile, node, | |
207 node.profileCode.normalizedExclusiveTicks, | |
208 node.percentage) { | |
209 // fill out attributes. | |
210 } | |
211 | |
212 bool hasChildren() => node.children.length > 0; | |
213 | |
214 void onShow() { | |
215 super.onShow(); | |
216 | |
217 if (children.length == 0) { | |
218 for (var childNode in node.children) { | |
219 var row = new CodeProfileTreeRow(tree, this, profile, childNode); | |
220 children.add(row); | |
221 } | |
222 } | |
223 | |
224 // Fill in method column. | |
225 var methodColumn = flexColumns[0]; | |
226 methodColumn.style.justifyContent = 'flex-start'; | |
227 methodColumn.style.position = 'relative'; | |
228 | |
229 // Percent. | |
230 var percentNode = new DivElement(); | |
231 percentNode.text = percent; | |
232 percentNode.style.minWidth = '5em'; | |
233 percentNode.style.textAlign = 'right'; | |
234 percentNode.title = 'Executing: $selfPercent'; | |
235 methodColumn.children.add(percentNode); | |
236 | |
237 // Gap. | |
238 var gap = new SpanElement(); | |
239 gap.style.minWidth = '1em'; | |
240 methodColumn.children.add(gap); | |
241 | |
242 // Code link. | |
243 var codeRef = newCodeRef(node.profileCode); | |
244 codeRef.style.alignSelf = 'center'; | |
245 methodColumn.children.add(codeRef); | |
246 | |
247 gap = new SpanElement(); | |
248 gap.style.minWidth = '1em'; | |
249 methodColumn.children.add(gap); | |
250 | |
251 for (var attribute in sorted(node.attributes)) { | |
252 methodColumn.children.add(newAttributeBox(attribute)); | |
253 } | |
254 | |
255 makeInfoBox(); | |
256 methodColumn.children.add(infoBox); | |
257 | |
258 infoBox.children.add(span('Code ')); | |
259 infoBox.children.add(newCodeRef(node.profileCode)); | |
260 infoBox.children.add(span(' ')); | |
261 for (var attribute in sorted(node.profileCode.attributes)) { | |
262 infoBox.children.add(newAttributeBox(attribute)); | |
263 } | |
264 infoBox.children.add(br()); | |
265 infoBox.children.add(br()); | |
266 var memberList = new DivElement(); | |
267 memberList.classes.add('memberList'); | |
268 infoBox.children.add(br()); | |
269 infoBox.children.add(memberList); | |
270 ProfileTreeRow._addToMemberList(memberList, { | |
271 'Exclusive ticks' : node.profileCode.formattedExclusiveTicks, | |
272 'Cpu time' : node.profileCode.formattedCpuTime, | |
273 'Inclusive ticks' : node.profileCode.formattedInclusiveTicks, | |
274 'Call stack time' : node.profileCode.formattedOnStackTime, | |
275 }); | |
276 | |
277 makeInfoButton(); | |
278 methodColumn.children.add(infoButton); | |
279 | |
280 // Fill in self column. | |
281 var selfColumn = flexColumns[1]; | |
282 selfColumn.style.position = 'relative'; | |
283 selfColumn.style.alignItems = 'center'; | |
284 selfColumn.text = selfPercent; | |
285 } | |
286 } | |
287 | |
288 class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> { | |
289 FunctionProfileTreeRow(TableTree tree, FunctionProfileTreeRow parent, | |
290 CpuProfile profile, FunctionCallTreeNode node) | |
291 : super(tree, parent, profile, node, | |
292 node.profileFunction.normalizedExclusiveTicks, | |
293 node.percentage) { | |
294 // fill out attributes. | |
295 } | |
296 | |
297 bool hasChildren() => node.children.length > 0; | |
298 | |
299 onShow() { | |
300 super.onShow(); | |
301 if (children.length == 0) { | |
302 for (var childNode in node.children) { | |
303 var row = new FunctionProfileTreeRow(tree, this, profile, childNode); | |
304 children.add(row); | |
305 } | |
306 } | |
307 | |
308 var methodColumn = flexColumns[0]; | |
309 methodColumn.style.justifyContent = 'flex-start'; | |
310 | |
311 var codeAndFunctionColumn = new DivElement(); | |
312 codeAndFunctionColumn.classes.add('flex-column'); | |
313 codeAndFunctionColumn.style.justifyContent = 'center'; | |
314 codeAndFunctionColumn.style.width = '100%'; | |
315 methodColumn.children.add(codeAndFunctionColumn); | |
316 | |
317 var functionRow = new DivElement(); | |
318 functionRow.classes.add('flex-row'); | |
319 functionRow.style.position = 'relative'; | |
320 functionRow.style.justifyContent = 'flex-start'; | |
321 codeAndFunctionColumn.children.add(functionRow); | |
322 | |
323 // Insert the parent percentage | |
324 var parentPercent = new SpanElement(); | |
325 parentPercent.text = percent; | |
326 parentPercent.style.minWidth = '4em'; | |
327 parentPercent.style.alignSelf = 'center'; | |
328 parentPercent.style.textAlign = 'right'; | |
329 parentPercent.title = 'Executing: $selfPercent'; | |
330 functionRow.children.add(parentPercent); | |
331 | |
332 // Gap. | |
333 var gap = new SpanElement(); | |
334 gap.style.minWidth = '1em'; | |
335 gap.text = ' '; | |
336 functionRow.children.add(gap); | |
337 | |
338 var functionRef = new Element.tag('function-ref'); | |
339 functionRef.ref = node.profileFunction.function; | |
340 functionRef.style.alignSelf = 'center'; | |
341 functionRow.children.add(functionRef); | |
342 | |
343 gap = new SpanElement(); | |
344 gap.style.minWidth = '1em'; | |
345 gap.text = ' '; | |
346 functionRow.children.add(gap); | |
347 | |
348 for (var attribute in sorted(node.attributes)) { | |
349 functionRow.children.add(newAttributeBox(attribute)); | |
350 } | |
351 | |
352 makeInfoBox(); | |
353 functionRow.children.add(infoBox); | |
354 | |
355 if (M.hasDartCode(node.profileFunction.function.kind)) { | |
356 infoBox.children.add(div('Code for current node')); | |
357 infoBox.children.add(br()); | |
358 var totalTicks = node.totalCodesTicks; | |
359 var numCodes = node.codes.length; | |
360 for (var i = 0; i < numCodes; i++) { | |
361 var codeRowSpan = new DivElement(); | |
362 codeRowSpan.style.paddingLeft = '1em'; | |
363 infoBox.children.add(codeRowSpan); | |
364 var nodeCode = node.codes[i]; | |
365 var ticks = nodeCode.ticks; | |
366 var percentage = Utils.formatPercent(ticks, totalTicks); | |
367 var percentageSpan = new SpanElement(); | |
368 percentageSpan.style.display = 'inline-block'; | |
369 percentageSpan.text = '$percentage'; | |
370 percentageSpan.style.minWidth = '5em'; | |
371 percentageSpan.style.textAlign = 'right'; | |
372 codeRowSpan.children.add(percentageSpan); | |
373 var codeRef = new Element.tag('code-ref'); | |
374 codeRef.ref = nodeCode.code.code; | |
375 codeRef.style.marginLeft = '1em'; | |
376 codeRef.style.marginRight = 'auto'; | |
377 codeRef.style.width = '100%'; | |
378 codeRowSpan.children.add(codeRef); | |
379 } | |
380 infoBox.children.add(hr()); | |
381 } | |
382 infoBox.children.add(span('Function ')); | |
383 infoBox.children.add(newFunctionRef(node.profileFunction)); | |
384 infoBox.children.add(span(' ')); | |
385 for (var attribute in sorted(node.profileFunction.attributes)) { | |
386 infoBox.children.add(newAttributeBox(attribute)); | |
387 } | |
388 var memberList = new DivElement(); | |
389 memberList.classes.add('memberList'); | |
390 infoBox.children.add(br()); | |
391 infoBox.children.add(br()); | |
392 infoBox.children.add(memberList); | |
393 infoBox.children.add(br()); | |
394 ProfileTreeRow._addToMemberList(memberList, { | |
395 'Exclusive ticks' : node.profileFunction.formattedExclusiveTicks, | |
396 'Cpu time' : node.profileFunction.formattedCpuTime, | |
397 'Inclusive ticks' : node.profileFunction.formattedInclusiveTicks, | |
398 'Call stack time' : node.profileFunction.formattedOnStackTime, | |
399 }); | |
400 | |
401 if (M.hasDartCode(node.profileFunction.function.kind)) { | |
402 infoBox.children.add(div('Code containing function')); | |
403 infoBox.children.add(br()); | |
404 var totalTicks = profile.sampleCount; | |
405 var codes = node.profileFunction.profileCodes; | |
406 var numCodes = codes.length; | |
407 for (var i = 0; i < numCodes; i++) { | |
408 var codeRowSpan = new DivElement(); | |
409 codeRowSpan.style.paddingLeft = '1em'; | |
410 infoBox.children.add(codeRowSpan); | |
411 var profileCode = codes[i]; | |
412 var code = profileCode.code; | |
413 var ticks = profileCode.inclusiveTicks; | |
414 var percentage = Utils.formatPercent(ticks, totalTicks); | |
415 var percentageSpan = new SpanElement(); | |
416 percentageSpan.style.display = 'inline-block'; | |
417 percentageSpan.text = '$percentage'; | |
418 percentageSpan.style.minWidth = '5em'; | |
419 percentageSpan.style.textAlign = 'right'; | |
420 percentageSpan.title = 'Inclusive ticks'; | |
421 codeRowSpan.children.add(percentageSpan); | |
422 var codeRef = new Element.tag('code-ref'); | |
423 codeRef.ref = code; | |
424 codeRef.style.marginLeft = '1em'; | |
425 codeRef.style.marginRight = 'auto'; | |
426 codeRef.style.width = '100%'; | |
427 codeRowSpan.children.add(codeRef); | |
428 } | |
429 } | |
430 | |
431 makeInfoButton(); | |
432 methodColumn.children.add(infoButton); | |
433 | |
434 // Fill in self column. | |
435 var selfColumn = flexColumns[1]; | |
436 selfColumn.style.position = 'relative'; | |
437 selfColumn.style.alignItems = 'center'; | |
438 selfColumn.text = selfPercent; | |
439 } | |
440 } | |
441 | |
442 class NameSortedTable extends SortedTable { | |
443 NameSortedTable(columns) : super(columns); | |
444 @override | 114 @override |
445 dynamic getSortKeyFor(int row, int col) { | |
446 if (col == FUNCTION_COLUMN) { | |
447 // Use name as sort key. | |
448 return rows[row].values[col].name; | |
449 } | |
450 return super.getSortKeyFor(row, col); | |
451 } | |
452 | |
453 SortedTableRow rowFromIndex(int tableIndex) { | |
454 final modelIndex = sortedRows[tableIndex]; | |
455 return rows[modelIndex]; | |
456 } | |
457 | |
458 static const FUNCTION_SPACER_COLUMNS = const []; | |
459 static const FUNCTION_COLUMN = 2; | |
460 TableRowElement _makeFunctionRow() { | |
461 var tr = new TableRowElement(); | |
462 var cell; | |
463 | |
464 // Add percentage. | |
465 cell = tr.insertCell(-1); | |
466 cell = tr.insertCell(-1); | |
467 | |
468 // Add function ref. | |
469 cell = tr.insertCell(-1); | |
470 var functionRef = new Element.tag('function-ref'); | |
471 cell.children.add(functionRef); | |
472 | |
473 return tr; | |
474 } | |
475 | |
476 static const CALL_SPACER_COLUMNS = const []; | |
477 static const CALL_FUNCTION_COLUMN = 1; | |
478 TableRowElement _makeCallRow() { | |
479 var tr = new TableRowElement(); | |
480 var cell; | |
481 | |
482 // Add percentage. | |
483 cell = tr.insertCell(-1); | |
484 // Add function ref. | |
485 cell = tr.insertCell(-1); | |
486 var functionRef = new Element.tag('function-ref'); | |
487 cell.children.add(functionRef); | |
488 return tr; | |
489 } | |
490 | |
491 _updateRow(TableRowElement tr, | |
492 int rowIndex, | |
493 List spacerColumns, | |
494 int refColumn) { | |
495 var row = rows[rowIndex]; | |
496 // Set reference | |
497 var ref = tr.children[refColumn].children[0]; | |
498 ref.ref = row.values[refColumn]; | |
499 | |
500 for (var i = 0; i < row.values.length; i++) { | |
501 if (spacerColumns.contains(i) || (i == refColumn)) { | |
502 // Skip spacer columns. | |
503 continue; | |
504 } | |
505 var cell = tr.children[i]; | |
506 cell.title = row.values[i].toString(); | |
507 cell.text = getFormattedValue(rowIndex, i); | |
508 } | |
509 } | |
510 | |
511 _updateTableView(HtmlElement table, | |
512 HtmlElement makeEmptyRow(), | |
513 void onRowClick(TableRowElement tr), | |
514 List spacerColumns, | |
515 int refColumn) { | |
516 assert(table != null); | |
517 | |
518 // Resize DOM table. | |
519 if (table.children.length > sortedRows.length) { | |
520 // Shrink the table. | |
521 var deadRows = table.children.length - sortedRows.length; | |
522 for (var i = 0; i < deadRows; i++) { | |
523 table.children.removeLast(); | |
524 } | |
525 } else if (table.children.length < sortedRows.length) { | |
526 // Grow table. | |
527 var newRows = sortedRows.length - table.children.length; | |
528 for (var i = 0; i < newRows; i++) { | |
529 var row = makeEmptyRow(); | |
530 row.onClick.listen((e) { | |
531 e.stopPropagation(); | |
532 e.preventDefault(); | |
533 onRowClick(row); | |
534 }); | |
535 table.children.add(row); | |
536 } | |
537 } | |
538 | |
539 assert(table.children.length == sortedRows.length); | |
540 | |
541 // Fill table. | |
542 for (var i = 0; i < sortedRows.length; i++) { | |
543 var rowIndex = sortedRows[i]; | |
544 var tr = table.children[i]; | |
545 _updateRow(tr, rowIndex, spacerColumns, refColumn); | |
546 } | |
547 } | |
548 } | |
549 | |
550 @CustomTag('cpu-profile-table') | |
551 class CpuProfileTableElement extends ObservatoryElement { | |
552 | |
553 | |
554 CpuProfileTableElement.created() : super.created() { | |
555 _updateTask = new Task(update); | |
556 _renderTask = new Task(render); | |
557 var columns = [ | |
558 new SortedTableColumn.withFormatter('Executing (%)', | |
559 Utils.formatPercentNormalized), | |
560 new SortedTableColumn.withFormatter('In stack (%)', | |
561 Utils.formatPercentNormalized), | |
562 new SortedTableColumn('Method'), | |
563 ]; | |
564 profileTable = new NameSortedTable(columns); | |
565 profileTable.sortColumnIndex = 0; | |
566 | |
567 columns = [ | |
568 new SortedTableColumn.withFormatter('Callees (%)', | |
569 Utils.formatPercentNormalized), | |
570 new SortedTableColumn('Method') | |
571 ]; | |
572 profileCalleesTable = new NameSortedTable(columns); | |
573 profileCalleesTable.sortColumnIndex = 0; | |
574 | |
575 columns = [ | |
576 new SortedTableColumn.withFormatter('Callers (%)', | |
577 Utils.formatPercentNormalized), | |
578 new SortedTableColumn('Method') | |
579 ]; | |
580 profileCallersTable = new NameSortedTable(columns); | |
581 profileCallersTable.sortColumnIndex = 0; | |
582 } | |
583 | |
584 attached() { | 115 attached() { |
585 super.attached(); | 116 super.attached(); |
586 _updateTask.queue(); | 117 _r.enable(); |
587 _resizeSubscription = window.onResize.listen((_) => _updateSize()); | 118 _request(); |
588 _updateSize(); | 119 } |
589 } | 120 |
590 | 121 @override |
591 detached() { | 122 detached() { |
592 super.detached(); | 123 super.detached(); |
593 if (_resizeSubscription != null) { | 124 _r.disable(notify: true); |
594 _resizeSubscription.cancel(); | 125 children = []; |
595 } | 126 } |
596 } | 127 |
597 | 128 void render() { |
598 _updateSize() { | 129 var content = [ |
599 HtmlElement e = $['main']; | 130 new NavBarElement(queue: _r.queue) |
600 final totalHeight = window.innerHeight; | 131 ..children = [ |
601 final top = e.offset.top; | 132 new NavTopMenuElement(queue: _r.queue), |
602 final bottomMargin = 32; | 133 new NavVMMenuElement(_vm, _events, queue: _r.queue), |
603 final mainHeight = totalHeight - top - bottomMargin; | 134 new NavIsolateMenuElement(_isolate, _events, queue: _r.queue), |
604 e.style.setProperty('height', '${mainHeight}px'); | 135 new NavMenuElement('cpu profile (table)', |
605 } | 136 link: Uris.profiler(_isolate), last: true, queue: _r.queue), |
606 | 137 new NavRefreshElement(queue: _r.queue) |
607 isolateChanged(oldValue) { | 138 ..onRefresh.listen(_refresh), |
608 _updateTask.queue(); | 139 new NavRefreshElement(label: 'Clear', queue: _r.queue) |
609 } | 140 ..onRefresh.listen(_clearCpuProfile), |
610 | 141 new NavNotifyElement(_notifications, queue: _r.queue) |
611 update() async { | 142 ], |
612 _clearView(); | 143 ]; |
613 if (isolate == null) { | 144 if (_progress == null) { |
| 145 children = content; |
614 return; | 146 return; |
615 } | 147 } |
616 final stream = _repository.get(isolate, M.SampleProfileTag.none); | 148 content.add(new SampleBufferControlElement(_progress, _progressStream, |
617 var progress = (await stream.first).progress; | 149 showTag: false, queue: _r.queue)); |
618 shadowRoot.querySelector('#sampleBufferControl').children = [ | 150 if (_progress.status == M.SampleProfileLoadingStatus.loaded) { |
619 sampleBufferControlElement = | 151 content.add(new BRElement()); |
620 new SampleBufferControlElement(progress, stream, showTag: false, | 152 content.addAll(_createTables()); |
621 selectedTag: M.SampleProfileTag.none, queue: app.queue) | 153 content.add(new BRElement()); |
| 154 content.addAll(_createTree()); |
| 155 } |
| 156 children = content; |
| 157 } |
| 158 |
| 159 M.ProfileFunction _selected; |
| 160 VirtualCollectionElement _functions; |
| 161 VirtualCollectionElement _callers; |
| 162 VirtualCollectionElement _callees; |
| 163 |
| 164 List<Element> _createTables() { |
| 165 _functions = _functions ?? new VirtualCollectionElement( |
| 166 _createFunction, |
| 167 _updateFunction, |
| 168 createHeader: _createFunctionHeader, |
| 169 queue: _r.queue |
| 170 ); |
| 171 _functions.items = _progress.profile.functions.toList() |
| 172 ..sort(_createSorter(_Table.functions)); |
| 173 _functions.takeIntoView(_selected); |
| 174 _callers = _callers ?? new VirtualCollectionElement( |
| 175 _createCaller, |
| 176 _updateCaller, |
| 177 createHeader: _createCallerHeader, |
| 178 queue: _r.queue |
| 179 ); |
| 180 _callees = _callees ?? new VirtualCollectionElement( |
| 181 _createCallee, |
| 182 _updateCallee, |
| 183 createHeader: _createCalleeHeader, |
| 184 queue: _r.queue |
| 185 ); |
| 186 if (_selected != null) { |
| 187 _callers.items = _selected.callers.keys.toList() |
| 188 ..sort(_createSorter(_Table.caller)); |
| 189 _callees.items = _selected.callees.keys.toList() |
| 190 ..sort(_createSorter(_Table.callee)); |
| 191 } else { |
| 192 _callers.items = const []; |
| 193 _callees.items = const []; |
| 194 } |
| 195 return [ |
| 196 new DivElement()..classes = ['profile-trees'] |
| 197 ..children = [ |
| 198 new DivElement()..classes = ['profile-trees-all'] |
| 199 ..children = [_functions], |
| 200 new DivElement()..classes = ['profile-trees-current'] |
| 201 ..children = [ |
| 202 new DivElement()..classes = ['profile-trees-caller'] |
| 203 ..children = [_callers], |
| 204 new DivElement()..classes = ['profile-trees-selected'] |
| 205 ..children = _selected == null |
| 206 ? [new SpanElement()..text = 'No element selected'] |
| 207 : [new FunctionRefElement(_isolate, _selected.function, |
| 208 queue : _r.queue)], |
| 209 new DivElement()..classes = ['profile-trees-callee'] |
| 210 ..children = [_callees] |
| 211 ] |
| 212 ] |
622 ]; | 213 ]; |
623 if (M.isSampleProcessRunning(progress.status)) { | 214 } |
624 progress = (await stream.last).progress; | 215 |
625 } | 216 Element _createFunction() { |
626 if (progress.status == M.SampleProfileLoadingStatus.loaded) { | 217 final element = new DivElement() |
627 _profile = progress.profile; | 218 ..classes = const ['function-item'] |
628 shadowRoot.querySelector('#stackTraceTreeConfig').children = [ | 219 ..children = [ |
629 stackTraceTreeConfigElement = | 220 new SpanElement()..classes = const ['exclusive'] |
630 new StackTraceTreeConfigElement(showMode: false, | 221 ..text = '0%', |
631 showDirection: false, mode: ProfileTreeMode.function, | 222 new SpanElement()..classes = const ['inclusive'] |
632 direction: M.ProfileTreeDirection.exclusive, queue: app.queue) | 223 ..text = '0%', |
633 ..onModeChange.listen((e) { | 224 new SpanElement()..classes = const ['name'] |
634 cpuProfileTreeElement.mode = e.element.mode; | 225 ]; |
635 _renderTask.queue(); | 226 element.onClick.listen((_) { |
636 }) | 227 _selected = _functions.getItemFromElement(element); |
637 ..onDirectionChange.listen((e) { | 228 _r.dirty(); |
638 cpuProfileTreeElement.direction = e.element.direction; | 229 }); |
639 _renderTask.queue(); | 230 return element; |
640 }) | 231 } |
641 ..onFilterChange.listen((e) =>_renderTask.queue()) | 232 |
642 ]; | 233 void _updateFunction(Element e, M.ProfileFunction item, int index) { |
643 shadowRoot.querySelector('#cpuProfileTree').children = [ | 234 if (item == _selected) { |
644 cpuProfileTreeElement = | 235 e.classes = const ['function-item', 'selected']; |
645 new CpuProfileVirtualTreeElement(isolate, _profile, | |
646 queue: app.queue) | |
647 ]; | |
648 } | |
649 _renderTask.queue(); | |
650 } | |
651 | |
652 Future clearCpuProfile() async { | |
653 await isolate.invokeRpc('_clearCpuProfile', { }); | |
654 _updateTask.queue(); | |
655 return new Future.value(null); | |
656 } | |
657 | |
658 Future refresh() { | |
659 _updateTask.queue(); | |
660 return new Future.value(null); | |
661 } | |
662 | |
663 render() { | |
664 _updateView(); | |
665 } | |
666 | |
667 checkParameters() { | |
668 if (isolate == null) { | |
669 return; | |
670 } | |
671 var functionId = app.locationManager.uri.queryParameters['functionId']; | |
672 var functionName = | |
673 app.locationManager.uri.queryParameters['functionName']; | |
674 if (functionId == '') { | |
675 // Fallback to searching by name. | |
676 _focusOnFunction(_findFunction(functionName)); | |
677 } else { | 236 } else { |
678 if (functionId == null) { | 237 e.classes = const ['function-item']; |
679 _focusOnFunction(null); | 238 } |
680 return; | 239 e.children[0].text = Utils.formatPercentNormalized(_getExclusiveT(item)); |
| 240 e.children[1].text = Utils.formatPercentNormalized(_getInclusiveT(item)); |
| 241 e.children[2] = new FunctionRefElement(_isolate, item.function, |
| 242 queue: _r.queue)..classes = const ['name']; |
| 243 } |
| 244 |
| 245 Element _createFunctionHeader() => |
| 246 new DivElement() |
| 247 ..classes = const ['function-item'] |
| 248 ..children = [ |
| 249 _createHeaderButton(const ['exclusive'], 'Execution(%)', |
| 250 _Table.functions, |
| 251 _SortingField.exclusive, |
| 252 _SortingDirection.descending), |
| 253 _createHeaderButton(const ['inclusive'], 'Stack(%)', |
| 254 _Table.functions, |
| 255 _SortingField.inclusive, |
| 256 _SortingDirection.descending), |
| 257 _createHeaderButton(const ['name'], 'Method', |
| 258 _Table.functions, |
| 259 _SortingField.method, |
| 260 _SortingDirection.descending), |
| 261 ]; |
| 262 |
| 263 void _setSorting(_Table table, |
| 264 _SortingField field, |
| 265 _SortingDirection defaultDirection) { |
| 266 if (_sortingField[table] == field) { |
| 267 switch (_sortingDirection[table]) { |
| 268 case _SortingDirection.descending: |
| 269 _sortingDirection[table] = _SortingDirection.ascending; |
| 270 break; |
| 271 case _SortingDirection.ascending: |
| 272 _sortingDirection[table] = _SortingDirection.descending; |
| 273 break; |
| 274 } |
| 275 } else { |
| 276 _sortingDirection[table] = defaultDirection; |
| 277 _sortingField[table] = field; |
681 } | 278 } |
682 isolate.getObject(functionId).then((func) => _focusOnFunction(func)); | 279 _r.dirty(); |
683 } | 280 } |
684 } | 281 |
685 | 282 Element _createCallee() { |
686 _clearView() { | 283 final element = new DivElement() |
687 profileTable.clearRows(); | 284 ..classes = const ['function-item'] |
688 _renderTable(); | 285 ..children = [ |
689 } | 286 new SpanElement()..classes = const ['inclusive'] |
690 | 287 ..text = '0%', |
691 _updateView() { | 288 new SpanElement()..classes = const ['name'] |
692 _buildFunctionTable(); | 289 ]; |
693 _renderTable(); | 290 element.onClick.listen((_) { |
694 _updateFunctionTreeView(); | 291 _selected = _callees.getItemFromElement(element); |
695 } | 292 _r.dirty(); |
696 | |
697 int _findFunctionRow(ServiceFunction function) { | |
698 for (var i = 0; i < profileTable.sortedRows.length; i++) { | |
699 var rowIndex = profileTable.sortedRows[i]; | |
700 var row = profileTable.rows[rowIndex]; | |
701 if (row.values[NameSortedTable.FUNCTION_COLUMN] == function) { | |
702 return i; | |
703 } | |
704 } | |
705 return -1; | |
706 } | |
707 | |
708 _scrollToFunction(ServiceFunction function) { | |
709 TableSectionElement tableBody = $['profile-table']; | |
710 var row = _findFunctionRow(function); | |
711 if (row == -1) { | |
712 return; | |
713 } | |
714 tableBody.children[row].classes.remove('shake'); | |
715 // trigger reflow. | |
716 tableBody.children[row].offsetHeight; | |
717 tableBody.children[row].scrollIntoView(ScrollAlignment.CENTER); | |
718 tableBody.children[row].classes.add('shake'); | |
719 // Focus on clicked function. | |
720 _focusOnFunction(function); | |
721 } | |
722 | |
723 _clearFocusedFunction() { | |
724 TableSectionElement tableBody = $['profile-table']; | |
725 // Clear current focus. | |
726 if (focusedRow != null) { | |
727 tableBody.children[focusedRow].classes.remove('focused'); | |
728 } | |
729 focusedRow = null; | |
730 focusedFunction = null; | |
731 } | |
732 | |
733 ServiceFunction _findFunction(String functionName) { | |
734 for (var func in _profile.functions) { | |
735 if (func.function.name == functionName) { | |
736 return func.function; | |
737 } | |
738 } | |
739 return null; | |
740 } | |
741 | |
742 _focusOnFunction(ServiceFunction function) { | |
743 if (focusedFunction == function) { | |
744 // Do nothing. | |
745 return; | |
746 } | |
747 | |
748 _clearFocusedFunction(); | |
749 | |
750 if (function == null) { | |
751 _updateFunctionTreeView(); | |
752 _clearCallTables(); | |
753 return; | |
754 } | |
755 | |
756 var row = _findFunctionRow(function); | |
757 if (row == -1) { | |
758 _updateFunctionTreeView(); | |
759 _clearCallTables(); | |
760 return; | |
761 } | |
762 | |
763 focusedRow = row; | |
764 focusedFunction = function; | |
765 | |
766 TableSectionElement tableBody = $['profile-table']; | |
767 tableBody.children[focusedRow].classes.add('focused'); | |
768 _updateFunctionTreeView(); | |
769 _buildCallersTable(focusedFunction); | |
770 _buildCalleesTable(focusedFunction); | |
771 } | |
772 | |
773 _onRowClick(TableRowElement tr) { | |
774 var tableBody = $['profile-table']; | |
775 var row = profileTable.rowFromIndex(tableBody.children.indexOf(tr)); | |
776 var function = row.values[NameSortedTable.FUNCTION_COLUMN]; | |
777 app.locationManager.goReplacingParameters( | |
778 { | |
779 'functionId': function.id, | |
780 'functionName': function.vmName | |
781 } | |
782 ); | |
783 } | |
784 | |
785 _renderTable() { | |
786 profileTable._updateTableView($['profile-table'], | |
787 profileTable._makeFunctionRow, | |
788 _onRowClick, | |
789 NameSortedTable.FUNCTION_SPACER_COLUMNS, | |
790 NameSortedTable.FUNCTION_COLUMN); | |
791 } | |
792 | |
793 _buildFunctionTable() { | |
794 for (var func in _profile.functions) { | |
795 if ((func.exclusiveTicks == 0) && (func.inclusiveTicks == 0)) { | |
796 // Skip. | |
797 continue; | |
798 } | |
799 var row = [ | |
800 func.normalizedExclusiveTicks, | |
801 func.normalizedInclusiveTicks, | |
802 func.function, | |
803 ]; | |
804 profileTable.addRow(new SortedTableRow(row)); | |
805 } | |
806 profileTable.sort(); | |
807 } | |
808 | |
809 _renderCallTable(TableSectionElement view, | |
810 NameSortedTable model, | |
811 void onRowClick(TableRowElement tr)) { | |
812 model._updateTableView(view, | |
813 model._makeCallRow, | |
814 onRowClick, | |
815 NameSortedTable.CALL_SPACER_COLUMNS, | |
816 NameSortedTable.CALL_FUNCTION_COLUMN); | |
817 } | |
818 | |
819 _buildCallTable(Map<ProfileFunction, int> calls, | |
820 NameSortedTable model) { | |
821 model.clearRows(); | |
822 if (calls == null) { | |
823 return; | |
824 } | |
825 var sum = 0; | |
826 calls.values.forEach((i) => sum += i); | |
827 calls.forEach((func, count) { | |
828 var row = [ | |
829 count / sum, | |
830 func.function, | |
831 ]; | |
832 model.addRow(new SortedTableRow(row)); | |
833 }); | 293 }); |
834 model.sort(); | 294 return element; |
835 } | 295 } |
836 | 296 |
837 _clearCallTables() { | 297 void _updateCallee(Element e, item, int index) { |
838 _buildCallersTable(null); | 298 e.children[0].text = Utils.formatPercentNormalized(_getCalleeT(item)); |
839 _buildCalleesTable(null); | 299 e.children[1] = new FunctionRefElement(_isolate, item.function, |
840 } | 300 queue: _r.queue)..classes = const ['name']; |
841 | 301 } |
842 _onCallersClick(TableRowElement tr) { | 302 |
843 var table = $['callers-table']; | 303 Element _createCalleeHeader() => |
844 final row = profileCallersTable.rowFromIndex(table.children.indexOf(tr)); | 304 new DivElement() |
845 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN]; | 305 ..classes = const ['function-item'] |
846 _scrollToFunction(function); | 306 ..children = [ |
847 } | 307 _createHeaderButton(const ['inclusive'], 'Callees(%)', |
848 | 308 _Table.callee, |
849 _buildCallersTable(ServiceFunction function) { | 309 _SortingField.callee, |
850 var calls = (function != null) ? function.profile.callers : null; | 310 _SortingDirection.descending), |
851 var table = $['callers-table']; | 311 _createHeaderButton(const ['name'], 'Method', |
852 _buildCallTable(calls, profileCallersTable); | 312 _Table.callee, |
853 _renderCallTable(table, profileCallersTable, _onCallersClick); | 313 _SortingField.method, |
854 } | 314 _SortingDirection.descending), |
855 | 315 ]; |
856 _onCalleesClick(TableRowElement tr) { | 316 |
857 var table = $['callees-table']; | 317 Element _createCaller() { |
858 final row = profileCalleesTable.rowFromIndex(table.children.indexOf(tr)); | 318 final element = new DivElement() |
859 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN]; | 319 ..classes = const ['function-item'] |
860 _scrollToFunction(function); | 320 ..children = [ |
861 } | 321 new SpanElement()..classes = const ['inclusive'] |
862 | 322 ..text = '0%', |
863 _buildCalleesTable(ServiceFunction function) { | 323 new SpanElement()..classes = const ['name'] |
864 var calls = (function != null) ? function.profile.callees : null; | 324 ]; |
865 var table = $['callees-table']; | 325 element.onClick.listen((_) { |
866 _buildCallTable(calls, profileCalleesTable); | 326 _selected = _callers.getItemFromElement(element); |
867 _renderCallTable(table, profileCalleesTable, _onCalleesClick); | 327 _r.dirty(); |
868 } | 328 }); |
869 | 329 return element; |
870 _changeSort(Element target, NameSortedTable table) { | 330 } |
871 if (target is TableCellElement) { | 331 |
872 if (table.sortColumnIndex != target.cellIndex) { | 332 void _updateCaller(Element e, item, int index) { |
873 table.sortColumnIndex = target.cellIndex; | 333 e.children[0].text = Utils.formatPercentNormalized(_getCallerT(item)); |
874 table.sortDescending = true; | 334 e.children[1] = new FunctionRefElement(_isolate, item.function, |
875 } else { | 335 queue: _r.queue)..classes = const ['name']; |
876 table.sortDescending = !profileTable.sortDescending; | 336 } |
877 } | 337 |
878 table.sort(); | 338 Element _createCallerHeader() => |
879 } | 339 new DivElement() |
880 } | 340 ..classes = const ['function-item'] |
881 | 341 ..children = [ |
882 changeSortProfile(Event e, var detail, Element target) { | 342 _createHeaderButton(const ['inclusive'], 'Callers(%)', |
883 _changeSort(target, profileTable); | 343 _Table.caller, |
884 _renderTable(); | 344 _SortingField.caller, |
885 } | 345 _SortingDirection.descending), |
886 | 346 _createHeaderButton(const ['name'], 'Method', |
887 changeSortCallers(Event e, var detail, Element target) { | 347 _Table.caller, |
888 _changeSort(target, profileCallersTable); | 348 _SortingField.method, |
889 _renderCallTable($['callers-table'], profileCallersTable, _onCallersClick); | 349 _SortingDirection.descending), |
890 } | 350 ]; |
891 | 351 |
892 changeSortCallees(Event e, var detail, Element target) { | 352 ButtonElement _createHeaderButton(List<String> classes, |
893 _changeSort(target, profileCalleesTable); | 353 String text, |
894 _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick); | 354 _Table table, |
895 } | 355 _SortingField field, |
896 | 356 _SortingDirection direction) => |
897 ////// | 357 new ButtonElement()..classes = classes |
898 /// | 358 ..text = _sortingField[table] != field ? text : |
899 /// Function tree. | 359 _sortingDirection[table] == _SortingDirection.ascending |
900 /// | 360 ? '$textâ–¼' : '$textâ–²' |
901 TableTree functionTree; | 361 ..onClick.listen((_) => _setSorting(table, field, direction)); |
902 _updateFunctionTreeView() { | 362 |
903 if (cpuProfileTreeElement == null) { | 363 List<Element> _createTree() { |
904 return; | 364 CpuProfileVirtualTreeElement tree; |
905 } | 365 return [ |
906 cpuProfileTreeElement.filter = (FunctionCallTreeNode node) { | 366 new StackTraceTreeConfigElement(showMode: false, |
907 return node.profileFunction.function == focusedFunction; | 367 showDirection: false, mode: ProfileTreeMode.function, |
908 }; | 368 direction: M.ProfileTreeDirection.exclusive, filter: _filter, |
909 } | 369 queue: _r.queue) |
910 | 370 ..onFilterChange.listen((e) { |
911 @published Isolate isolate; | 371 _filter = e.element.filter.trim(); |
912 @observable NameSortedTable profileTable; | 372 tree.filters = _filter.isNotEmpty |
913 @observable NameSortedTable profileCallersTable; | 373 ? [_filterTree, (node) { return node.name.contains(_filter); }] |
914 @observable NameSortedTable profileCalleesTable; | 374 : [_filterTree]; |
915 @observable ServiceFunction focusedFunction; | 375 }), |
916 @observable int focusedRow; | 376 new BRElement(), |
917 | 377 tree = new CpuProfileVirtualTreeElement(_isolate, _progress.profile, |
918 | 378 mode: ProfileTreeMode.function, |
919 StreamSubscription _resizeSubscription; | 379 direction: M.ProfileTreeDirection.exclusive, |
920 Task _updateTask; | 380 queue: _r.queue) |
921 Task _renderTask; | 381 ..filters = _filter.isNotEmpty |
922 | 382 ? [_filterTree, (node) { return node.name.contains(_filter); }] |
923 IsolateSampleProfileRepository _repository = | 383 : [_filterTree] |
924 new IsolateSampleProfileRepository(); | 384 ]; |
925 CpuProfile _profile; | 385 } |
926 SampleBufferControlElement sampleBufferControlElement; | 386 |
927 StackTraceTreeConfigElement stackTraceTreeConfigElement; | 387 bool _filterTree(M.FunctionCallTreeNode node) => |
928 CpuProfileVirtualTreeElement cpuProfileTreeElement; | 388 node.profileFunction == _selected; |
| 389 |
| 390 Future _request({bool clear: false, bool forceFetch: false}) async { |
| 391 _progress = null; |
| 392 _progressStream = _profiles.get(isolate, M.SampleProfileTag.none, |
| 393 clear: clear, forceFetch: forceFetch); |
| 394 _r.dirty(); |
| 395 _progress = (await _progressStream.first).progress; |
| 396 _r.dirty(); |
| 397 if (M.isSampleProcessRunning(_progress.status)) { |
| 398 _progress = (await _progressStream.last).progress; |
| 399 _r.dirty(); |
| 400 } |
| 401 } |
| 402 |
| 403 Future _clearCpuProfile(RefreshEvent e) async { |
| 404 e.element.disabled = true; |
| 405 await _request(clear: true); |
| 406 e.element.disabled = false; |
| 407 } |
| 408 |
| 409 Future _refresh(e) async { |
| 410 e.element.disabled = true; |
| 411 await _request(forceFetch: true); |
| 412 e.element.disabled = false; |
| 413 } |
| 414 |
| 415 _createSorter(_Table table) { |
| 416 var getter; |
| 417 switch (_sortingField[table]) { |
| 418 case _SortingField.exclusive: |
| 419 getter = _getExclusiveT; |
| 420 break; |
| 421 case _SortingField.inclusive: |
| 422 getter = _getInclusiveT; |
| 423 break; |
| 424 case _SortingField.callee: |
| 425 getter = _getCalleeT; |
| 426 break; |
| 427 case _SortingField.caller: |
| 428 getter = _getCallerT; |
| 429 break; |
| 430 case _SortingField.method: |
| 431 getter = (M.ProfileFunction s) => |
| 432 M.getFunctionFullName(s.function); |
| 433 break; |
| 434 } |
| 435 switch (_sortingDirection[table]) { |
| 436 case _SortingDirection.ascending: |
| 437 return (a, b) => getter(a).compareTo(getter(b)); |
| 438 case _SortingDirection.descending: |
| 439 return (a, b) => getter(b).compareTo(getter(a)); |
| 440 } |
| 441 } |
| 442 |
| 443 static double _getExclusiveT(M.ProfileFunction f) => |
| 444 f.normalizedExclusiveTicks; |
| 445 static double _getInclusiveT(M.ProfileFunction f) => |
| 446 f.normalizedExclusiveTicks; |
| 447 double _getCalleeT(M.ProfileFunction f) => |
| 448 _selected.callees[f] / _selected.callees.values.reduce((a, b) => a + b); |
| 449 double _getCallerT(M.ProfileFunction f) => |
| 450 _selected.callers[f] / _selected.callers.values.reduce((a, b) => a + b); |
929 } | 451 } |
OLD | NEW |