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 |