| 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 |