| 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 isolate_profile_element; | 5 library isolate_profile_element; |
| 6 | 6 |
| 7 import 'dart:html'; | 7 import 'dart:html'; |
| 8 import 'observatory_element.dart'; | 8 import 'observatory_element.dart'; |
| 9 import 'package:logging/logging.dart'; |
| 9 import 'package:observatory/service.dart'; | 10 import 'package:observatory/service.dart'; |
| 10 import 'package:observatory/app.dart'; | 11 import 'package:observatory/app.dart'; |
| 11 import 'package:polymer/polymer.dart'; | 12 import 'package:polymer/polymer.dart'; |
| 12 | 13 |
| 14 class ProfileCodeTrieNodeTreeRow extends TableTreeRow { |
| 15 final ServiceMap profile; |
| 16 @reflectable final CodeTrieNode root; |
| 17 @reflectable final CodeTrieNode node; |
| 18 @reflectable Code get code => node.code; |
| 19 |
| 20 static String formatPercent(num a, num total) { |
| 21 var percent = 100.0 * (a / total); |
| 22 return '${percent.toStringAsFixed(2)}%'; |
| 23 } |
| 24 |
| 25 ProfileCodeTrieNodeTreeRow(this.profile, this.root, this.node, |
| 26 ProfileCodeTrieNodeTreeRow parent) |
| 27 : super(parent) { |
| 28 assert(root != null); |
| 29 assert(node != null); |
| 30 var totalSamples = root.count; |
| 31 // When the row is created, fill out the columns. |
| 32 if (parent == null) { |
| 33 columns.add(formatPercent(node.count, root.count)); |
| 34 } else { |
| 35 columns.add(formatPercent(node.count, parent.node.count)); |
| 36 } |
| 37 columns.add(formatPercent(node.code.exclusiveTicks, totalSamples)); |
| 38 } |
| 39 |
| 40 bool shouldDisplayChild(CodeTrieNode childNode, double threshold) { |
| 41 return ((childNode.count / node.count) > threshold) || |
| 42 ((childNode.code.exclusiveTicks / root.count) > threshold); |
| 43 } |
| 44 |
| 45 void onShow() { |
| 46 var threshold = profile['threshold']; |
| 47 if (children.length > 0) { |
| 48 // Child rows already created. |
| 49 return; |
| 50 } |
| 51 for (var childNode in node.children) { |
| 52 if (!shouldDisplayChild(childNode, threshold)) { |
| 53 continue; |
| 54 } |
| 55 var row = new ProfileCodeTrieNodeTreeRow(profile, root, childNode, this); |
| 56 children.add(row); |
| 57 } |
| 58 } |
| 59 |
| 60 void onHide() { |
| 61 } |
| 62 } |
| 63 |
| 13 class ProfileCallerTreeRow extends TableTreeRow { | 64 class ProfileCallerTreeRow extends TableTreeRow { |
| 14 final ServiceMap profile; | 65 final ServiceMap profile; |
| 15 @reflectable final Code code; | 66 @reflectable final Code code; |
| 16 | 67 |
| 17 static String formatPercent(num a, num total) { | 68 static String formatPercent(num a, num total) { |
| 18 var percent = 100.0 * (a / total); | 69 var percent = 100.0 * (a / total); |
| 19 return '${percent.toStringAsFixed(2)}%'; | 70 return '${percent.toStringAsFixed(2)}%'; |
| 20 } | 71 } |
| 21 | 72 |
| 22 ProfileCallerTreeRow(this.profile, this.code, ProfileCallerTreeRow parent) : | 73 ProfileCallerTreeRow(this.profile, this.code, ProfileCallerTreeRow parent) : |
| 23 super(parent) { | 74 super(parent) { |
| 24 assert(profile != null); | 75 assert(profile != null); |
| 25 assert(code != null); | 76 assert(code != null); |
| 26 var totalSamples = profile['samples']; | 77 var totalSamples = profile['samples']; |
| 27 // When the row is created, fill out the columns. | 78 // When the row is created, fill out the columns. |
| 28 columns.add( | |
| 29 formatPercent(code.exclusiveTicks, totalSamples)); | |
| 30 if (parent == null) { | 79 if (parent == null) { |
| 31 // Fill with dummy data. | 80 var root = profile.isolate.codes.tagRoot(); |
| 32 columns.add(''); | 81 var totalAttributedCalls = root.callersCount(code); |
| 82 var totalParentCalls = root.sumCallersCount(); |
| 83 columns.add(formatPercent(totalAttributedCalls, totalParentCalls)); |
| 33 } else { | 84 } else { |
| 34 var totalAttributedCalls = parent.code.callersCount(code); | 85 var totalAttributedCalls = parent.code.callersCount(code); |
| 35 var totalParentCalls = parent.code.sumCallersCount(); | 86 var totalParentCalls = parent.code.sumCallersCount(); |
| 36 columns.add(formatPercent(totalAttributedCalls, totalParentCalls)); | 87 columns.add(formatPercent(totalAttributedCalls, totalParentCalls)); |
| 37 } | 88 } |
| 89 columns.add(formatPercent(code.exclusiveTicks, totalSamples)); |
| 90 } |
| 91 |
| 92 bool shouldDisplayChild(CodeCallCount childNode, totalSamples, |
| 93 double threshold) { |
| 94 var callerPercent = code.callersCount(childNode.code) / |
| 95 code.sumCallersCount(); |
| 96 return (callerPercent > threshold) || |
| 97 ((childNode.code.exclusiveTicks / totalSamples) > threshold); |
| 38 } | 98 } |
| 39 | 99 |
| 40 void onShow() { | 100 void onShow() { |
| 101 var threshold = profile['threshold']; |
| 102 var totalSamples = profile['samples']; |
| 41 if (children.length > 0) { | 103 if (children.length > 0) { |
| 42 // Child rows already created. | 104 // Child rows already created. |
| 43 return; | 105 return; |
| 44 } | 106 } |
| 45 // Create child rows on demand. | 107 for (var codeCaller in code.callers) { |
| 46 code.callers.forEach((CodeCallCount codeCaller) { | 108 if (!shouldDisplayChild(codeCaller, totalSamples, threshold)) { |
| 47 var row = | 109 continue; |
| 48 new ProfileCallerTreeRow(profile, codeCaller.code, this); | 110 } |
| 111 var row = new ProfileCallerTreeRow(profile, codeCaller.code, this); |
| 49 children.add(row); | 112 children.add(row); |
| 50 }); | 113 } |
| 51 } | 114 } |
| 52 | 115 |
| 53 void onHide() { | 116 void onHide() { |
| 54 } | 117 } |
| 55 } | 118 } |
| 56 | 119 |
| 57 /// Displays an IsolateProfile | 120 /// Displays an IsolateProfile |
| 58 @CustomTag('isolate-profile') | 121 @CustomTag('isolate-profile') |
| 59 class IsolateProfileElement extends ObservatoryElement { | 122 class IsolateProfileElement extends ObservatoryElement { |
| 60 IsolateProfileElement.created() : super.created(); | 123 IsolateProfileElement.created() : super.created(); |
| 61 @published ServiceMap profile; | 124 @published ServiceMap profile; |
| 62 @reflectable final topExclusiveCodes = new ObservableList<Code>(); | 125 @observable bool callGraphChecked; |
| 63 @observable int methodCountSelected = 0; | 126 @observable bool hideTagsChecked; |
| 64 @observable String sampleCount = ''; | 127 @observable String sampleCount = ''; |
| 65 @observable String refreshTime = ''; | 128 @observable String refreshTime = ''; |
| 129 @observable String sampleRate = ''; |
| 130 @observable String sampleDepth = ''; |
| 131 @observable String displayCutoff = ''; |
| 132 @reflectable double displayThreshold = 0.0001; // 0.5%. |
| 66 | 133 |
| 67 final List methodCounts = [10, 20, 50]; | |
| 68 final _id = '#tableTree'; | 134 final _id = '#tableTree'; |
| 69 TableTree tree; | 135 TableTree tree; |
| 70 | 136 |
| 137 static const MICROSECONDS_PER_SECOND = 1000000.0; |
| 138 |
| 71 void profileChanged(oldValue) { | 139 void profileChanged(oldValue) { |
| 72 if (profile == null) { | 140 if (profile == null) { |
| 73 return; | 141 return; |
| 74 } | 142 } |
| 75 var totalSamples = profile['samples']; | 143 var totalSamples = profile['samples']; |
| 76 var now = new DateTime.now(); | 144 var now = new DateTime.now(); |
| 77 sampleCount = totalSamples.toString(); | 145 sampleCount = totalSamples.toString(); |
| 78 refreshTime = now.toString(); | 146 refreshTime = now.toString(); |
| 147 sampleDepth = profile['depth'].toString(); |
| 148 var period = profile['period']; |
| 149 sampleRate = (MICROSECONDS_PER_SECOND / period).toStringAsFixed(0); |
| 150 displayCutoff = '${(displayThreshold * 100.0).toString()}%'; |
| 79 profile.isolate.processProfile(profile); | 151 profile.isolate.processProfile(profile); |
| 152 profile['threshold'] = displayThreshold; |
| 80 _update(); | 153 _update(); |
| 81 } | 154 } |
| 82 | 155 |
| 83 void enteredView() { | 156 void callGraphCheckedChanged(oldValue) { |
| 84 tree = new TableTree(['Method', 'Exclusive', 'Caller']); | |
| 85 _update(); | 157 _update(); |
| 86 } | 158 } |
| 87 | 159 |
| 88 methodCountSelectedChanged(oldValue) { | 160 |
| 161 void enteredView() { |
| 162 tree = new TableTree(); |
| 89 _update(); | 163 _update(); |
| 90 } | 164 } |
| 91 | 165 |
| 166 void hideTagsCheckedChanged(oldValue) { |
| 167 refresh(null); |
| 168 } |
| 169 |
| 92 void refresh(var done) { | 170 void refresh(var done) { |
| 93 profile.isolate.get('profile').then((ServiceMap m) { | 171 var request = 'profile'; |
| 172 if ((hideTagsChecked != null) && hideTagsChecked) { |
| 173 request += '?tags=hide'; |
| 174 } |
| 175 profile.isolate.get(request).then((ServiceMap m) { |
| 94 // Assert we got back the a profile. | 176 // Assert we got back the a profile. |
| 95 assert(m.serviceType == 'Profile'); | 177 assert(m.serviceType == 'Profile'); |
| 96 profile = m; | 178 profile = m; |
| 97 }).whenComplete(done); | 179 }).whenComplete(done); |
| 98 } | 180 } |
| 99 | 181 |
| 100 void _update() { | 182 void _update() { |
| 101 if (profile == null) { | 183 if (profile == null) { |
| 102 return; | 184 return; |
| 103 } | 185 } |
| 104 _refreshTopMethods(); | 186 _buildTree(); |
| 105 _rebuildTree(); | |
| 106 } | 187 } |
| 107 | 188 |
| 108 void _refreshTopMethods() { | 189 void _buildCallersTree() { |
| 109 assert(profile != null); | 190 assert(profile != null); |
| 110 var count = methodCounts[methodCountSelected]; | 191 var root = profile.isolate.codes.tagRoot(); |
| 111 topExclusiveCodes.clear(); | 192 if (root == null) { |
| 112 topExclusiveCodes.addAll(profile.isolate.codes.topExclusive(count)); | 193 Logger.root.warning('No profile root tag.'); |
| 194 } |
| 195 try { |
| 196 tree.initialize(new ProfileCallerTreeRow(profile, root, null)); |
| 197 } catch (e, stackTrace) { |
| 198 Logger.root.warning('_buildCallersTree', e, stackTrace); |
| 199 } |
| 200 |
| 201 notifyPropertyChange(#tree, null, tree); |
| 113 } | 202 } |
| 114 | 203 |
| 115 void _rebuildTree() { | 204 void _buildStackTree() { |
| 116 assert(profile != null); | 205 var root = profile.isolate.profileTrieRoot; |
| 117 var rootChildren = []; | 206 if (root == null) { |
| 118 for (var code in topExclusiveCodes) { | 207 Logger.root.warning('No profile trie root.'); |
| 119 var row = new ProfileCallerTreeRow(profile, code, null); | |
| 120 rootChildren.add(row); | |
| 121 } | 208 } |
| 122 tree.initialize(rootChildren); | 209 try { |
| 210 tree.initialize( |
| 211 new ProfileCodeTrieNodeTreeRow(profile, root, root, null)); |
| 212 } catch (e, stackTrace) { |
| 213 Logger.root.warning('_buildStackTree', e, stackTrace); |
| 214 } |
| 123 notifyPropertyChange(#tree, null, tree); | 215 notifyPropertyChange(#tree, null, tree); |
| 124 } | 216 } |
| 125 | 217 |
| 218 void _buildTree() { |
| 219 if ((callGraphChecked) != null && callGraphChecked) { |
| 220 _buildCallersTree(); |
| 221 } else { |
| 222 _buildStackTree(); |
| 223 } |
| 224 } |
| 225 |
| 126 @observable String padding(TableTreeRow row) { | 226 @observable String padding(TableTreeRow row) { |
| 127 return 'padding-left: ${row.depth * 16}px;'; | 227 return 'padding-left: ${row.depth * 16}px;'; |
| 128 } | 228 } |
| 129 | 229 |
| 130 @observable String coloring(TableTreeRow row) { | 230 @observable String coloring(TableTreeRow row) { |
| 131 const colors = const ['active', 'success', 'warning', 'danger', 'info']; | 231 const colors = const ['active', 'success', 'warning', 'danger', 'info']; |
| 132 var index = row.depth % colors.length; | 232 var index = row.depth % colors.length; |
| 133 return colors[index]; | 233 return colors[index]; |
| 134 } | 234 } |
| 135 | 235 |
| 136 @observable void toggleExpanded(Event e, var detail, Element target) { | 236 @observable void toggleExpanded(Event e, var detail, Element target) { |
| 137 var row = target.parent; | 237 var row = target.parent; |
| 138 if (row is TableRowElement) { | 238 if (row is TableRowElement) { |
| 139 // Subtract 1 to get 0 based indexing. | 239 // Subtract 1 to get 0 based indexing. |
| 140 tree.toggle(row.rowIndex - 1); | 240 tree.toggle(row.rowIndex - 1); |
| 141 } | 241 } |
| 142 } | 242 } |
| 143 | 243 |
| 144 } | 244 } |
| OLD | NEW |