1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/profiler/cleopatra/js/parserWorker.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1664 @@ 1.4 +/* -*- Mode: js2; indent-tabs-mode: nil; js2-basic-offset: 2; -*- */ 1.5 + 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +"use strict"; 1.11 + 1.12 +importScripts("ProgressReporter.js"); 1.13 + 1.14 +var gProfiles = []; 1.15 + 1.16 +var partialTaskData = {}; 1.17 + 1.18 +var gNextProfileID = 0; 1.19 + 1.20 +var gLogLines = []; 1.21 + 1.22 +var gDebugLog = false; 1.23 +var gDebugTrace = false; 1.24 +// Use for verbose tracing, otherwise use log 1.25 +function PROFILDERTRACE(msg) { 1.26 + if (gDebugTrace) 1.27 + PROFILERLOG(msg); 1.28 +} 1.29 +function PROFILERLOG(msg) { 1.30 + if (gDebugLog) { 1.31 + msg = "Cleo: " + msg; 1.32 + //if (window.dump) 1.33 + // window.dump(msg + "\n"); 1.34 + } 1.35 +} 1.36 +function PROFILERERROR(msg) { 1.37 + msg = "Cleo: " + msg; 1.38 + //if (window.dump) 1.39 + // window.dump(msg + "\n"); 1.40 +} 1.41 + 1.42 +// http://stackoverflow.com/a/2548133 1.43 +function endsWith(str, suffix) { 1.44 + return str.indexOf(suffix, this.length - suffix.length) !== -1; 1.45 +}; 1.46 + 1.47 +// https://bugzilla.mozilla.org/show_bug.cgi?id=728780 1.48 +if (!String.prototype.startsWith) { 1.49 + String.prototype.startsWith = 1.50 + function(s) { return this.lastIndexOf(s, 0) === 0; } 1.51 +} 1.52 + 1.53 +// functions for which lr is unconditionally valid. These are 1.54 +// largely going to be atomics and other similar functions 1.55 +// that don't touch lr. This is currently populated with 1.56 +// some functions from bionic, largely via manual inspection 1.57 +// of the assembly in e.g. 1.58 +// http://androidxref.com/source/xref/bionic/libc/arch-arm/syscalls/ 1.59 +var sARMFunctionsWithValidLR = [ 1.60 + "__atomic_dec", 1.61 + "__atomic_inc", 1.62 + "__atomic_cmpxchg", 1.63 + "__atomic_swap", 1.64 + "__atomic_dec", 1.65 + "__atomic_inc", 1.66 + "__atomic_cmpxchg", 1.67 + "__atomic_swap", 1.68 + "__futex_syscall3", 1.69 + "__futex_wait", 1.70 + "__futex_wake", 1.71 + "__futex_syscall3", 1.72 + "__futex_wait", 1.73 + "__futex_wake", 1.74 + "__futex_syscall4", 1.75 + "__ioctl", 1.76 + "__brk", 1.77 + "__wait4", 1.78 + "epoll_wait", 1.79 + "fsync", 1.80 + "futex", 1.81 + "nanosleep", 1.82 + "pause", 1.83 + "sched_yield", 1.84 + "syscall" 1.85 +]; 1.86 + 1.87 +function log() { 1.88 + var z = []; 1.89 + for (var i = 0; i < arguments.length; ++i) 1.90 + z.push(arguments[i]); 1.91 + gLogLines.push(z.join(" ")); 1.92 +} 1.93 + 1.94 +self.onmessage = function (msg) { 1.95 + try { 1.96 + var requestID = msg.data.requestID; 1.97 + var task = msg.data.task; 1.98 + var taskData = msg.data.taskData; 1.99 + if (!taskData && 1.100 + (["chunkedStart", "chunkedChunk", "chunkedEnd"].indexOf(task) == -1)) { 1.101 + taskData = partialTaskData[requestID]; 1.102 + delete partialTaskData[requestID]; 1.103 + } 1.104 + PROFILERLOG("Start task: " + task); 1.105 + 1.106 + gLogLines = []; 1.107 + 1.108 + switch (task) { 1.109 + case "initWorker": 1.110 + gDebugLog = taskData.debugLog; 1.111 + gDebugTrace = taskData.debugTrace; 1.112 + PROFILERLOG("Init logging in parserWorker"); 1.113 + return; 1.114 + case "chunkedStart": 1.115 + partialTaskData[requestID] = null; 1.116 + break; 1.117 + case "chunkedChunk": 1.118 + if (partialTaskData[requestID] === null) 1.119 + partialTaskData[requestID] = msg.data.chunk; 1.120 + else 1.121 + partialTaskData[requestID] = partialTaskData[requestID].concat(msg.data.chunk); 1.122 + break; 1.123 + case "chunkedEnd": 1.124 + break; 1.125 + case "parseRawProfile": 1.126 + parseRawProfile(requestID, msg.data.params, taskData); 1.127 + break; 1.128 + case "updateFilters": 1.129 + updateFilters(requestID, taskData.profileID, taskData.filters); 1.130 + break; 1.131 + case "updateViewOptions": 1.132 + updateViewOptions(requestID, taskData.profileID, taskData.options); 1.133 + break; 1.134 + case "getSerializedProfile": 1.135 + getSerializedProfile(requestID, taskData.profileID, taskData.complete); 1.136 + break; 1.137 + case "calculateHistogramData": 1.138 + calculateHistogramData(requestID, taskData.profileID); 1.139 + break; 1.140 + case "calculateDiagnosticItems": 1.141 + calculateDiagnosticItems(requestID, taskData.profileID, taskData.meta); 1.142 + break; 1.143 + default: 1.144 + sendError(requestID, "Unknown task " + task); 1.145 + break; 1.146 + } 1.147 + PROFILERLOG("Complete task: " + task); 1.148 + } catch (e) { 1.149 + PROFILERERROR("Exception: " + e + " (" + e.fileName + ":" + e.lineNumber + ")"); 1.150 + sendError(requestID, "Exception: " + e + " (" + e.fileName + ":" + e.lineNumber + ")"); 1.151 + } 1.152 +} 1.153 + 1.154 +function sendError(requestID, error) { 1.155 + // support sendError(msg) 1.156 + if (error == null) { 1.157 + error = requestID; 1.158 + requestID = null; 1.159 + } 1.160 + 1.161 + self.postMessage({ 1.162 + requestID: requestID, 1.163 + type: "error", 1.164 + error: error, 1.165 + log: gLogLines 1.166 + }); 1.167 +} 1.168 + 1.169 +function sendProgress(requestID, progress) { 1.170 + self.postMessage({ 1.171 + requestID: requestID, 1.172 + type: "progress", 1.173 + progress: progress 1.174 + }); 1.175 +} 1.176 + 1.177 +function sendFinished(requestID, result) { 1.178 + self.postMessage({ 1.179 + requestID: requestID, 1.180 + type: "finished", 1.181 + result: result, 1.182 + log: gLogLines 1.183 + }); 1.184 +} 1.185 + 1.186 +function bucketsBySplittingArray(array, maxCostPerBucket, costOfElementCallback) { 1.187 + var buckets = []; 1.188 + var currentBucket = []; 1.189 + var currentBucketCost = 0; 1.190 + for (var i = 0; i < array.length; i++) { 1.191 + var element = array[i]; 1.192 + var costOfCurrentElement = costOfElementCallback ? costOfElementCallback(element) : 1; 1.193 + if (currentBucketCost + costOfCurrentElement > maxCostPerBucket) { 1.194 + buckets.push(currentBucket); 1.195 + currentBucket = []; 1.196 + currentBucketCost = 0; 1.197 + } 1.198 + currentBucket.push(element); 1.199 + currentBucketCost += costOfCurrentElement; 1.200 + } 1.201 + buckets.push(currentBucket); 1.202 + return buckets; 1.203 +} 1.204 + 1.205 +function sendFinishedInChunks(requestID, result, maxChunkCost, costOfElementCallback) { 1.206 + if (result.length === undefined || result.slice === undefined) 1.207 + throw new Error("Can't slice result into chunks"); 1.208 + self.postMessage({ 1.209 + requestID: requestID, 1.210 + type: "finishedStart" 1.211 + }); 1.212 + var chunks = bucketsBySplittingArray(result, maxChunkCost, costOfElementCallback); 1.213 + for (var i = 0; i < chunks.length; i++) { 1.214 + self.postMessage({ 1.215 + requestID: requestID, 1.216 + type: "finishedChunk", 1.217 + chunk: chunks[i] 1.218 + }); 1.219 + } 1.220 + self.postMessage({ 1.221 + requestID: requestID, 1.222 + type: "finishedEnd", 1.223 + log: gLogLines 1.224 + }); 1.225 +} 1.226 + 1.227 +function makeSample(frames, extraInfo) { 1.228 + return { 1.229 + frames: frames, 1.230 + extraInfo: extraInfo 1.231 + }; 1.232 +} 1.233 + 1.234 +function cloneSample(sample) { 1.235 + return makeSample(sample.frames.slice(0), sample.extraInfo); 1.236 +} 1.237 + 1.238 +function parseRawProfile(requestID, params, rawProfile) { 1.239 + var progressReporter = new ProgressReporter(); 1.240 + progressReporter.addListener(function (r) { 1.241 + sendProgress(requestID, r.getProgress()); 1.242 + }); 1.243 + progressReporter.begin("Parsing..."); 1.244 + 1.245 + var symbolicationTable = {}; 1.246 + var symbols = []; 1.247 + var symbolIndices = {}; 1.248 + var resources = {}; 1.249 + var functions = []; 1.250 + var functionIndices = {}; 1.251 + var samples = []; 1.252 + var meta = {}; 1.253 + var armIncludePCIndex = {}; 1.254 + 1.255 + if (rawProfile == null) { 1.256 + throw "rawProfile is null"; 1.257 + } 1.258 + 1.259 + if (typeof rawProfile == "string" && rawProfile[0] == "{") { 1.260 + // rawProfile is a JSON string. 1.261 + rawProfile = JSON.parse(rawProfile); 1.262 + if (rawProfile === null) { 1.263 + throw "rawProfile couldn't not successfully be parsed using JSON.parse. Make sure that the profile is a valid JSON encoding."; 1.264 + } 1.265 + } 1.266 + 1.267 + if (rawProfile.profileJSON && !rawProfile.profileJSON.meta && rawProfile.meta) { 1.268 + rawProfile.profileJSON.meta = rawProfile.meta; 1.269 + } 1.270 + 1.271 + if (typeof rawProfile == "object") { 1.272 + switch (rawProfile.format || null) { 1.273 + case "profileStringWithSymbolicationTable,1": 1.274 + symbolicationTable = rawProfile.symbolicationTable; 1.275 + parseProfileString(rawProfile.profileString); 1.276 + break; 1.277 + case "profileJSONWithSymbolicationTable,1": 1.278 + symbolicationTable = rawProfile.symbolicationTable; 1.279 + parseProfileJSON(rawProfile.profileJSON); 1.280 + break; 1.281 + default: 1.282 + parseProfileJSON(rawProfile); 1.283 + } 1.284 + } else { 1.285 + parseProfileString(rawProfile); 1.286 + } 1.287 + 1.288 + if (params.profileId) { 1.289 + meta.profileId = params.profileId; 1.290 + } 1.291 + 1.292 + function cleanFunctionName(functionName) { 1.293 + var ignoredPrefix = "non-virtual thunk to "; 1.294 + if (functionName.startsWith(ignoredPrefix)) 1.295 + return functionName.substr(ignoredPrefix.length); 1.296 + return functionName; 1.297 + } 1.298 + 1.299 + function resourceNameForAddon(addon) { 1.300 + if (!addon) 1.301 + return ""; 1.302 + 1.303 + var iconHTML = ""; 1.304 + if (addon.iconURL) 1.305 + iconHTML = "<img src=\"" + addon.iconURL + "\" style='width:12px; height:12px;'> " 1.306 + return iconHTML + " " + (/@jetpack$/.exec(addon.id) ? "Jetpack: " : "") + addon.name; 1.307 + } 1.308 + 1.309 + function addonWithID(addonID) { 1.310 + return firstMatch(meta.addons, function addonHasID(addon) { 1.311 + return addon.id.toLowerCase() == addonID.toLowerCase(); 1.312 + }) 1.313 + } 1.314 + 1.315 + function resourceNameForAddonWithID(addonID) { 1.316 + return resourceNameForAddon(addonWithID(addonID)); 1.317 + } 1.318 + 1.319 + function findAddonForChromeURIHost(host) { 1.320 + return firstMatch(meta.addons, function addonUsesChromeURIHost(addon) { 1.321 + return addon.chromeURIHosts && addon.chromeURIHosts.indexOf(host) != -1; 1.322 + }); 1.323 + } 1.324 + 1.325 + function ensureResource(name, resourceDescription) { 1.326 + if (!(name in resources)) { 1.327 + resources[name] = resourceDescription; 1.328 + } 1.329 + return name; 1.330 + } 1.331 + 1.332 + function resourceNameFromLibrary(library) { 1.333 + return ensureResource("lib_" + library, { 1.334 + type: "library", 1.335 + name: library 1.336 + }); 1.337 + } 1.338 + 1.339 + function getAddonForScriptURI(url, host) { 1.340 + if (!meta || !meta.addons) 1.341 + return null; 1.342 + 1.343 + if (url.startsWith("resource:") && endsWith(host, "-at-jetpack")) { 1.344 + // Assume this is a jetpack url 1.345 + var jetpackID = host.substring(0, host.length - 11) + "@jetpack"; 1.346 + return addonWithID(jetpackID); 1.347 + } 1.348 + 1.349 + if (url.startsWith("file:///") && url.indexOf("/extensions/") != -1) { 1.350 + var unpackedAddonNameMatch = /\/extensions\/(.*?)\//.exec(url); 1.351 + if (unpackedAddonNameMatch) 1.352 + return addonWithID(decodeURIComponent(unpackedAddonNameMatch[1])); 1.353 + return null; 1.354 + } 1.355 + 1.356 + if (url.startsWith("jar:file:///") && url.indexOf("/extensions/") != -1) { 1.357 + var packedAddonNameMatch = /\/extensions\/(.*?).xpi/.exec(url); 1.358 + if (packedAddonNameMatch) 1.359 + return addonWithID(decodeURIComponent(packedAddonNameMatch[1])); 1.360 + return null; 1.361 + } 1.362 + 1.363 + if (url.startsWith("chrome://")) { 1.364 + var chromeURIMatch = /chrome\:\/\/(.*?)\//.exec(url); 1.365 + if (chromeURIMatch) 1.366 + return findAddonForChromeURIHost(chromeURIMatch[1]); 1.367 + return null; 1.368 + } 1.369 + 1.370 + return null; 1.371 + } 1.372 + 1.373 + function resourceNameFromURI(url) { 1.374 + if (!url) 1.375 + return ensureResource("unknown", {type: "unknown", name: "<unknown>"}); 1.376 + 1.377 + var match = /^(.*):\/\/(.*?)\//.exec(url); 1.378 + 1.379 + if (!match) { 1.380 + // Can this happen? If so, we should change the regular expression above. 1.381 + return ensureResource("url_" + url, {type: "url", name: url}); 1.382 + } 1.383 + 1.384 + var urlRoot = match[0]; 1.385 + var protocol = match[1]; 1.386 + var host = match[2]; 1.387 + 1.388 + var addon = getAddonForScriptURI(url, host); 1.389 + if (addon) { 1.390 + return ensureResource("addon_" + addon.id, { 1.391 + type: "addon", 1.392 + name: addon.name, 1.393 + addonID: addon.id, 1.394 + icon: addon.iconURL 1.395 + }); 1.396 + } 1.397 + 1.398 + if (protocol.startsWith("http")) { 1.399 + return ensureResource("webhost_" + host, { 1.400 + type: "webhost", 1.401 + name: host, 1.402 + icon: urlRoot + "favicon.ico" 1.403 + }); 1.404 + } 1.405 + 1.406 + if (protocol.startsWith("file")) { 1.407 + return ensureResource("file_" + host, { 1.408 + type: "file", 1.409 + name: host 1.410 + }); 1.411 + } 1.412 + 1.413 + return ensureResource("otherhost_" + host, { 1.414 + type: "otherhost", 1.415 + name: host 1.416 + }); 1.417 + } 1.418 + 1.419 + function parseScriptFile(url) { 1.420 + var match = /([^\/]*)$/.exec(url); 1.421 + if (match && match[1]) 1.422 + return match[1]; 1.423 + 1.424 + return url; 1.425 + } 1.426 + 1.427 + // JS File information sometimes comes with multiple URIs which are chained 1.428 + // with " -> ". We only want the last URI in this list. 1.429 + function getRealScriptURI(url) { 1.430 + if (url) { 1.431 + var urls = url.split(" -> "); 1.432 + return urls[urls.length - 1]; 1.433 + } 1.434 + return url; 1.435 + } 1.436 + 1.437 + function getFunctionInfo(fullName) { 1.438 + 1.439 + function getCPPFunctionInfo(fullName) { 1.440 + var match = 1.441 + /^(.*) \(in ([^\)]*)\) (\+ [0-9]+)$/.exec(fullName) || 1.442 + /^(.*) \(in ([^\)]*)\) (\(.*:.*\))$/.exec(fullName) || 1.443 + /^(.*) \(in ([^\)]*)\)$/.exec(fullName); 1.444 + 1.445 + if (!match) 1.446 + return null; 1.447 + 1.448 + return { 1.449 + functionName: cleanFunctionName(match[1]), 1.450 + libraryName: resourceNameFromLibrary(match[2]), 1.451 + lineInformation: match[3] || "", 1.452 + isRoot: false, 1.453 + isJSFrame: false 1.454 + }; 1.455 + } 1.456 + 1.457 + function getJSFunctionInfo(fullName) { 1.458 + var jsMatch = 1.459 + /^(.*) \((.*):([0-9]+)\)$/.exec(fullName) || 1.460 + /^()(.*):([0-9]+)$/.exec(fullName); 1.461 + 1.462 + if (!jsMatch) 1.463 + return null; 1.464 + 1.465 + var functionName = jsMatch[1] || "<Anonymous>"; 1.466 + var scriptURI = getRealScriptURI(jsMatch[2]); 1.467 + var lineNumber = jsMatch[3]; 1.468 + var scriptFile = parseScriptFile(scriptURI); 1.469 + var resourceName = resourceNameFromURI(scriptURI); 1.470 + 1.471 + return { 1.472 + functionName: functionName + "() @ " + scriptFile + ":" + lineNumber, 1.473 + libraryName: resourceName, 1.474 + lineInformation: "", 1.475 + isRoot: false, 1.476 + isJSFrame: true, 1.477 + scriptLocation: { 1.478 + scriptURI: scriptURI, 1.479 + lineInformation: lineNumber 1.480 + } 1.481 + }; 1.482 + } 1.483 + 1.484 + function getFallbackFunctionInfo(fullName) { 1.485 + return { 1.486 + functionName: cleanFunctionName(fullName), 1.487 + libraryName: "", 1.488 + lineInformation: "", 1.489 + isRoot: fullName == "(root)", 1.490 + isJSFrame: false 1.491 + }; 1.492 + } 1.493 + 1.494 + return getCPPFunctionInfo(fullName) || 1.495 + getJSFunctionInfo(fullName) || 1.496 + getFallbackFunctionInfo(fullName); 1.497 + } 1.498 + 1.499 + function indexForFunction(symbol, info) { 1.500 + var resolve = info.functionName + "__" + info.libraryName; 1.501 + if (resolve in functionIndices) 1.502 + return functionIndices[resolve]; 1.503 + var newIndex = functions.length; 1.504 + info.symbol = symbol; 1.505 + functions[newIndex] = info; 1.506 + functionIndices[resolve] = newIndex; 1.507 + return newIndex; 1.508 + } 1.509 + 1.510 + function parseSymbol(symbol) { 1.511 + var info = getFunctionInfo(symbol); 1.512 + //dump("Parse symbol: " + symbol + "\n"); 1.513 + return { 1.514 + symbolName: symbol, 1.515 + functionName: info.functionName, 1.516 + functionIndex: indexForFunction(symbol, info), 1.517 + lineInformation: info.lineInformation, 1.518 + isRoot: info.isRoot, 1.519 + isJSFrame: info.isJSFrame, 1.520 + scriptLocation: info.scriptLocation 1.521 + }; 1.522 + } 1.523 + 1.524 + function translatedSymbol(symbol) { 1.525 + return symbolicationTable[symbol] || symbol; 1.526 + } 1.527 + 1.528 + function indexForSymbol(symbol) { 1.529 + if (symbol in symbolIndices) 1.530 + return symbolIndices[symbol]; 1.531 + var newIndex = symbols.length; 1.532 + symbols[newIndex] = parseSymbol(translatedSymbol(symbol)); 1.533 + symbolIndices[symbol] = newIndex; 1.534 + return newIndex; 1.535 + } 1.536 + 1.537 + function clearRegExpLastMatch() { 1.538 + /./.exec(" "); 1.539 + } 1.540 + 1.541 + function shouldIncludeARMLRForPC(pcIndex) { 1.542 + if (pcIndex in armIncludePCIndex) 1.543 + return armIncludePCIndex[pcIndex]; 1.544 + 1.545 + var pcName = symbols[pcIndex].functionName; 1.546 + var include = sARMFunctionsWithValidLR.indexOf(pcName) != -1; 1.547 + armIncludePCIndex[pcIndex] = include; 1.548 + return include; 1.549 + } 1.550 + 1.551 + function parseProfileString(data) { 1.552 + var extraInfo = {}; 1.553 + var lines = data.split("\n"); 1.554 + var sample = null; 1.555 + for (var i = 0; i < lines.length; ++i) { 1.556 + var line = lines[i]; 1.557 + if (line.length < 2 || line[1] != '-') { 1.558 + // invalid line, ignore it 1.559 + continue; 1.560 + } 1.561 + var info = line.substring(2); 1.562 + switch (line[0]) { 1.563 + //case 'l': 1.564 + // // leaf name 1.565 + // if ("leafName" in extraInfo) { 1.566 + // extraInfo.leafName += ":" + info; 1.567 + // } else { 1.568 + // extraInfo.leafName = info; 1.569 + // } 1.570 + // break; 1.571 + case 'm': 1.572 + // marker 1.573 + if (!("marker" in extraInfo)) { 1.574 + extraInfo.marker = []; 1.575 + } 1.576 + extraInfo.marker.push(info); 1.577 + break; 1.578 + case 's': 1.579 + // sample 1.580 + var sampleName = info; 1.581 + sample = makeSample([indexForSymbol(sampleName)], extraInfo); 1.582 + samples.push(sample); 1.583 + extraInfo = {}; // reset the extra info for future rounds 1.584 + break; 1.585 + case 'c': 1.586 + case 'l': 1.587 + // continue sample 1.588 + if (sample) { // ignore the case where we see a 'c' before an 's' 1.589 + sample.frames.push(indexForSymbol(info)); 1.590 + } 1.591 + break; 1.592 + case 'L': 1.593 + // continue sample; this is an ARM LR record. Stick it before the 1.594 + // PC if it's one of the functions where we know LR is good. 1.595 + if (sample && sample.frames.length > 1) { 1.596 + var pcIndex = sample.frames[sample.frames.length - 1]; 1.597 + if (shouldIncludeARMLRForPC(pcIndex)) { 1.598 + sample.frames.splice(-1, 0, indexForSymbol(info)); 1.599 + } 1.600 + } 1.601 + break; 1.602 + case 't': 1.603 + // time 1.604 + if (sample) { 1.605 + sample.extraInfo["time"] = parseFloat(info); 1.606 + } 1.607 + break; 1.608 + case 'r': 1.609 + // responsiveness 1.610 + if (sample) { 1.611 + sample.extraInfo["responsiveness"] = parseFloat(info); 1.612 + } 1.613 + break; 1.614 + } 1.615 + progressReporter.setProgress((i + 1) / lines.length); 1.616 + } 1.617 + } 1.618 + 1.619 + function parseProfileJSON(profile) { 1.620 + // Thread 0 will always be the main thread of interest 1.621 + // TODO support all the thread in the profile 1.622 + var profileSamples = null; 1.623 + meta = profile.meta || {}; 1.624 + if (params.appendVideoCapture) { 1.625 + meta.videoCapture = { 1.626 + src: params.appendVideoCapture, 1.627 + }; 1.628 + } 1.629 + // Support older format that aren't thread aware 1.630 + if (profile.threads != null) { 1.631 + profileSamples = profile.threads[0].samples; 1.632 + } else { 1.633 + profileSamples = profile; 1.634 + } 1.635 + var rootSymbol = null; 1.636 + var insertCommonRoot = false; 1.637 + var frameStart = {}; 1.638 + meta.frameStart = frameStart; 1.639 + for (var j = 0; j < profileSamples.length; j++) { 1.640 + var sample = profileSamples[j]; 1.641 + var indicedFrames = []; 1.642 + if (!sample) { 1.643 + // This sample was filtered before saving 1.644 + samples.push(null); 1.645 + progressReporter.setProgress((j + 1) / profileSamples.length); 1.646 + continue; 1.647 + } 1.648 + for (var k = 0; sample.frames && k < sample.frames.length; k++) { 1.649 + var frame = sample.frames[k]; 1.650 + var pcIndex; 1.651 + if (frame.location !== undefined) { 1.652 + pcIndex = indexForSymbol(frame.location); 1.653 + } else { 1.654 + pcIndex = indexForSymbol(frame); 1.655 + } 1.656 + 1.657 + if (frame.lr !== undefined && shouldIncludeARMLRForPC(pcIndex)) { 1.658 + indicedFrames.push(indexForSymbol(frame.lr)); 1.659 + } 1.660 + 1.661 + indicedFrames.push(pcIndex); 1.662 + } 1.663 + if (indicedFrames.length >= 1) { 1.664 + if (rootSymbol && rootSymbol != indicedFrames[0]) { 1.665 + insertCommonRoot = true; 1.666 + } 1.667 + rootSymbol = rootSymbol || indicedFrames[0]; 1.668 + } 1.669 + if (sample.extraInfo == null) { 1.670 + sample.extraInfo = {}; 1.671 + } 1.672 + if (sample.responsiveness) { 1.673 + sample.extraInfo["responsiveness"] = sample.responsiveness; 1.674 + } 1.675 + if (sample.marker) { 1.676 + sample.extraInfo["marker"] = sample.marker; 1.677 + } 1.678 + if (sample.time) { 1.679 + sample.extraInfo["time"] = sample.time; 1.680 + } 1.681 + if (sample.frameNumber) { 1.682 + sample.extraInfo["frameNumber"] = sample.frameNumber; 1.683 + //dump("Got frame number: " + sample.frameNumber + "\n"); 1.684 + frameStart[sample.frameNumber] = samples.length; 1.685 + } 1.686 + samples.push(makeSample(indicedFrames, sample.extraInfo)); 1.687 + progressReporter.setProgress((j + 1) / profileSamples.length); 1.688 + } 1.689 + if (insertCommonRoot) { 1.690 + var rootIndex = indexForSymbol("(root)"); 1.691 + for (var i = 0; i < samples.length; i++) { 1.692 + var sample = samples[i]; 1.693 + if (!sample) continue; 1.694 + // If length == 0 then the sample was filtered when saving the profile 1.695 + if (sample.frames.length >= 1 && sample.frames[0] != rootIndex) 1.696 + sample.frames.unshift(rootIndex) 1.697 + } 1.698 + } 1.699 + } 1.700 + 1.701 + progressReporter.finish(); 1.702 + // Don't increment the profile ID now because (1) it's buggy 1.703 + // and (2) for now there's no point in storing each profile 1.704 + // here if we're storing them in the local storage. 1.705 + //var profileID = gNextProfileID++; 1.706 + var profileID = gNextProfileID; 1.707 + gProfiles[profileID] = JSON.parse(JSON.stringify({ 1.708 + meta: meta, 1.709 + symbols: symbols, 1.710 + functions: functions, 1.711 + resources: resources, 1.712 + allSamples: samples 1.713 + })); 1.714 + clearRegExpLastMatch(); 1.715 + sendFinished(requestID, { 1.716 + meta: meta, 1.717 + numSamples: samples.length, 1.718 + profileID: profileID, 1.719 + symbols: symbols, 1.720 + functions: functions, 1.721 + resources: resources 1.722 + }); 1.723 +} 1.724 + 1.725 +function getSerializedProfile(requestID, profileID, complete) { 1.726 + var profile = gProfiles[profileID]; 1.727 + var symbolicationTable = {}; 1.728 + if (complete || !profile.filterSettings.mergeFunctions) { 1.729 + for (var symbolIndex in profile.symbols) { 1.730 + symbolicationTable[symbolIndex] = profile.symbols[symbolIndex].symbolName; 1.731 + } 1.732 + } else { 1.733 + for (var functionIndex in profile.functions) { 1.734 + var f = profile.functions[functionIndex]; 1.735 + symbolicationTable[functionIndex] = f.symbol; 1.736 + } 1.737 + } 1.738 + var serializedProfile = JSON.stringify({ 1.739 + format: "profileJSONWithSymbolicationTable,1", 1.740 + meta: profile.meta, 1.741 + profileJSON: complete ? profile.allSamples : profile.filteredSamples, 1.742 + symbolicationTable: symbolicationTable 1.743 + }); 1.744 + sendFinished(requestID, serializedProfile); 1.745 +} 1.746 + 1.747 +function TreeNode(name, parent, startCount) { 1.748 + this.name = name; 1.749 + this.children = []; 1.750 + this.counter = startCount; 1.751 + this.parent = parent; 1.752 +} 1.753 +TreeNode.prototype.getDepth = function TreeNode__getDepth() { 1.754 + if (this.parent) 1.755 + return this.parent.getDepth() + 1; 1.756 + return 0; 1.757 +}; 1.758 +TreeNode.prototype.findChild = function TreeNode_findChild(name) { 1.759 + for (var i = 0; i < this.children.length; i++) { 1.760 + var child = this.children[i]; 1.761 + if (child.name == name) 1.762 + return child; 1.763 + } 1.764 + return null; 1.765 +} 1.766 +// path is an array of strings which is matched to our nodes' names. 1.767 +// Try to walk path in our own tree and return the last matching node. The 1.768 +// length of the match can be calculated by the caller by comparing the 1.769 +// returned node's depth with the depth of the path's start node. 1.770 +TreeNode.prototype.followPath = function TreeNode_followPath(path) { 1.771 + if (path.length == 0) 1.772 + return this; 1.773 + 1.774 + var matchingChild = this.findChild(path[0]); 1.775 + if (!matchingChild) 1.776 + return this; 1.777 + 1.778 + return matchingChild.followPath(path.slice(1)); 1.779 +}; 1.780 +TreeNode.prototype.incrementCountersInParentChain = function TreeNode_incrementCountersInParentChain() { 1.781 + this.counter++; 1.782 + if (this.parent) 1.783 + this.parent.incrementCountersInParentChain(); 1.784 +}; 1.785 + 1.786 +function convertToCallTree(samples, isReverse) { 1.787 + function areSamplesMultiroot(samples) { 1.788 + var previousRoot; 1.789 + for (var i = 0; i < samples.length; ++i) { 1.790 + if (!previousRoot) { 1.791 + previousRoot = samples[i].frames[0]; 1.792 + continue; 1.793 + } 1.794 + if (previousRoot != samples[i].frames[0]) { 1.795 + return true; 1.796 + } 1.797 + } 1.798 + return false; 1.799 + } 1.800 + samples = samples.filter(function noNullSamples(sample) { 1.801 + return sample != null; 1.802 + }); 1.803 + if (samples.length == 0) 1.804 + return new TreeNode("(empty)", null, 0); 1.805 + var firstRoot = null; 1.806 + for (var i = 0; i < samples.length; ++i) { 1.807 + firstRoot = samples[i].frames[0]; 1.808 + break; 1.809 + } 1.810 + if (firstRoot == null) { 1.811 + return new TreeNode("(all filtered)", null, 0); 1.812 + } 1.813 + var multiRoot = areSamplesMultiroot(samples); 1.814 + var treeRoot = new TreeNode((isReverse || multiRoot) ? "(total)" : firstRoot, null, 0); 1.815 + for (var i = 0; i < samples.length; ++i) { 1.816 + var sample = samples[i]; 1.817 + var callstack = sample.frames.slice(0); 1.818 + callstack.shift(); 1.819 + if (isReverse) 1.820 + callstack.reverse(); 1.821 + var deepestExistingNode = treeRoot.followPath(callstack); 1.822 + var remainingCallstack = callstack.slice(deepestExistingNode.getDepth()); 1.823 + deepestExistingNode.incrementCountersInParentChain(); 1.824 + var node = deepestExistingNode; 1.825 + for (var j = 0; j < remainingCallstack.length; ++j) { 1.826 + var frame = remainingCallstack[j]; 1.827 + var child = new TreeNode(frame, node, 1); 1.828 + node.children.push(child); 1.829 + node = child; 1.830 + } 1.831 + } 1.832 + return treeRoot; 1.833 +} 1.834 + 1.835 +function filterByJank(samples, filterThreshold) { 1.836 + return samples.map(function nullNonJank(sample) { 1.837 + if (!sample || 1.838 + !("responsiveness" in sample.extraInfo) || 1.839 + sample.extraInfo["responsiveness"] < filterThreshold) 1.840 + return null; 1.841 + return sample; 1.842 + }); 1.843 +} 1.844 + 1.845 +function filterBySymbol(samples, symbolOrFunctionIndex) { 1.846 + return samples.map(function filterSample(origSample) { 1.847 + if (!origSample) 1.848 + return null; 1.849 + var sample = cloneSample(origSample); 1.850 + for (var i = 0; i < sample.frames.length; i++) { 1.851 + if (symbolOrFunctionIndex == sample.frames[i]) { 1.852 + sample.frames = sample.frames.slice(i); 1.853 + return sample; 1.854 + } 1.855 + } 1.856 + return null; // no frame matched; filter out complete sample 1.857 + }); 1.858 +} 1.859 + 1.860 +function filterByCallstackPrefix(samples, symbols, functions, callstack, appliesToJS, useFunctions) { 1.861 + var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) { 1.862 + return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot); 1.863 + } : function isJSSymbolOrRoot(symbolIndex) { 1.864 + return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot); 1.865 + }; 1.866 + return samples.map(function filterSample(sample) { 1.867 + if (!sample) 1.868 + return null; 1.869 + if (sample.frames.length < callstack.length) 1.870 + return null; 1.871 + for (var i = 0, j = 0; j < callstack.length; i++) { 1.872 + if (i >= sample.frames.length) 1.873 + return null; 1.874 + if (appliesToJS && !isJSFrameOrRoot(sample.frames[i])) 1.875 + continue; 1.876 + if (sample.frames[i] != callstack[j]) 1.877 + return null; 1.878 + j++; 1.879 + } 1.880 + return makeSample(sample.frames.slice(i - 1), sample.extraInfo); 1.881 + }); 1.882 +} 1.883 + 1.884 +function filterByCallstackPostfix(samples, symbols, functions, callstack, appliesToJS, useFunctions) { 1.885 + var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) { 1.886 + return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot); 1.887 + } : function isJSSymbolOrRoot(symbolIndex) { 1.888 + return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot); 1.889 + }; 1.890 + return samples.map(function filterSample(sample) { 1.891 + if (!sample) 1.892 + return null; 1.893 + if (sample.frames.length < callstack.length) 1.894 + return null; 1.895 + for (var i = 0, j = 0; j < callstack.length; i++) { 1.896 + if (i >= sample.frames.length) 1.897 + return null; 1.898 + if (appliesToJS && !isJSFrameOrRoot(sample.frames[sample.frames.length - i - 1])) 1.899 + continue; 1.900 + if (sample.frames[sample.frames.length - i - 1] != callstack[j]) 1.901 + return null; 1.902 + j++; 1.903 + } 1.904 + var newFrames = sample.frames.slice(0, sample.frames.length - i + 1); 1.905 + return makeSample(newFrames, sample.extraInfo); 1.906 + }); 1.907 +} 1.908 + 1.909 +function chargeNonJSToCallers(samples, symbols, functions, useFunctions) { 1.910 + var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) { 1.911 + return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot); 1.912 + } : function isJSSymbolOrRoot(symbolIndex) { 1.913 + return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot); 1.914 + }; 1.915 + samples = samples.slice(0); 1.916 + for (var i = 0; i < samples.length; ++i) { 1.917 + var sample = samples[i]; 1.918 + if (!sample) 1.919 + continue; 1.920 + var newFrames = sample.frames.filter(isJSFrameOrRoot); 1.921 + if (!newFrames.length) { 1.922 + samples[i] = null; 1.923 + } else { 1.924 + samples[i].frames = newFrames; 1.925 + } 1.926 + } 1.927 + return samples; 1.928 +} 1.929 + 1.930 +function filterByName(samples, symbols, functions, filterName, useFunctions) { 1.931 + function getSymbolOrFunctionName(index, useFunctions) { 1.932 + if (useFunctions) { 1.933 + if (!(index in functions)) 1.934 + return ""; 1.935 + return functions[index].functionName; 1.936 + } 1.937 + if (!(index in symbols)) 1.938 + return ""; 1.939 + return symbols[index].symbolName; 1.940 + } 1.941 + function getLibraryName(index, useFunctions) { 1.942 + if (useFunctions) { 1.943 + if (!(index in functions)) 1.944 + return ""; 1.945 + return functions[index].libraryName; 1.946 + } 1.947 + if (!(index in symbols)) 1.948 + return ""; 1.949 + return symbols[index].libraryName; 1.950 + } 1.951 + samples = samples.slice(0); 1.952 + filterName = filterName.toLowerCase(); 1.953 + calltrace_it: for (var i = 0; i < samples.length; ++i) { 1.954 + var sample = samples[i]; 1.955 + if (!sample) 1.956 + continue; 1.957 + var callstack = sample.frames; 1.958 + for (var j = 0; j < callstack.length; ++j) { 1.959 + var symbolOrFunctionName = getSymbolOrFunctionName(callstack[j], useFunctions); 1.960 + var libraryName = getLibraryName(callstack[j], useFunctions); 1.961 + if (symbolOrFunctionName.toLowerCase().indexOf(filterName) != -1 || 1.962 + libraryName.toLowerCase().indexOf(filterName) != -1) { 1.963 + continue calltrace_it; 1.964 + } 1.965 + } 1.966 + samples[i] = null; 1.967 + } 1.968 + return samples; 1.969 +} 1.970 + 1.971 +function discardLineLevelInformation(samples, symbols, functions) { 1.972 + var data = samples; 1.973 + var filteredData = []; 1.974 + for (var i = 0; i < data.length; i++) { 1.975 + if (!data[i]) { 1.976 + filteredData.push(null); 1.977 + continue; 1.978 + } 1.979 + filteredData.push(cloneSample(data[i])); 1.980 + var frames = filteredData[i].frames; 1.981 + for (var j = 0; j < frames.length; j++) { 1.982 + if (!(frames[j] in symbols)) 1.983 + continue; 1.984 + frames[j] = symbols[frames[j]].functionIndex; 1.985 + } 1.986 + } 1.987 + return filteredData; 1.988 +} 1.989 + 1.990 +function mergeUnbranchedCallPaths(root) { 1.991 + var mergedNames = [root.name]; 1.992 + var node = root; 1.993 + while (node.children.length == 1 && node.counter == node.children[0].counter) { 1.994 + node = node.children[0]; 1.995 + mergedNames.push(node.name); 1.996 + } 1.997 + if (node != root) { 1.998 + // Merge path from root to node into root. 1.999 + root.children = node.children; 1.1000 + root.mergedNames = mergedNames; 1.1001 + //root.name = clipText(root.name, 50) + " to " + this._clipText(node.name, 50); 1.1002 + } 1.1003 + for (var i = 0; i < root.children.length; i++) { 1.1004 + mergeUnbranchedCallPaths(root.children[i]); 1.1005 + } 1.1006 +} 1.1007 + 1.1008 +function FocusedFrameSampleFilter(focusedSymbol) { 1.1009 + this._focusedSymbol = focusedSymbol; 1.1010 +} 1.1011 +FocusedFrameSampleFilter.prototype = { 1.1012 + filter: function FocusedFrameSampleFilter_filter(samples, symbols, functions, useFunctions) { 1.1013 + return filterBySymbol(samples, this._focusedSymbol); 1.1014 + } 1.1015 +}; 1.1016 + 1.1017 +function FocusedCallstackPrefixSampleFilter(focusedCallstack, appliesToJS) { 1.1018 + this._focusedCallstackPrefix = focusedCallstack; 1.1019 + this._appliesToJS = appliesToJS; 1.1020 +} 1.1021 +FocusedCallstackPrefixSampleFilter.prototype = { 1.1022 + filter: function FocusedCallstackPrefixSampleFilter_filter(samples, symbols, functions, useFunctions) { 1.1023 + return filterByCallstackPrefix(samples, symbols, functions, this._focusedCallstackPrefix, this._appliesToJS, useFunctions); 1.1024 + } 1.1025 +}; 1.1026 + 1.1027 +function FocusedCallstackPostfixSampleFilter(focusedCallstack, appliesToJS) { 1.1028 + this._focusedCallstackPostfix = focusedCallstack; 1.1029 + this._appliesToJS = appliesToJS; 1.1030 +} 1.1031 +FocusedCallstackPostfixSampleFilter.prototype = { 1.1032 + filter: function FocusedCallstackPostfixSampleFilter_filter(samples, symbols, functions, useFunctions) { 1.1033 + return filterByCallstackPostfix(samples, symbols, functions, this._focusedCallstackPostfix, this._appliesToJS, useFunctions); 1.1034 + } 1.1035 +}; 1.1036 + 1.1037 +function RangeSampleFilter(start, end) { 1.1038 + this._start = start; 1.1039 + this._end = end; 1.1040 +} 1.1041 +RangeSampleFilter.prototype = { 1.1042 + filter: function RangeSampleFilter_filter(samples, symbols, functions) { 1.1043 + return samples.slice(this._start, this._end); 1.1044 + } 1.1045 +} 1.1046 + 1.1047 +function unserializeSampleFilters(filters) { 1.1048 + return filters.map(function (filter) { 1.1049 + switch (filter.type) { 1.1050 + case "FocusedFrameSampleFilter": 1.1051 + return new FocusedFrameSampleFilter(filter.focusedSymbol); 1.1052 + case "FocusedCallstackPrefixSampleFilter": 1.1053 + return new FocusedCallstackPrefixSampleFilter(filter.focusedCallstack, filter.appliesToJS); 1.1054 + case "FocusedCallstackPostfixSampleFilter": 1.1055 + return new FocusedCallstackPostfixSampleFilter(filter.focusedCallstack, filter.appliesToJS); 1.1056 + case "RangeSampleFilter": 1.1057 + return new RangeSampleFilter(filter.start, filter.end); 1.1058 + case "PluginView": 1.1059 + return null; 1.1060 + default: 1.1061 + throw new Error("Unknown filter"); 1.1062 + } 1.1063 + }) 1.1064 +} 1.1065 + 1.1066 +var gJankThreshold = 50 /* ms */; 1.1067 + 1.1068 +function updateFilters(requestID, profileID, filters) { 1.1069 + var profile = gProfiles[profileID]; 1.1070 + var samples = profile.allSamples; 1.1071 + var symbols = profile.symbols; 1.1072 + var functions = profile.functions; 1.1073 + 1.1074 + if (filters.mergeFunctions) { 1.1075 + samples = discardLineLevelInformation(samples, symbols, functions); 1.1076 + } 1.1077 + if (filters.nameFilter) { 1.1078 + try { 1.1079 + samples = filterByName(samples, symbols, functions, filters.nameFilter, filters.mergeFunctions); 1.1080 + } catch (e) { 1.1081 + dump("Could not filer by name: " + e + "\n"); 1.1082 + } 1.1083 + } 1.1084 + samples = unserializeSampleFilters(filters.sampleFilters).reduce(function (filteredSamples, currentFilter) { 1.1085 + if (currentFilter===null) return filteredSamples; 1.1086 + return currentFilter.filter(filteredSamples, symbols, functions, filters.mergeFunctions); 1.1087 + }, samples); 1.1088 + if (filters.jankOnly) { 1.1089 + samples = filterByJank(samples, gJankThreshold); 1.1090 + } 1.1091 + if (filters.javascriptOnly) { 1.1092 + samples = chargeNonJSToCallers(samples, symbols, functions, filters.mergeFunctions); 1.1093 + } 1.1094 + 1.1095 + gProfiles[profileID].filterSettings = filters; 1.1096 + gProfiles[profileID].filteredSamples = samples; 1.1097 + sendFinishedInChunks(requestID, samples, 40000, 1.1098 + function (sample) { return sample ? sample.frames.length : 1; }); 1.1099 +} 1.1100 + 1.1101 +function updateViewOptions(requestID, profileID, options) { 1.1102 + var profile = gProfiles[profileID]; 1.1103 + var samples = profile.filteredSamples; 1.1104 + var symbols = profile.symbols; 1.1105 + var functions = profile.functions; 1.1106 + 1.1107 + var treeData = convertToCallTree(samples, options.invertCallstack); 1.1108 + if (options.mergeUnbranched) 1.1109 + mergeUnbranchedCallPaths(treeData); 1.1110 + sendFinished(requestID, treeData); 1.1111 +} 1.1112 + 1.1113 +// The responsiveness threshold (in ms) after which the sample shuold become 1.1114 +// completely red in the histogram. 1.1115 +var kDelayUntilWorstResponsiveness = 1000; 1.1116 + 1.1117 +function calculateHistogramData(requestID, profileID) { 1.1118 + 1.1119 + function getStepColor(step) { 1.1120 + if (step.extraInfo && "responsiveness" in step.extraInfo) { 1.1121 + var res = step.extraInfo.responsiveness; 1.1122 + var redComponent = Math.round(255 * Math.min(1, res / kDelayUntilWorstResponsiveness)); 1.1123 + return "rgb(" + redComponent + ",0,0)"; 1.1124 + } 1.1125 + 1.1126 + return "rgb(0,0,0)"; 1.1127 + } 1.1128 + 1.1129 + var profile = gProfiles[profileID]; 1.1130 + var data = profile.filteredSamples; 1.1131 + var histogramData = []; 1.1132 + var maxHeight = 0; 1.1133 + for (var i = 0; i < data.length; ++i) { 1.1134 + if (!data[i]) 1.1135 + continue; 1.1136 + var value = data[i].frames.length; 1.1137 + if (maxHeight < value) 1.1138 + maxHeight = value; 1.1139 + } 1.1140 + maxHeight += 1; 1.1141 + var nextX = 0; 1.1142 + // The number of data items per histogramData rects. 1.1143 + // Except when seperated by a marker. 1.1144 + // This is used to cut down the number of rects, since 1.1145 + // there's no point in having more rects then pixels 1.1146 + var samplesPerStep = Math.max(1, Math.floor(data.length / 2000)); 1.1147 + var frameStart = {}; 1.1148 + for (var i = 0; i < data.length; i++) { 1.1149 + var step = data[i]; 1.1150 + if (!step) { 1.1151 + // Add a gap for the sample that was filtered out. 1.1152 + nextX += 1 / samplesPerStep; 1.1153 + continue; 1.1154 + } 1.1155 + nextX = Math.ceil(nextX); 1.1156 + var value = step.frames.length / maxHeight; 1.1157 + var frames = step.frames; 1.1158 + var currHistogramData = histogramData[histogramData.length-1]; 1.1159 + if (step.extraInfo && "marker" in step.extraInfo) { 1.1160 + // A new marker boundary has been discovered. 1.1161 + histogramData.push({ 1.1162 + frames: "marker", 1.1163 + x: nextX, 1.1164 + width: 2, 1.1165 + value: 1, 1.1166 + marker: step.extraInfo.marker, 1.1167 + color: "fuchsia" 1.1168 + }); 1.1169 + nextX += 2; 1.1170 + histogramData.push({ 1.1171 + frames: [step.frames], 1.1172 + x: nextX, 1.1173 + width: 1, 1.1174 + value: value, 1.1175 + color: getStepColor(step), 1.1176 + }); 1.1177 + nextX += 1; 1.1178 + } else if (currHistogramData != null && 1.1179 + currHistogramData.frames.length < samplesPerStep && 1.1180 + !(step.extraInfo && "frameNumber" in step.extraInfo)) { 1.1181 + currHistogramData.frames.push(step.frames); 1.1182 + // When merging data items take the average: 1.1183 + currHistogramData.value = 1.1184 + (currHistogramData.value * (currHistogramData.frames.length - 1) + value) / 1.1185 + currHistogramData.frames.length; 1.1186 + // Merge the colors? For now we keep the first color set. 1.1187 + } else { 1.1188 + // A new name boundary has been discovered. 1.1189 + currHistogramData = { 1.1190 + frames: [step.frames], 1.1191 + x: nextX, 1.1192 + width: 1, 1.1193 + value: value, 1.1194 + color: getStepColor(step), 1.1195 + }; 1.1196 + if (step.extraInfo && "frameNumber" in step.extraInfo) { 1.1197 + currHistogramData.frameNumber = step.extraInfo.frameNumber; 1.1198 + frameStart[step.extraInfo.frameNumber] = histogramData.length; 1.1199 + } 1.1200 + histogramData.push(currHistogramData); 1.1201 + nextX += 1; 1.1202 + } 1.1203 + } 1.1204 + sendFinished(requestID, { histogramData: histogramData, frameStart: frameStart, widthSum: Math.ceil(nextX) }); 1.1205 +} 1.1206 + 1.1207 +var diagnosticList = [ 1.1208 + // *************** Known bugs first (highest priority) 1.1209 + { 1.1210 + image: "io.png", 1.1211 + title: "Main Thread IO - Bug 765135 - TISCreateInputSourceList", 1.1212 + check: function(frames, symbols, meta) { 1.1213 + 1.1214 + if (!stepContains('TISCreateInputSourceList', frames, symbols)) 1.1215 + return false; 1.1216 + 1.1217 + return stepContains('__getdirentries64', frames, symbols) 1.1218 + || stepContains('__read', frames, symbols) 1.1219 + || stepContains('__open', frames, symbols) 1.1220 + || stepContains('stat$INODE64', frames, symbols) 1.1221 + ; 1.1222 + }, 1.1223 + }, 1.1224 + 1.1225 + { 1.1226 + image: "js.png", 1.1227 + title: "Bug 772916 - Gradients are slow on mobile", 1.1228 + bugNumber: "772916", 1.1229 + check: function(frames, symbols, meta) { 1.1230 + 1.1231 + return stepContains('PaintGradient', frames, symbols) 1.1232 + && stepContains('ClientTiledLayerBuffer::PaintThebesSingleBufferDraw', frames, symbols) 1.1233 + ; 1.1234 + }, 1.1235 + }, 1.1236 + { 1.1237 + image: "cache.png", 1.1238 + title: "Bug 717761 - Main thread can be blocked by IO on the cache thread", 1.1239 + bugNumber: "717761", 1.1240 + check: function(frames, symbols, meta) { 1.1241 + 1.1242 + return stepContains('nsCacheEntryDescriptor::GetStoragePolicy', frames, symbols) 1.1243 + ; 1.1244 + }, 1.1245 + }, 1.1246 + { 1.1247 + image: "js.png", 1.1248 + title: "Web Content Shutdown Notification", 1.1249 + check: function(frames, symbols, meta) { 1.1250 + 1.1251 + return stepContains('nsAppStartup::Quit', frames, symbols) 1.1252 + && stepContains('nsDocShell::FirePageHideNotification', frames, symbols) 1.1253 + ; 1.1254 + }, 1.1255 + }, 1.1256 + { 1.1257 + image: "js.png", 1.1258 + title: "Bug 789193 - AMI_startup() takes 200ms on startup", 1.1259 + bugNumber: "789193", 1.1260 + check: function(frames, symbols, meta) { 1.1261 + 1.1262 + return stepContains('AMI_startup()', frames, symbols) 1.1263 + ; 1.1264 + }, 1.1265 + }, 1.1266 + { 1.1267 + image: "js.png", 1.1268 + title: "Bug 818296 - [Shutdown] js::NukeCrossCompartmentWrappers takes up 300ms on shutdown", 1.1269 + bugNumber: "818296", 1.1270 + check: function(frames, symbols, meta) { 1.1271 + return stepContains('js::NukeCrossCompartmentWrappers', frames, symbols) 1.1272 + && (stepContains('WindowDestroyedEvent', frames, symbols) || stepContains('DoShutdown', frames, symbols)) 1.1273 + ; 1.1274 + }, 1.1275 + }, 1.1276 + { 1.1277 + image: "js.png", 1.1278 + title: "Bug 818274 - [Shutdown] Telemetry takes ~10ms on shutdown", 1.1279 + bugNumber: "818274", 1.1280 + check: function(frames, symbols, meta) { 1.1281 + return stepContains('TelemetryPing.js', frames, symbols) 1.1282 + ; 1.1283 + }, 1.1284 + }, 1.1285 + { 1.1286 + image: "plugin.png", 1.1287 + title: "Bug 818265 - [Shutdown] Plug-in shutdown takes ~90ms on shutdown", 1.1288 + bugNumber: "818265", 1.1289 + check: function(frames, symbols, meta) { 1.1290 + return stepContains('PluginInstanceParent::Destroy', frames, symbols) 1.1291 + ; 1.1292 + }, 1.1293 + }, 1.1294 + { 1.1295 + image: "snapshot.png", 1.1296 + title: "Bug 720575 - Make thumbnailing faster and/or asynchronous", 1.1297 + bugNumber: "720575", 1.1298 + check: function(frames, symbols, meta) { 1.1299 + return stepContains('Thumbnails_capture()', frames, symbols) 1.1300 + ; 1.1301 + }, 1.1302 + }, 1.1303 + 1.1304 + { 1.1305 + image: "js.png", 1.1306 + title: "Bug 789185 - LoginManagerStorage_mozStorage.init() takes 300ms on startup ", 1.1307 + bugNumber: "789185", 1.1308 + check: function(frames, symbols, meta) { 1.1309 + 1.1310 + return stepContains('LoginManagerStorage_mozStorage.prototype.init()', frames, symbols) 1.1311 + ; 1.1312 + }, 1.1313 + }, 1.1314 + 1.1315 + { 1.1316 + image: "js.png", 1.1317 + title: "JS - Bug 767070 - Text selection performance is bad on android", 1.1318 + bugNumber: "767070", 1.1319 + check: function(frames, symbols, meta) { 1.1320 + 1.1321 + if (!stepContains('FlushPendingNotifications', frames, symbols)) 1.1322 + return false; 1.1323 + 1.1324 + return stepContains('sh_', frames, symbols) 1.1325 + && stepContains('browser.js', frames, symbols) 1.1326 + ; 1.1327 + }, 1.1328 + }, 1.1329 + 1.1330 + { 1.1331 + image: "js.png", 1.1332 + title: "JS - Bug 765930 - Reader Mode: Optimize readability check", 1.1333 + bugNumber: "765930", 1.1334 + check: function(frames, symbols, meta) { 1.1335 + 1.1336 + return stepContains('Readability.js', frames, symbols) 1.1337 + ; 1.1338 + }, 1.1339 + }, 1.1340 + 1.1341 + // **************** General issues 1.1342 + { 1.1343 + image: "js.png", 1.1344 + title: "JS is triggering a sync reflow", 1.1345 + check: function(frames, symbols, meta) { 1.1346 + return symbolSequence(['js::RunScript','layout::DoReflow'], frames, symbols) || 1.1347 + symbolSequence(['js::RunScript','layout::Flush'], frames, symbols) 1.1348 + ; 1.1349 + }, 1.1350 + }, 1.1351 + 1.1352 + { 1.1353 + image: "gc.png", 1.1354 + title: "Garbage Collection Slice", 1.1355 + canMergeWithGC: false, 1.1356 + check: function(frames, symbols, meta, step) { 1.1357 + var slice = findGCSlice(frames, symbols, meta, step); 1.1358 + 1.1359 + if (slice) { 1.1360 + var gcEvent = findGCEvent(frames, symbols, meta, step); 1.1361 + //dump("found event matching diagnostic\n"); 1.1362 + //dump(JSON.stringify(gcEvent) + "\n"); 1.1363 + return true; 1.1364 + } 1.1365 + return false; 1.1366 + }, 1.1367 + details: function(frames, symbols, meta, step) { 1.1368 + var slice = findGCSlice(frames, symbols, meta, step); 1.1369 + if (slice) { 1.1370 + return "" + 1.1371 + "Reason: " + slice.reason + "\n" + 1.1372 + "Slice: " + slice.slice + "\n" + 1.1373 + "Pause: " + slice.pause + " ms"; 1.1374 + } 1.1375 + return null; 1.1376 + }, 1.1377 + onclickDetails: function(frames, symbols, meta, step) { 1.1378 + var gcEvent = findGCEvent(frames, symbols, meta, step); 1.1379 + if (gcEvent) { 1.1380 + return JSON.stringify(gcEvent); 1.1381 + } else { 1.1382 + return null; 1.1383 + } 1.1384 + }, 1.1385 + }, 1.1386 + { 1.1387 + image: "cc.png", 1.1388 + title: "Cycle Collect", 1.1389 + check: function(frames, symbols, meta, step) { 1.1390 + var ccEvent = findCCEvent(frames, symbols, meta, step); 1.1391 + 1.1392 + if (ccEvent) { 1.1393 + return true; 1.1394 + } 1.1395 + return false; 1.1396 + }, 1.1397 + details: function(frames, symbols, meta, step) { 1.1398 + var ccEvent = findCCEvent(frames, symbols, meta, step); 1.1399 + if (ccEvent) { 1.1400 + return "" + 1.1401 + "Duration: " + ccEvent.duration + " ms\n" + 1.1402 + "Suspected: " + ccEvent.suspected; 1.1403 + } 1.1404 + return null; 1.1405 + }, 1.1406 + onclickDetails: function(frames, symbols, meta, step) { 1.1407 + var ccEvent = findCCEvent(frames, symbols, meta, step); 1.1408 + if (ccEvent) { 1.1409 + return JSON.stringify(ccEvent); 1.1410 + } else { 1.1411 + return null; 1.1412 + } 1.1413 + }, 1.1414 + }, 1.1415 + { 1.1416 + image: "gc.png", 1.1417 + title: "Garbage Collection", 1.1418 + canMergeWithGC: false, 1.1419 + check: function(frames, symbols, meta) { 1.1420 + return stepContainsRegEx(/.*Collect.*Runtime.*Invocation.*/, frames, symbols) 1.1421 + || stepContains('GarbageCollectNow', frames, symbols) // Label 1.1422 + || stepContains('JS_GC(', frames, symbols) // Label 1.1423 + || stepContains('CycleCollect__', frames, symbols) // Label 1.1424 + ; 1.1425 + }, 1.1426 + }, 1.1427 + { 1.1428 + image: "cc.png", 1.1429 + title: "Cycle Collect", 1.1430 + check: function(frames, symbols, meta) { 1.1431 + return stepContains('nsCycleCollector::Collect', frames, symbols) 1.1432 + || stepContains('CycleCollect__', frames, symbols) // Label 1.1433 + || stepContains('nsCycleCollectorRunner::Collect', frames, symbols) // Label 1.1434 + ; 1.1435 + }, 1.1436 + }, 1.1437 + { 1.1438 + image: "plugin.png", 1.1439 + title: "Sync Plugin Constructor", 1.1440 + check: function(frames, symbols, meta) { 1.1441 + return stepContains('CallPPluginInstanceConstructor', frames, symbols) 1.1442 + || stepContains('CallPCrashReporterConstructor', frames, symbols) 1.1443 + || stepContains('PPluginModuleParent::CallNP_Initialize', frames, symbols) 1.1444 + || stepContains('GeckoChildProcessHost::SyncLaunch', frames, symbols) 1.1445 + ; 1.1446 + }, 1.1447 + }, 1.1448 + { 1.1449 + image: "text.png", 1.1450 + title: "Font Loading", 1.1451 + check: function(frames, symbols, meta) { 1.1452 + return stepContains('gfxFontGroup::BuildFontList', frames, symbols); 1.1453 + }, 1.1454 + }, 1.1455 + { 1.1456 + image: "io.png", 1.1457 + title: "Main Thread IO!", 1.1458 + check: function(frames, symbols, meta) { 1.1459 + return stepContains('__getdirentries64', frames, symbols) 1.1460 + || stepContains('__open', frames, symbols) 1.1461 + || stepContains('NtFlushBuffersFile', frames, symbols) 1.1462 + || stepContains('storage:::Statement::ExecuteStep', frames, symbols) 1.1463 + || stepContains('__unlink', frames, symbols) 1.1464 + || stepContains('fsync', frames, symbols) 1.1465 + || stepContains('stat$INODE64', frames, symbols) 1.1466 + ; 1.1467 + }, 1.1468 + }, 1.1469 +]; 1.1470 + 1.1471 +function hasJSFrame(frames, symbols) { 1.1472 + for (var i = 0; i < frames.length; i++) { 1.1473 + if (symbols[frames[i]].isJSFrame === true) { 1.1474 + return true; 1.1475 + } 1.1476 + } 1.1477 + return false; 1.1478 +} 1.1479 +function findCCEvent(frames, symbols, meta, step) { 1.1480 + if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats) 1.1481 + return null; 1.1482 + 1.1483 + var time = step.extraInfo.time; 1.1484 + 1.1485 + for (var i = 0; i < meta.gcStats.ccEvents.length; i++) { 1.1486 + var ccEvent = meta.gcStats.ccEvents[i]; 1.1487 + if (ccEvent.start_timestamp <= time && ccEvent.end_timestamp >= time) { 1.1488 + //dump("JSON: " + js_beautify(JSON.stringify(ccEvent)) + "\n"); 1.1489 + return ccEvent; 1.1490 + } 1.1491 + } 1.1492 + 1.1493 + return null; 1.1494 +} 1.1495 +function findGCEvent(frames, symbols, meta, step) { 1.1496 + if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats) 1.1497 + return null; 1.1498 + 1.1499 + var time = step.extraInfo.time; 1.1500 + 1.1501 + for (var i = 0; i < meta.gcStats.gcEvents.length; i++) { 1.1502 + var gcEvent = meta.gcStats.gcEvents[i]; 1.1503 + if (!gcEvent.slices) 1.1504 + continue; 1.1505 + for (var j = 0; j < gcEvent.slices.length; j++) { 1.1506 + var slice = gcEvent.slices[j]; 1.1507 + if (slice.start_timestamp <= time && slice.end_timestamp >= time) { 1.1508 + return gcEvent; 1.1509 + } 1.1510 + } 1.1511 + } 1.1512 + 1.1513 + return null; 1.1514 +} 1.1515 +function findGCSlice(frames, symbols, meta, step) { 1.1516 + if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats) 1.1517 + return null; 1.1518 + 1.1519 + var time = step.extraInfo.time; 1.1520 + 1.1521 + for (var i = 0; i < meta.gcStats.gcEvents.length; i++) { 1.1522 + var gcEvent = meta.gcStats.gcEvents[i]; 1.1523 + if (!gcEvent.slices) 1.1524 + continue; 1.1525 + for (var j = 0; j < gcEvent.slices.length; j++) { 1.1526 + var slice = gcEvent.slices[j]; 1.1527 + if (slice.start_timestamp <= time && slice.end_timestamp >= time) { 1.1528 + return slice; 1.1529 + } 1.1530 + } 1.1531 + } 1.1532 + 1.1533 + return null; 1.1534 +} 1.1535 +function stepContains(substring, frames, symbols) { 1.1536 + for (var i = 0; frames && i < frames.length; i++) { 1.1537 + if (!(frames[i] in symbols)) 1.1538 + continue; 1.1539 + var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; 1.1540 + if (frameSym.indexOf(substring) != -1) { 1.1541 + return true; 1.1542 + } 1.1543 + } 1.1544 + return false; 1.1545 +} 1.1546 +function stepContainsRegEx(regex, frames, symbols) { 1.1547 + for (var i = 0; frames && i < frames.length; i++) { 1.1548 + if (!(frames[i] in symbols)) 1.1549 + continue; 1.1550 + var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; 1.1551 + if (regex.exec(frameSym)) { 1.1552 + return true; 1.1553 + } 1.1554 + } 1.1555 + return false; 1.1556 +} 1.1557 +function symbolSequence(symbolsOrder, frames, symbols) { 1.1558 + var symbolIndex = 0; 1.1559 + for (var i = 0; frames && i < frames.length; i++) { 1.1560 + if (!(frames[i] in symbols)) 1.1561 + continue; 1.1562 + var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; 1.1563 + var substring = symbolsOrder[symbolIndex]; 1.1564 + if (frameSym.indexOf(substring) != -1) { 1.1565 + symbolIndex++; 1.1566 + if (symbolIndex == symbolsOrder.length) { 1.1567 + return true; 1.1568 + } 1.1569 + } 1.1570 + } 1.1571 + return false; 1.1572 +} 1.1573 +function firstMatch(array, matchFunction) { 1.1574 + for (var i = 0; i < array.length; i++) { 1.1575 + if (matchFunction(array[i])) 1.1576 + return array[i]; 1.1577 + } 1.1578 + return undefined; 1.1579 +} 1.1580 + 1.1581 +function calculateDiagnosticItems(requestID, profileID, meta) { 1.1582 + /* 1.1583 + if (!histogramData || histogramData.length < 1) { 1.1584 + sendFinished(requestID, []); 1.1585 + return; 1.1586 + }*/ 1.1587 + 1.1588 + var profile = gProfiles[profileID]; 1.1589 + //var symbols = profile.symbols; 1.1590 + var symbols = profile.functions; 1.1591 + var data = profile.filteredSamples; 1.1592 + 1.1593 + var lastStep = data[data.length-1]; 1.1594 + var widthSum = data.length; 1.1595 + var pendingDiagnosticInfo = null; 1.1596 + 1.1597 + var diagnosticItems = []; 1.1598 + 1.1599 + function finishPendingDiagnostic(endX) { 1.1600 + if (!pendingDiagnosticInfo) 1.1601 + return; 1.1602 + 1.1603 + var diagnostic = pendingDiagnosticInfo.diagnostic; 1.1604 + var currDiagnostic = { 1.1605 + x: pendingDiagnosticInfo.x / widthSum, 1.1606 + width: (endX - pendingDiagnosticInfo.x) / widthSum, 1.1607 + imageFile: pendingDiagnosticInfo.diagnostic.image, 1.1608 + title: pendingDiagnosticInfo.diagnostic.title, 1.1609 + details: pendingDiagnosticInfo.details, 1.1610 + onclickDetails: pendingDiagnosticInfo.onclickDetails 1.1611 + }; 1.1612 + 1.1613 + if (!currDiagnostic.onclickDetails && diagnostic.bugNumber) { 1.1614 + currDiagnostic.onclickDetails = "bug " + diagnostic.bugNumber; 1.1615 + } 1.1616 + 1.1617 + diagnosticItems.push(currDiagnostic); 1.1618 + 1.1619 + pendingDiagnosticInfo = null; 1.1620 + } 1.1621 + 1.1622 +/* 1.1623 + dump("meta: " + meta.gcStats + "\n"); 1.1624 + if (meta && meta.gcStats) { 1.1625 + dump("GC Stats: " + JSON.stringify(meta.gcStats) + "\n"); 1.1626 + } 1.1627 +*/ 1.1628 + 1.1629 + data.forEach(function diagnoseStep(step, x) { 1.1630 + if (step) { 1.1631 + var frames = step.frames; 1.1632 + 1.1633 + var diagnostic = firstMatch(diagnosticList, function (diagnostic) { 1.1634 + return diagnostic.check(frames, symbols, meta, step); 1.1635 + }); 1.1636 + } 1.1637 + 1.1638 + if (!diagnostic) { 1.1639 + finishPendingDiagnostic(x); 1.1640 + return; 1.1641 + } 1.1642 + 1.1643 + var details = diagnostic.details ? diagnostic.details(frames, symbols, meta, step) : null; 1.1644 + 1.1645 + if (pendingDiagnosticInfo) { 1.1646 + // We're already inside a diagnostic range. 1.1647 + if (diagnostic == pendingDiagnosticInfo.diagnostic && pendingDiagnosticInfo.details == details) { 1.1648 + // We're still inside the same diagnostic. 1.1649 + return; 1.1650 + } 1.1651 + 1.1652 + // We have left the old diagnostic and found a new one. Finish the old one. 1.1653 + finishPendingDiagnostic(x); 1.1654 + } 1.1655 + 1.1656 + pendingDiagnosticInfo = { 1.1657 + diagnostic: diagnostic, 1.1658 + x: x, 1.1659 + details: details, 1.1660 + onclickDetails: diagnostic.onclickDetails ? diagnostic.onclickDetails(frames, symbols, meta, step) : null 1.1661 + }; 1.1662 + }); 1.1663 + if (pendingDiagnosticInfo) 1.1664 + finishPendingDiagnostic(data.length); 1.1665 + 1.1666 + sendFinished(requestID, diagnosticItems); 1.1667 +}