Chromium Code Reviews| 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_element; | 5 library cpu_profile_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 'package:observatory/models.dart' as M; | 9 import 'package:observatory/models.dart' as M; |
| 11 import 'package:observatory/service.dart'; | 10 import 'package:observatory/src/elements/cpu_profile/virtual_tree.dart'; |
| 12 import 'package:observatory/app.dart'; | 11 import 'package:observatory/src/elements/helpers/rendering_scheduler.dart'; |
| 13 import 'package:observatory/cpu_profile.dart'; | 12 import 'package:observatory/src/elements/helpers/tag.dart'; |
| 14 import 'package:observatory/elements.dart'; | 13 import 'package:observatory/src/elements/helpers/uris.dart'; |
| 15 import 'package:polymer/polymer.dart'; | 14 import 'package:observatory/src/elements/nav/bar.dart'; |
| 15 import 'package:observatory/src/elements/nav/isolate_menu.dart'; | |
| 16 import 'package:observatory/src/elements/nav/menu.dart'; | |
| 17 import 'package:observatory/src/elements/nav/notify.dart'; | |
| 18 import 'package:observatory/src/elements/nav/refresh.dart'; | |
| 19 import 'package:observatory/src/elements/nav/top_menu.dart'; | |
| 20 import 'package:observatory/src/elements/nav/vm_menu.dart'; | |
| 21 import 'package:observatory/src/elements/sample_buffer_control.dart'; | |
| 22 import 'package:observatory/src/elements/stack_trace_tree_config.dart'; | |
| 16 | 23 |
| 17 List<String> sorted(Set<String> attributes) { | 24 class CpuProfileElement extends HtmlElement implements Renderable { |
| 18 var list = attributes.toList(); | 25 static const tag = |
| 19 list.sort(); | 26 const Tag<CpuProfileElement>('cpu-profile'); |
| 20 return list; | |
| 21 } | |
| 22 | 27 |
| 23 abstract class ProfileTreeRow<T> extends TableTreeRow { | 28 RenderingScheduler<CpuProfileElement> _r; |
| 24 final CpuProfile profile; | |
| 25 final T node; | |
| 26 final String selfPercent; | |
| 27 final String percent; | |
| 28 bool _infoBoxShown = false; | |
| 29 HtmlElement infoBox; | |
| 30 HtmlElement infoButton; | |
| 31 | 29 |
| 32 ProfileTreeRow(TableTree tree, TableTreeRow parent, | 30 Stream<RenderedEvent<CpuProfileElement>> get onRendered => |
| 33 this.profile, this.node, double selfPercent, double percent) | 31 _r.onRendered; |
|
Cutch
2016/08/02 15:52:20
weird indentation
cbernaschina
2016/08/02 17:40:24
Done.
| |
| 34 : super(tree, parent), | |
| 35 selfPercent = Utils.formatPercentNormalized(selfPercent), | |
| 36 percent = Utils.formatPercentNormalized(percent); | |
| 37 | 32 |
| 38 static _addToMemberList(DivElement memberList, Map<String, String> items) { | 33 M.IsolateRef _isolate; |
| 39 items.forEach((k, v) { | 34 Stream<M.IsolateUpdateEvent> _isolateUpdates; |
| 40 var item = new DivElement(); | 35 M.NotificationRepository _notifications; |
| 41 item.classes.add('memberItem'); | 36 M.VMRef _vm; |
| 42 var name = new DivElement(); | 37 Stream<M.VMUpdateEvent> _vmUpdates; |
| 43 name.classes.add('memberName'); | 38 M.IsolateSampleProfileRepository _profiles; |
| 44 name.text = k; | 39 M.SampleProfileLoadingProgress _progress; |
| 45 var value = new DivElement(); | 40 M.SampleProfileTag _tag = M.SampleProfileTag.None; |
| 46 value.classes.add('memberValue'); | 41 |
| 47 value.text = v; | 42 M.IsolateRef get isolate => _isolate; |
| 48 item.children.add(name); | 43 M.NotificationRepository get notifications => _notifications; |
| 49 item.children.add(value); | 44 M.IsolateSampleProfileRepository get profiles => _profiles; |
| 50 memberList.children.add(item); | 45 M.VMRef get vm => _vm; |
| 51 }); | 46 |
| 47 factory CpuProfileElement(M.VMRef vm, Stream<M.VMUpdateEvent> vmUpdates, | |
| 48 M.IsolateRef isolate, Stream<M.IsolateUpdateEvent> isolateUpdates, | |
| 49 M.IsolateSampleProfileRepository profiles, | |
| 50 M.NotificationRepository notifications, | |
| 51 {RenderingQueue queue}) { | |
| 52 assert(isolate != null); | |
| 53 assert(isolateUpdates != null); | |
| 54 assert(notifications != null); | |
| 55 assert(profiles != null); | |
| 56 assert(vm != null); | |
| 57 assert(vmUpdates != null); | |
| 58 CpuProfileElement e = document.createElement(tag.name); | |
| 59 e._r = new RenderingScheduler(e, queue: queue); | |
| 60 e._isolate = isolate; | |
| 61 e._isolateUpdates = isolateUpdates; | |
| 62 e._notifications = notifications; | |
| 63 e._profiles = profiles; | |
| 64 e._vm = vm; | |
| 65 e._vmUpdates = vmUpdates; | |
| 66 return e; | |
| 52 } | 67 } |
| 53 | 68 |
| 54 makeInfoBox() { | 69 CpuProfileElement.created() : super.created(); |
| 55 if (infoBox != null) { | 70 |
| 56 return; | 71 @override |
| 57 } | 72 attached() { |
| 58 infoBox = new DivElement(); | 73 super.attached(); |
| 59 infoBox.classes.add('infoBox'); | 74 _r.enable(); |
| 60 infoBox.classes.add('shadow'); | 75 _request(); |
| 61 infoBox.style.display = 'none'; | |
| 62 listeners.add(infoBox.onClick.listen((e) => e.stopPropagation())); | |
| 63 } | 76 } |
| 64 | 77 |
| 65 makeInfoButton() { | 78 @override |
| 66 infoButton = new SpanElement(); | |
| 67 infoButton.style.marginLeft = 'auto'; | |
| 68 infoButton.style.marginRight = '1em'; | |
| 69 infoButton.children.add(new Element.tag('icon-info-outline')); | |
| 70 listeners.add(infoButton.onClick.listen((event) { | |
| 71 event.stopPropagation(); | |
| 72 toggleInfoBox(); | |
| 73 })); | |
| 74 } | |
| 75 | |
| 76 static const attributes = const { | |
| 77 'optimized' : const ['O', null, 'Optimized'], | |
| 78 'unoptimized' : const ['U', null, 'Unoptimized'], | |
| 79 'inlined' : const ['I', null, 'Inlined'], | |
| 80 'intrinsic' : const ['It', null, 'Intrinsic'], | |
| 81 'ffi' : const ['F', null, 'FFI'], | |
| 82 'dart' : const ['D', null, 'Dart'], | |
| 83 'tag' : const ['T', null, 'Tag'], | |
| 84 'native' : const ['N', null, 'Native'], | |
| 85 'stub': const ['S', null, 'Stub'], | |
| 86 'synthetic' : const ['?', null, 'Synthetic'], | |
| 87 }; | |
| 88 | |
| 89 HtmlElement newAttributeBox(String attribute) { | |
| 90 List attributeDetails = attributes[attribute]; | |
| 91 if (attributeDetails == null) { | |
| 92 print('could not find attribute $attribute'); | |
| 93 return null; | |
| 94 } | |
| 95 var element = new SpanElement(); | |
| 96 element.style.border = 'solid 2px #ECECEC'; | |
| 97 element.style.height = '100%'; | |
| 98 element.style.display = 'inline-block'; | |
| 99 element.style.textAlign = 'center'; | |
| 100 element.style.minWidth = '1.5em'; | |
| 101 element.style.fontWeight = 'bold'; | |
| 102 if (attributeDetails[1] != null) { | |
| 103 element.style.backgroundColor = attributeDetails[1]; | |
| 104 } | |
| 105 element.text = attributeDetails[0]; | |
| 106 element.title = attributeDetails[2]; | |
| 107 return element; | |
| 108 } | |
| 109 | |
| 110 onHide() { | |
| 111 super.onHide(); | |
| 112 infoBox = null; | |
| 113 infoButton = null; | |
| 114 } | |
| 115 | |
| 116 showInfoBox() { | |
| 117 if ((infoButton == null) || (infoBox == null)) { | |
| 118 return; | |
| 119 } | |
| 120 _infoBoxShown = true; | |
| 121 infoBox.style.display = 'block'; | |
| 122 infoButton.children.clear(); | |
| 123 infoButton.children.add(new Element.tag('icon-info')); | |
| 124 } | |
| 125 | |
| 126 hideInfoBox() { | |
| 127 _infoBoxShown = false; | |
| 128 if ((infoButton == null) || (infoBox == null)) { | |
| 129 return; | |
| 130 } | |
| 131 infoBox.style.display = 'none'; | |
| 132 infoButton.children.clear(); | |
| 133 infoButton.children.add(new Element.tag('icon-info-outline')); | |
| 134 } | |
| 135 | |
| 136 toggleInfoBox() { | |
| 137 if (_infoBoxShown) { | |
| 138 hideInfoBox(); | |
| 139 } else { | |
| 140 showInfoBox(); | |
| 141 } | |
| 142 } | |
| 143 | |
| 144 hideAllInfoBoxes() { | |
| 145 final List<ProfileTreeRow> rows = tree.rows; | |
| 146 for (var row in rows) { | |
| 147 row.hideInfoBox(); | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 onClick(MouseEvent e) { | |
| 152 e.stopPropagation(); | |
| 153 if (e.altKey) { | |
| 154 bool show = !_infoBoxShown; | |
| 155 hideAllInfoBoxes(); | |
| 156 if (show) { | |
| 157 showInfoBox(); | |
| 158 } | |
| 159 return; | |
| 160 } | |
| 161 super.onClick(e); | |
| 162 } | |
| 163 | |
| 164 HtmlElement newCodeRef(ProfileCode code) { | |
| 165 var codeRef = new Element.tag('code-ref'); | |
| 166 codeRef.ref = code.code; | |
| 167 return codeRef; | |
| 168 } | |
| 169 | |
| 170 HtmlElement newFunctionRef(ProfileFunction function) { | |
| 171 var ref = new Element.tag('function-ref'); | |
| 172 ref.ref = function.function; | |
| 173 return ref; | |
| 174 } | |
| 175 | |
| 176 HtmlElement hr() { | |
| 177 var element = new HRElement(); | |
| 178 return element; | |
| 179 } | |
| 180 | |
| 181 HtmlElement div(String text) { | |
| 182 var element = new DivElement(); | |
| 183 element.text = text; | |
| 184 return element; | |
| 185 } | |
| 186 | |
| 187 HtmlElement br() { | |
| 188 return new BRElement(); | |
| 189 } | |
| 190 | |
| 191 HtmlElement span(String text) { | |
| 192 var element = new SpanElement(); | |
| 193 element.style.minWidth = '1em'; | |
| 194 element.text = text; | |
| 195 return element; | |
| 196 } | |
| 197 } | |
| 198 | |
| 199 class CodeProfileTreeRow extends ProfileTreeRow<CodeCallTreeNode> { | |
| 200 CodeProfileTreeRow(TableTree tree, CodeProfileTreeRow parent, | |
| 201 CpuProfile profile, CodeCallTreeNode node) | |
| 202 : super(tree, parent, profile, node, | |
| 203 node.profileCode.normalizedExclusiveTicks, | |
| 204 node.percentage) { | |
| 205 // fill out attributes. | |
| 206 } | |
| 207 | |
| 208 bool hasChildren() => node.children.length > 0; | |
| 209 | |
| 210 void onShow() { | |
| 211 super.onShow(); | |
| 212 | |
| 213 if (children.length == 0) { | |
| 214 for (var childNode in node.children) { | |
| 215 var row = new CodeProfileTreeRow(tree, this, profile, childNode); | |
| 216 children.add(row); | |
| 217 } | |
| 218 } | |
| 219 | |
| 220 // Fill in method column. | |
| 221 var methodColumn = flexColumns[0]; | |
| 222 methodColumn.style.justifyContent = 'flex-start'; | |
| 223 methodColumn.style.position = 'relative'; | |
| 224 | |
| 225 // Percent. | |
| 226 var percentNode = new DivElement(); | |
| 227 percentNode.text = percent; | |
| 228 percentNode.style.minWidth = '5em'; | |
| 229 percentNode.style.textAlign = 'right'; | |
| 230 percentNode.title = 'Executing: $selfPercent'; | |
| 231 methodColumn.children.add(percentNode); | |
| 232 | |
| 233 // Gap. | |
| 234 var gap = new SpanElement(); | |
| 235 gap.style.minWidth = '1em'; | |
| 236 methodColumn.children.add(gap); | |
| 237 | |
| 238 // Code link. | |
| 239 var codeRef = newCodeRef(node.profileCode); | |
| 240 codeRef.style.alignSelf = 'center'; | |
| 241 methodColumn.children.add(codeRef); | |
| 242 | |
| 243 gap = new SpanElement(); | |
| 244 gap.style.minWidth = '1em'; | |
| 245 methodColumn.children.add(gap); | |
| 246 | |
| 247 for (var attribute in sorted(node.attributes)) { | |
| 248 methodColumn.children.add(newAttributeBox(attribute)); | |
| 249 } | |
| 250 | |
| 251 makeInfoBox(); | |
| 252 methodColumn.children.add(infoBox); | |
| 253 | |
| 254 infoBox.children.add(span('Code ')); | |
| 255 infoBox.children.add(newCodeRef(node.profileCode)); | |
| 256 infoBox.children.add(span(' ')); | |
| 257 for (var attribute in sorted(node.profileCode.attributes)) { | |
| 258 infoBox.children.add(newAttributeBox(attribute)); | |
| 259 } | |
| 260 infoBox.children.add(br()); | |
| 261 infoBox.children.add(br()); | |
| 262 var memberList = new DivElement(); | |
| 263 memberList.classes.add('memberList'); | |
| 264 infoBox.children.add(br()); | |
| 265 infoBox.children.add(memberList); | |
| 266 ProfileTreeRow._addToMemberList(memberList, { | |
| 267 'Exclusive ticks' : node.profileCode.formattedExclusiveTicks, | |
| 268 'Cpu time' : node.profileCode.formattedCpuTime, | |
| 269 'Inclusive ticks' : node.profileCode.formattedInclusiveTicks, | |
| 270 'Call stack time' : node.profileCode.formattedOnStackTime, | |
| 271 }); | |
| 272 | |
| 273 makeInfoButton(); | |
| 274 methodColumn.children.add(infoButton); | |
| 275 | |
| 276 // Fill in self column. | |
| 277 var selfColumn = flexColumns[1]; | |
| 278 selfColumn.style.position = 'relative'; | |
| 279 selfColumn.style.alignItems = 'center'; | |
| 280 selfColumn.text = selfPercent; | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> { | |
| 285 FunctionProfileTreeRow(TableTree tree, FunctionProfileTreeRow parent, | |
| 286 CpuProfile profile, FunctionCallTreeNode node) | |
| 287 : super(tree, parent, profile, node, | |
| 288 node.profileFunction.normalizedExclusiveTicks, | |
| 289 node.percentage) { | |
| 290 // fill out attributes. | |
| 291 } | |
| 292 | |
| 293 bool hasChildren() => node.children.length > 0; | |
| 294 | |
| 295 onShow() { | |
| 296 super.onShow(); | |
| 297 if (children.length == 0) { | |
| 298 for (var childNode in node.children) { | |
| 299 var row = new FunctionProfileTreeRow(tree, this, profile, childNode); | |
| 300 children.add(row); | |
| 301 } | |
| 302 } | |
| 303 | |
| 304 var methodColumn = flexColumns[0]; | |
| 305 methodColumn.style.justifyContent = 'flex-start'; | |
| 306 | |
| 307 var codeAndFunctionColumn = new DivElement(); | |
| 308 codeAndFunctionColumn.classes.add('flex-column'); | |
| 309 codeAndFunctionColumn.style.justifyContent = 'center'; | |
| 310 codeAndFunctionColumn.style.width = '100%'; | |
| 311 methodColumn.children.add(codeAndFunctionColumn); | |
| 312 | |
| 313 var functionRow = new DivElement(); | |
| 314 functionRow.classes.add('flex-row'); | |
| 315 functionRow.style.position = 'relative'; | |
| 316 functionRow.style.justifyContent = 'flex-start'; | |
| 317 codeAndFunctionColumn.children.add(functionRow); | |
| 318 | |
| 319 // Insert the parent percentage | |
| 320 var parentPercent = new SpanElement(); | |
| 321 parentPercent.text = percent; | |
| 322 parentPercent.style.minWidth = '4em'; | |
| 323 parentPercent.style.alignSelf = 'center'; | |
| 324 parentPercent.style.textAlign = 'right'; | |
| 325 parentPercent.title = 'Executing: $selfPercent'; | |
| 326 functionRow.children.add(parentPercent); | |
| 327 | |
| 328 // Gap. | |
| 329 var gap = new SpanElement(); | |
| 330 gap.style.minWidth = '1em'; | |
| 331 gap.text = ' '; | |
| 332 functionRow.children.add(gap); | |
| 333 | |
| 334 var functionRef = new Element.tag('function-ref'); | |
| 335 functionRef.ref = node.profileFunction.function; | |
| 336 functionRef.style.alignSelf = 'center'; | |
| 337 functionRow.children.add(functionRef); | |
| 338 | |
| 339 gap = new SpanElement(); | |
| 340 gap.style.minWidth = '1em'; | |
| 341 gap.text = ' '; | |
| 342 functionRow.children.add(gap); | |
| 343 | |
| 344 for (var attribute in sorted(node.attributes)) { | |
| 345 functionRow.children.add(newAttributeBox(attribute)); | |
| 346 } | |
| 347 | |
| 348 makeInfoBox(); | |
| 349 functionRow.children.add(infoBox); | |
| 350 | |
| 351 if (M.hasDartCode(node.profileFunction.function.kind)) { | |
| 352 infoBox.children.add(div('Code for current node')); | |
| 353 infoBox.children.add(br()); | |
| 354 var totalTicks = node.totalCodesTicks; | |
| 355 var numCodes = node.codes.length; | |
| 356 for (var i = 0; i < numCodes; i++) { | |
| 357 var codeRowSpan = new DivElement(); | |
| 358 codeRowSpan.style.paddingLeft = '1em'; | |
| 359 infoBox.children.add(codeRowSpan); | |
| 360 var nodeCode = node.codes[i]; | |
| 361 var ticks = nodeCode.ticks; | |
| 362 var percentage = Utils.formatPercent(ticks, totalTicks); | |
| 363 var percentageSpan = new SpanElement(); | |
| 364 percentageSpan.style.display = 'inline-block'; | |
| 365 percentageSpan.text = '$percentage'; | |
| 366 percentageSpan.style.minWidth = '5em'; | |
| 367 percentageSpan.style.textAlign = 'right'; | |
| 368 codeRowSpan.children.add(percentageSpan); | |
| 369 var codeRef = new Element.tag('code-ref'); | |
| 370 codeRef.ref = nodeCode.code.code; | |
| 371 codeRef.style.marginLeft = '1em'; | |
| 372 codeRef.style.marginRight = 'auto'; | |
| 373 codeRef.style.width = '100%'; | |
| 374 codeRowSpan.children.add(codeRef); | |
| 375 } | |
| 376 infoBox.children.add(hr()); | |
| 377 } | |
| 378 infoBox.children.add(span('Function ')); | |
| 379 infoBox.children.add(newFunctionRef(node.profileFunction)); | |
| 380 infoBox.children.add(span(' ')); | |
| 381 for (var attribute in sorted(node.profileFunction.attributes)) { | |
| 382 infoBox.children.add(newAttributeBox(attribute)); | |
| 383 } | |
| 384 var memberList = new DivElement(); | |
| 385 memberList.classes.add('memberList'); | |
| 386 infoBox.children.add(br()); | |
| 387 infoBox.children.add(br()); | |
| 388 infoBox.children.add(memberList); | |
| 389 infoBox.children.add(br()); | |
| 390 ProfileTreeRow._addToMemberList(memberList, { | |
| 391 'Exclusive ticks' : node.profileFunction.formattedExclusiveTicks, | |
| 392 'Cpu time' : node.profileFunction.formattedCpuTime, | |
| 393 'Inclusive ticks' : node.profileFunction.formattedInclusiveTicks, | |
| 394 'Call stack time' : node.profileFunction.formattedOnStackTime, | |
| 395 }); | |
| 396 | |
| 397 if (M.hasDartCode(node.profileFunction.function.kind)) { | |
| 398 infoBox.children.add(div('Code containing function')); | |
| 399 infoBox.children.add(br()); | |
| 400 var totalTicks = profile.sampleCount; | |
| 401 var codes = node.profileFunction.profileCodes; | |
| 402 var numCodes = codes.length; | |
| 403 for (var i = 0; i < numCodes; i++) { | |
| 404 var codeRowSpan = new DivElement(); | |
| 405 codeRowSpan.style.paddingLeft = '1em'; | |
| 406 infoBox.children.add(codeRowSpan); | |
| 407 var profileCode = codes[i]; | |
| 408 var code = profileCode.code; | |
| 409 var ticks = profileCode.inclusiveTicks; | |
| 410 var percentage = Utils.formatPercent(ticks, totalTicks); | |
| 411 var percentageSpan = new SpanElement(); | |
| 412 percentageSpan.style.display = 'inline-block'; | |
| 413 percentageSpan.text = '$percentage'; | |
| 414 percentageSpan.style.minWidth = '5em'; | |
| 415 percentageSpan.style.textAlign = 'right'; | |
| 416 percentageSpan.title = 'Inclusive ticks'; | |
| 417 codeRowSpan.children.add(percentageSpan); | |
| 418 var codeRef = new Element.tag('code-ref'); | |
| 419 codeRef.ref = code; | |
| 420 codeRef.style.marginLeft = '1em'; | |
| 421 codeRef.style.marginRight = 'auto'; | |
| 422 codeRef.style.width = '100%'; | |
| 423 codeRowSpan.children.add(codeRef); | |
| 424 } | |
| 425 } | |
| 426 | |
| 427 makeInfoButton(); | |
| 428 methodColumn.children.add(infoButton); | |
| 429 | |
| 430 // Fill in self column. | |
| 431 var selfColumn = flexColumns[1]; | |
| 432 selfColumn.style.position = 'relative'; | |
| 433 selfColumn.style.alignItems = 'center'; | |
| 434 selfColumn.text = selfPercent; | |
| 435 } | |
| 436 } | |
| 437 | |
| 438 @CustomTag('sample-buffer-control') | |
| 439 class SampleBufferControlElement extends ObservatoryElement { | |
| 440 SampleBufferControlElement.created() : super.created() { | |
| 441 _stopWatch.start(); | |
| 442 } | |
| 443 | |
| 444 Future<CpuProfile> reload(Isolate isolate) async { | |
| 445 profile.clear(); | |
| 446 if (isolate == null) { | |
| 447 _update(profile); | |
| 448 // Notify listener. | |
| 449 onSampleBufferUpdate(profile); | |
| 450 return profile; | |
| 451 } | |
| 452 profileVM = isolate.vm.profileVM; | |
| 453 if (tagSelector == null) { | |
| 454 // Set default value. | |
| 455 tagSelector = profileVM ? 'UserVM' : 'None'; | |
| 456 } | |
| 457 await _changeState(kFetchingState); | |
| 458 try { | |
| 459 var response; | |
| 460 if (allocationProfileClass != null) { | |
| 461 response = | |
| 462 await allocationProfileClass.getAllocationSamples(tagSelector); | |
| 463 } else { | |
| 464 var params = { 'tags': tagSelector }; | |
| 465 response = await isolate.invokeRpc('_getCpuProfile', params); | |
| 466 } | |
| 467 await _changeState(kLoadingState); | |
| 468 profile.load(isolate, response); | |
| 469 profile.buildFunctionCallerAndCallees(); | |
| 470 _update(profile); | |
| 471 await _changeState(kLoadedState); | |
| 472 // Notify listener. | |
| 473 onSampleBufferUpdate(profile); | |
| 474 return profile; | |
| 475 } catch (e, st) { | |
| 476 if (e is ServerRpcException) { | |
| 477 ServerRpcException se = e; | |
| 478 if (se.code == ServerRpcException.kFeatureDisabled) { | |
| 479 await _changeState(kDisabledState); | |
| 480 return profile; | |
| 481 } | |
| 482 } | |
| 483 await _changeState(kExceptionState, e, st); | |
| 484 rethrow; | |
| 485 } | |
| 486 } | |
| 487 | |
| 488 Future _changeState(String newState, [exception, stackTrace]) { | |
| 489 if ((newState == kDisabledState) || | |
| 490 (newState == kFetchingState) || | |
| 491 (newState == kExceptionState)) { | |
| 492 loadTime = ''; | |
| 493 fetchTime = ''; | |
| 494 } else if (newState == kLoadingState) { | |
| 495 fetchTime = formatTimeMilliseconds(_stopWatch.elapsedMilliseconds); | |
| 496 loadTime = ''; | |
| 497 } else if (newState == kLoadedState) { | |
| 498 loadTime = formatTimeMilliseconds(_stopWatch.elapsedMilliseconds); | |
| 499 } | |
| 500 state = newState; | |
| 501 this.exception = exception; | |
| 502 this.stackTrace = stackTrace; | |
| 503 _stopWatch.reset(); | |
| 504 return window.animationFrame; | |
| 505 } | |
| 506 | |
| 507 _update(CpuProfile sampleBuffer) { | |
| 508 sampleCount = profile.sampleCount.toString(); | |
| 509 refreshTime = new DateTime.now().toString(); | |
| 510 stackDepth = profile.stackDepth.toString(); | |
| 511 sampleRate = profile.sampleRate.toStringAsFixed(0); | |
| 512 if (profile.sampleCount == 0) { | |
| 513 timeSpan = '0s'; | |
| 514 } else { | |
| 515 timeSpan = formatTime(profile.timeSpan); | |
| 516 } | |
| 517 } | |
| 518 | |
| 519 void tagSelectorChanged(oldValue) { | |
| 520 reload(profile.isolate); | |
| 521 } | |
| 522 | |
| 523 Function onSampleBufferUpdate; | |
| 524 @observable bool showTagSelector = true; | |
| 525 @observable bool profileVM = false; | |
| 526 @observable String sampleCount = ''; | |
| 527 @observable String refreshTime = ''; | |
| 528 @observable String sampleRate = ''; | |
| 529 @observable String stackDepth = ''; | |
| 530 @observable String timeSpan = ''; | |
| 531 @observable String fetchTime = ''; | |
| 532 @observable String loadTime = ''; | |
| 533 @observable String tagSelector; | |
| 534 @observable String state = kFetchingState; | |
| 535 @observable var exception; | |
| 536 @observable var stackTrace; | |
| 537 | |
| 538 static const kDisabledState = 'kDisabled'; | |
| 539 static const kExceptionState = 'Exception'; | |
| 540 static const kFetchingState = 'kFetching'; | |
| 541 static const kLoadedState = 'kLoaded'; | |
| 542 static const kLoadingState = 'kLoading'; | |
| 543 static const kNotLoadedState = 'kNotLoaded'; | |
| 544 | |
| 545 Isolate isolate; | |
| 546 Class allocationProfileClass; | |
| 547 | |
| 548 final CpuProfile profile = new CpuProfile(); | |
| 549 final Stopwatch _stopWatch = new Stopwatch(); | |
| 550 } | |
| 551 | |
| 552 @CustomTag('stack-trace-tree-config') | |
| 553 class StackTraceTreeConfigElement extends ObservatoryElement { | |
| 554 StackTraceTreeConfigElement.created() : super.created(); | |
| 555 | |
| 556 attached() { | |
| 557 super.attached(); | |
| 558 var filterElement = shadowRoot.querySelector('#filterInput'); | |
| 559 keyDownSubscription = filterElement.onKeyDown.listen(_onKeyDown); | |
| 560 blurSubscription = filterElement.onBlur.listen(_onBlur); | |
| 561 } | |
| 562 | |
| 563 detached() { | 79 detached() { |
| 564 super.detached(); | 80 super.detached(); _r.disable(notify: true); |
| 565 keyDownSubscription?.cancel(); | 81 children = []; |
| 566 blurSubscription?.cancel(); | |
| 567 } | |
| 568 | |
| 569 void _onKeyDown(KeyboardEvent keyEvent) { | |
| 570 if (keyEvent.keyCode == 13) { | |
| 571 // On enter, update the filter string. | |
| 572 filterString = | |
| 573 (shadowRoot.querySelector('#filterInput') as InputElement).value; | |
| 574 if (onTreeConfigChange == null) { | |
| 575 return; | |
| 576 } | |
| 577 onTreeConfigChange(modeSelector, directionSelector, filterString); | |
| 578 } | |
| 579 } | |
| 580 | |
| 581 void _onBlur(Event event) { | |
| 582 // Input box has lost focus, update the display to match the active | |
| 583 // filter string. | |
| 584 (shadowRoot.querySelector('#filterInput') as InputElement).value = | |
| 585 filterString; | |
| 586 } | |
| 587 | |
| 588 void modeSelectorChanged(oldValue) { | |
| 589 if (onTreeConfigChange == null) { | |
| 590 return; | |
| 591 } | |
| 592 onTreeConfigChange(modeSelector, directionSelector, filterString); | |
| 593 } | |
| 594 | |
| 595 void directionSelectorChanged(oldValue) { | |
| 596 if (onTreeConfigChange == null) { | |
| 597 return; | |
| 598 } | |
| 599 onTreeConfigChange(modeSelector, directionSelector, filterString); | |
| 600 } | |
| 601 | |
| 602 Function onTreeConfigChange; | |
| 603 StreamSubscription keyDownSubscription; | |
| 604 StreamSubscription blurSubscription; | |
| 605 @observable bool show = true; | |
| 606 @observable bool showModeSelector = true; | |
| 607 @observable bool showDirectionSelector = true; | |
| 608 @observable bool showFilter = true; | |
| 609 @observable String modeSelector = 'Function'; | |
| 610 @observable String directionSelector = 'Up'; | |
| 611 @observable String filterString; | |
| 612 } | |
| 613 | |
| 614 class FunctionCallTreeNodeRow extends VirtualTreeRow { | |
| 615 final CpuProfile profile; | |
| 616 final FunctionCallTreeNode node; | |
| 617 String selfPercent; | |
| 618 String totalPercent; | |
| 619 String percent; | |
| 620 | |
| 621 static const kHotThreshold = 0.05; // 5%. | |
| 622 static const kMediumThreshold = 0.02; // 2%. | |
| 623 | |
| 624 double _percent(bool self) { | |
| 625 if (self) { | |
| 626 return node.profileFunction.normalizedExclusiveTicks; | |
| 627 } else { | |
| 628 return node.profileFunction.normalizedInclusiveTicks; | |
| 629 } | |
| 630 } | |
| 631 | |
| 632 bool isHot(bool self) => _percent(self) > kHotThreshold; | |
| 633 bool isMedium(bool self) => _percent(self) > kMediumThreshold; | |
| 634 | |
| 635 String rowClass(bool self) { | |
| 636 if (isHot(self)) { | |
| 637 return 'hotProfile'; | |
| 638 } else if (isMedium(self)) { | |
| 639 return 'mediumProfile'; | |
| 640 } else { | |
| 641 return 'coldProfile'; | |
| 642 } | |
| 643 } | |
| 644 | |
| 645 FunctionCallTreeNodeRow(VirtualTree tree, | |
| 646 int depth, | |
| 647 this.profile, | |
| 648 FunctionCallTreeNode node) | |
| 649 : node = node, | |
| 650 super(tree, depth) { | |
| 651 if ((node.profileFunction.function.kind == M.FunctionKind.tag) && | |
| 652 (node.profileFunction.normalizedExclusiveTicks == 0) && | |
| 653 (node.profileFunction.normalizedInclusiveTicks == 0)) { | |
| 654 selfPercent = ''; | |
| 655 totalPercent = ''; | |
| 656 } else { | |
| 657 selfPercent = Utils.formatPercentNormalized( | |
| 658 node.profileFunction.normalizedExclusiveTicks); | |
| 659 totalPercent = Utils.formatPercentNormalized( | |
| 660 node.profileFunction.normalizedInclusiveTicks); | |
| 661 } | |
| 662 percent = Utils.formatPercentNormalized(node.percentage); | |
| 663 } | |
| 664 | |
| 665 void onRender(DivElement rowDiv) { | |
| 666 rowDiv.children.add(makeGap(ems:0.1)); | |
| 667 rowDiv.children.add( | |
| 668 makeText(totalPercent, | |
| 669 toolTip: 'global % on stack')); | |
| 670 rowDiv.children.add(makeGap()); | |
| 671 rowDiv.children.add( | |
| 672 makeText(selfPercent, | |
| 673 toolTip: 'global % executing')); | |
| 674 rowDiv.children.add(makeGap()); | |
| 675 rowDiv.children.add(makeIndenter(depth, colored: false)); | |
| 676 rowDiv.children.add(makeColorBar(depth)); | |
| 677 rowDiv.children.add(makeGap(ems: 1.0)); | |
| 678 rowDiv.children.add(makeExpander()); | |
| 679 rowDiv.children.add( | |
| 680 makeText(percent, toolTip: 'tree node %', flexBasis: null)); | |
| 681 rowDiv.children.add(makeGap(ems: 0.5)); | |
| 682 functionRef.ref = node.profileFunction.function; | |
| 683 rowDiv.children.add(functionRef); | |
| 684 } | |
| 685 | |
| 686 var functionRef = new Element.tag('function-ref'); | |
| 687 | |
| 688 int get childCount => node.children.length; | |
| 689 | |
| 690 void onShow() { | |
| 691 if (children.length > 0) { | |
| 692 return; | |
| 693 } | |
| 694 for (var childNode in node.children) { | |
| 695 var row = new FunctionCallTreeNodeRow(tree, depth + 1, profile, childNode) ; | |
| 696 children.add(row); | |
| 697 } | |
| 698 } | |
| 699 } | |
| 700 | |
| 701 | |
| 702 class CodeCallTreeNodeRow extends VirtualTreeRow { | |
| 703 final CpuProfile profile; | |
| 704 final CodeCallTreeNode node; | |
| 705 String selfPercent; | |
| 706 String totalPercent; | |
| 707 String percent; | |
| 708 | |
| 709 static const kHotThreshold = 0.05; // 5%. | |
| 710 static const kMediumThreshold = 0.02; // 2%. | |
| 711 | |
| 712 double _percent(bool self) { | |
| 713 if (self) { | |
| 714 return node.profileCode.normalizedExclusiveTicks; | |
| 715 } else { | |
| 716 return node.profileCode.normalizedInclusiveTicks; | |
| 717 } | |
| 718 } | |
| 719 | |
| 720 bool isHot(bool self) => _percent(self) > kHotThreshold; | |
| 721 bool isMedium(bool self) => _percent(self) > kMediumThreshold; | |
| 722 | |
| 723 String rowClass(bool self) { | |
| 724 if (isHot(self)) { | |
| 725 return 'hotProfile'; | |
| 726 } else if (isMedium(self)) { | |
| 727 return 'mediumProfile'; | |
| 728 } else { | |
| 729 return 'coldProfile'; | |
| 730 } | |
| 731 } | |
| 732 | |
| 733 CodeCallTreeNodeRow(VirtualTree tree, | |
| 734 int depth, | |
| 735 this.profile, | |
| 736 CodeCallTreeNode node) | |
| 737 : node = node, | |
| 738 super(tree, depth) { | |
| 739 if ((node.profileCode.code.kind == M.CodeKind.tag) && | |
| 740 (node.profileCode.normalizedExclusiveTicks == 0) && | |
| 741 (node.profileCode.normalizedInclusiveTicks == 0)) { | |
| 742 selfPercent = ''; | |
| 743 totalPercent = ''; | |
| 744 } else { | |
| 745 selfPercent = Utils.formatPercentNormalized( | |
| 746 node.profileCode.normalizedExclusiveTicks); | |
| 747 totalPercent = Utils.formatPercentNormalized( | |
| 748 node.profileCode.normalizedInclusiveTicks); | |
| 749 } | |
| 750 percent = Utils.formatPercentNormalized(node.percentage); | |
| 751 } | |
| 752 | |
| 753 void onRender(DivElement rowDiv) { | |
| 754 rowDiv.children.add(makeGap(ems:0.1)); | |
| 755 rowDiv.children.add( | |
| 756 makeText(totalPercent, | |
| 757 toolTip: 'global % on stack')); | |
| 758 rowDiv.children.add(makeGap()); | |
| 759 rowDiv.children.add( | |
| 760 makeText(selfPercent, | |
| 761 toolTip: 'global % executing')); | |
| 762 rowDiv.children.add(makeGap()); | |
| 763 rowDiv.children.add(makeIndenter(depth, colored: false)); | |
| 764 rowDiv.children.add(makeColorBar(depth)); | |
| 765 rowDiv.children.add(makeGap(ems: 1.0)); | |
| 766 rowDiv.children.add(makeExpander()); | |
| 767 rowDiv.children.add( | |
| 768 makeText(percent, toolTip: 'tree node %', flexBasis: null)); | |
| 769 rowDiv.children.add(makeGap(ems: 0.5)); | |
| 770 codeRef.ref = node.profileCode.code; | |
| 771 rowDiv.children.add(codeRef); | |
| 772 } | |
| 773 | |
| 774 var codeRef = new Element.tag('code-ref'); | |
| 775 | |
| 776 int get childCount => node.children.length; | |
| 777 | |
| 778 void onShow() { | |
| 779 if (children.length > 0) { | |
| 780 return; | |
| 781 } | |
| 782 for (var childNode in node.children) { | |
| 783 var row = new CodeCallTreeNodeRow(tree, depth + 1, profile, childNode); | |
| 784 children.add(row); | |
| 785 } | |
| 786 } | |
| 787 } | |
| 788 | |
| 789 /// Displays a CpuProfile | |
| 790 @CustomTag('cpu-profile') | |
| 791 class CpuProfileElement extends ObservatoryElement { | |
| 792 CpuProfileElement.created() : super.created() { | |
| 793 _updateTask = new Task(update); | |
| 794 _renderTask = new Task(render); | |
| 795 } | |
| 796 | |
| 797 attached() { | |
| 798 super.attached(); | |
| 799 sampleBufferControlElement = | |
| 800 shadowRoot.querySelector('#sampleBufferControl'); | |
| 801 assert(sampleBufferControlElement != null); | |
| 802 sampleBufferControlElement.onSampleBufferUpdate = onSampleBufferChange; | |
| 803 stackTraceTreeConfigElement = | |
| 804 shadowRoot.querySelector('#stackTraceTreeConfig'); | |
| 805 assert(stackTraceTreeConfigElement != null); | |
| 806 stackTraceTreeConfigElement.onTreeConfigChange = onTreeConfigChange; | |
| 807 cpuProfileVirtualTreeElement = shadowRoot.querySelector('#cpuProfileVirtualT ree'); | |
| 808 assert(cpuProfileVirtualTreeElement != null); | |
| 809 cpuProfileVirtualTreeElement.profile = sampleBufferControlElement.profile; | |
| 810 _resizeSubscription = window.onResize.listen((_) => _updateSize()); | |
| 811 _updateTask.queue(); | |
| 812 _updateSize(); | |
| 813 } | |
| 814 | |
| 815 detached() { | |
| 816 super.detached(); | |
| 817 if (_resizeSubscription != null) { | |
| 818 _resizeSubscription.cancel(); | |
| 819 } | |
| 820 } | |
| 821 | |
| 822 _updateSize() { | |
| 823 var applySize = (e) { | |
| 824 Rectangle rect = e.getBoundingClientRect(); | |
| 825 final totalHeight = window.innerHeight; | |
| 826 final bottomMargin = 200; | |
| 827 final mainHeight = totalHeight - bottomMargin; | |
| 828 e.style.setProperty('height', '${mainHeight}px'); | |
| 829 }; | |
| 830 HtmlElement e2 = $['cpuProfileVirtualTree']; | |
| 831 applySize(e2); | |
| 832 } | |
| 833 | |
| 834 isolateChanged(oldValue) { | |
| 835 _updateTask.queue(); | |
| 836 } | |
| 837 | |
| 838 update() { | |
| 839 if (sampleBufferControlElement != null) { | |
| 840 sampleBufferControlElement.reload(isolate); | |
| 841 } | |
| 842 } | |
| 843 | |
| 844 onSampleBufferChange(CpuProfile sampleBuffer) { | |
| 845 _renderTask.queue(); | |
| 846 } | |
| 847 | |
| 848 onTreeConfigChange(String modeSelector, | |
| 849 String directionSelector, | |
| 850 String filterString) { | |
| 851 ProfileTreeDirection direction = ProfileTreeDirection.Exclusive; | |
| 852 if (directionSelector != 'Up') { | |
| 853 direction = ProfileTreeDirection.Inclusive; | |
| 854 } | |
| 855 ProfileTreeMode mode = ProfileTreeMode.Function; | |
| 856 if (modeSelector == 'Code') { | |
| 857 mode = ProfileTreeMode.Code; | |
| 858 } | |
| 859 // Clear the filter. | |
| 860 cpuProfileVirtualTreeElement.filter = null; | |
| 861 if (filterString != null) { | |
| 862 filterString = filterString.trim(); | |
| 863 if (filterString.isNotEmpty) { | |
| 864 cpuProfileVirtualTreeElement.filter = (CallTreeNode node) { | |
| 865 return node.name.contains(filterString); | |
| 866 }; | |
| 867 } | |
| 868 } | |
| 869 cpuProfileVirtualTreeElement.direction = direction; | |
| 870 cpuProfileVirtualTreeElement.mode = mode; | |
| 871 _renderTask.queue(); | |
| 872 } | |
| 873 | |
| 874 Future clearCpuProfile() async { | |
| 875 await isolate.invokeRpc('_clearCpuProfile', { }); | |
| 876 _updateTask.queue(); | |
| 877 return new Future.value(null); | |
| 878 } | |
| 879 | |
| 880 Future refresh() { | |
| 881 _updateTask.queue(); | |
| 882 return new Future.value(null); | |
| 883 } | |
| 884 | |
| 885 render() { | |
| 886 cpuProfileVirtualTreeElement.render(); | |
| 887 } | |
| 888 | |
| 889 @published Isolate isolate; | |
| 890 | |
| 891 StreamSubscription _resizeSubscription; | |
| 892 Task _updateTask; | |
| 893 Task _renderTask; | |
| 894 SampleBufferControlElement sampleBufferControlElement; | |
| 895 StackTraceTreeConfigElement stackTraceTreeConfigElement; | |
| 896 CpuProfileVirtualTreeElement cpuProfileVirtualTreeElement; | |
| 897 } | |
| 898 | |
| 899 class NameSortedTable extends SortedTable { | |
| 900 NameSortedTable(columns) : super(columns); | |
| 901 @override | |
| 902 dynamic getSortKeyFor(int row, int col) { | |
| 903 if (col == FUNCTION_COLUMN) { | |
| 904 // Use name as sort key. | |
| 905 return rows[row].values[col].name; | |
| 906 } | |
| 907 return super.getSortKeyFor(row, col); | |
| 908 } | |
| 909 | |
| 910 SortedTableRow rowFromIndex(int tableIndex) { | |
| 911 final modelIndex = sortedRows[tableIndex]; | |
| 912 return rows[modelIndex]; | |
| 913 } | |
| 914 | |
| 915 static const FUNCTION_SPACER_COLUMNS = const []; | |
| 916 static const FUNCTION_COLUMN = 2; | |
| 917 TableRowElement _makeFunctionRow() { | |
| 918 var tr = new TableRowElement(); | |
| 919 var cell; | |
| 920 | |
| 921 // Add percentage. | |
| 922 cell = tr.insertCell(-1); | |
| 923 cell = tr.insertCell(-1); | |
| 924 | |
| 925 // Add function ref. | |
| 926 cell = tr.insertCell(-1); | |
| 927 var functionRef = new Element.tag('function-ref'); | |
| 928 cell.children.add(functionRef); | |
| 929 | |
| 930 return tr; | |
| 931 } | |
| 932 | |
| 933 static const CALL_SPACER_COLUMNS = const []; | |
| 934 static const CALL_FUNCTION_COLUMN = 1; | |
| 935 TableRowElement _makeCallRow() { | |
| 936 var tr = new TableRowElement(); | |
| 937 var cell; | |
| 938 | |
| 939 // Add percentage. | |
| 940 cell = tr.insertCell(-1); | |
| 941 // Add function ref. | |
| 942 cell = tr.insertCell(-1); | |
| 943 var functionRef = new Element.tag('function-ref'); | |
| 944 cell.children.add(functionRef); | |
| 945 return tr; | |
| 946 } | |
| 947 | |
| 948 _updateRow(TableRowElement tr, | |
| 949 int rowIndex, | |
| 950 List spacerColumns, | |
| 951 int refColumn) { | |
| 952 var row = rows[rowIndex]; | |
| 953 // Set reference | |
| 954 var ref = tr.children[refColumn].children[0]; | |
| 955 ref.ref = row.values[refColumn]; | |
| 956 | |
| 957 for (var i = 0; i < row.values.length; i++) { | |
| 958 if (spacerColumns.contains(i) || (i == refColumn)) { | |
| 959 // Skip spacer columns. | |
| 960 continue; | |
| 961 } | |
| 962 var cell = tr.children[i]; | |
| 963 cell.title = row.values[i].toString(); | |
| 964 cell.text = getFormattedValue(rowIndex, i); | |
| 965 } | |
| 966 } | |
| 967 | |
| 968 _updateTableView(HtmlElement table, | |
| 969 HtmlElement makeEmptyRow(), | |
| 970 void onRowClick(TableRowElement tr), | |
| 971 List spacerColumns, | |
| 972 int refColumn) { | |
| 973 assert(table != null); | |
| 974 | |
| 975 // Resize DOM table. | |
| 976 if (table.children.length > sortedRows.length) { | |
| 977 // Shrink the table. | |
| 978 var deadRows = table.children.length - sortedRows.length; | |
| 979 for (var i = 0; i < deadRows; i++) { | |
| 980 table.children.removeLast(); | |
| 981 } | |
| 982 } else if (table.children.length < sortedRows.length) { | |
| 983 // Grow table. | |
| 984 var newRows = sortedRows.length - table.children.length; | |
| 985 for (var i = 0; i < newRows; i++) { | |
| 986 var row = makeEmptyRow(); | |
| 987 row.onClick.listen((e) { | |
| 988 e.stopPropagation(); | |
| 989 e.preventDefault(); | |
| 990 onRowClick(row); | |
| 991 }); | |
| 992 table.children.add(row); | |
| 993 } | |
| 994 } | |
| 995 | |
| 996 assert(table.children.length == sortedRows.length); | |
| 997 | |
| 998 // Fill table. | |
| 999 for (var i = 0; i < sortedRows.length; i++) { | |
| 1000 var rowIndex = sortedRows[i]; | |
| 1001 var tr = table.children[i]; | |
| 1002 _updateRow(tr, rowIndex, spacerColumns, refColumn); | |
| 1003 } | |
| 1004 } | |
| 1005 } | |
| 1006 | |
| 1007 @CustomTag('cpu-profile-table') | |
| 1008 class CpuProfileTableElement extends ObservatoryElement { | |
| 1009 CpuProfileTableElement.created() : super.created() { | |
| 1010 _updateTask = new Task(update); | |
| 1011 _renderTask = new Task(render); | |
| 1012 var columns = [ | |
| 1013 new SortedTableColumn.withFormatter('Executing (%)', | |
| 1014 Utils.formatPercentNormalized), | |
| 1015 new SortedTableColumn.withFormatter('In stack (%)', | |
| 1016 Utils.formatPercentNormalized), | |
| 1017 new SortedTableColumn('Method'), | |
| 1018 ]; | |
| 1019 profileTable = new NameSortedTable(columns); | |
| 1020 profileTable.sortColumnIndex = 0; | |
| 1021 | |
| 1022 columns = [ | |
| 1023 new SortedTableColumn.withFormatter('Callees (%)', | |
| 1024 Utils.formatPercentNormalized), | |
| 1025 new SortedTableColumn('Method') | |
| 1026 ]; | |
| 1027 profileCalleesTable = new NameSortedTable(columns); | |
| 1028 profileCalleesTable.sortColumnIndex = 0; | |
| 1029 | |
| 1030 columns = [ | |
| 1031 new SortedTableColumn.withFormatter('Callers (%)', | |
| 1032 Utils.formatPercentNormalized), | |
| 1033 new SortedTableColumn('Method') | |
| 1034 ]; | |
| 1035 profileCallersTable = new NameSortedTable(columns); | |
| 1036 profileCallersTable.sortColumnIndex = 0; | |
| 1037 } | |
| 1038 | |
| 1039 attached() { | |
| 1040 super.attached(); | |
| 1041 sampleBufferControlElement = | |
| 1042 shadowRoot.querySelector('#sampleBufferControl'); | |
| 1043 assert(sampleBufferControlElement != null); | |
| 1044 sampleBufferControlElement.onSampleBufferUpdate = onSampleBufferChange; | |
| 1045 // Disable the tag selector- we always want no tags. | |
| 1046 sampleBufferControlElement.tagSelector = 'None'; | |
| 1047 sampleBufferControlElement.showTagSelector = false; | |
| 1048 stackTraceTreeConfigElement = | |
| 1049 shadowRoot.querySelector('#stackTraceTreeConfig'); | |
| 1050 assert(stackTraceTreeConfigElement != null); | |
| 1051 stackTraceTreeConfigElement.onTreeConfigChange = onTreeConfigChange; | |
| 1052 stackTraceTreeConfigElement.modeSelector = 'Function'; | |
| 1053 stackTraceTreeConfigElement.showModeSelector = false; | |
| 1054 stackTraceTreeConfigElement.directionSelector = 'Down'; | |
| 1055 stackTraceTreeConfigElement.showDirectionSelector = false; | |
| 1056 cpuProfileTreeElement = shadowRoot.querySelector('#cpuProfileTree'); | |
| 1057 assert(cpuProfileTreeElement != null); | |
| 1058 cpuProfileTreeElement.profile = sampleBufferControlElement.profile; | |
| 1059 _updateTask.queue(); | |
| 1060 _resizeSubscription = window.onResize.listen((_) => _updateSize()); | |
| 1061 _updateSize(); | |
| 1062 } | |
| 1063 | |
| 1064 detached() { | |
| 1065 super.detached(); | |
| 1066 if (_resizeSubscription != null) { | |
| 1067 _resizeSubscription.cancel(); | |
| 1068 } | |
| 1069 } | |
| 1070 | |
| 1071 _updateSize() { | |
| 1072 HtmlElement e = $['main']; | |
| 1073 final totalHeight = window.innerHeight; | |
| 1074 final top = e.offset.top; | |
| 1075 final bottomMargin = 32; | |
| 1076 final mainHeight = totalHeight - top - bottomMargin; | |
| 1077 e.style.setProperty('height', '${mainHeight}px'); | |
| 1078 } | |
| 1079 | |
| 1080 isolateChanged(oldValue) { | |
| 1081 _updateTask.queue(); | |
| 1082 } | |
| 1083 | |
| 1084 update() { | |
| 1085 _clearView(); | |
| 1086 if (sampleBufferControlElement != null) { | |
| 1087 sampleBufferControlElement.reload(isolate).whenComplete(checkParameters); | |
| 1088 } | |
| 1089 } | |
| 1090 | |
| 1091 onSampleBufferChange(CpuProfile sampleBuffer) { | |
| 1092 _renderTask.queue(); | |
| 1093 } | |
| 1094 | |
| 1095 onTreeConfigChange(String modeSelector, | |
| 1096 String directionSelector, | |
| 1097 String filterString) { | |
| 1098 ProfileTreeDirection direction = ProfileTreeDirection.Exclusive; | |
| 1099 if (directionSelector != 'Up') { | |
| 1100 direction = ProfileTreeDirection.Inclusive; | |
| 1101 } | |
| 1102 ProfileTreeMode mode = ProfileTreeMode.Function; | |
| 1103 if (modeSelector == 'Code') { | |
| 1104 mode = ProfileTreeMode.Code; | |
| 1105 } | |
| 1106 cpuProfileTreeElement.direction = direction; | |
| 1107 cpuProfileTreeElement.mode = mode; | |
| 1108 _renderTask.queue(); | |
| 1109 } | |
| 1110 | |
| 1111 Future clearCpuProfile() async { | |
| 1112 await isolate.invokeRpc('_clearCpuProfile', { }); | |
| 1113 _updateTask.queue(); | |
| 1114 return new Future.value(null); | |
| 1115 } | |
| 1116 | |
| 1117 Future refresh() { | |
| 1118 _updateTask.queue(); | |
| 1119 return new Future.value(null); | |
| 1120 } | |
| 1121 | |
| 1122 render() { | |
| 1123 _updateView(); | |
| 1124 } | |
| 1125 | |
| 1126 checkParameters() { | |
| 1127 if (isolate == null) { | |
| 1128 return; | |
| 1129 } | |
| 1130 var functionId = app.locationManager.uri.queryParameters['functionId']; | |
| 1131 var functionName = | |
| 1132 app.locationManager.uri.queryParameters['functionName']; | |
| 1133 if (functionId == '') { | |
| 1134 // Fallback to searching by name. | |
| 1135 _focusOnFunction(_findFunction(functionName)); | |
| 1136 } else { | |
| 1137 if (functionId == null) { | |
| 1138 _focusOnFunction(null); | |
| 1139 return; | |
| 1140 } | |
| 1141 isolate.getObject(functionId).then((func) => _focusOnFunction(func)); | |
| 1142 } | |
| 1143 } | |
| 1144 | |
| 1145 _clearView() { | |
| 1146 profileTable.clearRows(); | |
| 1147 _renderTable(); | |
| 1148 } | |
| 1149 | |
| 1150 _updateView() { | |
| 1151 _buildFunctionTable(); | |
| 1152 _renderTable(); | |
| 1153 _updateFunctionTreeView(); | |
| 1154 } | |
| 1155 | |
| 1156 int _findFunctionRow(ServiceFunction function) { | |
| 1157 for (var i = 0; i < profileTable.sortedRows.length; i++) { | |
| 1158 var rowIndex = profileTable.sortedRows[i]; | |
| 1159 var row = profileTable.rows[rowIndex]; | |
| 1160 if (row.values[NameSortedTable.FUNCTION_COLUMN] == function) { | |
| 1161 return i; | |
| 1162 } | |
| 1163 } | |
| 1164 return -1; | |
| 1165 } | |
| 1166 | |
| 1167 _scrollToFunction(ServiceFunction function) { | |
| 1168 TableSectionElement tableBody = $['profile-table']; | |
| 1169 var row = _findFunctionRow(function); | |
| 1170 if (row == -1) { | |
| 1171 return; | |
| 1172 } | |
| 1173 tableBody.children[row].classes.remove('shake'); | |
| 1174 // trigger reflow. | |
| 1175 tableBody.children[row].offsetHeight; | |
| 1176 tableBody.children[row].scrollIntoView(ScrollAlignment.CENTER); | |
| 1177 tableBody.children[row].classes.add('shake'); | |
| 1178 // Focus on clicked function. | |
| 1179 _focusOnFunction(function); | |
| 1180 } | |
| 1181 | |
| 1182 _clearFocusedFunction() { | |
| 1183 TableSectionElement tableBody = $['profile-table']; | |
| 1184 // Clear current focus. | |
| 1185 if (focusedRow != null) { | |
| 1186 tableBody.children[focusedRow].classes.remove('focused'); | |
| 1187 } | |
| 1188 focusedRow = null; | |
| 1189 focusedFunction = null; | |
| 1190 } | |
| 1191 | |
| 1192 ServiceFunction _findFunction(String functionName) { | |
| 1193 for (var func in profile.functions) { | |
| 1194 if (func.function.name == functionName) { | |
| 1195 return func.function; | |
| 1196 } | |
| 1197 } | |
| 1198 return null; | |
| 1199 } | |
| 1200 | |
| 1201 _focusOnFunction(ServiceFunction function) { | |
| 1202 if (focusedFunction == function) { | |
| 1203 // Do nothing. | |
| 1204 return; | |
| 1205 } | |
| 1206 | |
| 1207 _clearFocusedFunction(); | |
| 1208 | |
| 1209 if (function == null) { | |
| 1210 _updateFunctionTreeView(); | |
| 1211 _clearCallTables(); | |
| 1212 return; | |
| 1213 } | |
| 1214 | |
| 1215 var row = _findFunctionRow(function); | |
| 1216 if (row == -1) { | |
| 1217 _updateFunctionTreeView(); | |
| 1218 _clearCallTables(); | |
| 1219 return; | |
| 1220 } | |
| 1221 | |
| 1222 focusedRow = row; | |
| 1223 focusedFunction = function; | |
| 1224 | |
| 1225 TableSectionElement tableBody = $['profile-table']; | |
| 1226 tableBody.children[focusedRow].classes.add('focused'); | |
| 1227 _updateFunctionTreeView(); | |
| 1228 _buildCallersTable(focusedFunction); | |
| 1229 _buildCalleesTable(focusedFunction); | |
| 1230 } | |
| 1231 | |
| 1232 _onRowClick(TableRowElement tr) { | |
| 1233 var tableBody = $['profile-table']; | |
| 1234 var row = profileTable.rowFromIndex(tableBody.children.indexOf(tr)); | |
| 1235 var function = row.values[NameSortedTable.FUNCTION_COLUMN]; | |
| 1236 app.locationManager.goReplacingParameters( | |
| 1237 { | |
| 1238 'functionId': function.id, | |
| 1239 'functionName': function.vmName | |
| 1240 } | |
| 1241 ); | |
| 1242 } | |
| 1243 | |
| 1244 _renderTable() { | |
| 1245 profileTable._updateTableView($['profile-table'], | |
| 1246 profileTable._makeFunctionRow, | |
| 1247 _onRowClick, | |
| 1248 NameSortedTable.FUNCTION_SPACER_COLUMNS, | |
| 1249 NameSortedTable.FUNCTION_COLUMN); | |
| 1250 } | |
| 1251 | |
| 1252 _buildFunctionTable() { | |
| 1253 for (var func in profile.functions) { | |
| 1254 if ((func.exclusiveTicks == 0) && (func.inclusiveTicks == 0)) { | |
| 1255 // Skip. | |
| 1256 continue; | |
| 1257 } | |
| 1258 var row = [ | |
| 1259 func.normalizedExclusiveTicks, | |
| 1260 func.normalizedInclusiveTicks, | |
| 1261 func.function, | |
| 1262 ]; | |
| 1263 profileTable.addRow(new SortedTableRow(row)); | |
| 1264 } | |
| 1265 profileTable.sort(); | |
| 1266 } | |
| 1267 | |
| 1268 _renderCallTable(TableSectionElement view, | |
| 1269 NameSortedTable model, | |
| 1270 void onRowClick(TableRowElement tr)) { | |
| 1271 model._updateTableView(view, | |
| 1272 model._makeCallRow, | |
| 1273 onRowClick, | |
| 1274 NameSortedTable.CALL_SPACER_COLUMNS, | |
| 1275 NameSortedTable.CALL_FUNCTION_COLUMN); | |
| 1276 } | |
| 1277 | |
| 1278 _buildCallTable(Map<ProfileFunction, int> calls, | |
| 1279 NameSortedTable model) { | |
| 1280 model.clearRows(); | |
| 1281 if (calls == null) { | |
| 1282 return; | |
| 1283 } | |
| 1284 var sum = 0; | |
| 1285 calls.values.forEach((i) => sum += i); | |
| 1286 calls.forEach((func, count) { | |
| 1287 var row = [ | |
| 1288 count / sum, | |
| 1289 func.function, | |
| 1290 ]; | |
| 1291 model.addRow(new SortedTableRow(row)); | |
| 1292 }); | |
| 1293 model.sort(); | |
| 1294 } | |
| 1295 | |
| 1296 _clearCallTables() { | |
| 1297 _buildCallersTable(null); | |
| 1298 _buildCalleesTable(null); | |
| 1299 } | |
| 1300 | |
| 1301 _onCallersClick(TableRowElement tr) { | |
| 1302 var table = $['callers-table']; | |
| 1303 final row = profileCallersTable.rowFromIndex(table.children.indexOf(tr)); | |
| 1304 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN]; | |
| 1305 _scrollToFunction(function); | |
| 1306 } | |
| 1307 | |
| 1308 _buildCallersTable(ServiceFunction function) { | |
| 1309 var calls = (function != null) ? function.profile.callers : null; | |
| 1310 var table = $['callers-table']; | |
| 1311 _buildCallTable(calls, profileCallersTable); | |
| 1312 _renderCallTable(table, profileCallersTable, _onCallersClick); | |
| 1313 } | |
| 1314 | |
| 1315 _onCalleesClick(TableRowElement tr) { | |
| 1316 var table = $['callees-table']; | |
| 1317 final row = profileCalleesTable.rowFromIndex(table.children.indexOf(tr)); | |
| 1318 var function = row.values[NameSortedTable.CALL_FUNCTION_COLUMN]; | |
| 1319 _scrollToFunction(function); | |
| 1320 } | |
| 1321 | |
| 1322 _buildCalleesTable(ServiceFunction function) { | |
| 1323 var calls = (function != null) ? function.profile.callees : null; | |
| 1324 var table = $['callees-table']; | |
| 1325 _buildCallTable(calls, profileCalleesTable); | |
| 1326 _renderCallTable(table, profileCalleesTable, _onCalleesClick); | |
| 1327 } | |
| 1328 | |
| 1329 _changeSort(Element target, NameSortedTable table) { | |
| 1330 if (target is TableCellElement) { | |
| 1331 if (table.sortColumnIndex != target.cellIndex) { | |
| 1332 table.sortColumnIndex = target.cellIndex; | |
| 1333 table.sortDescending = true; | |
| 1334 } else { | |
| 1335 table.sortDescending = !profileTable.sortDescending; | |
| 1336 } | |
| 1337 table.sort(); | |
| 1338 } | |
| 1339 } | |
| 1340 | |
| 1341 changeSortProfile(Event e, var detail, Element target) { | |
| 1342 _changeSort(target, profileTable); | |
| 1343 _renderTable(); | |
| 1344 } | |
| 1345 | |
| 1346 changeSortCallers(Event e, var detail, Element target) { | |
| 1347 _changeSort(target, profileCallersTable); | |
| 1348 _renderCallTable($['callers-table'], profileCallersTable, _onCallersClick); | |
| 1349 } | |
| 1350 | |
| 1351 changeSortCallees(Event e, var detail, Element target) { | |
| 1352 _changeSort(target, profileCalleesTable); | |
| 1353 _renderCallTable($['callees-table'], profileCalleesTable, _onCalleesClick); | |
| 1354 } | |
| 1355 | |
| 1356 ////// | |
| 1357 /// | |
| 1358 /// Function tree. | |
| 1359 /// | |
| 1360 TableTree functionTree; | |
| 1361 _updateFunctionTreeView() { | |
| 1362 cpuProfileTreeElement.filter = (FunctionCallTreeNode node) { | |
| 1363 return node.profileFunction.function == focusedFunction; | |
| 1364 }; | |
| 1365 cpuProfileTreeElement.render(); | |
| 1366 } | |
| 1367 | |
| 1368 @published Isolate isolate; | |
| 1369 @observable NameSortedTable profileTable; | |
| 1370 @observable NameSortedTable profileCallersTable; | |
| 1371 @observable NameSortedTable profileCalleesTable; | |
| 1372 @observable ServiceFunction focusedFunction; | |
| 1373 @observable int focusedRow; | |
| 1374 | |
| 1375 | |
| 1376 StreamSubscription _resizeSubscription; | |
| 1377 Task _updateTask; | |
| 1378 Task _renderTask; | |
| 1379 | |
| 1380 CpuProfile get profile => sampleBufferControlElement.profile; | |
| 1381 SampleBufferControlElement sampleBufferControlElement; | |
| 1382 StackTraceTreeConfigElement stackTraceTreeConfigElement; | |
| 1383 CpuProfileVirtualTreeElement cpuProfileTreeElement; | |
| 1384 } | |
| 1385 | |
| 1386 enum ProfileTreeDirection { | |
| 1387 Exclusive, | |
| 1388 Inclusive | |
| 1389 } | |
| 1390 | |
| 1391 enum ProfileTreeMode { | |
| 1392 Code, | |
| 1393 Function, | |
| 1394 } | |
| 1395 | |
| 1396 @CustomTag('cpu-profile-virtual-tree') | |
| 1397 class CpuProfileVirtualTreeElement extends ObservatoryElement { | |
| 1398 ProfileTreeDirection direction = ProfileTreeDirection.Exclusive; | |
| 1399 ProfileTreeMode mode = ProfileTreeMode.Function; | |
| 1400 CpuProfile profile; | |
| 1401 VirtualTree virtualTree; | |
| 1402 CallTreeNodeFilter filter; | |
| 1403 StreamSubscription _resizeSubscription; | |
| 1404 @observable bool show = true; | |
| 1405 | |
| 1406 CpuProfileVirtualTreeElement.created() : super.created(); | |
| 1407 | |
| 1408 attached() { | |
| 1409 super.attached(); | |
| 1410 _resizeSubscription = window.onResize.listen((_) => _updateSize()); | |
| 1411 } | |
| 1412 | |
| 1413 detached() { | |
| 1414 super.detached(); | |
| 1415 _resizeSubscription?.cancel(); | |
| 1416 } | 82 } |
| 1417 | 83 |
| 1418 void render() { | 84 void render() { |
| 1419 _updateView(); | 85 var content = [ |
| 86 new NavBarElement(queue: _r.queue) | |
| 87 ..children = [ | |
| 88 new NavTopMenuElement(queue: _r.queue), | |
| 89 new NavVMMenuElement(_vm, _vmUpdates, queue: _r.queue), | |
| 90 new NavIsolateMenuElement(_isolate, _isolateUpdates, queue: _r.queue), | |
| 91 new NavMenuElement('cpu profile', link: Uris.profiler(_isolate), | |
| 92 last: true, queue: _r.queue), | |
| 93 new NavRefreshElement(queue: _r.queue) | |
| 94 ..onRefresh.listen(_refresh), | |
| 95 new NavRefreshElement(label: 'Clear', queue: _r.queue) | |
| 96 ..onRefresh.listen(_clearCpuProfile), | |
| 97 new NavNotifyElement(_notifications, queue: _r.queue) | |
| 98 ], | |
| 99 new SampleBufferControlElement(_progress, selectedTag: _tag, | |
| 100 queue: _r.queue) | |
| 101 ..onTagChange.listen((e) { | |
| 102 _tag = e.element.selectedTag; | |
| 103 _request(); | |
| 104 }), | |
| 105 ]; | |
| 106 if (_progress.status == M.SampleProfileLoadingStatus.Loaded) { | |
| 107 CpuProfileVirtualTreeElement tree; | |
| 108 content.addAll([ | |
| 109 new BRElement(), | |
| 110 new StackTraceTreeConfigElement(queue: _r.queue) | |
| 111 ..onModeChange.listen((e) { | |
| 112 tree.mode = e.element.mode; | |
| 113 }) | |
| 114 ..onFilterChange.listen((e) { | |
| 115 var filterString = e.element.filter.trim(); | |
| 116 tree.filter = filterString.isNotEmpty | |
| 117 ? (node) { return node.name.contains(filterString); } | |
| 118 : null; | |
| 119 }) | |
| 120 ..onDirectionChange.listen((e) { | |
| 121 tree.direction = e.element.direction; | |
| 122 }), | |
| 123 new BRElement(), | |
| 124 tree = new CpuProfileVirtualTreeElement(_isolate, _progress.profile, | |
| 125 queue: _r.queue) | |
| 126 ]); | |
| 127 } | |
| 128 children = content; | |
| 1420 } | 129 } |
| 1421 | 130 |
| 1422 showChanged(oldValue) { | 131 Future _request({bool clear: false, bool forceFetch: false}) async { |
| 1423 if (show) { | 132 _r.dirty(); |
| 1424 virtualTree?.root?.style?.display = 'block'; | 133 _progress = _profiles.get(isolate, _tag, clear: clear, |
| 1425 } else { | 134 forceFetch: forceFetch); |
| 1426 virtualTree?.root?.style?.display = 'none'; | 135 await _progress.onProgress.last; |
| 1427 } | 136 _r.dirty(); |
| 1428 } | 137 } |
| 1429 | 138 |
| 1430 void _updateView() { | 139 Future _clearCpuProfile(RefreshEvent e) async { |
| 1431 _updateSize(); | 140 e.element.disabled = true; |
| 1432 virtualTree?.clear(); | 141 await _request(clear: true); |
| 1433 virtualTree?.uninstall(); | 142 e.element.disabled = false; |
| 1434 virtualTree = null; | |
| 1435 bool exclusive = direction == ProfileTreeDirection.Exclusive; | |
| 1436 if (mode == ProfileTreeMode.Code) { | |
| 1437 _buildCodeTree(exclusive); | |
| 1438 } else { | |
| 1439 assert(mode == ProfileTreeMode.Function); | |
| 1440 _buildFunctionTree(exclusive); | |
| 1441 } | |
| 1442 virtualTree?.refresh(); | |
| 1443 } | 143 } |
| 1444 | 144 |
| 1445 void _updateSize() { | 145 Future _refresh(e) async { |
| 1446 var treeBody = shadowRoot.querySelector('#tree'); | 146 e.element.disabled = true; |
| 1447 assert(treeBody != null); | 147 await _request(forceFetch: true); |
| 1448 int windowHeight = window.innerHeight - 32; | 148 e.element.disabled = false; |
| 1449 treeBody.style.height = '${windowHeight}px'; | |
| 1450 } | |
| 1451 | |
| 1452 void _buildFunctionTree(bool exclusive) { | |
| 1453 var treeBody = shadowRoot.querySelector('#tree'); | |
| 1454 assert(treeBody != null); | |
| 1455 virtualTree = new VirtualTree(32, treeBody); | |
| 1456 if (profile == null) { | |
| 1457 return; | |
| 1458 } | |
| 1459 var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive'); | |
| 1460 if (tree == null) { | |
| 1461 return; | |
| 1462 } | |
| 1463 if (filter != null) { | |
| 1464 tree = tree.filtered(filter); | |
| 1465 } | |
| 1466 for (var child in tree.root.children) { | |
| 1467 virtualTree.rows.add( | |
| 1468 new FunctionCallTreeNodeRow(virtualTree, 0, profile, child)); | |
| 1469 } | |
| 1470 if (virtualTree.rows.length == 1) { | |
| 1471 virtualTree.rows[0].expanded = true; | |
| 1472 } | |
| 1473 } | |
| 1474 | |
| 1475 void _buildCodeTree(bool exclusive) { | |
| 1476 var treeBody = shadowRoot.querySelector('#tree'); | |
| 1477 assert(treeBody != null); | |
| 1478 virtualTree = new VirtualTree(32, treeBody); | |
| 1479 if (profile == null) { | |
| 1480 return; | |
| 1481 } | |
| 1482 var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive'); | |
| 1483 if (tree == null) { | |
| 1484 return; | |
| 1485 } | |
| 1486 if (filter != null) { | |
| 1487 tree = tree.filtered(filter); | |
| 1488 } | |
| 1489 for (var child in tree.root.children) { | |
| 1490 virtualTree.rows.add( | |
| 1491 new CodeCallTreeNodeRow(virtualTree, 0, profile, child)); | |
| 1492 } | |
| 1493 if (virtualTree.rows.length == 1) { | |
| 1494 virtualTree.rows[0].expanded = true; | |
| 1495 } | |
| 1496 } | 149 } |
| 1497 } | 150 } |
| 1498 | |
| 1499 @CustomTag('cpu-profile-tree') | |
| 1500 class CpuProfileTreeElement extends ObservatoryElement { | |
| 1501 ProfileTreeDirection direction = ProfileTreeDirection.Exclusive; | |
| 1502 ProfileTreeMode mode = ProfileTreeMode.Function; | |
| 1503 CpuProfile profile; | |
| 1504 TableTree codeTree; | |
| 1505 TableTree functionTree; | |
| 1506 CallTreeNodeFilter functionFilter; | |
| 1507 @observable bool show = true; | |
| 1508 | |
| 1509 CpuProfileTreeElement.created() : super.created(); | |
| 1510 | |
| 1511 void render() { | |
| 1512 _updateView(); | |
| 1513 } | |
| 1514 | |
| 1515 showChanged(oldValue) { | |
| 1516 var treeTable = shadowRoot.querySelector('#treeTable'); | |
| 1517 assert(treeTable != null); | |
| 1518 treeTable.style.display = show ? 'table' : 'none'; | |
| 1519 } | |
| 1520 | |
| 1521 void _updateView() { | |
| 1522 if (functionTree != null) { | |
| 1523 functionTree.clear(); | |
| 1524 functionTree = null; | |
| 1525 } | |
| 1526 if (codeTree != null) { | |
| 1527 codeTree.clear(); | |
| 1528 codeTree = null; | |
| 1529 } | |
| 1530 bool exclusive = direction == ProfileTreeDirection.Exclusive; | |
| 1531 if (mode == ProfileTreeMode.Code) { | |
| 1532 _buildCodeTree(exclusive); | |
| 1533 } else { | |
| 1534 assert(mode == ProfileTreeMode.Function); | |
| 1535 _buildFunctionTree(exclusive); | |
| 1536 } | |
| 1537 } | |
| 1538 | |
| 1539 void _buildFunctionTree(bool exclusive) { | |
| 1540 if (functionTree == null) { | |
| 1541 var tableBody = shadowRoot.querySelector('#treeBody'); | |
| 1542 assert(tableBody != null); | |
| 1543 functionTree = new TableTree(tableBody, 2); | |
| 1544 } | |
| 1545 if (profile == null) { | |
| 1546 return; | |
| 1547 } | |
| 1548 var tree = profile.loadFunctionTree(exclusive ? 'exclusive' : 'inclusive'); | |
| 1549 if (tree == null) { | |
| 1550 return; | |
| 1551 } | |
| 1552 if (functionFilter != null) { | |
| 1553 tree = tree.filtered(functionFilter); | |
| 1554 } | |
| 1555 var rootRow = | |
| 1556 new FunctionProfileTreeRow(functionTree, null, profile, tree.root); | |
| 1557 functionTree.initialize(rootRow); | |
| 1558 } | |
| 1559 | |
| 1560 void _buildCodeTree(bool exclusive) { | |
| 1561 if (codeTree == null) { | |
| 1562 var tableBody = shadowRoot.querySelector('#treeBody'); | |
| 1563 assert(tableBody != null); | |
| 1564 codeTree = new TableTree(tableBody, 2); | |
| 1565 } | |
| 1566 if (profile == null) { | |
| 1567 return; | |
| 1568 } | |
| 1569 var tree = profile.loadCodeTree(exclusive ? 'exclusive' : 'inclusive'); | |
| 1570 if (tree == null) { | |
| 1571 return; | |
| 1572 } | |
| 1573 var rootRow = new CodeProfileTreeRow(codeTree, null, profile, tree.root); | |
| 1574 codeTree.initialize(rootRow); | |
| 1575 } | |
| 1576 } | |
| OLD | NEW |