OLD | NEW |
1 /* | 1 /* |
2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. | 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. |
3 * | 3 * |
4 * Redistribution and use in source and binary forms, with or without | 4 * Redistribution and use in source and binary forms, with or without |
5 * modification, are permitted provided that the following conditions | 5 * modification, are permitted provided that the following conditions |
6 * are met: | 6 * are met: |
7 * 1. Redistributions of source code must retain the above copyright | 7 * 1. Redistributions of source code must retain the above copyright |
8 * notice, this list of conditions and the following disclaimer. | 8 * notice, this list of conditions and the following disclaimer. |
9 * 2. Redistributions in binary form must reproduce the above copyright | 9 * 2. Redistributions in binary form must reproduce the above copyright |
10 * notice, this list of conditions and the following disclaimer in the | 10 * notice, this list of conditions and the following disclaimer in the |
11 * documentation and/or other materials provided with the distribution. | 11 * documentation and/or other materials provided with the distribution. |
12 * | 12 * |
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY | 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR | 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR | 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, | 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, | 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR | 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY | 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 */ | 24 */ |
25 | 25 |
26 | 26 |
27 /** | 27 /** |
28 * @constructor | 28 * @constructor |
29 * @param {!ProfilerAgent.CPUProfile} profile | |
30 */ | |
31 WebInspector.CPUProfileDataModel = function(profile) | |
32 { | |
33 this.profileHead = profile.head; | |
34 this.samples = profile.samples; | |
35 this._calculateTimes(profile); | |
36 this._assignParentsInProfile(); | |
37 if (this.samples) | |
38 this._buildIdToNodeMap(); | |
39 } | |
40 | |
41 WebInspector.CPUProfileDataModel.prototype = { | |
42 /** | |
43 * @param {!ProfilerAgent.CPUProfile} profile | |
44 */ | |
45 _calculateTimes: function(profile) | |
46 { | |
47 function totalHitCount(node) { | |
48 var result = node.hitCount; | |
49 for (var i = 0; i < node.children.length; i++) | |
50 result += totalHitCount(node.children[i]); | |
51 return result; | |
52 } | |
53 profile.totalHitCount = totalHitCount(profile.head); | |
54 | |
55 var durationMs = 1000 * (profile.endTime - profile.startTime); | |
56 var samplingInterval = durationMs / profile.totalHitCount; | |
57 this.samplingIntervalMs = samplingInterval; | |
58 | |
59 function calculateTimesForNode(node) { | |
60 node.selfTime = node.hitCount * samplingInterval; | |
61 var totalHitCount = node.hitCount; | |
62 for (var i = 0; i < node.children.length; i++) | |
63 totalHitCount += calculateTimesForNode(node.children[i]); | |
64 node.totalTime = totalHitCount * samplingInterval; | |
65 return totalHitCount; | |
66 } | |
67 calculateTimesForNode(profile.head); | |
68 }, | |
69 | |
70 _assignParentsInProfile: function() | |
71 { | |
72 var head = this.profileHead; | |
73 head.parent = null; | |
74 head.head = null; | |
75 var nodesToTraverse = [ head ]; | |
76 while (nodesToTraverse.length) { | |
77 var parent = nodesToTraverse.pop(); | |
78 var children = parent.children; | |
79 var length = children.length; | |
80 for (var i = 0; i < length; ++i) { | |
81 var child = children[i]; | |
82 child.head = head; | |
83 child.parent = parent; | |
84 if (child.children.length) | |
85 nodesToTraverse.push(child); | |
86 } | |
87 } | |
88 }, | |
89 | |
90 _buildIdToNodeMap: function() | |
91 { | |
92 /** @type {!Object.<number, !ProfilerAgent.CPUProfileNode>} */ | |
93 this._idToNode = {}; | |
94 var idToNode = this._idToNode; | |
95 var stack = [this.profileHead]; | |
96 while (stack.length) { | |
97 var node = stack.pop(); | |
98 idToNode[node.id] = node; | |
99 for (var i = 0; i < node.children.length; i++) | |
100 stack.push(node.children[i]); | |
101 } | |
102 | |
103 var topLevelNodes = this.profileHead.children; | |
104 for (var i = 0; i < topLevelNodes.length; i++) { | |
105 var node = topLevelNodes[i]; | |
106 if (node.functionName === "(garbage collector)") { | |
107 this._gcNode = node; | |
108 break; | |
109 } | |
110 } | |
111 } | |
112 } | |
113 | |
114 | |
115 /** | |
116 * @constructor | |
117 * @extends {WebInspector.VBox} | 29 * @extends {WebInspector.VBox} |
118 * @param {!WebInspector.CPUProfileHeader} profileHeader | 30 * @param {!WebInspector.CPUProfileHeader} profileHeader |
119 */ | 31 */ |
120 WebInspector.CPUProfileView = function(profileHeader) | 32 WebInspector.CPUProfileView = function(profileHeader) |
121 { | 33 { |
122 WebInspector.VBox.call(this); | 34 WebInspector.VBox.call(this); |
123 this.element.classList.add("cpu-profile-view"); | 35 this.element.classList.add("cpu-profile-view"); |
124 | 36 |
125 this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebI
nspector.CPUProfileView._TypeHeavy); | 37 this._viewType = WebInspector.settings.createSetting("cpuProfilerView", WebI
nspector.CPUProfileView._TypeHeavy); |
126 | 38 |
(...skipping 862 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
989 { | 901 { |
990 if (!WebInspector.CPUProfileView._colorGenerator) { | 902 if (!WebInspector.CPUProfileView._colorGenerator) { |
991 var colorGenerator = new WebInspector.CPUProfileFlameChart.ColorGenerato
r(); | 903 var colorGenerator = new WebInspector.CPUProfileFlameChart.ColorGenerato
r(); |
992 colorGenerator.colorForID("(idle)::0", 50); | 904 colorGenerator.colorForID("(idle)::0", 50); |
993 colorGenerator.colorForID("(program)::0", 50); | 905 colorGenerator.colorForID("(program)::0", 50); |
994 colorGenerator.colorForID("(garbage collector)::0", 50); | 906 colorGenerator.colorForID("(garbage collector)::0", 50); |
995 WebInspector.CPUProfileView._colorGenerator = colorGenerator; | 907 WebInspector.CPUProfileView._colorGenerator = colorGenerator; |
996 } | 908 } |
997 return WebInspector.CPUProfileView._colorGenerator; | 909 return WebInspector.CPUProfileView._colorGenerator; |
998 } | 910 } |
999 | |
1000 /** | |
1001 * @constructor | |
1002 * @implements {WebInspector.FlameChartDataProvider} | |
1003 * @param {!WebInspector.CPUProfileDataModel} cpuProfile | |
1004 * @param {!WebInspector.Target} target | |
1005 */ | |
1006 WebInspector.CPUFlameChartDataProvider = function(cpuProfile, target) | |
1007 { | |
1008 WebInspector.FlameChartDataProvider.call(this); | |
1009 this._cpuProfile = cpuProfile; | |
1010 this._target = target; | |
1011 this._colorGenerator = WebInspector.CPUProfileView.colorGenerator(); | |
1012 } | |
1013 | |
1014 WebInspector.CPUFlameChartDataProvider.prototype = { | |
1015 /** | |
1016 * @return {number} | |
1017 */ | |
1018 barHeight: function() | |
1019 { | |
1020 return 15; | |
1021 }, | |
1022 | |
1023 /** | |
1024 * @return {number} | |
1025 */ | |
1026 textBaseline: function() | |
1027 { | |
1028 return 4; | |
1029 }, | |
1030 | |
1031 /** | |
1032 * @return {number} | |
1033 */ | |
1034 textPadding: function() | |
1035 { | |
1036 return 2; | |
1037 }, | |
1038 | |
1039 /** | |
1040 * @param {number} startTime | |
1041 * @param {number} endTime | |
1042 * @return {?Array.<number>} | |
1043 */ | |
1044 dividerOffsets: function(startTime, endTime) | |
1045 { | |
1046 return null; | |
1047 }, | |
1048 | |
1049 /** | |
1050 * @return {number} | |
1051 */ | |
1052 zeroTime: function() | |
1053 { | |
1054 return 0; | |
1055 }, | |
1056 | |
1057 /** | |
1058 * @return {number} | |
1059 */ | |
1060 totalTime: function() | |
1061 { | |
1062 return this._cpuProfile.profileHead.totalTime; | |
1063 }, | |
1064 | |
1065 /** | |
1066 * @return {number} | |
1067 */ | |
1068 maxStackDepth: function() | |
1069 { | |
1070 return this._maxStackDepth; | |
1071 }, | |
1072 | |
1073 /** | |
1074 * @return {?WebInspector.FlameChart.TimelineData} | |
1075 */ | |
1076 timelineData: function() | |
1077 { | |
1078 return this._timelineData || this._calculateTimelineData(); | |
1079 }, | |
1080 | |
1081 /** | |
1082 * @return {?WebInspector.FlameChart.TimelineData} | |
1083 */ | |
1084 _calculateTimelineData: function() | |
1085 { | |
1086 if (!this._cpuProfile.profileHead) | |
1087 return null; | |
1088 | |
1089 var samples = this._cpuProfile.samples; | |
1090 var idToNode = this._cpuProfile._idToNode; | |
1091 var gcNode = this._cpuProfile._gcNode; | |
1092 var samplesCount = samples.length; | |
1093 var samplingInterval = this._cpuProfile.samplingIntervalMs; | |
1094 | |
1095 var index = 0; | |
1096 | |
1097 var openIntervals = []; | |
1098 var stackTrace = []; | |
1099 var maxDepth = 5; // minimum stack depth for the case when we see no act
ivity. | |
1100 var depth = 0; | |
1101 | |
1102 /** | |
1103 * @constructor | |
1104 * @param {number} depth | |
1105 * @param {number} duration | |
1106 * @param {number} startTime | |
1107 * @param {!Object} node | |
1108 */ | |
1109 function ChartEntry(depth, duration, startTime, node) | |
1110 { | |
1111 this.depth = depth; | |
1112 this.duration = duration; | |
1113 this.startTime = startTime; | |
1114 this.node = node; | |
1115 this.selfTime = 0; | |
1116 } | |
1117 var entries = /** @type {!Array.<!ChartEntry>} */ ([]); | |
1118 | |
1119 for (var sampleIndex = 0; sampleIndex < samplesCount; sampleIndex++) { | |
1120 var node = idToNode[samples[sampleIndex]]; | |
1121 stackTrace.length = 0; | |
1122 while (node) { | |
1123 stackTrace.push(node); | |
1124 node = node.parent; | |
1125 } | |
1126 stackTrace.pop(); // Remove (root) node | |
1127 | |
1128 maxDepth = Math.max(maxDepth, depth); | |
1129 depth = 0; | |
1130 node = stackTrace.pop(); | |
1131 var intervalIndex; | |
1132 | |
1133 // GC samples have no stack, so we just put GC node on top of the la
st recoreded sample. | |
1134 if (node === gcNode) { | |
1135 while (depth < openIntervals.length) { | |
1136 intervalIndex = openIntervals[depth].index; | |
1137 entries[intervalIndex].duration += samplingInterval; | |
1138 ++depth; | |
1139 } | |
1140 // If previous stack is also GC then just continue. | |
1141 if (openIntervals.length > 0 && openIntervals.peekLast().node ==
= node) { | |
1142 entries[intervalIndex].selfTime += samplingInterval; | |
1143 continue; | |
1144 } | |
1145 } | |
1146 | |
1147 while (node && depth < openIntervals.length && node === openInterval
s[depth].node) { | |
1148 intervalIndex = openIntervals[depth].index; | |
1149 entries[intervalIndex].duration += samplingInterval; | |
1150 node = stackTrace.pop(); | |
1151 ++depth; | |
1152 } | |
1153 if (depth < openIntervals.length) | |
1154 openIntervals.length = depth; | |
1155 if (!node) { | |
1156 entries[intervalIndex].selfTime += samplingInterval; | |
1157 continue; | |
1158 } | |
1159 | |
1160 var colorGenerator = this._colorGenerator; | |
1161 var color = ""; | |
1162 while (node) { | |
1163 entries.push(new ChartEntry(depth, samplingInterval, sampleIndex
* samplingInterval, node)); | |
1164 openIntervals.push({node: node, index: index}); | |
1165 ++index; | |
1166 | |
1167 node = stackTrace.pop(); | |
1168 ++depth; | |
1169 } | |
1170 entries[entries.length - 1].selfTime += samplingInterval; | |
1171 } | |
1172 | |
1173 /** @type {!Array.<!ProfilerAgent.CPUProfileNode>} */ | |
1174 var entryNodes = new Array(entries.length); | |
1175 var entryLevels = new Uint8Array(entries.length); | |
1176 var entryTotalTimes = new Float32Array(entries.length); | |
1177 var entrySelfTimes = new Float32Array(entries.length); | |
1178 var entryOffsets = new Float32Array(entries.length); | |
1179 | |
1180 for (var i = 0; i < entries.length; ++i) { | |
1181 var entry = entries[i]; | |
1182 entryNodes[i] = entry.node; | |
1183 entryLevels[i] = entry.depth; | |
1184 entryTotalTimes[i] = entry.duration; | |
1185 entryOffsets[i] = entry.startTime; | |
1186 entrySelfTimes[i] = entry.selfTime; | |
1187 } | |
1188 | |
1189 this._maxStackDepth = Math.max(maxDepth, depth); | |
1190 | |
1191 this._timelineData = { | |
1192 entryLevels: entryLevels, | |
1193 entryTotalTimes: entryTotalTimes, | |
1194 entryOffsets: entryOffsets, | |
1195 }; | |
1196 | |
1197 /** @type {!Array.<!ProfilerAgent.CPUProfileNode>} */ | |
1198 this._entryNodes = entryNodes; | |
1199 this._entrySelfTimes = entrySelfTimes; | |
1200 | |
1201 return /** @type {!WebInspector.FlameChart.TimelineData} */ (this._timel
ineData); | |
1202 }, | |
1203 | |
1204 /** | |
1205 * @param {number} ms | |
1206 * @return {string} | |
1207 */ | |
1208 _millisecondsToString: function(ms) | |
1209 { | |
1210 if (ms === 0) | |
1211 return "0"; | |
1212 if (ms < 1000) | |
1213 return WebInspector.UIString("%.1f\u2009ms", ms); | |
1214 return Number.secondsToString(ms / 1000, true); | |
1215 }, | |
1216 | |
1217 /** | |
1218 * @param {number} entryIndex | |
1219 * @return {?Array.<!{title: string, text: string}>} | |
1220 */ | |
1221 prepareHighlightedEntryInfo: function(entryIndex) | |
1222 { | |
1223 var timelineData = this._timelineData; | |
1224 var node = this._entryNodes[entryIndex]; | |
1225 if (!node) | |
1226 return null; | |
1227 | |
1228 var entryInfo = []; | |
1229 function pushEntryInfoRow(title, text) | |
1230 { | |
1231 var row = {}; | |
1232 row.title = title; | |
1233 row.text = text; | |
1234 entryInfo.push(row); | |
1235 } | |
1236 | |
1237 pushEntryInfoRow(WebInspector.UIString("Name"), node.functionName); | |
1238 var selfTime = this._millisecondsToString(this._entrySelfTimes[entryInde
x]); | |
1239 var totalTime = this._millisecondsToString(timelineData.entryTotalTimes[
entryIndex]); | |
1240 pushEntryInfoRow(WebInspector.UIString("Self time"), selfTime); | |
1241 pushEntryInfoRow(WebInspector.UIString("Total time"), totalTime); | |
1242 var target = this._target; | |
1243 var text = WebInspector.Linkifier.liveLocationText(target, node.scriptId
, node.lineNumber, node.columnNumber); | |
1244 pushEntryInfoRow(WebInspector.UIString("URL"), text); | |
1245 pushEntryInfoRow(WebInspector.UIString("Aggregated self time"), Number.s
econdsToString(node.selfTime / 1000, true)); | |
1246 pushEntryInfoRow(WebInspector.UIString("Aggregated total time"), Number.
secondsToString(node.totalTime / 1000, true)); | |
1247 if (node.deoptReason && node.deoptReason !== "no reason") | |
1248 pushEntryInfoRow(WebInspector.UIString("Not optimized"), node.deoptR
eason); | |
1249 | |
1250 return entryInfo; | |
1251 }, | |
1252 | |
1253 /** | |
1254 * @param {number} entryIndex | |
1255 * @return {boolean} | |
1256 */ | |
1257 canJumpToEntry: function(entryIndex) | |
1258 { | |
1259 return this._entryNodes[entryIndex].scriptId !== "0"; | |
1260 }, | |
1261 | |
1262 /** | |
1263 * @param {number} entryIndex | |
1264 * @return {?string} | |
1265 */ | |
1266 entryTitle: function(entryIndex) | |
1267 { | |
1268 var node = this._entryNodes[entryIndex]; | |
1269 return node.functionName; | |
1270 }, | |
1271 | |
1272 /** | |
1273 * @param {number} entryIndex | |
1274 * @return {?string} | |
1275 */ | |
1276 entryFont: function(entryIndex) | |
1277 { | |
1278 if (!this._font) { | |
1279 this._font = (this.barHeight() - 4) + "px " + WebInspector.fontFamil
y(); | |
1280 this._boldFont = "bold " + this._font; | |
1281 } | |
1282 var node = this._entryNodes[entryIndex]; | |
1283 var reason = node.deoptReason; | |
1284 return (reason && reason !== "no reason") ? this._boldFont : this._font; | |
1285 }, | |
1286 | |
1287 /** | |
1288 * @param {number} entryIndex | |
1289 * @return {!string} | |
1290 */ | |
1291 entryColor: function(entryIndex) | |
1292 { | |
1293 var node = this._entryNodes[entryIndex]; | |
1294 return this._colorGenerator.colorForID(node.functionName + ":" + node.ur
l + ":" + node.lineNumber); | |
1295 }, | |
1296 | |
1297 /** | |
1298 * @param {number} entryIndex | |
1299 * @param {!CanvasRenderingContext2D} context | |
1300 * @param {?string} text | |
1301 * @param {number} barX | |
1302 * @param {number} barY | |
1303 * @param {number} barWidth | |
1304 * @param {number} barHeight | |
1305 * @param {function(number):number} offsetToPosition | |
1306 * @return {boolean} | |
1307 */ | |
1308 decorateEntry: function(entryIndex, context, text, barX, barY, barWidth, bar
Height, offsetToPosition) | |
1309 { | |
1310 return false; | |
1311 }, | |
1312 | |
1313 /** | |
1314 * @param {number} entryIndex | |
1315 * @return {boolean} | |
1316 */ | |
1317 forceDecoration: function(entryIndex) | |
1318 { | |
1319 return false; | |
1320 }, | |
1321 | |
1322 /** | |
1323 * @param {number} entryIndex | |
1324 * @return {!{startTimeOffset: number, endTimeOffset: number}} | |
1325 */ | |
1326 highlightTimeRange: function(entryIndex) | |
1327 { | |
1328 var startTimeOffset = this._timelineData.entryOffsets[entryIndex]; | |
1329 return { | |
1330 startTimeOffset: startTimeOffset, | |
1331 endTimeOffset: startTimeOffset + this._timelineData.entryTotalTimes[
entryIndex] | |
1332 }; | |
1333 }, | |
1334 | |
1335 /** | |
1336 * @return {number} | |
1337 */ | |
1338 paddingLeft: function() | |
1339 { | |
1340 return 15; | |
1341 }, | |
1342 | |
1343 /** | |
1344 * @param {number} entryIndex | |
1345 * @return {!string} | |
1346 */ | |
1347 textColor: function(entryIndex) | |
1348 { | |
1349 return "#333"; | |
1350 } | |
1351 } | |
OLD | NEW |