| 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:html'; | 8 import 'dart:html'; |
| 8 import 'observatory_element.dart'; | 9 import 'observatory_element.dart'; |
| 9 import 'package:logging/logging.dart'; | 10 import 'package:logging/logging.dart'; |
| 10 import 'package:observatory/service.dart'; | 11 import 'package:observatory/service.dart'; |
| 11 import 'package:observatory/app.dart'; | 12 import 'package:observatory/app.dart'; |
| 13 import 'package:observatory/cpu_profile.dart'; |
| 12 import 'package:observatory/elements.dart'; | 14 import 'package:observatory/elements.dart'; |
| 13 import 'package:polymer/polymer.dart'; | 15 import 'package:polymer/polymer.dart'; |
| 14 | 16 |
| 15 class ProfileCodeTrieNodeTreeRow extends TableTreeRow { | 17 class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
| 16 final ServiceMap profile; | 18 final CpuProfile profile; |
| 17 @reflectable final CodeTrieNode root; | 19 @reflectable final CodeTrieNode root; |
| 18 @reflectable final CodeTrieNode node; | 20 @reflectable final CodeTrieNode node; |
| 19 @reflectable Code get code => node.code; | 21 @reflectable Code get code => node.profileCode.code; |
| 20 | 22 |
| 21 @reflectable String tipKind = ''; | 23 @reflectable String tipKind = ''; |
| 22 @reflectable String tipParent = ''; | 24 @reflectable String tipParent = ''; |
| 23 @reflectable String tipExclusive = ''; | 25 @reflectable String tipExclusive = ''; |
| 24 @reflectable String tipTicks = ''; | 26 @reflectable String tipTicks = ''; |
| 25 @reflectable String tipTime = ''; | 27 @reflectable String tipTime = ''; |
| 26 | 28 |
| 27 ProfileCodeTrieNodeTreeRow(this.profile, this.root, this.node, | 29 ProfileCodeTrieNodeTreeRow(this.profile, this.root, this.node, |
| 28 TableTree tree, | 30 TableTree tree, |
| 29 ProfileCodeTrieNodeTreeRow parent) | 31 ProfileCodeTrieNodeTreeRow parent) |
| 30 : super(tree, parent) { | 32 : super(tree, parent) { |
| 31 assert(root != null); | 33 assert(root != null); |
| 32 assert(node != null); | 34 assert(node != null); |
| 33 tipTicks = '${node.count}'; | 35 tipTicks = '${node.count}'; |
| 34 var period = profile['period']; | 36 var seconds = profile.approximateSecondsForCount(node.count); |
| 35 var MICROSECONDS_PER_SECOND = 1000000.0; | |
| 36 var seconds = (period * node.count) / MICROSECONDS_PER_SECOND; // seconds | |
| 37 tipTime = Utils.formatTimePrecise(seconds); | 37 tipTime = Utils.formatTimePrecise(seconds); |
| 38 if (code.kind == CodeKind.Tag) { | 38 if (code.kind == CodeKind.Tag) { |
| 39 tipKind = 'Tag (category)'; | 39 tipKind = 'Tag (category)'; |
| 40 if (parent == null) { | 40 if (parent == null) { |
| 41 tipParent = Utils.formatPercent(node.count, root.count); | 41 tipParent = Utils.formatPercent(node.count, root.count); |
| 42 } else { | 42 } else { |
| 43 tipParent = Utils.formatPercent(node.count, parent.node.count); | 43 tipParent = Utils.formatPercent(node.count, parent.node.count); |
| 44 } | 44 } |
| 45 tipExclusive = Utils.formatPercent(node.count, root.count); | 45 tipExclusive = Utils.formatPercent(node.count, root.count); |
| 46 } else { | 46 } else { |
| 47 if ((code.kind == CodeKind.Collected) || | 47 if ((code.kind == CodeKind.Collected) || |
| 48 (code.kind == CodeKind.Reused)) { | 48 (code.kind == CodeKind.Reused)) { |
| 49 tipKind = 'Garbage Collected Code'; | 49 tipKind = 'Garbage Collected Code'; |
| 50 } else { | 50 } else { |
| 51 tipKind = '${code.kind} (Function)'; | 51 tipKind = '${code.kind} (Function)'; |
| 52 } | 52 } |
| 53 if (parent == null) { | 53 if (parent == null) { |
| 54 tipParent = Utils.formatPercent(node.count, root.count); | 54 tipParent = Utils.formatPercent(node.count, root.count); |
| 55 } else { | 55 } else { |
| 56 tipParent = Utils.formatPercent(node.count, parent.node.count); | 56 tipParent = Utils.formatPercent(node.count, parent.node.count); |
| 57 } | 57 } |
| 58 tipExclusive = Utils.formatPercent(node.code.exclusiveTicks, root.count); | 58 tipExclusive = |
| 59 Utils.formatPercent(node.profileCode.exclusiveTicks, root.count); |
| 59 } | 60 } |
| 60 } | 61 } |
| 61 | 62 |
| 62 bool shouldDisplayChild(CodeTrieNode childNode, double threshold) { | 63 bool shouldDisplayChild(CodeTrieNode childNode, double threshold) { |
| 63 return ((childNode.count / node.count) > threshold) || | 64 return ((childNode.count / node.count) > threshold) || |
| 64 ((childNode.code.exclusiveTicks / root.count) > threshold); | 65 ((childNode.profileCode.exclusiveTicks / root.count) > threshold); |
| 65 } | 66 } |
| 66 | 67 |
| 67 void _buildTooltip(DivElement memberList, Map<String, String> items) { | 68 void _buildTooltip(DivElement memberList, Map<String, String> items) { |
| 68 items.forEach((k, v) { | 69 items.forEach((k, v) { |
| 69 var item = new DivElement(); | 70 var item = new DivElement(); |
| 70 item.classes.add('memberItem'); | 71 item.classes.add('memberItem'); |
| 71 var name = new DivElement(); | 72 var name = new DivElement(); |
| 72 name.classes.add('memberName'); | 73 name.classes.add('memberName'); |
| 73 name.classes.add('white'); | 74 name.classes.add('white'); |
| 74 name.text = k; | 75 name.text = k; |
| 75 var value = new DivElement(); | 76 var value = new DivElement(); |
| 76 value.classes.add('memberValue'); | 77 value.classes.add('memberValue'); |
| 77 value.classes.add('white'); | 78 value.classes.add('white'); |
| 78 value.text = v; | 79 value.text = v; |
| 79 item.children.add(name); | 80 item.children.add(name); |
| 80 item.children.add(value); | 81 item.children.add(value); |
| 81 memberList.children.add(item); | 82 memberList.children.add(item); |
| 82 }); | 83 }); |
| 83 } | 84 } |
| 84 | 85 |
| 85 void onShow() { | 86 void onShow() { |
| 86 super.onShow(); | 87 super.onShow(); |
| 87 if (children.length == 0) { | 88 if (children.length == 0) { |
| 88 var threshold = profile['threshold']; | 89 var threshold = profile.displayThreshold; |
| 89 for (var childNode in node.children) { | 90 for (var childNode in node.children) { |
| 90 if (!shouldDisplayChild(childNode, threshold)) { | 91 if (!shouldDisplayChild(childNode, threshold)) { |
| 91 continue; | 92 continue; |
| 92 } | 93 } |
| 93 var row = | 94 var row = |
| 94 new ProfileCodeTrieNodeTreeRow(profile, root, childNode, tree, this)
; | 95 new ProfileCodeTrieNodeTreeRow(profile, root, childNode, tree, this); |
| 95 children.add(row); | 96 children.add(row); |
| 96 } | 97 } |
| 97 } | 98 } |
| 98 var row = tr; | |
| 99 | 99 |
| 100 var methodCell = tableColumns[0]; | 100 var methodCell = tableColumns[0]; |
| 101 // Enable expansion by clicking anywhere on the method column. | 101 // Enable expansion by clicking anywhere on the method column. |
| 102 methodCell.onClick.listen(onClick); | 102 methodCell.onClick.listen(onClick); |
| 103 | 103 |
| 104 // Grab the flex-row Div inside the methodCell. |
| 105 methodCell = methodCell.children[0]; |
| 106 |
| 104 // Insert the parent percentage | 107 // Insert the parent percentage |
| 105 var parentPercent = new DivElement(); | 108 var parentPercent = new DivElement(); |
| 106 parentPercent.style.position = 'relative'; | |
| 107 parentPercent.style.display = 'inline'; | |
| 108 parentPercent.text = tipParent; | 109 parentPercent.text = tipParent; |
| 109 methodCell.children.add(parentPercent); | 110 methodCell.children.add(parentPercent); |
| 110 | 111 |
| 112 var gap = new SpanElement(); |
| 113 gap.style.minWidth = '1em'; |
| 114 methodCell.children.add(gap); |
| 115 |
| 111 var codeRef = new Element.tag('code-ref'); | 116 var codeRef = new Element.tag('code-ref'); |
| 112 codeRef.ref = code; | 117 codeRef.ref = code; |
| 113 methodCell.children.add(codeRef); | 118 methodCell.children.add(codeRef); |
| 114 | 119 |
| 115 var selfCell = tableColumns[1]; | 120 var selfCell = tableColumns[1]; |
| 116 selfCell.style.position = 'relative'; | 121 selfCell.style.position = 'relative'; |
| 117 selfCell.text = tipExclusive; | 122 selfCell.text = tipExclusive; |
| 118 | 123 |
| 119 var tooltipDiv = new DivElement(); | 124 var tooltipDiv = new DivElement(); |
| 120 tooltipDiv.classes.add('tooltip'); | 125 tooltipDiv.classes.add('tooltip'); |
| 121 | 126 |
| 122 var memberListDiv = new DivElement(); | 127 var memberListDiv = new DivElement(); |
| 123 memberListDiv.classes.add('memberList'); | 128 memberListDiv.classes.add('memberList'); |
| 124 tooltipDiv.children.add(memberListDiv); | 129 tooltipDiv.children.add(memberListDiv); |
| 125 _buildTooltip(memberListDiv, { | 130 _buildTooltip(memberListDiv, { |
| 126 'Kind' : tipKind, | 131 'Kind' : tipKind, |
| 127 'Percent of Parent' : tipParent, | 132 'Percent of Parent' : tipParent, |
| 128 'Sample Count' : tipTicks, | 133 'Sample Count' : tipTicks, |
| 129 'Approximate Execution Time': tipTime, | 134 'Approximate Execution Time': tipTime, |
| 130 }); | 135 }); |
| 131 selfCell.children.add(tooltipDiv); | 136 selfCell.children.add(tooltipDiv); |
| 132 } | 137 } |
| 133 | 138 |
| 134 bool hasChildren() { | 139 bool hasChildren() { |
| 135 return node.children.length > 0; | 140 return node.children.length > 0; |
| 136 } | 141 } |
| 137 } | 142 } |
| 138 | 143 |
| 144 class ProfileFunctionTrieNodeTreeRow extends TableTreeRow { |
| 145 final CpuProfile profile; |
| 146 @reflectable final FunctionTrieNode root; |
| 147 @reflectable final FunctionTrieNode node; |
| 148 ProfileFunction get profileFunction => node.profileFunction; |
| 149 @reflectable ServiceFunction get function => node.profileFunction.function; |
| 150 @reflectable String tipKind = ''; |
| 151 @reflectable String tipParent = ''; |
| 152 @reflectable String tipExclusive = ''; |
| 153 @reflectable String tipTime = ''; |
| 154 @reflectable String tipTicks = ''; |
| 155 |
| 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 if (function.kind == FunctionKind.kTag) { |
| 187 tipOptimized = 'N/A'; |
| 188 } else { |
| 189 tipOptimized = |
| 190 Utils.formatPercent(node.profileFunction.inclusiveOptimizedTicks, |
| 191 node.profileFunction.inclusiveTicks); |
| 192 } |
| 193 } |
| 194 |
| 195 bool hasChildren() { |
| 196 return node.children.length > 0; |
| 197 } |
| 198 |
| 199 void _buildTooltip(DivElement memberList, Map<String, String> items) { |
| 200 items.forEach((k, v) { |
| 201 var item = new DivElement(); |
| 202 item.classes.add('memberItem'); |
| 203 var name = new DivElement(); |
| 204 name.classes.add('memberName'); |
| 205 name.classes.add('white'); |
| 206 name.text = k; |
| 207 var value = new DivElement(); |
| 208 value.classes.add('memberValue'); |
| 209 value.classes.add('white'); |
| 210 value.text = v; |
| 211 item.children.add(name); |
| 212 item.children.add(value); |
| 213 memberList.children.add(item); |
| 214 }); |
| 215 } |
| 216 |
| 217 void onShow() { |
| 218 super.onShow(); |
| 219 if (children.length == 0) { |
| 220 for (var childNode in node.children) { |
| 221 var row = new ProfileFunctionTrieNodeTreeRow(profile, |
| 222 root, |
| 223 childNode, tree, this); |
| 224 children.add(row); |
| 225 } |
| 226 } |
| 227 |
| 228 var methodCell = tableColumns[0]; |
| 229 // Enable expansion by clicking anywhere on the method column. |
| 230 methodCell.onClick.listen(onClick); |
| 231 |
| 232 // Grab the flex-row Div inside the methodCell. |
| 233 methodCell = methodCell.children[0]; |
| 234 |
| 235 // Insert the parent percentage |
| 236 var parentPercent = new DivElement(); |
| 237 parentPercent.text = tipParent; |
| 238 methodCell.children.add(parentPercent); |
| 239 |
| 240 var gap = new SpanElement(); |
| 241 gap.style.minWidth = '1em'; |
| 242 methodCell.children.add(gap); |
| 243 |
| 244 var functionAndCodeContainer = new DivElement(); |
| 245 methodCell.children.add(functionAndCodeContainer); |
| 246 |
| 247 var functionRef = new Element.tag('function-ref'); |
| 248 functionRef.ref = function; |
| 249 functionAndCodeContainer.children.add(functionRef); |
| 250 |
| 251 var codeRow = new DivElement(); |
| 252 codeRow.style.paddingTop = '1em'; |
| 253 functionAndCodeContainer.children.add(codeRow); |
| 254 if (!function.kind.isSynthetic()) { |
| 255 var totalInclusiveTicks = profileFunction.inclusiveTicks; |
| 256 for (var code in profileFunction.profileCodes) { |
| 257 var codeInclusiveTicks = code.inclusiveTicks; |
| 258 var inclusivePercentage = |
| 259 Utils.formatPercent(codeInclusiveTicks, totalInclusiveTicks); |
| 260 var percentageSpan = new SpanElement(); |
| 261 percentageSpan.text = '($inclusivePercentage) '; |
| 262 codeRow.children.add(percentageSpan); |
| 263 var codeRef = new Element.tag('code-ref'); |
| 264 codeRef.ref = code.code; |
| 265 codeRow.children.add(codeRef); |
| 266 } |
| 267 } |
| 268 |
| 269 var selfCell = tableColumns[1]; |
| 270 selfCell.style.position = 'relative'; |
| 271 selfCell.text = tipExclusive; |
| 272 |
| 273 var tooltipDiv = new DivElement(); |
| 274 tooltipDiv.classes.add('tooltip'); |
| 275 |
| 276 var memberListDiv = new DivElement(); |
| 277 memberListDiv.classes.add('memberList'); |
| 278 tooltipDiv.children.add(memberListDiv); |
| 279 _buildTooltip(memberListDiv, { |
| 280 'Kind' : tipKind, |
| 281 'Percent of Parent' : tipParent, |
| 282 'Sample Count' : tipTicks, |
| 283 'Approximate Execution Time': tipTime, |
| 284 'Optimized Execution Percentage': tipOptimized, |
| 285 }); |
| 286 selfCell.children.add(tooltipDiv); |
| 287 } |
| 288 } |
| 289 |
| 139 /// Displays a CpuProfile | 290 /// Displays a CpuProfile |
| 140 @CustomTag('cpu-profile') | 291 @CustomTag('cpu-profile') |
| 141 class CpuProfileElement extends ObservatoryElement { | 292 class CpuProfileElement extends ObservatoryElement { |
| 142 CpuProfileElement.created() : super.created(); | 293 static const MICROSECONDS_PER_SECOND = 1000000.0; |
| 294 |
| 143 @published Isolate isolate; | 295 @published Isolate isolate; |
| 144 | |
| 145 @observable ServiceMap profile; | |
| 146 @observable bool hideTagsChecked; | |
| 147 @observable String sampleCount = ''; | 296 @observable String sampleCount = ''; |
| 148 @observable String refreshTime = ''; | 297 @observable String refreshTime = ''; |
| 149 @observable String sampleRate = ''; | 298 @observable String sampleRate = ''; |
| 150 @observable String sampleDepth = ''; | 299 @observable String stackDepth = ''; |
| 151 @observable String displayCutoff = ''; | 300 @observable String displayCutoff = ''; |
| 152 @observable String timeSpan = ''; | 301 @observable String timeSpan = ''; |
| 153 @reflectable double displayThreshold = 0.0002; // 0.02%. | |
| 154 | 302 |
| 155 @observable String tagSelector = 'UserVM'; | 303 @observable String tagSelector = 'UserVM'; |
| 156 | 304 @observable String modeSelector = 'Function'; |
| 157 final _id = '#tableTree'; | 305 |
| 158 TableTree tree; | 306 final CpuProfile profile = new CpuProfile(); |
| 159 | 307 |
| 160 static const MICROSECONDS_PER_SECOND = 1000000.0; | 308 CpuProfileElement.created() : super.created(); |
| 161 | |
| 162 void isolateChanged(oldValue) { | |
| 163 if (isolate == null) { | |
| 164 profile = null; | |
| 165 return; | |
| 166 } | |
| 167 isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) | |
| 168 .then((ServiceObject obj) { | |
| 169 print(obj); | |
| 170 // Assert we got back the a profile. | |
| 171 assert(obj.type == 'CpuProfile'); | |
| 172 profile = obj; | |
| 173 _update(); | |
| 174 }); | |
| 175 } | |
| 176 | 309 |
| 177 @override | 310 @override |
| 178 void attached() { | 311 void attached() { |
| 179 super.attached(); | 312 super.attached(); |
| 180 var tableBody = shadowRoot.querySelector('#tableTreeBody'); | 313 } |
| 181 assert(tableBody != null); | 314 |
| 182 tree = new TableTree(tableBody, 2); | 315 void isolateChanged(oldValue) { |
| 183 _update(); | 316 _getCpuProfile(); |
| 184 } | 317 } |
| 185 | 318 |
| 186 void tagSelectorChanged(oldValue) { | 319 void tagSelectorChanged(oldValue) { |
| 187 isolateChanged(null); | 320 _getCpuProfile(); |
| 321 } |
| 322 |
| 323 void modeSelectorChanged(oldValue) { |
| 324 _getCpuProfile(); |
| 188 } | 325 } |
| 189 | 326 |
| 190 void refresh(var done) { | 327 void refresh(var done) { |
| 191 isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) | 328 _getCpuProfile().whenComplete(done); |
| 192 .then((ServiceObject obj) { | 329 } |
| 193 // Assert we got back the a profile. | 330 |
| 194 assert(obj.type == 'CpuProfile'); | 331 Future _getCpuProfile() { |
| 195 profile = obj; | 332 profile.clear(); |
| 196 _update(); | 333 if (isolate == null) { |
| 197 }).whenComplete(done); | 334 return new Future.value(null); |
| 198 } | 335 } |
| 199 | 336 return isolate.invokeRpc('getCpuProfile', { 'tags': tagSelector }) |
| 200 void _update() { | 337 .then((ServiceMap response) { |
| 201 if (profile == null) { | 338 profile.load(isolate, response); |
| 202 return; | 339 _updateView(); |
| 203 } | 340 }); |
| 204 var totalSamples = profile['samples']; | 341 } |
| 205 var now = new DateTime.now(); | 342 |
| 206 sampleCount = totalSamples.toString(); | 343 void _updateView() { |
| 207 refreshTime = now.toString(); | 344 sampleCount = profile.sampleCount.toString(); |
| 208 sampleDepth = profile['depth'].toString(); | 345 refreshTime = new DateTime.now().toString(); |
| 209 var period = profile['period']; | 346 stackDepth = profile.stackDepth.toString(); |
| 210 sampleRate = (MICROSECONDS_PER_SECOND / period).toStringAsFixed(0); | 347 sampleRate = profile.sampleRate.toStringAsFixed(0); |
| 211 timeSpan = formatTime(profile['timeSpan']); | 348 timeSpan = formatTime(profile.timeSpan); |
| 212 displayCutoff = '${(displayThreshold * 100.0).toString()}%'; | 349 displayCutoff = '${(profile.displayThreshold * 100.0).toString()}%'; |
| 213 profile.isolate.processProfile(profile); | 350 if (functionTree != null) { |
| 214 profile['threshold'] = displayThreshold; | 351 functionTree.clear(); |
| 215 _buildTree(); | 352 } |
| 216 } | 353 if (codeTree != null) { |
| 217 | 354 codeTree.clear(); |
| 218 void _buildStackTree() { | 355 } |
| 219 var root = profile.isolate.profileTrieRoot; | 356 if (modeSelector == 'Code') { |
| 357 _buildCodeTree(); |
| 358 } else { |
| 359 _buildFunctionTree(); |
| 360 } |
| 361 } |
| 362 |
| 363 TableTree codeTree; |
| 364 TableTree functionTree; |
| 365 |
| 366 void _buildFunctionTree() { |
| 367 if (functionTree == null) { |
| 368 var tableBody = shadowRoot.querySelector('#treeBody'); |
| 369 assert(tableBody != null); |
| 370 functionTree = new TableTree(tableBody, 2); |
| 371 } |
| 372 var root = profile.functionTrieRoot; |
| 220 if (root == null) { | 373 if (root == null) { |
| 221 return; | 374 return; |
| 222 } | 375 } |
| 223 try { | 376 try { |
| 224 tree.initialize( | 377 functionTree.initialize( |
| 225 new ProfileCodeTrieNodeTreeRow(profile, root, root, tree, null)); | 378 new ProfileFunctionTrieNodeTreeRow(profile, |
| 379 root, root, functionTree, null)); |
| 226 } catch (e, stackTrace) { | 380 } catch (e, stackTrace) { |
| 227 print(e); | 381 print(e); |
| 228 print(stackTrace); | 382 print(stackTrace); |
| 229 Logger.root.warning('_buildStackTree', e, stackTrace); | 383 Logger.root.warning('_buildFunctionTree', e, stackTrace); |
| 230 } | 384 } |
| 231 // Check if we only have one node at the root and expand it. | 385 // Check if we only have one node at the root and expand it. |
| 232 if (tree.rows.length == 1) { | 386 if (functionTree.rows.length == 1) { |
| 233 tree.toggle(tree.rows[0]); | 387 functionTree.toggle(functionTree.rows[0]); |
| 234 } | 388 } |
| 235 notifyPropertyChange(#tree, null, tree); | 389 } |
| 236 } | 390 |
| 237 | 391 void _buildCodeTree() { |
| 238 void _buildTree() { | 392 if (codeTree == null) { |
| 239 _buildStackTree(); | 393 var tableBody = shadowRoot.querySelector('#treeBody'); |
| 394 assert(tableBody != null); |
| 395 codeTree = new TableTree(tableBody, 2); |
| 396 } |
| 397 var root = profile.codeTrieRoot; |
| 398 if (root == null) { |
| 399 return; |
| 400 } |
| 401 try { |
| 402 codeTree.initialize( |
| 403 new ProfileCodeTrieNodeTreeRow(profile, root, root, codeTree, null)); |
| 404 } catch (e, stackTrace) { |
| 405 print(e); |
| 406 print(stackTrace); |
| 407 Logger.root.warning('_buildCodeTree', e, stackTrace); |
| 408 } |
| 409 // Check if we only have one node at the root and expand it. |
| 410 if (codeTree.rows.length == 1) { |
| 411 codeTree.toggle(codeTree.rows[0]); |
| 412 } |
| 240 } | 413 } |
| 241 } | 414 } |
| OLD | NEW |