| 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'; | 9 import 'observatory_element.dart'; |
| 10 import 'package:logging/logging.dart'; | 10 import 'package:logging/logging.dart'; |
| 11 import 'package:observatory/service.dart'; | 11 import 'package:observatory/service.dart'; |
| 12 import 'package:observatory/app.dart'; | 12 import 'package:observatory/app.dart'; |
| 13 import 'package:observatory/cpu_profile.dart'; | 13 import 'package:observatory/cpu_profile.dart'; |
| 14 import 'package:observatory/elements.dart'; | 14 import 'package:observatory/elements.dart'; |
| 15 import 'package:polymer/polymer.dart'; | 15 import 'package:polymer/polymer.dart'; |
| 16 | 16 |
| 17 class ProfileCodeTrieNodeTreeRow extends TableTreeRow { | 17 List<String> sorted(Set<String> attributes) { |
| 18 var list = attributes.toList(); |
| 19 list.sort(); |
| 20 return list; |
| 21 } |
| 22 |
| 23 abstract class ProfileTreeRow<T> extends TableTreeRow { |
| 18 final CpuProfile profile; | 24 final CpuProfile profile; |
| 19 @reflectable final CodeTrieNode root; | 25 final T node; |
| 20 @reflectable final CodeTrieNode node; | 26 final String selfPercent; |
| 21 @reflectable Code get code => node.profileCode.code; | 27 final String percent; |
| 28 bool _infoBoxShown = false; |
| 29 HtmlElement infoBox; |
| 30 HtmlElement infoButton; |
| 22 | 31 |
| 23 @reflectable String tipKind = ''; | 32 ProfileTreeRow(TableTree tree, TableTreeRow parent, |
| 24 @reflectable String tipParent = ''; | 33 this.profile, this.node, double selfPercent, double percent) |
| 25 @reflectable String tipExclusive = ''; | 34 : super(tree, parent), |
| 26 @reflectable String tipTicks = ''; | 35 selfPercent = Utils.formatPercentNormalized(selfPercent), |
| 27 @reflectable String tipTime = ''; | 36 percent = Utils.formatPercentNormalized(percent); |
| 28 | 37 |
| 29 ProfileCodeTrieNodeTreeRow(this.profile, this.root, this.node, | 38 static _addToMemberList(DivElement memberList, Map<String, String> items) { |
| 30 TableTree tree, | |
| 31 ProfileCodeTrieNodeTreeRow parent) | |
| 32 : super(tree, parent) { | |
| 33 assert(root != null); | |
| 34 assert(node != null); | |
| 35 tipTicks = '${node.count}'; | |
| 36 var seconds = profile.approximateSecondsForCount(node.count); | |
| 37 tipTime = Utils.formatTimePrecise(seconds); | |
| 38 if (code.kind == CodeKind.Tag) { | |
| 39 tipKind = 'Tag (category)'; | |
| 40 if (parent == null) { | |
| 41 tipParent = Utils.formatPercent(node.count, root.count); | |
| 42 } else { | |
| 43 tipParent = Utils.formatPercent(node.count, parent.node.count); | |
| 44 } | |
| 45 tipExclusive = Utils.formatPercent(node.count, root.count); | |
| 46 } else { | |
| 47 if ((code.kind == CodeKind.Collected) || | |
| 48 (code.kind == CodeKind.Reused)) { | |
| 49 tipKind = 'Garbage Collected Code'; | |
| 50 } else { | |
| 51 tipKind = '${code.kind} (Function)'; | |
| 52 } | |
| 53 if (parent == null) { | |
| 54 tipParent = Utils.formatPercent(node.count, root.count); | |
| 55 } else { | |
| 56 tipParent = Utils.formatPercent(node.count, parent.node.count); | |
| 57 } | |
| 58 tipExclusive = | |
| 59 Utils.formatPercent(node.profileCode.exclusiveTicks, root.count); | |
| 60 } | |
| 61 } | |
| 62 | |
| 63 bool shouldDisplayChild(CodeTrieNode childNode, double threshold) { | |
| 64 return ((childNode.count / node.count) > threshold) || | |
| 65 ((childNode.profileCode.exclusiveTicks / root.count) > threshold); | |
| 66 } | |
| 67 | |
| 68 void _buildTooltip(DivElement memberList, Map<String, String> items) { | |
| 69 items.forEach((k, v) { | 39 items.forEach((k, v) { |
| 70 var item = new DivElement(); | 40 var item = new DivElement(); |
| 71 item.classes.add('memberItem'); | 41 item.classes.add('memberItem'); |
| 72 var name = new DivElement(); | 42 var name = new DivElement(); |
| 73 name.classes.add('memberName'); | 43 name.classes.add('memberName'); |
| 74 name.classes.add('white'); | |
| 75 name.text = k; | 44 name.text = k; |
| 76 var value = new DivElement(); | 45 var value = new DivElement(); |
| 77 value.classes.add('memberValue'); | 46 value.classes.add('memberValue'); |
| 78 value.classes.add('white'); | |
| 79 value.text = v; | 47 value.text = v; |
| 80 item.children.add(name); | 48 item.children.add(name); |
| 81 item.children.add(value); | 49 item.children.add(value); |
| 82 memberList.children.add(item); | 50 memberList.children.add(item); |
| 83 }); | 51 }); |
| 84 } | 52 } |
| 85 | 53 |
| 54 makeInfoBox() { |
| 55 if (infoBox != null) { |
| 56 return; |
| 57 } |
| 58 infoBox = new DivElement(); |
| 59 infoBox.classes.add('infoBox'); |
| 60 infoBox.classes.add('shadow'); |
| 61 infoBox.style.display = 'none'; |
| 62 infoBox.onClick.listen((e) => e.stopPropagation()); |
| 63 } |
| 64 |
| 65 makeInfoButton() { |
| 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 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 'dart' : const ['D', null, 'Dart'], |
| 81 'tag' : const ['T', null, 'Tag'], |
| 82 'native' : const ['N', null, 'Native'], |
| 83 'stub': const ['S', null, 'Stub'], |
| 84 'synthetic' : const ['?', null, 'Synthetic'], |
| 85 }; |
| 86 |
| 87 HtmlElement newAttributeBox(String attribute) { |
| 88 List attributeDetails = attributes[attribute]; |
| 89 if (attributeDetails == null) { |
| 90 print('could not find attribute $attribute'); |
| 91 return null; |
| 92 } |
| 93 var element = new SpanElement(); |
| 94 element.style.border = 'solid 2px #ECECEC'; |
| 95 element.style.height = '100%'; |
| 96 element.style.display = 'inline-block'; |
| 97 element.style.textAlign = 'center'; |
| 98 element.style.minWidth = '1.5em'; |
| 99 element.style.fontWeight = 'bold'; |
| 100 if (attributeDetails[1] != null) { |
| 101 element.style.backgroundColor = attributeDetails[1]; |
| 102 } |
| 103 element.text = attributeDetails[0]; |
| 104 element.title = attributeDetails[2]; |
| 105 return element; |
| 106 } |
| 107 |
| 108 onHide() { |
| 109 super.onHide(); |
| 110 infoBox = null; |
| 111 infoButton = null; |
| 112 } |
| 113 |
| 114 showInfoBox() { |
| 115 if ((infoButton == null) || (infoBox == null)) { |
| 116 return; |
| 117 } |
| 118 _infoBoxShown = true; |
| 119 infoBox.style.display = 'block'; |
| 120 infoButton.children.clear(); |
| 121 infoButton.children.add(new Element.tag('icon-info')); |
| 122 } |
| 123 |
| 124 hideInfoBox() { |
| 125 _infoBoxShown = false; |
| 126 if ((infoButton == null) || (infoBox == null)) { |
| 127 return; |
| 128 } |
| 129 infoBox.style.display = 'none'; |
| 130 infoButton.children.clear(); |
| 131 infoButton.children.add(new Element.tag('icon-info-outline')); |
| 132 } |
| 133 |
| 134 toggleInfoBox() { |
| 135 if (_infoBoxShown) { |
| 136 hideInfoBox(); |
| 137 } else { |
| 138 showInfoBox(); |
| 139 } |
| 140 } |
| 141 |
| 142 hideAllInfoBoxes() { |
| 143 final List<ProfileTreeRow> rows = tree.rows; |
| 144 for (var row in rows) { |
| 145 row.hideInfoBox(); |
| 146 } |
| 147 } |
| 148 |
| 149 onClick(MouseEvent e) { |
| 150 e.stopPropagation(); |
| 151 if (e.altKey) { |
| 152 bool show = !_infoBoxShown; |
| 153 hideAllInfoBoxes(); |
| 154 if (show) { |
| 155 showInfoBox(); |
| 156 } |
| 157 return; |
| 158 } |
| 159 super.onClick(e); |
| 160 } |
| 161 |
| 162 HtmlElement newCodeRef(ProfileCode code) { |
| 163 var codeRef = new Element.tag('code-ref'); |
| 164 codeRef.ref = code.code; |
| 165 return codeRef; |
| 166 } |
| 167 |
| 168 HtmlElement newFunctionRef(ProfileFunction function) { |
| 169 var ref = new Element.tag('function-ref'); |
| 170 ref.ref = function.function; |
| 171 return ref; |
| 172 } |
| 173 |
| 174 HtmlElement hr() { |
| 175 var element = new HRElement(); |
| 176 return element; |
| 177 } |
| 178 |
| 179 HtmlElement div(String text) { |
| 180 var element = new DivElement(); |
| 181 element.text = text; |
| 182 return element; |
| 183 } |
| 184 |
| 185 HtmlElement br() { |
| 186 return new BRElement(); |
| 187 } |
| 188 |
| 189 HtmlElement span(String text) { |
| 190 var element = new SpanElement(); |
| 191 element.style.minWidth = '1em'; |
| 192 element.text = text; |
| 193 return element; |
| 194 } |
| 195 } |
| 196 |
| 197 class CodeProfileTreeRow extends ProfileTreeRow<CodeCallTreeNode> { |
| 198 CodeProfileTreeRow(TableTree tree, CodeProfileTreeRow parent, |
| 199 CpuProfile profile, CodeCallTreeNode node) |
| 200 : super(tree, parent, profile, node, |
| 201 node.profileCode.normalizedExclusiveTicks, |
| 202 node.percentage) { |
| 203 // fill out attributes. |
| 204 } |
| 205 |
| 206 bool hasChildren() => node.children.length > 0; |
| 207 |
| 86 void onShow() { | 208 void onShow() { |
| 87 super.onShow(); | 209 super.onShow(); |
| 210 |
| 88 if (children.length == 0) { | 211 if (children.length == 0) { |
| 89 var threshold = profile.displayThreshold; | |
| 90 for (var childNode in node.children) { | 212 for (var childNode in node.children) { |
| 91 if (!shouldDisplayChild(childNode, threshold)) { | 213 var row = new CodeProfileTreeRow(tree, this, profile, childNode); |
| 92 continue; | |
| 93 } | |
| 94 var row = | |
| 95 new ProfileCodeTrieNodeTreeRow(profile, root, childNode, tree, this); | |
| 96 children.add(row); | 214 children.add(row); |
| 97 } | 215 } |
| 98 } | 216 } |
| 99 | 217 |
| 100 var methodCell = tableColumns[0]; | 218 // Fill in method column. |
| 101 // Enable expansion by clicking anywhere on the method column. | 219 var methodColumn = flexColumns[0]; |
| 102 methodCell.onClick.listen(onClick); | 220 methodColumn.style.justifyContent = 'flex-start'; |
| 103 | 221 methodColumn.style.position = 'relative'; |
| 104 // Grab the flex-row Div inside the methodCell. | 222 |
| 105 methodCell = methodCell.children[0]; | 223 // Percent. |
| 106 | 224 var percentNode = new DivElement(); |
| 107 // Insert the parent percentage | 225 percentNode.text = percent; |
| 108 var parentPercent = new DivElement(); | 226 percentNode.style.minWidth = '5em'; |
| 109 parentPercent.text = tipParent; | 227 percentNode.style.textAlign = 'right'; |
| 110 methodCell.children.add(parentPercent); | 228 percentNode.title = 'Self: $selfPercent'; |
| 111 | 229 methodColumn.children.add(percentNode); |
| 230 |
| 231 // Gap. |
| 112 var gap = new SpanElement(); | 232 var gap = new SpanElement(); |
| 113 gap.style.minWidth = '1em'; | 233 gap.style.minWidth = '1em'; |
| 114 methodCell.children.add(gap); | 234 methodColumn.children.add(gap); |
| 115 | 235 |
| 116 var codeRef = new Element.tag('code-ref'); | 236 // Code link. |
| 117 codeRef.ref = code; | 237 var codeRef = newCodeRef(node.profileCode); |
| 118 methodCell.children.add(codeRef); | 238 codeRef.style.alignSelf = 'center'; |
| 119 | 239 methodColumn.children.add(codeRef); |
| 120 var selfCell = tableColumns[1]; | 240 |
| 121 selfCell.style.position = 'relative'; | 241 gap = new SpanElement(); |
| 122 selfCell.text = tipExclusive; | 242 gap.style.minWidth = '1em'; |
| 123 | 243 methodColumn.children.add(gap); |
| 124 var tooltipDiv = new DivElement(); | 244 |
| 125 tooltipDiv.classes.add('tooltip'); | 245 for (var attribute in sorted(node.attributes)) { |
| 126 | 246 methodColumn.children.add(newAttributeBox(attribute)); |
| 127 var memberListDiv = new DivElement(); | 247 } |
| 128 memberListDiv.classes.add('memberList'); | 248 |
| 129 tooltipDiv.children.add(memberListDiv); | 249 makeInfoBox(); |
| 130 _buildTooltip(memberListDiv, { | 250 methodColumn.children.add(infoBox); |
| 131 'Kind' : tipKind, | 251 |
| 132 'Percent of Parent' : tipParent, | 252 infoBox.children.add(span('Code ')); |
| 133 'Sample Count' : tipTicks, | 253 infoBox.children.add(newCodeRef(node.profileCode)); |
| 134 'Approximate Execution Time': tipTime, | 254 infoBox.children.add(span(' ')); |
| 255 for (var attribute in sorted(node.profileCode.attributes)) { |
| 256 infoBox.children.add(newAttributeBox(attribute)); |
| 257 } |
| 258 infoBox.children.add(br()); |
| 259 infoBox.children.add(br()); |
| 260 var memberList = new DivElement(); |
| 261 memberList.classes.add('memberList'); |
| 262 infoBox.children.add(br()); |
| 263 infoBox.children.add(memberList); |
| 264 ProfileTreeRow._addToMemberList(memberList, { |
| 265 'Exclusive ticks' : node.profileCode.formattedExclusiveTicks, |
| 266 'Cpu time' : node.profileCode.formattedCpuTime, |
| 267 'Inclusive ticks' : node.profileCode.formattedInclusiveTicks, |
| 268 'Call stack time' : node.profileCode.formattedOnStackTime, |
| 135 }); | 269 }); |
| 136 selfCell.children.add(tooltipDiv); | 270 |
| 137 } | 271 makeInfoButton(); |
| 138 | 272 methodColumn.children.add(infoButton); |
| 139 bool hasChildren() { | 273 |
| 140 return node.children.length > 0; | 274 // Fill in self column. |
| 275 var selfColumn = flexColumns[1]; |
| 276 selfColumn.style.position = 'relative'; |
| 277 selfColumn.style.alignItems = 'center'; |
| 278 selfColumn.text = selfPercent; |
| 141 } | 279 } |
| 142 } | 280 } |
| 143 | 281 |
| 144 class ProfileFunctionTrieNodeTreeRow extends TableTreeRow { | 282 class FunctionProfileTreeRow extends ProfileTreeRow<FunctionCallTreeNode> { |
| 145 final CpuProfile profile; | 283 FunctionProfileTreeRow(TableTree tree, FunctionProfileTreeRow parent, |
| 146 @reflectable final FunctionTrieNode root; | 284 CpuProfile profile, FunctionCallTreeNode node) |
| 147 @reflectable final FunctionTrieNode node; | 285 : super(tree, parent, profile, node, |
| 148 ProfileFunction get profileFunction => node.profileFunction; | 286 node.profileFunction.normalizedExclusiveTicks, |
| 149 @reflectable ServiceFunction get function => node.profileFunction.function; | 287 node.percentage) { |
| 150 @reflectable String tipKind = ''; | 288 // fill out attributes. |
| 151 @reflectable String tipParent = ''; | 289 } |
| 152 @reflectable String tipExclusive = ''; | 290 |
| 153 @reflectable String tipTime = ''; | 291 bool hasChildren() => node.children.length > 0; |
| 154 @reflectable String tipTicks = ''; | 292 |
| 155 | 293 onShow() { |
| 156 String tipOptimized = ''; | |
| 157 | |
| 158 ProfileFunctionTrieNodeTreeRow(this.profile, this.root, this.node, | |
| 159 TableTree tree, | |
| 160 ProfileFunctionTrieNodeTreeRow parent) | |
| 161 : super(tree, parent) { | |
| 162 assert(root != null); | |
| 163 assert(node != null); | |
| 164 tipTicks = '${node.count}'; | |
| 165 var seconds = profile.approximateSecondsForCount(node.count); | |
| 166 tipTime = Utils.formatTimePrecise(seconds); | |
| 167 if (parent == null) { | |
| 168 tipParent = Utils.formatPercent(node.count, root.count); | |
| 169 } else { | |
| 170 tipParent = Utils.formatPercent(node.count, parent.node.count); | |
| 171 } | |
| 172 if (function.kind == FunctionKind.kTag) { | |
| 173 tipExclusive = Utils.formatPercent(node.count, root.count); | |
| 174 } else { | |
| 175 tipExclusive = | |
| 176 Utils.formatPercent(node.profileFunction.exclusiveTicks, root.count); | |
| 177 } | |
| 178 | |
| 179 if (function.kind == FunctionKind.kTag) { | |
| 180 tipKind = 'Tag (category)'; | |
| 181 } else if (function.kind == FunctionKind.kCollected) { | |
| 182 tipKind = 'Garbage Collected Code'; | |
| 183 } else { | |
| 184 tipKind = '${function.kind} (Function)'; | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 bool hasChildren() { | |
| 189 return node.children.length > 0; | |
| 190 } | |
| 191 | |
| 192 void _buildTooltip(DivElement memberList, Map<String, String> items) { | |
| 193 items.forEach((k, v) { | |
| 194 var item = new DivElement(); | |
| 195 item.classes.add('memberItem'); | |
| 196 var name = new DivElement(); | |
| 197 name.classes.add('memberName'); | |
| 198 name.classes.add('white'); | |
| 199 name.text = k; | |
| 200 var value = new DivElement(); | |
| 201 value.classes.add('memberValue'); | |
| 202 value.classes.add('white'); | |
| 203 value.text = v; | |
| 204 item.children.add(name); | |
| 205 item.children.add(value); | |
| 206 memberList.children.add(item); | |
| 207 }); | |
| 208 } | |
| 209 | |
| 210 void onShow() { | |
| 211 super.onShow(); | 294 super.onShow(); |
| 212 if (children.length == 0) { | 295 if (children.length == 0) { |
| 213 for (var childNode in node.children) { | 296 for (var childNode in node.children) { |
| 214 var row = new ProfileFunctionTrieNodeTreeRow(profile, | 297 var row = new FunctionProfileTreeRow(tree, this, profile, childNode); |
| 215 root, | |
| 216 childNode, tree, this); | |
| 217 children.add(row); | 298 children.add(row); |
| 218 } | 299 } |
| 219 } | 300 } |
| 220 | 301 |
| 221 var selfCell = tableColumns[1]; | 302 var methodColumn = flexColumns[0]; |
| 222 selfCell.style.position = 'relative'; | 303 methodColumn.style.justifyContent = 'flex-start'; |
| 223 selfCell.text = tipExclusive; | 304 |
| 224 | 305 var codeAndFunctionColumn = new DivElement(); |
| 225 var methodCell = tableColumns[0]; | 306 codeAndFunctionColumn.classes.add('flex-column'); |
| 226 // Enable expansion by clicking anywhere on the method column. | 307 codeAndFunctionColumn.style.justifyContent = 'center'; |
| 227 methodCell.onClick.listen(onClick); | 308 codeAndFunctionColumn.style.width = '100%'; |
| 228 | 309 methodColumn.children.add(codeAndFunctionColumn); |
| 229 // Grab the flex-row Div inside the methodCell. | 310 |
| 230 methodCell = methodCell.children[0]; | 311 var functionRow = new DivElement(); |
| 312 functionRow.classes.add('flex-row'); |
| 313 functionRow.style.position = 'relative'; |
| 314 functionRow.style.justifyContent = 'flex-start'; |
| 315 codeAndFunctionColumn.children.add(functionRow); |
| 231 | 316 |
| 232 // Insert the parent percentage | 317 // Insert the parent percentage |
| 233 var parentPercent = new DivElement(); | 318 var parentPercent = new SpanElement(); |
| 234 parentPercent.text = tipParent; | 319 parentPercent.text = percent; |
| 235 methodCell.children.add(parentPercent); | 320 parentPercent.style.minWidth = '4em'; |
| 236 | 321 parentPercent.style.alignSelf = 'center'; |
| 322 parentPercent.style.textAlign = 'right'; |
| 323 parentPercent.title = 'Self: $selfPercent'; |
| 324 functionRow.children.add(parentPercent); |
| 325 |
| 326 // Gap. |
| 237 var gap = new SpanElement(); | 327 var gap = new SpanElement(); |
| 238 gap.style.minWidth = '1em'; | 328 gap.style.minWidth = '1em'; |
| 239 methodCell.children.add(gap); | 329 gap.text = ' '; |
| 240 | 330 functionRow.children.add(gap); |
| 241 var functionAndCodeContainer = new DivElement(); | |
| 242 methodCell.children.add(functionAndCodeContainer); | |
| 243 | 331 |
| 244 var functionRef = new Element.tag('function-ref'); | 332 var functionRef = new Element.tag('function-ref'); |
| 245 functionRef.ref = function; | 333 functionRef.ref = node.profileFunction.function; |
| 246 functionAndCodeContainer.children.add(functionRef); | 334 functionRef.style.alignSelf = 'center'; |
| 247 | 335 functionRow.children.add(functionRef); |
| 248 var codeRow = new DivElement(); | 336 |
| 249 codeRow.style.paddingTop = '1em'; | 337 gap = new SpanElement(); |
| 250 functionAndCodeContainer.children.add(codeRow); | 338 gap.style.minWidth = '1em'; |
| 251 if (!function.kind.isSynthetic()) { | 339 gap.text = ' '; |
| 252 | 340 functionRow.children.add(gap); |
| 341 |
| 342 for (var attribute in sorted(node.attributes)) { |
| 343 functionRow.children.add(newAttributeBox(attribute)); |
| 344 } |
| 345 |
| 346 makeInfoBox(); |
| 347 functionRow.children.add(infoBox); |
| 348 |
| 349 if (node.profileFunction.function.kind.hasDartCode()) { |
| 350 infoBox.children.add(div('Hot code for current node')); |
| 351 infoBox.children.add(br()); |
| 253 var totalTicks = node.totalCodesTicks; | 352 var totalTicks = node.totalCodesTicks; |
| 254 var numCodes = node.codes.length; | 353 var numCodes = node.codes.length; |
| 255 var label = new SpanElement(); | |
| 256 label.text = 'Compiled into:\n'; | |
| 257 codeRow.children.add(label); | |
| 258 var curlyBlock = new Element.tag('curly-block'); | |
| 259 codeRow.children.add(curlyBlock); | |
| 260 for (var i = 0; i < numCodes; i++) { | 354 for (var i = 0; i < numCodes; i++) { |
| 261 var codeRowSpan = new DivElement(); | 355 var codeRowSpan = new DivElement(); |
| 262 codeRowSpan.style.paddingLeft = '1em'; | 356 codeRowSpan.style.paddingLeft = '1em'; |
| 263 curlyBlock.children.add(codeRowSpan); | 357 infoBox.children.add(codeRowSpan); |
| 264 var nodeCode = node.codes[i]; | 358 var nodeCode = node.codes[i]; |
| 265 var ticks = nodeCode.ticks; | 359 var ticks = nodeCode.ticks; |
| 266 var percentage = Utils.formatPercent(ticks, totalTicks); | 360 var percentage = Utils.formatPercent(ticks, totalTicks); |
| 267 var percentageSpan = new SpanElement(); | 361 var percentageSpan = new SpanElement(); |
| 268 percentageSpan.text = '($percentage) '; | 362 percentageSpan.style.display = 'inline-block'; |
| 363 percentageSpan.text = '$percentage'; |
| 364 percentageSpan.style.minWidth = '5em'; |
| 365 percentageSpan.style.textAlign = 'right'; |
| 269 codeRowSpan.children.add(percentageSpan); | 366 codeRowSpan.children.add(percentageSpan); |
| 270 var codeRef = new Element.tag('code-ref'); | 367 var codeRef = new Element.tag('code-ref'); |
| 271 codeRef.ref = nodeCode.code.code; | 368 codeRef.ref = nodeCode.code.code; |
| 369 codeRef.style.marginLeft = '1em'; |
| 370 codeRef.style.marginRight = 'auto'; |
| 371 codeRef.style.width = '100%'; |
| 272 codeRowSpan.children.add(codeRef); | 372 codeRowSpan.children.add(codeRef); |
| 273 } | 373 } |
| 274 } | 374 infoBox.children.add(hr()); |
| 275 | 375 } |
| 276 var tooltipDiv = new DivElement(); | 376 infoBox.children.add(span('Function ')); |
| 277 tooltipDiv.classes.add('tooltip'); | 377 infoBox.children.add(newFunctionRef(node.profileFunction)); |
| 278 | 378 infoBox.children.add(span(' ')); |
| 279 var memberListDiv = new DivElement(); | 379 for (var attribute in sorted(node.profileFunction.attributes)) { |
| 280 memberListDiv.classes.add('memberList'); | 380 infoBox.children.add(newAttributeBox(attribute)); |
| 281 tooltipDiv.children.add(memberListDiv); | 381 } |
| 282 _buildTooltip(memberListDiv, { | 382 var memberList = new DivElement(); |
| 283 'Kind' : tipKind, | 383 memberList.classes.add('memberList'); |
| 284 'Percent of Parent' : tipParent, | 384 infoBox.children.add(br()); |
| 285 'Sample Count' : tipTicks, | 385 infoBox.children.add(br()); |
| 286 'Approximate Execution Time': tipTime, | 386 infoBox.children.add(memberList); |
| 387 infoBox.children.add(br()); |
| 388 ProfileTreeRow._addToMemberList(memberList, { |
| 389 'Exclusive ticks' : node.profileFunction.formattedExclusiveTicks, |
| 390 'Cpu time' : node.profileFunction.formattedCpuTime, |
| 391 'Inclusive ticks' : node.profileFunction.formattedInclusiveTicks, |
| 392 'Call stack time' : node.profileFunction.formattedOnStackTime, |
| 287 }); | 393 }); |
| 288 selfCell.children.add(tooltipDiv); | 394 |
| 395 if (node.profileFunction.function.kind.hasDartCode()) { |
| 396 infoBox.children.add(div('Hot code containing function')); |
| 397 infoBox.children.add(br()); |
| 398 var totalTicks = profile.sampleCount; |
| 399 var codes = node.profileFunction.profileCodes; |
| 400 var numCodes = codes.length; |
| 401 for (var i = 0; i < numCodes; i++) { |
| 402 var codeRowSpan = new DivElement(); |
| 403 codeRowSpan.style.paddingLeft = '1em'; |
| 404 infoBox.children.add(codeRowSpan); |
| 405 var profileCode = codes[i]; |
| 406 var code = profileCode.code; |
| 407 var ticks = profileCode.inclusiveTicks; |
| 408 var percentage = Utils.formatPercent(ticks, totalTicks); |
| 409 var percentageSpan = new SpanElement(); |
| 410 percentageSpan.style.display = 'inline-block'; |
| 411 percentageSpan.text = '$percentage'; |
| 412 percentageSpan.style.minWidth = '5em'; |
| 413 percentageSpan.style.textAlign = 'right'; |
| 414 percentageSpan.title = 'Inclusive ticks'; |
| 415 codeRowSpan.children.add(percentageSpan); |
| 416 var codeRef = new Element.tag('code-ref'); |
| 417 codeRef.ref = code; |
| 418 codeRef.style.marginLeft = '1em'; |
| 419 codeRef.style.marginRight = 'auto'; |
| 420 codeRef.style.width = '100%'; |
| 421 codeRowSpan.children.add(codeRef); |
| 422 } |
| 423 } |
| 424 |
| 425 makeInfoButton(); |
| 426 methodColumn.children.add(infoButton); |
| 427 |
| 428 // Fill in self column. |
| 429 var selfColumn = flexColumns[1]; |
| 430 selfColumn.style.position = 'relative'; |
| 431 selfColumn.style.alignItems = 'center'; |
| 432 selfColumn.text = selfPercent; |
| 289 } | 433 } |
| 290 } | 434 } |
| 291 | 435 |
| 292 /// Displays a CpuProfile | 436 /// Displays a CpuProfile |
| 293 @CustomTag('cpu-profile') | 437 @CustomTag('cpu-profile') |
| 294 class CpuProfileElement extends ObservatoryElement { | 438 class CpuProfileElement extends ObservatoryElement { |
| 295 static const MICROSECONDS_PER_SECOND = 1000000.0; | 439 static const MICROSECONDS_PER_SECOND = 1000000.0; |
| 296 | 440 |
| 297 @published Isolate isolate; | 441 @published Isolate isolate; |
| 298 @observable String sampleCount = ''; | 442 @observable String sampleCount = ''; |
| 299 @observable String refreshTime = ''; | 443 @observable String refreshTime = ''; |
| 300 @observable String sampleRate = ''; | 444 @observable String sampleRate = ''; |
| 301 @observable String stackDepth = ''; | 445 @observable String stackDepth = ''; |
| 302 @observable String displayCutoff = ''; | |
| 303 @observable String timeSpan = ''; | 446 @observable String timeSpan = ''; |
| 304 | 447 @observable String fetchTime = ''; |
| 448 @observable String loadTime = ''; |
| 305 @observable String tagSelector = 'UserVM'; | 449 @observable String tagSelector = 'UserVM'; |
| 306 @observable String modeSelector = 'Function'; | 450 @observable String modeSelector = 'Function'; |
| 451 @observable String directionSelector = 'Up'; |
| 452 |
| 453 @observable String state = 'Requested'; |
| 454 @observable var exception; |
| 455 @observable var stackTrace; |
| 456 |
| 457 final Stopwatch _sw = new Stopwatch(); |
| 307 | 458 |
| 308 final CpuProfile profile = new CpuProfile(); | 459 final CpuProfile profile = new CpuProfile(); |
| 309 | 460 |
| 310 CpuProfileElement.created() : super.created(); | 461 CpuProfileElement.created() : super.created(); |
| 311 | 462 |
| 312 @override | 463 @override |
| 313 void attached() { | 464 void attached() { |
| 314 super.attached(); | 465 super.attached(); |
| 315 } | 466 } |
| 316 | 467 |
| 317 void isolateChanged(oldValue) { | 468 void isolateChanged(oldValue) { |
| 318 _getCpuProfile(); | 469 _getCpuProfile(); |
| 319 } | 470 } |
| 320 | 471 |
| 321 void tagSelectorChanged(oldValue) { | 472 void tagSelectorChanged(oldValue) { |
| 322 _getCpuProfile(); | 473 _getCpuProfile(); |
| 323 } | 474 } |
| 324 | 475 |
| 325 void modeSelectorChanged(oldValue) { | 476 void modeSelectorChanged(oldValue) { |
| 326 _updateView(); | 477 _updateView(); |
| 327 } | 478 } |
| 328 | 479 |
| 480 void directionSelectorChanged(oldValue) { |
| 481 _updateView(); |
| 482 } |
| 483 |
| 329 void clear(var done) { | 484 void clear(var done) { |
| 330 _clearCpuProfile().whenComplete(done); | 485 _clearCpuProfile().whenComplete(done); |
| 331 } | 486 } |
| 332 | 487 |
| 333 Future _clearCpuProfile() { | 488 Future _clearCpuProfile() { |
| 334 profile.clear(); | 489 profile.clear(); |
| 335 if (isolate == null) { | 490 if (isolate == null) { |
| 336 return new Future.value(null); | 491 return new Future.value(null); |
| 337 } | 492 } |
| 338 return isolate.invokeRpc('clearCpuProfile', { }) | 493 return isolate.invokeRpc('clearCpuProfile', { }) |
| 339 .then((ServiceMap response) { | 494 .then((ServiceMap response) { |
| 340 _updateView(); | 495 _updateView(); |
| 341 }); | 496 }); |
| 342 } | 497 } |
| 343 | 498 |
| 344 void refresh(var done) { | 499 void refresh(var done) { |
| 345 _getCpuProfile().whenComplete(done); | 500 _getCpuProfile().whenComplete(done); |
| 346 } | 501 } |
| 347 | 502 |
| 348 Future _getCpuProfile() { | 503 _onFetchStarted() { |
| 504 _sw.reset(); |
| 505 _sw.start(); |
| 506 state = 'Requested'; |
| 507 } |
| 508 |
| 509 _onFetchFinished() { |
| 510 _sw.stop(); |
| 511 fetchTime = formatTimeMilliseconds(_sw.elapsedMilliseconds); |
| 512 } |
| 513 |
| 514 _onLoadStarted() { |
| 515 _sw.reset(); |
| 516 _sw.start(); |
| 517 state = 'Loading'; |
| 518 } |
| 519 |
| 520 _onLoadFinished() { |
| 521 _sw.stop(); |
| 522 loadTime = formatTimeMilliseconds(_sw.elapsedMilliseconds); |
| 523 state = 'Loaded'; |
| 524 } |
| 525 |
| 526 Future _getCpuProfile() async { |
| 349 profile.clear(); | 527 profile.clear(); |
| 528 if (functionTree != null) { |
| 529 functionTree.clear(); |
| 530 } |
| 531 if (codeTree != null) { |
| 532 codeTree.clear(); |
| 533 } |
| 350 if (isolate == null) { | 534 if (isolate == null) { |
| 351 return new Future.value(null); | 535 return new Future.value(null); |
| 352 } | 536 } |
| 353 return isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) | 537 _onFetchStarted(); |
| 354 .then((ServiceMap response) { | 538 var response = |
| 355 profile.load(isolate, response); | 539 await isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }); |
| 356 _updateView(); | 540 _onFetchFinished(); |
| 357 }); | 541 _onLoadStarted(); |
| 542 await window.animationFrame; |
| 543 try { |
| 544 profile.load(isolate, response); |
| 545 _onLoadFinished(); |
| 546 _updateView(); |
| 547 } catch (e, st) { |
| 548 state = 'Exception'; |
| 549 exception = e; |
| 550 stackTrace = st; |
| 551 } |
| 358 } | 552 } |
| 359 | 553 |
| 360 void _updateView() { | 554 void _updateView() { |
| 361 sampleCount = profile.sampleCount.toString(); | 555 sampleCount = profile.sampleCount.toString(); |
| 362 refreshTime = new DateTime.now().toString(); | 556 refreshTime = new DateTime.now().toString(); |
| 363 stackDepth = profile.stackDepth.toString(); | 557 stackDepth = profile.stackDepth.toString(); |
| 364 sampleRate = profile.sampleRate.toStringAsFixed(0); | 558 sampleRate = profile.sampleRate.toStringAsFixed(0); |
| 365 timeSpan = formatTime(profile.timeSpan); | 559 timeSpan = formatTime(profile.timeSpan); |
| 366 displayCutoff = '${(profile.displayThreshold * 100.0).toString()}%'; | |
| 367 if (functionTree != null) { | 560 if (functionTree != null) { |
| 368 functionTree.clear(); | 561 functionTree.clear(); |
| 369 } | 562 } |
| 370 if (codeTree != null) { | 563 if (codeTree != null) { |
| 371 codeTree.clear(); | 564 codeTree.clear(); |
| 372 } | 565 } |
| 566 bool exclusive = directionSelector == 'Up'; |
| 373 if (modeSelector == 'Code') { | 567 if (modeSelector == 'Code') { |
| 374 _buildCodeTree(); | 568 _buildCodeTree(exclusive); |
| 375 } else { | 569 } else { |
| 376 _buildFunctionTree(); | 570 _buildFunctionTree(exclusive); |
| 377 } | 571 } |
| 378 } | 572 } |
| 379 | 573 |
| 380 TableTree codeTree; | 574 TableTree codeTree; |
| 381 TableTree functionTree; | 575 TableTree functionTree; |
| 382 | 576 |
| 383 void _buildFunctionTree() { | 577 void _buildFunctionTree(bool exclusive) { |
| 384 if (functionTree == null) { | 578 if (functionTree == null) { |
| 385 var tableBody = shadowRoot.querySelector('#treeBody'); | 579 var tableBody = shadowRoot.querySelector('#treeBody'); |
| 386 assert(tableBody != null); | 580 assert(tableBody != null); |
| 387 functionTree = new TableTree(tableBody, 2); | 581 functionTree = new TableTree(tableBody, 2); |
| 388 } | 582 } |
| 389 var root = profile.functionTrieRoot; | 583 var tree = profile.functionTrees[exclusive ? 'exclusive' : 'inclusive']; |
| 390 if (root == null) { | 584 if (tree == null) { |
| 391 return; | 585 return; |
| 392 } | 586 } |
| 393 try { | 587 var rootRow = |
| 394 functionTree.initialize( | 588 new FunctionProfileTreeRow(functionTree, null, profile, tree.root); |
| 395 new ProfileFunctionTrieNodeTreeRow(profile, | 589 functionTree.initialize(rootRow); |
| 396 root, root, functionTree, null)); | |
| 397 } catch (e, stackTrace) { | |
| 398 print(e); | |
| 399 print(stackTrace); | |
| 400 Logger.root.warning('_buildFunctionTree', e, stackTrace); | |
| 401 } | |
| 402 // Check if we only have one node at the root and expand it. | |
| 403 if (functionTree.rows.length == 1) { | |
| 404 functionTree.toggle(functionTree.rows[0]); | |
| 405 } | |
| 406 } | 590 } |
| 407 | 591 |
| 408 void _buildCodeTree() { | 592 void _buildCodeTree(bool exclusive) { |
| 409 if (codeTree == null) { | 593 if (codeTree == null) { |
| 410 var tableBody = shadowRoot.querySelector('#treeBody'); | 594 var tableBody = shadowRoot.querySelector('#treeBody'); |
| 411 assert(tableBody != null); | 595 assert(tableBody != null); |
| 412 codeTree = new TableTree(tableBody, 2); | 596 codeTree = new TableTree(tableBody, 2); |
| 413 } | 597 } |
| 414 var root = profile.codeTrieRoot; | 598 var tree = profile.codeTrees[exclusive ? 'exclusive' : 'inclusive']; |
| 415 if (root == null) { | 599 if (tree == null) { |
| 416 return; | 600 return; |
| 417 } | 601 } |
| 418 try { | 602 var rootRow = new CodeProfileTreeRow(codeTree, null, profile, tree.root); |
| 419 codeTree.initialize( | 603 codeTree.initialize(rootRow); |
| 420 new ProfileCodeTrieNodeTreeRow(profile, root, root, codeTree, null)); | |
| 421 } catch (e, stackTrace) { | |
| 422 print(e); | |
| 423 print(stackTrace); | |
| 424 Logger.root.warning('_buildCodeTree', e, stackTrace); | |
| 425 } | |
| 426 // Check if we only have one node at the root and expand it. | |
| 427 if (codeTree.rows.length == 1) { | |
| 428 codeTree.toggle(codeTree.rows[0]); | |
| 429 } | |
| 430 } | 604 } |
| 431 } | 605 } |
| OLD | NEW |