browser/devtools/profiler/cleopatra/js/parserWorker.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rwxr-xr-x

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: js2; indent-tabs-mode: nil; js2-basic-offset: 2; -*- */
michael@0 2
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 "use strict";
michael@0 8
michael@0 9 importScripts("ProgressReporter.js");
michael@0 10
michael@0 11 var gProfiles = [];
michael@0 12
michael@0 13 var partialTaskData = {};
michael@0 14
michael@0 15 var gNextProfileID = 0;
michael@0 16
michael@0 17 var gLogLines = [];
michael@0 18
michael@0 19 var gDebugLog = false;
michael@0 20 var gDebugTrace = false;
michael@0 21 // Use for verbose tracing, otherwise use log
michael@0 22 function PROFILDERTRACE(msg) {
michael@0 23 if (gDebugTrace)
michael@0 24 PROFILERLOG(msg);
michael@0 25 }
michael@0 26 function PROFILERLOG(msg) {
michael@0 27 if (gDebugLog) {
michael@0 28 msg = "Cleo: " + msg;
michael@0 29 //if (window.dump)
michael@0 30 // window.dump(msg + "\n");
michael@0 31 }
michael@0 32 }
michael@0 33 function PROFILERERROR(msg) {
michael@0 34 msg = "Cleo: " + msg;
michael@0 35 //if (window.dump)
michael@0 36 // window.dump(msg + "\n");
michael@0 37 }
michael@0 38
michael@0 39 // http://stackoverflow.com/a/2548133
michael@0 40 function endsWith(str, suffix) {
michael@0 41 return str.indexOf(suffix, this.length - suffix.length) !== -1;
michael@0 42 };
michael@0 43
michael@0 44 // https://bugzilla.mozilla.org/show_bug.cgi?id=728780
michael@0 45 if (!String.prototype.startsWith) {
michael@0 46 String.prototype.startsWith =
michael@0 47 function(s) { return this.lastIndexOf(s, 0) === 0; }
michael@0 48 }
michael@0 49
michael@0 50 // functions for which lr is unconditionally valid. These are
michael@0 51 // largely going to be atomics and other similar functions
michael@0 52 // that don't touch lr. This is currently populated with
michael@0 53 // some functions from bionic, largely via manual inspection
michael@0 54 // of the assembly in e.g.
michael@0 55 // http://androidxref.com/source/xref/bionic/libc/arch-arm/syscalls/
michael@0 56 var sARMFunctionsWithValidLR = [
michael@0 57 "__atomic_dec",
michael@0 58 "__atomic_inc",
michael@0 59 "__atomic_cmpxchg",
michael@0 60 "__atomic_swap",
michael@0 61 "__atomic_dec",
michael@0 62 "__atomic_inc",
michael@0 63 "__atomic_cmpxchg",
michael@0 64 "__atomic_swap",
michael@0 65 "__futex_syscall3",
michael@0 66 "__futex_wait",
michael@0 67 "__futex_wake",
michael@0 68 "__futex_syscall3",
michael@0 69 "__futex_wait",
michael@0 70 "__futex_wake",
michael@0 71 "__futex_syscall4",
michael@0 72 "__ioctl",
michael@0 73 "__brk",
michael@0 74 "__wait4",
michael@0 75 "epoll_wait",
michael@0 76 "fsync",
michael@0 77 "futex",
michael@0 78 "nanosleep",
michael@0 79 "pause",
michael@0 80 "sched_yield",
michael@0 81 "syscall"
michael@0 82 ];
michael@0 83
michael@0 84 function log() {
michael@0 85 var z = [];
michael@0 86 for (var i = 0; i < arguments.length; ++i)
michael@0 87 z.push(arguments[i]);
michael@0 88 gLogLines.push(z.join(" "));
michael@0 89 }
michael@0 90
michael@0 91 self.onmessage = function (msg) {
michael@0 92 try {
michael@0 93 var requestID = msg.data.requestID;
michael@0 94 var task = msg.data.task;
michael@0 95 var taskData = msg.data.taskData;
michael@0 96 if (!taskData &&
michael@0 97 (["chunkedStart", "chunkedChunk", "chunkedEnd"].indexOf(task) == -1)) {
michael@0 98 taskData = partialTaskData[requestID];
michael@0 99 delete partialTaskData[requestID];
michael@0 100 }
michael@0 101 PROFILERLOG("Start task: " + task);
michael@0 102
michael@0 103 gLogLines = [];
michael@0 104
michael@0 105 switch (task) {
michael@0 106 case "initWorker":
michael@0 107 gDebugLog = taskData.debugLog;
michael@0 108 gDebugTrace = taskData.debugTrace;
michael@0 109 PROFILERLOG("Init logging in parserWorker");
michael@0 110 return;
michael@0 111 case "chunkedStart":
michael@0 112 partialTaskData[requestID] = null;
michael@0 113 break;
michael@0 114 case "chunkedChunk":
michael@0 115 if (partialTaskData[requestID] === null)
michael@0 116 partialTaskData[requestID] = msg.data.chunk;
michael@0 117 else
michael@0 118 partialTaskData[requestID] = partialTaskData[requestID].concat(msg.data.chunk);
michael@0 119 break;
michael@0 120 case "chunkedEnd":
michael@0 121 break;
michael@0 122 case "parseRawProfile":
michael@0 123 parseRawProfile(requestID, msg.data.params, taskData);
michael@0 124 break;
michael@0 125 case "updateFilters":
michael@0 126 updateFilters(requestID, taskData.profileID, taskData.filters);
michael@0 127 break;
michael@0 128 case "updateViewOptions":
michael@0 129 updateViewOptions(requestID, taskData.profileID, taskData.options);
michael@0 130 break;
michael@0 131 case "getSerializedProfile":
michael@0 132 getSerializedProfile(requestID, taskData.profileID, taskData.complete);
michael@0 133 break;
michael@0 134 case "calculateHistogramData":
michael@0 135 calculateHistogramData(requestID, taskData.profileID);
michael@0 136 break;
michael@0 137 case "calculateDiagnosticItems":
michael@0 138 calculateDiagnosticItems(requestID, taskData.profileID, taskData.meta);
michael@0 139 break;
michael@0 140 default:
michael@0 141 sendError(requestID, "Unknown task " + task);
michael@0 142 break;
michael@0 143 }
michael@0 144 PROFILERLOG("Complete task: " + task);
michael@0 145 } catch (e) {
michael@0 146 PROFILERERROR("Exception: " + e + " (" + e.fileName + ":" + e.lineNumber + ")");
michael@0 147 sendError(requestID, "Exception: " + e + " (" + e.fileName + ":" + e.lineNumber + ")");
michael@0 148 }
michael@0 149 }
michael@0 150
michael@0 151 function sendError(requestID, error) {
michael@0 152 // support sendError(msg)
michael@0 153 if (error == null) {
michael@0 154 error = requestID;
michael@0 155 requestID = null;
michael@0 156 }
michael@0 157
michael@0 158 self.postMessage({
michael@0 159 requestID: requestID,
michael@0 160 type: "error",
michael@0 161 error: error,
michael@0 162 log: gLogLines
michael@0 163 });
michael@0 164 }
michael@0 165
michael@0 166 function sendProgress(requestID, progress) {
michael@0 167 self.postMessage({
michael@0 168 requestID: requestID,
michael@0 169 type: "progress",
michael@0 170 progress: progress
michael@0 171 });
michael@0 172 }
michael@0 173
michael@0 174 function sendFinished(requestID, result) {
michael@0 175 self.postMessage({
michael@0 176 requestID: requestID,
michael@0 177 type: "finished",
michael@0 178 result: result,
michael@0 179 log: gLogLines
michael@0 180 });
michael@0 181 }
michael@0 182
michael@0 183 function bucketsBySplittingArray(array, maxCostPerBucket, costOfElementCallback) {
michael@0 184 var buckets = [];
michael@0 185 var currentBucket = [];
michael@0 186 var currentBucketCost = 0;
michael@0 187 for (var i = 0; i < array.length; i++) {
michael@0 188 var element = array[i];
michael@0 189 var costOfCurrentElement = costOfElementCallback ? costOfElementCallback(element) : 1;
michael@0 190 if (currentBucketCost + costOfCurrentElement > maxCostPerBucket) {
michael@0 191 buckets.push(currentBucket);
michael@0 192 currentBucket = [];
michael@0 193 currentBucketCost = 0;
michael@0 194 }
michael@0 195 currentBucket.push(element);
michael@0 196 currentBucketCost += costOfCurrentElement;
michael@0 197 }
michael@0 198 buckets.push(currentBucket);
michael@0 199 return buckets;
michael@0 200 }
michael@0 201
michael@0 202 function sendFinishedInChunks(requestID, result, maxChunkCost, costOfElementCallback) {
michael@0 203 if (result.length === undefined || result.slice === undefined)
michael@0 204 throw new Error("Can't slice result into chunks");
michael@0 205 self.postMessage({
michael@0 206 requestID: requestID,
michael@0 207 type: "finishedStart"
michael@0 208 });
michael@0 209 var chunks = bucketsBySplittingArray(result, maxChunkCost, costOfElementCallback);
michael@0 210 for (var i = 0; i < chunks.length; i++) {
michael@0 211 self.postMessage({
michael@0 212 requestID: requestID,
michael@0 213 type: "finishedChunk",
michael@0 214 chunk: chunks[i]
michael@0 215 });
michael@0 216 }
michael@0 217 self.postMessage({
michael@0 218 requestID: requestID,
michael@0 219 type: "finishedEnd",
michael@0 220 log: gLogLines
michael@0 221 });
michael@0 222 }
michael@0 223
michael@0 224 function makeSample(frames, extraInfo) {
michael@0 225 return {
michael@0 226 frames: frames,
michael@0 227 extraInfo: extraInfo
michael@0 228 };
michael@0 229 }
michael@0 230
michael@0 231 function cloneSample(sample) {
michael@0 232 return makeSample(sample.frames.slice(0), sample.extraInfo);
michael@0 233 }
michael@0 234
michael@0 235 function parseRawProfile(requestID, params, rawProfile) {
michael@0 236 var progressReporter = new ProgressReporter();
michael@0 237 progressReporter.addListener(function (r) {
michael@0 238 sendProgress(requestID, r.getProgress());
michael@0 239 });
michael@0 240 progressReporter.begin("Parsing...");
michael@0 241
michael@0 242 var symbolicationTable = {};
michael@0 243 var symbols = [];
michael@0 244 var symbolIndices = {};
michael@0 245 var resources = {};
michael@0 246 var functions = [];
michael@0 247 var functionIndices = {};
michael@0 248 var samples = [];
michael@0 249 var meta = {};
michael@0 250 var armIncludePCIndex = {};
michael@0 251
michael@0 252 if (rawProfile == null) {
michael@0 253 throw "rawProfile is null";
michael@0 254 }
michael@0 255
michael@0 256 if (typeof rawProfile == "string" && rawProfile[0] == "{") {
michael@0 257 // rawProfile is a JSON string.
michael@0 258 rawProfile = JSON.parse(rawProfile);
michael@0 259 if (rawProfile === null) {
michael@0 260 throw "rawProfile couldn't not successfully be parsed using JSON.parse. Make sure that the profile is a valid JSON encoding.";
michael@0 261 }
michael@0 262 }
michael@0 263
michael@0 264 if (rawProfile.profileJSON && !rawProfile.profileJSON.meta && rawProfile.meta) {
michael@0 265 rawProfile.profileJSON.meta = rawProfile.meta;
michael@0 266 }
michael@0 267
michael@0 268 if (typeof rawProfile == "object") {
michael@0 269 switch (rawProfile.format || null) {
michael@0 270 case "profileStringWithSymbolicationTable,1":
michael@0 271 symbolicationTable = rawProfile.symbolicationTable;
michael@0 272 parseProfileString(rawProfile.profileString);
michael@0 273 break;
michael@0 274 case "profileJSONWithSymbolicationTable,1":
michael@0 275 symbolicationTable = rawProfile.symbolicationTable;
michael@0 276 parseProfileJSON(rawProfile.profileJSON);
michael@0 277 break;
michael@0 278 default:
michael@0 279 parseProfileJSON(rawProfile);
michael@0 280 }
michael@0 281 } else {
michael@0 282 parseProfileString(rawProfile);
michael@0 283 }
michael@0 284
michael@0 285 if (params.profileId) {
michael@0 286 meta.profileId = params.profileId;
michael@0 287 }
michael@0 288
michael@0 289 function cleanFunctionName(functionName) {
michael@0 290 var ignoredPrefix = "non-virtual thunk to ";
michael@0 291 if (functionName.startsWith(ignoredPrefix))
michael@0 292 return functionName.substr(ignoredPrefix.length);
michael@0 293 return functionName;
michael@0 294 }
michael@0 295
michael@0 296 function resourceNameForAddon(addon) {
michael@0 297 if (!addon)
michael@0 298 return "";
michael@0 299
michael@0 300 var iconHTML = "";
michael@0 301 if (addon.iconURL)
michael@0 302 iconHTML = "<img src=\"" + addon.iconURL + "\" style='width:12px; height:12px;'> "
michael@0 303 return iconHTML + " " + (/@jetpack$/.exec(addon.id) ? "Jetpack: " : "") + addon.name;
michael@0 304 }
michael@0 305
michael@0 306 function addonWithID(addonID) {
michael@0 307 return firstMatch(meta.addons, function addonHasID(addon) {
michael@0 308 return addon.id.toLowerCase() == addonID.toLowerCase();
michael@0 309 })
michael@0 310 }
michael@0 311
michael@0 312 function resourceNameForAddonWithID(addonID) {
michael@0 313 return resourceNameForAddon(addonWithID(addonID));
michael@0 314 }
michael@0 315
michael@0 316 function findAddonForChromeURIHost(host) {
michael@0 317 return firstMatch(meta.addons, function addonUsesChromeURIHost(addon) {
michael@0 318 return addon.chromeURIHosts && addon.chromeURIHosts.indexOf(host) != -1;
michael@0 319 });
michael@0 320 }
michael@0 321
michael@0 322 function ensureResource(name, resourceDescription) {
michael@0 323 if (!(name in resources)) {
michael@0 324 resources[name] = resourceDescription;
michael@0 325 }
michael@0 326 return name;
michael@0 327 }
michael@0 328
michael@0 329 function resourceNameFromLibrary(library) {
michael@0 330 return ensureResource("lib_" + library, {
michael@0 331 type: "library",
michael@0 332 name: library
michael@0 333 });
michael@0 334 }
michael@0 335
michael@0 336 function getAddonForScriptURI(url, host) {
michael@0 337 if (!meta || !meta.addons)
michael@0 338 return null;
michael@0 339
michael@0 340 if (url.startsWith("resource:") && endsWith(host, "-at-jetpack")) {
michael@0 341 // Assume this is a jetpack url
michael@0 342 var jetpackID = host.substring(0, host.length - 11) + "@jetpack";
michael@0 343 return addonWithID(jetpackID);
michael@0 344 }
michael@0 345
michael@0 346 if (url.startsWith("file:///") && url.indexOf("/extensions/") != -1) {
michael@0 347 var unpackedAddonNameMatch = /\/extensions\/(.*?)\//.exec(url);
michael@0 348 if (unpackedAddonNameMatch)
michael@0 349 return addonWithID(decodeURIComponent(unpackedAddonNameMatch[1]));
michael@0 350 return null;
michael@0 351 }
michael@0 352
michael@0 353 if (url.startsWith("jar:file:///") && url.indexOf("/extensions/") != -1) {
michael@0 354 var packedAddonNameMatch = /\/extensions\/(.*?).xpi/.exec(url);
michael@0 355 if (packedAddonNameMatch)
michael@0 356 return addonWithID(decodeURIComponent(packedAddonNameMatch[1]));
michael@0 357 return null;
michael@0 358 }
michael@0 359
michael@0 360 if (url.startsWith("chrome://")) {
michael@0 361 var chromeURIMatch = /chrome\:\/\/(.*?)\//.exec(url);
michael@0 362 if (chromeURIMatch)
michael@0 363 return findAddonForChromeURIHost(chromeURIMatch[1]);
michael@0 364 return null;
michael@0 365 }
michael@0 366
michael@0 367 return null;
michael@0 368 }
michael@0 369
michael@0 370 function resourceNameFromURI(url) {
michael@0 371 if (!url)
michael@0 372 return ensureResource("unknown", {type: "unknown", name: "<unknown>"});
michael@0 373
michael@0 374 var match = /^(.*):\/\/(.*?)\//.exec(url);
michael@0 375
michael@0 376 if (!match) {
michael@0 377 // Can this happen? If so, we should change the regular expression above.
michael@0 378 return ensureResource("url_" + url, {type: "url", name: url});
michael@0 379 }
michael@0 380
michael@0 381 var urlRoot = match[0];
michael@0 382 var protocol = match[1];
michael@0 383 var host = match[2];
michael@0 384
michael@0 385 var addon = getAddonForScriptURI(url, host);
michael@0 386 if (addon) {
michael@0 387 return ensureResource("addon_" + addon.id, {
michael@0 388 type: "addon",
michael@0 389 name: addon.name,
michael@0 390 addonID: addon.id,
michael@0 391 icon: addon.iconURL
michael@0 392 });
michael@0 393 }
michael@0 394
michael@0 395 if (protocol.startsWith("http")) {
michael@0 396 return ensureResource("webhost_" + host, {
michael@0 397 type: "webhost",
michael@0 398 name: host,
michael@0 399 icon: urlRoot + "favicon.ico"
michael@0 400 });
michael@0 401 }
michael@0 402
michael@0 403 if (protocol.startsWith("file")) {
michael@0 404 return ensureResource("file_" + host, {
michael@0 405 type: "file",
michael@0 406 name: host
michael@0 407 });
michael@0 408 }
michael@0 409
michael@0 410 return ensureResource("otherhost_" + host, {
michael@0 411 type: "otherhost",
michael@0 412 name: host
michael@0 413 });
michael@0 414 }
michael@0 415
michael@0 416 function parseScriptFile(url) {
michael@0 417 var match = /([^\/]*)$/.exec(url);
michael@0 418 if (match && match[1])
michael@0 419 return match[1];
michael@0 420
michael@0 421 return url;
michael@0 422 }
michael@0 423
michael@0 424 // JS File information sometimes comes with multiple URIs which are chained
michael@0 425 // with " -> ". We only want the last URI in this list.
michael@0 426 function getRealScriptURI(url) {
michael@0 427 if (url) {
michael@0 428 var urls = url.split(" -> ");
michael@0 429 return urls[urls.length - 1];
michael@0 430 }
michael@0 431 return url;
michael@0 432 }
michael@0 433
michael@0 434 function getFunctionInfo(fullName) {
michael@0 435
michael@0 436 function getCPPFunctionInfo(fullName) {
michael@0 437 var match =
michael@0 438 /^(.*) \(in ([^\)]*)\) (\+ [0-9]+)$/.exec(fullName) ||
michael@0 439 /^(.*) \(in ([^\)]*)\) (\(.*:.*\))$/.exec(fullName) ||
michael@0 440 /^(.*) \(in ([^\)]*)\)$/.exec(fullName);
michael@0 441
michael@0 442 if (!match)
michael@0 443 return null;
michael@0 444
michael@0 445 return {
michael@0 446 functionName: cleanFunctionName(match[1]),
michael@0 447 libraryName: resourceNameFromLibrary(match[2]),
michael@0 448 lineInformation: match[3] || "",
michael@0 449 isRoot: false,
michael@0 450 isJSFrame: false
michael@0 451 };
michael@0 452 }
michael@0 453
michael@0 454 function getJSFunctionInfo(fullName) {
michael@0 455 var jsMatch =
michael@0 456 /^(.*) \((.*):([0-9]+)\)$/.exec(fullName) ||
michael@0 457 /^()(.*):([0-9]+)$/.exec(fullName);
michael@0 458
michael@0 459 if (!jsMatch)
michael@0 460 return null;
michael@0 461
michael@0 462 var functionName = jsMatch[1] || "<Anonymous>";
michael@0 463 var scriptURI = getRealScriptURI(jsMatch[2]);
michael@0 464 var lineNumber = jsMatch[3];
michael@0 465 var scriptFile = parseScriptFile(scriptURI);
michael@0 466 var resourceName = resourceNameFromURI(scriptURI);
michael@0 467
michael@0 468 return {
michael@0 469 functionName: functionName + "() @ " + scriptFile + ":" + lineNumber,
michael@0 470 libraryName: resourceName,
michael@0 471 lineInformation: "",
michael@0 472 isRoot: false,
michael@0 473 isJSFrame: true,
michael@0 474 scriptLocation: {
michael@0 475 scriptURI: scriptURI,
michael@0 476 lineInformation: lineNumber
michael@0 477 }
michael@0 478 };
michael@0 479 }
michael@0 480
michael@0 481 function getFallbackFunctionInfo(fullName) {
michael@0 482 return {
michael@0 483 functionName: cleanFunctionName(fullName),
michael@0 484 libraryName: "",
michael@0 485 lineInformation: "",
michael@0 486 isRoot: fullName == "(root)",
michael@0 487 isJSFrame: false
michael@0 488 };
michael@0 489 }
michael@0 490
michael@0 491 return getCPPFunctionInfo(fullName) ||
michael@0 492 getJSFunctionInfo(fullName) ||
michael@0 493 getFallbackFunctionInfo(fullName);
michael@0 494 }
michael@0 495
michael@0 496 function indexForFunction(symbol, info) {
michael@0 497 var resolve = info.functionName + "__" + info.libraryName;
michael@0 498 if (resolve in functionIndices)
michael@0 499 return functionIndices[resolve];
michael@0 500 var newIndex = functions.length;
michael@0 501 info.symbol = symbol;
michael@0 502 functions[newIndex] = info;
michael@0 503 functionIndices[resolve] = newIndex;
michael@0 504 return newIndex;
michael@0 505 }
michael@0 506
michael@0 507 function parseSymbol(symbol) {
michael@0 508 var info = getFunctionInfo(symbol);
michael@0 509 //dump("Parse symbol: " + symbol + "\n");
michael@0 510 return {
michael@0 511 symbolName: symbol,
michael@0 512 functionName: info.functionName,
michael@0 513 functionIndex: indexForFunction(symbol, info),
michael@0 514 lineInformation: info.lineInformation,
michael@0 515 isRoot: info.isRoot,
michael@0 516 isJSFrame: info.isJSFrame,
michael@0 517 scriptLocation: info.scriptLocation
michael@0 518 };
michael@0 519 }
michael@0 520
michael@0 521 function translatedSymbol(symbol) {
michael@0 522 return symbolicationTable[symbol] || symbol;
michael@0 523 }
michael@0 524
michael@0 525 function indexForSymbol(symbol) {
michael@0 526 if (symbol in symbolIndices)
michael@0 527 return symbolIndices[symbol];
michael@0 528 var newIndex = symbols.length;
michael@0 529 symbols[newIndex] = parseSymbol(translatedSymbol(symbol));
michael@0 530 symbolIndices[symbol] = newIndex;
michael@0 531 return newIndex;
michael@0 532 }
michael@0 533
michael@0 534 function clearRegExpLastMatch() {
michael@0 535 /./.exec(" ");
michael@0 536 }
michael@0 537
michael@0 538 function shouldIncludeARMLRForPC(pcIndex) {
michael@0 539 if (pcIndex in armIncludePCIndex)
michael@0 540 return armIncludePCIndex[pcIndex];
michael@0 541
michael@0 542 var pcName = symbols[pcIndex].functionName;
michael@0 543 var include = sARMFunctionsWithValidLR.indexOf(pcName) != -1;
michael@0 544 armIncludePCIndex[pcIndex] = include;
michael@0 545 return include;
michael@0 546 }
michael@0 547
michael@0 548 function parseProfileString(data) {
michael@0 549 var extraInfo = {};
michael@0 550 var lines = data.split("\n");
michael@0 551 var sample = null;
michael@0 552 for (var i = 0; i < lines.length; ++i) {
michael@0 553 var line = lines[i];
michael@0 554 if (line.length < 2 || line[1] != '-') {
michael@0 555 // invalid line, ignore it
michael@0 556 continue;
michael@0 557 }
michael@0 558 var info = line.substring(2);
michael@0 559 switch (line[0]) {
michael@0 560 //case 'l':
michael@0 561 // // leaf name
michael@0 562 // if ("leafName" in extraInfo) {
michael@0 563 // extraInfo.leafName += ":" + info;
michael@0 564 // } else {
michael@0 565 // extraInfo.leafName = info;
michael@0 566 // }
michael@0 567 // break;
michael@0 568 case 'm':
michael@0 569 // marker
michael@0 570 if (!("marker" in extraInfo)) {
michael@0 571 extraInfo.marker = [];
michael@0 572 }
michael@0 573 extraInfo.marker.push(info);
michael@0 574 break;
michael@0 575 case 's':
michael@0 576 // sample
michael@0 577 var sampleName = info;
michael@0 578 sample = makeSample([indexForSymbol(sampleName)], extraInfo);
michael@0 579 samples.push(sample);
michael@0 580 extraInfo = {}; // reset the extra info for future rounds
michael@0 581 break;
michael@0 582 case 'c':
michael@0 583 case 'l':
michael@0 584 // continue sample
michael@0 585 if (sample) { // ignore the case where we see a 'c' before an 's'
michael@0 586 sample.frames.push(indexForSymbol(info));
michael@0 587 }
michael@0 588 break;
michael@0 589 case 'L':
michael@0 590 // continue sample; this is an ARM LR record. Stick it before the
michael@0 591 // PC if it's one of the functions where we know LR is good.
michael@0 592 if (sample && sample.frames.length > 1) {
michael@0 593 var pcIndex = sample.frames[sample.frames.length - 1];
michael@0 594 if (shouldIncludeARMLRForPC(pcIndex)) {
michael@0 595 sample.frames.splice(-1, 0, indexForSymbol(info));
michael@0 596 }
michael@0 597 }
michael@0 598 break;
michael@0 599 case 't':
michael@0 600 // time
michael@0 601 if (sample) {
michael@0 602 sample.extraInfo["time"] = parseFloat(info);
michael@0 603 }
michael@0 604 break;
michael@0 605 case 'r':
michael@0 606 // responsiveness
michael@0 607 if (sample) {
michael@0 608 sample.extraInfo["responsiveness"] = parseFloat(info);
michael@0 609 }
michael@0 610 break;
michael@0 611 }
michael@0 612 progressReporter.setProgress((i + 1) / lines.length);
michael@0 613 }
michael@0 614 }
michael@0 615
michael@0 616 function parseProfileJSON(profile) {
michael@0 617 // Thread 0 will always be the main thread of interest
michael@0 618 // TODO support all the thread in the profile
michael@0 619 var profileSamples = null;
michael@0 620 meta = profile.meta || {};
michael@0 621 if (params.appendVideoCapture) {
michael@0 622 meta.videoCapture = {
michael@0 623 src: params.appendVideoCapture,
michael@0 624 };
michael@0 625 }
michael@0 626 // Support older format that aren't thread aware
michael@0 627 if (profile.threads != null) {
michael@0 628 profileSamples = profile.threads[0].samples;
michael@0 629 } else {
michael@0 630 profileSamples = profile;
michael@0 631 }
michael@0 632 var rootSymbol = null;
michael@0 633 var insertCommonRoot = false;
michael@0 634 var frameStart = {};
michael@0 635 meta.frameStart = frameStart;
michael@0 636 for (var j = 0; j < profileSamples.length; j++) {
michael@0 637 var sample = profileSamples[j];
michael@0 638 var indicedFrames = [];
michael@0 639 if (!sample) {
michael@0 640 // This sample was filtered before saving
michael@0 641 samples.push(null);
michael@0 642 progressReporter.setProgress((j + 1) / profileSamples.length);
michael@0 643 continue;
michael@0 644 }
michael@0 645 for (var k = 0; sample.frames && k < sample.frames.length; k++) {
michael@0 646 var frame = sample.frames[k];
michael@0 647 var pcIndex;
michael@0 648 if (frame.location !== undefined) {
michael@0 649 pcIndex = indexForSymbol(frame.location);
michael@0 650 } else {
michael@0 651 pcIndex = indexForSymbol(frame);
michael@0 652 }
michael@0 653
michael@0 654 if (frame.lr !== undefined && shouldIncludeARMLRForPC(pcIndex)) {
michael@0 655 indicedFrames.push(indexForSymbol(frame.lr));
michael@0 656 }
michael@0 657
michael@0 658 indicedFrames.push(pcIndex);
michael@0 659 }
michael@0 660 if (indicedFrames.length >= 1) {
michael@0 661 if (rootSymbol && rootSymbol != indicedFrames[0]) {
michael@0 662 insertCommonRoot = true;
michael@0 663 }
michael@0 664 rootSymbol = rootSymbol || indicedFrames[0];
michael@0 665 }
michael@0 666 if (sample.extraInfo == null) {
michael@0 667 sample.extraInfo = {};
michael@0 668 }
michael@0 669 if (sample.responsiveness) {
michael@0 670 sample.extraInfo["responsiveness"] = sample.responsiveness;
michael@0 671 }
michael@0 672 if (sample.marker) {
michael@0 673 sample.extraInfo["marker"] = sample.marker;
michael@0 674 }
michael@0 675 if (sample.time) {
michael@0 676 sample.extraInfo["time"] = sample.time;
michael@0 677 }
michael@0 678 if (sample.frameNumber) {
michael@0 679 sample.extraInfo["frameNumber"] = sample.frameNumber;
michael@0 680 //dump("Got frame number: " + sample.frameNumber + "\n");
michael@0 681 frameStart[sample.frameNumber] = samples.length;
michael@0 682 }
michael@0 683 samples.push(makeSample(indicedFrames, sample.extraInfo));
michael@0 684 progressReporter.setProgress((j + 1) / profileSamples.length);
michael@0 685 }
michael@0 686 if (insertCommonRoot) {
michael@0 687 var rootIndex = indexForSymbol("(root)");
michael@0 688 for (var i = 0; i < samples.length; i++) {
michael@0 689 var sample = samples[i];
michael@0 690 if (!sample) continue;
michael@0 691 // If length == 0 then the sample was filtered when saving the profile
michael@0 692 if (sample.frames.length >= 1 && sample.frames[0] != rootIndex)
michael@0 693 sample.frames.unshift(rootIndex)
michael@0 694 }
michael@0 695 }
michael@0 696 }
michael@0 697
michael@0 698 progressReporter.finish();
michael@0 699 // Don't increment the profile ID now because (1) it's buggy
michael@0 700 // and (2) for now there's no point in storing each profile
michael@0 701 // here if we're storing them in the local storage.
michael@0 702 //var profileID = gNextProfileID++;
michael@0 703 var profileID = gNextProfileID;
michael@0 704 gProfiles[profileID] = JSON.parse(JSON.stringify({
michael@0 705 meta: meta,
michael@0 706 symbols: symbols,
michael@0 707 functions: functions,
michael@0 708 resources: resources,
michael@0 709 allSamples: samples
michael@0 710 }));
michael@0 711 clearRegExpLastMatch();
michael@0 712 sendFinished(requestID, {
michael@0 713 meta: meta,
michael@0 714 numSamples: samples.length,
michael@0 715 profileID: profileID,
michael@0 716 symbols: symbols,
michael@0 717 functions: functions,
michael@0 718 resources: resources
michael@0 719 });
michael@0 720 }
michael@0 721
michael@0 722 function getSerializedProfile(requestID, profileID, complete) {
michael@0 723 var profile = gProfiles[profileID];
michael@0 724 var symbolicationTable = {};
michael@0 725 if (complete || !profile.filterSettings.mergeFunctions) {
michael@0 726 for (var symbolIndex in profile.symbols) {
michael@0 727 symbolicationTable[symbolIndex] = profile.symbols[symbolIndex].symbolName;
michael@0 728 }
michael@0 729 } else {
michael@0 730 for (var functionIndex in profile.functions) {
michael@0 731 var f = profile.functions[functionIndex];
michael@0 732 symbolicationTable[functionIndex] = f.symbol;
michael@0 733 }
michael@0 734 }
michael@0 735 var serializedProfile = JSON.stringify({
michael@0 736 format: "profileJSONWithSymbolicationTable,1",
michael@0 737 meta: profile.meta,
michael@0 738 profileJSON: complete ? profile.allSamples : profile.filteredSamples,
michael@0 739 symbolicationTable: symbolicationTable
michael@0 740 });
michael@0 741 sendFinished(requestID, serializedProfile);
michael@0 742 }
michael@0 743
michael@0 744 function TreeNode(name, parent, startCount) {
michael@0 745 this.name = name;
michael@0 746 this.children = [];
michael@0 747 this.counter = startCount;
michael@0 748 this.parent = parent;
michael@0 749 }
michael@0 750 TreeNode.prototype.getDepth = function TreeNode__getDepth() {
michael@0 751 if (this.parent)
michael@0 752 return this.parent.getDepth() + 1;
michael@0 753 return 0;
michael@0 754 };
michael@0 755 TreeNode.prototype.findChild = function TreeNode_findChild(name) {
michael@0 756 for (var i = 0; i < this.children.length; i++) {
michael@0 757 var child = this.children[i];
michael@0 758 if (child.name == name)
michael@0 759 return child;
michael@0 760 }
michael@0 761 return null;
michael@0 762 }
michael@0 763 // path is an array of strings which is matched to our nodes' names.
michael@0 764 // Try to walk path in our own tree and return the last matching node. The
michael@0 765 // length of the match can be calculated by the caller by comparing the
michael@0 766 // returned node's depth with the depth of the path's start node.
michael@0 767 TreeNode.prototype.followPath = function TreeNode_followPath(path) {
michael@0 768 if (path.length == 0)
michael@0 769 return this;
michael@0 770
michael@0 771 var matchingChild = this.findChild(path[0]);
michael@0 772 if (!matchingChild)
michael@0 773 return this;
michael@0 774
michael@0 775 return matchingChild.followPath(path.slice(1));
michael@0 776 };
michael@0 777 TreeNode.prototype.incrementCountersInParentChain = function TreeNode_incrementCountersInParentChain() {
michael@0 778 this.counter++;
michael@0 779 if (this.parent)
michael@0 780 this.parent.incrementCountersInParentChain();
michael@0 781 };
michael@0 782
michael@0 783 function convertToCallTree(samples, isReverse) {
michael@0 784 function areSamplesMultiroot(samples) {
michael@0 785 var previousRoot;
michael@0 786 for (var i = 0; i < samples.length; ++i) {
michael@0 787 if (!previousRoot) {
michael@0 788 previousRoot = samples[i].frames[0];
michael@0 789 continue;
michael@0 790 }
michael@0 791 if (previousRoot != samples[i].frames[0]) {
michael@0 792 return true;
michael@0 793 }
michael@0 794 }
michael@0 795 return false;
michael@0 796 }
michael@0 797 samples = samples.filter(function noNullSamples(sample) {
michael@0 798 return sample != null;
michael@0 799 });
michael@0 800 if (samples.length == 0)
michael@0 801 return new TreeNode("(empty)", null, 0);
michael@0 802 var firstRoot = null;
michael@0 803 for (var i = 0; i < samples.length; ++i) {
michael@0 804 firstRoot = samples[i].frames[0];
michael@0 805 break;
michael@0 806 }
michael@0 807 if (firstRoot == null) {
michael@0 808 return new TreeNode("(all filtered)", null, 0);
michael@0 809 }
michael@0 810 var multiRoot = areSamplesMultiroot(samples);
michael@0 811 var treeRoot = new TreeNode((isReverse || multiRoot) ? "(total)" : firstRoot, null, 0);
michael@0 812 for (var i = 0; i < samples.length; ++i) {
michael@0 813 var sample = samples[i];
michael@0 814 var callstack = sample.frames.slice(0);
michael@0 815 callstack.shift();
michael@0 816 if (isReverse)
michael@0 817 callstack.reverse();
michael@0 818 var deepestExistingNode = treeRoot.followPath(callstack);
michael@0 819 var remainingCallstack = callstack.slice(deepestExistingNode.getDepth());
michael@0 820 deepestExistingNode.incrementCountersInParentChain();
michael@0 821 var node = deepestExistingNode;
michael@0 822 for (var j = 0; j < remainingCallstack.length; ++j) {
michael@0 823 var frame = remainingCallstack[j];
michael@0 824 var child = new TreeNode(frame, node, 1);
michael@0 825 node.children.push(child);
michael@0 826 node = child;
michael@0 827 }
michael@0 828 }
michael@0 829 return treeRoot;
michael@0 830 }
michael@0 831
michael@0 832 function filterByJank(samples, filterThreshold) {
michael@0 833 return samples.map(function nullNonJank(sample) {
michael@0 834 if (!sample ||
michael@0 835 !("responsiveness" in sample.extraInfo) ||
michael@0 836 sample.extraInfo["responsiveness"] < filterThreshold)
michael@0 837 return null;
michael@0 838 return sample;
michael@0 839 });
michael@0 840 }
michael@0 841
michael@0 842 function filterBySymbol(samples, symbolOrFunctionIndex) {
michael@0 843 return samples.map(function filterSample(origSample) {
michael@0 844 if (!origSample)
michael@0 845 return null;
michael@0 846 var sample = cloneSample(origSample);
michael@0 847 for (var i = 0; i < sample.frames.length; i++) {
michael@0 848 if (symbolOrFunctionIndex == sample.frames[i]) {
michael@0 849 sample.frames = sample.frames.slice(i);
michael@0 850 return sample;
michael@0 851 }
michael@0 852 }
michael@0 853 return null; // no frame matched; filter out complete sample
michael@0 854 });
michael@0 855 }
michael@0 856
michael@0 857 function filterByCallstackPrefix(samples, symbols, functions, callstack, appliesToJS, useFunctions) {
michael@0 858 var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
michael@0 859 return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
michael@0 860 } : function isJSSymbolOrRoot(symbolIndex) {
michael@0 861 return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
michael@0 862 };
michael@0 863 return samples.map(function filterSample(sample) {
michael@0 864 if (!sample)
michael@0 865 return null;
michael@0 866 if (sample.frames.length < callstack.length)
michael@0 867 return null;
michael@0 868 for (var i = 0, j = 0; j < callstack.length; i++) {
michael@0 869 if (i >= sample.frames.length)
michael@0 870 return null;
michael@0 871 if (appliesToJS && !isJSFrameOrRoot(sample.frames[i]))
michael@0 872 continue;
michael@0 873 if (sample.frames[i] != callstack[j])
michael@0 874 return null;
michael@0 875 j++;
michael@0 876 }
michael@0 877 return makeSample(sample.frames.slice(i - 1), sample.extraInfo);
michael@0 878 });
michael@0 879 }
michael@0 880
michael@0 881 function filterByCallstackPostfix(samples, symbols, functions, callstack, appliesToJS, useFunctions) {
michael@0 882 var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
michael@0 883 return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
michael@0 884 } : function isJSSymbolOrRoot(symbolIndex) {
michael@0 885 return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
michael@0 886 };
michael@0 887 return samples.map(function filterSample(sample) {
michael@0 888 if (!sample)
michael@0 889 return null;
michael@0 890 if (sample.frames.length < callstack.length)
michael@0 891 return null;
michael@0 892 for (var i = 0, j = 0; j < callstack.length; i++) {
michael@0 893 if (i >= sample.frames.length)
michael@0 894 return null;
michael@0 895 if (appliesToJS && !isJSFrameOrRoot(sample.frames[sample.frames.length - i - 1]))
michael@0 896 continue;
michael@0 897 if (sample.frames[sample.frames.length - i - 1] != callstack[j])
michael@0 898 return null;
michael@0 899 j++;
michael@0 900 }
michael@0 901 var newFrames = sample.frames.slice(0, sample.frames.length - i + 1);
michael@0 902 return makeSample(newFrames, sample.extraInfo);
michael@0 903 });
michael@0 904 }
michael@0 905
michael@0 906 function chargeNonJSToCallers(samples, symbols, functions, useFunctions) {
michael@0 907 var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) {
michael@0 908 return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot);
michael@0 909 } : function isJSSymbolOrRoot(symbolIndex) {
michael@0 910 return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot);
michael@0 911 };
michael@0 912 samples = samples.slice(0);
michael@0 913 for (var i = 0; i < samples.length; ++i) {
michael@0 914 var sample = samples[i];
michael@0 915 if (!sample)
michael@0 916 continue;
michael@0 917 var newFrames = sample.frames.filter(isJSFrameOrRoot);
michael@0 918 if (!newFrames.length) {
michael@0 919 samples[i] = null;
michael@0 920 } else {
michael@0 921 samples[i].frames = newFrames;
michael@0 922 }
michael@0 923 }
michael@0 924 return samples;
michael@0 925 }
michael@0 926
michael@0 927 function filterByName(samples, symbols, functions, filterName, useFunctions) {
michael@0 928 function getSymbolOrFunctionName(index, useFunctions) {
michael@0 929 if (useFunctions) {
michael@0 930 if (!(index in functions))
michael@0 931 return "";
michael@0 932 return functions[index].functionName;
michael@0 933 }
michael@0 934 if (!(index in symbols))
michael@0 935 return "";
michael@0 936 return symbols[index].symbolName;
michael@0 937 }
michael@0 938 function getLibraryName(index, useFunctions) {
michael@0 939 if (useFunctions) {
michael@0 940 if (!(index in functions))
michael@0 941 return "";
michael@0 942 return functions[index].libraryName;
michael@0 943 }
michael@0 944 if (!(index in symbols))
michael@0 945 return "";
michael@0 946 return symbols[index].libraryName;
michael@0 947 }
michael@0 948 samples = samples.slice(0);
michael@0 949 filterName = filterName.toLowerCase();
michael@0 950 calltrace_it: for (var i = 0; i < samples.length; ++i) {
michael@0 951 var sample = samples[i];
michael@0 952 if (!sample)
michael@0 953 continue;
michael@0 954 var callstack = sample.frames;
michael@0 955 for (var j = 0; j < callstack.length; ++j) {
michael@0 956 var symbolOrFunctionName = getSymbolOrFunctionName(callstack[j], useFunctions);
michael@0 957 var libraryName = getLibraryName(callstack[j], useFunctions);
michael@0 958 if (symbolOrFunctionName.toLowerCase().indexOf(filterName) != -1 ||
michael@0 959 libraryName.toLowerCase().indexOf(filterName) != -1) {
michael@0 960 continue calltrace_it;
michael@0 961 }
michael@0 962 }
michael@0 963 samples[i] = null;
michael@0 964 }
michael@0 965 return samples;
michael@0 966 }
michael@0 967
michael@0 968 function discardLineLevelInformation(samples, symbols, functions) {
michael@0 969 var data = samples;
michael@0 970 var filteredData = [];
michael@0 971 for (var i = 0; i < data.length; i++) {
michael@0 972 if (!data[i]) {
michael@0 973 filteredData.push(null);
michael@0 974 continue;
michael@0 975 }
michael@0 976 filteredData.push(cloneSample(data[i]));
michael@0 977 var frames = filteredData[i].frames;
michael@0 978 for (var j = 0; j < frames.length; j++) {
michael@0 979 if (!(frames[j] in symbols))
michael@0 980 continue;
michael@0 981 frames[j] = symbols[frames[j]].functionIndex;
michael@0 982 }
michael@0 983 }
michael@0 984 return filteredData;
michael@0 985 }
michael@0 986
michael@0 987 function mergeUnbranchedCallPaths(root) {
michael@0 988 var mergedNames = [root.name];
michael@0 989 var node = root;
michael@0 990 while (node.children.length == 1 && node.counter == node.children[0].counter) {
michael@0 991 node = node.children[0];
michael@0 992 mergedNames.push(node.name);
michael@0 993 }
michael@0 994 if (node != root) {
michael@0 995 // Merge path from root to node into root.
michael@0 996 root.children = node.children;
michael@0 997 root.mergedNames = mergedNames;
michael@0 998 //root.name = clipText(root.name, 50) + " to " + this._clipText(node.name, 50);
michael@0 999 }
michael@0 1000 for (var i = 0; i < root.children.length; i++) {
michael@0 1001 mergeUnbranchedCallPaths(root.children[i]);
michael@0 1002 }
michael@0 1003 }
michael@0 1004
michael@0 1005 function FocusedFrameSampleFilter(focusedSymbol) {
michael@0 1006 this._focusedSymbol = focusedSymbol;
michael@0 1007 }
michael@0 1008 FocusedFrameSampleFilter.prototype = {
michael@0 1009 filter: function FocusedFrameSampleFilter_filter(samples, symbols, functions, useFunctions) {
michael@0 1010 return filterBySymbol(samples, this._focusedSymbol);
michael@0 1011 }
michael@0 1012 };
michael@0 1013
michael@0 1014 function FocusedCallstackPrefixSampleFilter(focusedCallstack, appliesToJS) {
michael@0 1015 this._focusedCallstackPrefix = focusedCallstack;
michael@0 1016 this._appliesToJS = appliesToJS;
michael@0 1017 }
michael@0 1018 FocusedCallstackPrefixSampleFilter.prototype = {
michael@0 1019 filter: function FocusedCallstackPrefixSampleFilter_filter(samples, symbols, functions, useFunctions) {
michael@0 1020 return filterByCallstackPrefix(samples, symbols, functions, this._focusedCallstackPrefix, this._appliesToJS, useFunctions);
michael@0 1021 }
michael@0 1022 };
michael@0 1023
michael@0 1024 function FocusedCallstackPostfixSampleFilter(focusedCallstack, appliesToJS) {
michael@0 1025 this._focusedCallstackPostfix = focusedCallstack;
michael@0 1026 this._appliesToJS = appliesToJS;
michael@0 1027 }
michael@0 1028 FocusedCallstackPostfixSampleFilter.prototype = {
michael@0 1029 filter: function FocusedCallstackPostfixSampleFilter_filter(samples, symbols, functions, useFunctions) {
michael@0 1030 return filterByCallstackPostfix(samples, symbols, functions, this._focusedCallstackPostfix, this._appliesToJS, useFunctions);
michael@0 1031 }
michael@0 1032 };
michael@0 1033
michael@0 1034 function RangeSampleFilter(start, end) {
michael@0 1035 this._start = start;
michael@0 1036 this._end = end;
michael@0 1037 }
michael@0 1038 RangeSampleFilter.prototype = {
michael@0 1039 filter: function RangeSampleFilter_filter(samples, symbols, functions) {
michael@0 1040 return samples.slice(this._start, this._end);
michael@0 1041 }
michael@0 1042 }
michael@0 1043
michael@0 1044 function unserializeSampleFilters(filters) {
michael@0 1045 return filters.map(function (filter) {
michael@0 1046 switch (filter.type) {
michael@0 1047 case "FocusedFrameSampleFilter":
michael@0 1048 return new FocusedFrameSampleFilter(filter.focusedSymbol);
michael@0 1049 case "FocusedCallstackPrefixSampleFilter":
michael@0 1050 return new FocusedCallstackPrefixSampleFilter(filter.focusedCallstack, filter.appliesToJS);
michael@0 1051 case "FocusedCallstackPostfixSampleFilter":
michael@0 1052 return new FocusedCallstackPostfixSampleFilter(filter.focusedCallstack, filter.appliesToJS);
michael@0 1053 case "RangeSampleFilter":
michael@0 1054 return new RangeSampleFilter(filter.start, filter.end);
michael@0 1055 case "PluginView":
michael@0 1056 return null;
michael@0 1057 default:
michael@0 1058 throw new Error("Unknown filter");
michael@0 1059 }
michael@0 1060 })
michael@0 1061 }
michael@0 1062
michael@0 1063 var gJankThreshold = 50 /* ms */;
michael@0 1064
michael@0 1065 function updateFilters(requestID, profileID, filters) {
michael@0 1066 var profile = gProfiles[profileID];
michael@0 1067 var samples = profile.allSamples;
michael@0 1068 var symbols = profile.symbols;
michael@0 1069 var functions = profile.functions;
michael@0 1070
michael@0 1071 if (filters.mergeFunctions) {
michael@0 1072 samples = discardLineLevelInformation(samples, symbols, functions);
michael@0 1073 }
michael@0 1074 if (filters.nameFilter) {
michael@0 1075 try {
michael@0 1076 samples = filterByName(samples, symbols, functions, filters.nameFilter, filters.mergeFunctions);
michael@0 1077 } catch (e) {
michael@0 1078 dump("Could not filer by name: " + e + "\n");
michael@0 1079 }
michael@0 1080 }
michael@0 1081 samples = unserializeSampleFilters(filters.sampleFilters).reduce(function (filteredSamples, currentFilter) {
michael@0 1082 if (currentFilter===null) return filteredSamples;
michael@0 1083 return currentFilter.filter(filteredSamples, symbols, functions, filters.mergeFunctions);
michael@0 1084 }, samples);
michael@0 1085 if (filters.jankOnly) {
michael@0 1086 samples = filterByJank(samples, gJankThreshold);
michael@0 1087 }
michael@0 1088 if (filters.javascriptOnly) {
michael@0 1089 samples = chargeNonJSToCallers(samples, symbols, functions, filters.mergeFunctions);
michael@0 1090 }
michael@0 1091
michael@0 1092 gProfiles[profileID].filterSettings = filters;
michael@0 1093 gProfiles[profileID].filteredSamples = samples;
michael@0 1094 sendFinishedInChunks(requestID, samples, 40000,
michael@0 1095 function (sample) { return sample ? sample.frames.length : 1; });
michael@0 1096 }
michael@0 1097
michael@0 1098 function updateViewOptions(requestID, profileID, options) {
michael@0 1099 var profile = gProfiles[profileID];
michael@0 1100 var samples = profile.filteredSamples;
michael@0 1101 var symbols = profile.symbols;
michael@0 1102 var functions = profile.functions;
michael@0 1103
michael@0 1104 var treeData = convertToCallTree(samples, options.invertCallstack);
michael@0 1105 if (options.mergeUnbranched)
michael@0 1106 mergeUnbranchedCallPaths(treeData);
michael@0 1107 sendFinished(requestID, treeData);
michael@0 1108 }
michael@0 1109
michael@0 1110 // The responsiveness threshold (in ms) after which the sample shuold become
michael@0 1111 // completely red in the histogram.
michael@0 1112 var kDelayUntilWorstResponsiveness = 1000;
michael@0 1113
michael@0 1114 function calculateHistogramData(requestID, profileID) {
michael@0 1115
michael@0 1116 function getStepColor(step) {
michael@0 1117 if (step.extraInfo && "responsiveness" in step.extraInfo) {
michael@0 1118 var res = step.extraInfo.responsiveness;
michael@0 1119 var redComponent = Math.round(255 * Math.min(1, res / kDelayUntilWorstResponsiveness));
michael@0 1120 return "rgb(" + redComponent + ",0,0)";
michael@0 1121 }
michael@0 1122
michael@0 1123 return "rgb(0,0,0)";
michael@0 1124 }
michael@0 1125
michael@0 1126 var profile = gProfiles[profileID];
michael@0 1127 var data = profile.filteredSamples;
michael@0 1128 var histogramData = [];
michael@0 1129 var maxHeight = 0;
michael@0 1130 for (var i = 0; i < data.length; ++i) {
michael@0 1131 if (!data[i])
michael@0 1132 continue;
michael@0 1133 var value = data[i].frames.length;
michael@0 1134 if (maxHeight < value)
michael@0 1135 maxHeight = value;
michael@0 1136 }
michael@0 1137 maxHeight += 1;
michael@0 1138 var nextX = 0;
michael@0 1139 // The number of data items per histogramData rects.
michael@0 1140 // Except when seperated by a marker.
michael@0 1141 // This is used to cut down the number of rects, since
michael@0 1142 // there's no point in having more rects then pixels
michael@0 1143 var samplesPerStep = Math.max(1, Math.floor(data.length / 2000));
michael@0 1144 var frameStart = {};
michael@0 1145 for (var i = 0; i < data.length; i++) {
michael@0 1146 var step = data[i];
michael@0 1147 if (!step) {
michael@0 1148 // Add a gap for the sample that was filtered out.
michael@0 1149 nextX += 1 / samplesPerStep;
michael@0 1150 continue;
michael@0 1151 }
michael@0 1152 nextX = Math.ceil(nextX);
michael@0 1153 var value = step.frames.length / maxHeight;
michael@0 1154 var frames = step.frames;
michael@0 1155 var currHistogramData = histogramData[histogramData.length-1];
michael@0 1156 if (step.extraInfo && "marker" in step.extraInfo) {
michael@0 1157 // A new marker boundary has been discovered.
michael@0 1158 histogramData.push({
michael@0 1159 frames: "marker",
michael@0 1160 x: nextX,
michael@0 1161 width: 2,
michael@0 1162 value: 1,
michael@0 1163 marker: step.extraInfo.marker,
michael@0 1164 color: "fuchsia"
michael@0 1165 });
michael@0 1166 nextX += 2;
michael@0 1167 histogramData.push({
michael@0 1168 frames: [step.frames],
michael@0 1169 x: nextX,
michael@0 1170 width: 1,
michael@0 1171 value: value,
michael@0 1172 color: getStepColor(step),
michael@0 1173 });
michael@0 1174 nextX += 1;
michael@0 1175 } else if (currHistogramData != null &&
michael@0 1176 currHistogramData.frames.length < samplesPerStep &&
michael@0 1177 !(step.extraInfo && "frameNumber" in step.extraInfo)) {
michael@0 1178 currHistogramData.frames.push(step.frames);
michael@0 1179 // When merging data items take the average:
michael@0 1180 currHistogramData.value =
michael@0 1181 (currHistogramData.value * (currHistogramData.frames.length - 1) + value) /
michael@0 1182 currHistogramData.frames.length;
michael@0 1183 // Merge the colors? For now we keep the first color set.
michael@0 1184 } else {
michael@0 1185 // A new name boundary has been discovered.
michael@0 1186 currHistogramData = {
michael@0 1187 frames: [step.frames],
michael@0 1188 x: nextX,
michael@0 1189 width: 1,
michael@0 1190 value: value,
michael@0 1191 color: getStepColor(step),
michael@0 1192 };
michael@0 1193 if (step.extraInfo && "frameNumber" in step.extraInfo) {
michael@0 1194 currHistogramData.frameNumber = step.extraInfo.frameNumber;
michael@0 1195 frameStart[step.extraInfo.frameNumber] = histogramData.length;
michael@0 1196 }
michael@0 1197 histogramData.push(currHistogramData);
michael@0 1198 nextX += 1;
michael@0 1199 }
michael@0 1200 }
michael@0 1201 sendFinished(requestID, { histogramData: histogramData, frameStart: frameStart, widthSum: Math.ceil(nextX) });
michael@0 1202 }
michael@0 1203
michael@0 1204 var diagnosticList = [
michael@0 1205 // *************** Known bugs first (highest priority)
michael@0 1206 {
michael@0 1207 image: "io.png",
michael@0 1208 title: "Main Thread IO - Bug 765135 - TISCreateInputSourceList",
michael@0 1209 check: function(frames, symbols, meta) {
michael@0 1210
michael@0 1211 if (!stepContains('TISCreateInputSourceList', frames, symbols))
michael@0 1212 return false;
michael@0 1213
michael@0 1214 return stepContains('__getdirentries64', frames, symbols)
michael@0 1215 || stepContains('__read', frames, symbols)
michael@0 1216 || stepContains('__open', frames, symbols)
michael@0 1217 || stepContains('stat$INODE64', frames, symbols)
michael@0 1218 ;
michael@0 1219 },
michael@0 1220 },
michael@0 1221
michael@0 1222 {
michael@0 1223 image: "js.png",
michael@0 1224 title: "Bug 772916 - Gradients are slow on mobile",
michael@0 1225 bugNumber: "772916",
michael@0 1226 check: function(frames, symbols, meta) {
michael@0 1227
michael@0 1228 return stepContains('PaintGradient', frames, symbols)
michael@0 1229 && stepContains('ClientTiledLayerBuffer::PaintThebesSingleBufferDraw', frames, symbols)
michael@0 1230 ;
michael@0 1231 },
michael@0 1232 },
michael@0 1233 {
michael@0 1234 image: "cache.png",
michael@0 1235 title: "Bug 717761 - Main thread can be blocked by IO on the cache thread",
michael@0 1236 bugNumber: "717761",
michael@0 1237 check: function(frames, symbols, meta) {
michael@0 1238
michael@0 1239 return stepContains('nsCacheEntryDescriptor::GetStoragePolicy', frames, symbols)
michael@0 1240 ;
michael@0 1241 },
michael@0 1242 },
michael@0 1243 {
michael@0 1244 image: "js.png",
michael@0 1245 title: "Web Content Shutdown Notification",
michael@0 1246 check: function(frames, symbols, meta) {
michael@0 1247
michael@0 1248 return stepContains('nsAppStartup::Quit', frames, symbols)
michael@0 1249 && stepContains('nsDocShell::FirePageHideNotification', frames, symbols)
michael@0 1250 ;
michael@0 1251 },
michael@0 1252 },
michael@0 1253 {
michael@0 1254 image: "js.png",
michael@0 1255 title: "Bug 789193 - AMI_startup() takes 200ms on startup",
michael@0 1256 bugNumber: "789193",
michael@0 1257 check: function(frames, symbols, meta) {
michael@0 1258
michael@0 1259 return stepContains('AMI_startup()', frames, symbols)
michael@0 1260 ;
michael@0 1261 },
michael@0 1262 },
michael@0 1263 {
michael@0 1264 image: "js.png",
michael@0 1265 title: "Bug 818296 - [Shutdown] js::NukeCrossCompartmentWrappers takes up 300ms on shutdown",
michael@0 1266 bugNumber: "818296",
michael@0 1267 check: function(frames, symbols, meta) {
michael@0 1268 return stepContains('js::NukeCrossCompartmentWrappers', frames, symbols)
michael@0 1269 && (stepContains('WindowDestroyedEvent', frames, symbols) || stepContains('DoShutdown', frames, symbols))
michael@0 1270 ;
michael@0 1271 },
michael@0 1272 },
michael@0 1273 {
michael@0 1274 image: "js.png",
michael@0 1275 title: "Bug 818274 - [Shutdown] Telemetry takes ~10ms on shutdown",
michael@0 1276 bugNumber: "818274",
michael@0 1277 check: function(frames, symbols, meta) {
michael@0 1278 return stepContains('TelemetryPing.js', frames, symbols)
michael@0 1279 ;
michael@0 1280 },
michael@0 1281 },
michael@0 1282 {
michael@0 1283 image: "plugin.png",
michael@0 1284 title: "Bug 818265 - [Shutdown] Plug-in shutdown takes ~90ms on shutdown",
michael@0 1285 bugNumber: "818265",
michael@0 1286 check: function(frames, symbols, meta) {
michael@0 1287 return stepContains('PluginInstanceParent::Destroy', frames, symbols)
michael@0 1288 ;
michael@0 1289 },
michael@0 1290 },
michael@0 1291 {
michael@0 1292 image: "snapshot.png",
michael@0 1293 title: "Bug 720575 - Make thumbnailing faster and/or asynchronous",
michael@0 1294 bugNumber: "720575",
michael@0 1295 check: function(frames, symbols, meta) {
michael@0 1296 return stepContains('Thumbnails_capture()', frames, symbols)
michael@0 1297 ;
michael@0 1298 },
michael@0 1299 },
michael@0 1300
michael@0 1301 {
michael@0 1302 image: "js.png",
michael@0 1303 title: "Bug 789185 - LoginManagerStorage_mozStorage.init() takes 300ms on startup ",
michael@0 1304 bugNumber: "789185",
michael@0 1305 check: function(frames, symbols, meta) {
michael@0 1306
michael@0 1307 return stepContains('LoginManagerStorage_mozStorage.prototype.init()', frames, symbols)
michael@0 1308 ;
michael@0 1309 },
michael@0 1310 },
michael@0 1311
michael@0 1312 {
michael@0 1313 image: "js.png",
michael@0 1314 title: "JS - Bug 767070 - Text selection performance is bad on android",
michael@0 1315 bugNumber: "767070",
michael@0 1316 check: function(frames, symbols, meta) {
michael@0 1317
michael@0 1318 if (!stepContains('FlushPendingNotifications', frames, symbols))
michael@0 1319 return false;
michael@0 1320
michael@0 1321 return stepContains('sh_', frames, symbols)
michael@0 1322 && stepContains('browser.js', frames, symbols)
michael@0 1323 ;
michael@0 1324 },
michael@0 1325 },
michael@0 1326
michael@0 1327 {
michael@0 1328 image: "js.png",
michael@0 1329 title: "JS - Bug 765930 - Reader Mode: Optimize readability check",
michael@0 1330 bugNumber: "765930",
michael@0 1331 check: function(frames, symbols, meta) {
michael@0 1332
michael@0 1333 return stepContains('Readability.js', frames, symbols)
michael@0 1334 ;
michael@0 1335 },
michael@0 1336 },
michael@0 1337
michael@0 1338 // **************** General issues
michael@0 1339 {
michael@0 1340 image: "js.png",
michael@0 1341 title: "JS is triggering a sync reflow",
michael@0 1342 check: function(frames, symbols, meta) {
michael@0 1343 return symbolSequence(['js::RunScript','layout::DoReflow'], frames, symbols) ||
michael@0 1344 symbolSequence(['js::RunScript','layout::Flush'], frames, symbols)
michael@0 1345 ;
michael@0 1346 },
michael@0 1347 },
michael@0 1348
michael@0 1349 {
michael@0 1350 image: "gc.png",
michael@0 1351 title: "Garbage Collection Slice",
michael@0 1352 canMergeWithGC: false,
michael@0 1353 check: function(frames, symbols, meta, step) {
michael@0 1354 var slice = findGCSlice(frames, symbols, meta, step);
michael@0 1355
michael@0 1356 if (slice) {
michael@0 1357 var gcEvent = findGCEvent(frames, symbols, meta, step);
michael@0 1358 //dump("found event matching diagnostic\n");
michael@0 1359 //dump(JSON.stringify(gcEvent) + "\n");
michael@0 1360 return true;
michael@0 1361 }
michael@0 1362 return false;
michael@0 1363 },
michael@0 1364 details: function(frames, symbols, meta, step) {
michael@0 1365 var slice = findGCSlice(frames, symbols, meta, step);
michael@0 1366 if (slice) {
michael@0 1367 return "" +
michael@0 1368 "Reason: " + slice.reason + "\n" +
michael@0 1369 "Slice: " + slice.slice + "\n" +
michael@0 1370 "Pause: " + slice.pause + " ms";
michael@0 1371 }
michael@0 1372 return null;
michael@0 1373 },
michael@0 1374 onclickDetails: function(frames, symbols, meta, step) {
michael@0 1375 var gcEvent = findGCEvent(frames, symbols, meta, step);
michael@0 1376 if (gcEvent) {
michael@0 1377 return JSON.stringify(gcEvent);
michael@0 1378 } else {
michael@0 1379 return null;
michael@0 1380 }
michael@0 1381 },
michael@0 1382 },
michael@0 1383 {
michael@0 1384 image: "cc.png",
michael@0 1385 title: "Cycle Collect",
michael@0 1386 check: function(frames, symbols, meta, step) {
michael@0 1387 var ccEvent = findCCEvent(frames, symbols, meta, step);
michael@0 1388
michael@0 1389 if (ccEvent) {
michael@0 1390 return true;
michael@0 1391 }
michael@0 1392 return false;
michael@0 1393 },
michael@0 1394 details: function(frames, symbols, meta, step) {
michael@0 1395 var ccEvent = findCCEvent(frames, symbols, meta, step);
michael@0 1396 if (ccEvent) {
michael@0 1397 return "" +
michael@0 1398 "Duration: " + ccEvent.duration + " ms\n" +
michael@0 1399 "Suspected: " + ccEvent.suspected;
michael@0 1400 }
michael@0 1401 return null;
michael@0 1402 },
michael@0 1403 onclickDetails: function(frames, symbols, meta, step) {
michael@0 1404 var ccEvent = findCCEvent(frames, symbols, meta, step);
michael@0 1405 if (ccEvent) {
michael@0 1406 return JSON.stringify(ccEvent);
michael@0 1407 } else {
michael@0 1408 return null;
michael@0 1409 }
michael@0 1410 },
michael@0 1411 },
michael@0 1412 {
michael@0 1413 image: "gc.png",
michael@0 1414 title: "Garbage Collection",
michael@0 1415 canMergeWithGC: false,
michael@0 1416 check: function(frames, symbols, meta) {
michael@0 1417 return stepContainsRegEx(/.*Collect.*Runtime.*Invocation.*/, frames, symbols)
michael@0 1418 || stepContains('GarbageCollectNow', frames, symbols) // Label
michael@0 1419 || stepContains('JS_GC(', frames, symbols) // Label
michael@0 1420 || stepContains('CycleCollect__', frames, symbols) // Label
michael@0 1421 ;
michael@0 1422 },
michael@0 1423 },
michael@0 1424 {
michael@0 1425 image: "cc.png",
michael@0 1426 title: "Cycle Collect",
michael@0 1427 check: function(frames, symbols, meta) {
michael@0 1428 return stepContains('nsCycleCollector::Collect', frames, symbols)
michael@0 1429 || stepContains('CycleCollect__', frames, symbols) // Label
michael@0 1430 || stepContains('nsCycleCollectorRunner::Collect', frames, symbols) // Label
michael@0 1431 ;
michael@0 1432 },
michael@0 1433 },
michael@0 1434 {
michael@0 1435 image: "plugin.png",
michael@0 1436 title: "Sync Plugin Constructor",
michael@0 1437 check: function(frames, symbols, meta) {
michael@0 1438 return stepContains('CallPPluginInstanceConstructor', frames, symbols)
michael@0 1439 || stepContains('CallPCrashReporterConstructor', frames, symbols)
michael@0 1440 || stepContains('PPluginModuleParent::CallNP_Initialize', frames, symbols)
michael@0 1441 || stepContains('GeckoChildProcessHost::SyncLaunch', frames, symbols)
michael@0 1442 ;
michael@0 1443 },
michael@0 1444 },
michael@0 1445 {
michael@0 1446 image: "text.png",
michael@0 1447 title: "Font Loading",
michael@0 1448 check: function(frames, symbols, meta) {
michael@0 1449 return stepContains('gfxFontGroup::BuildFontList', frames, symbols);
michael@0 1450 },
michael@0 1451 },
michael@0 1452 {
michael@0 1453 image: "io.png",
michael@0 1454 title: "Main Thread IO!",
michael@0 1455 check: function(frames, symbols, meta) {
michael@0 1456 return stepContains('__getdirentries64', frames, symbols)
michael@0 1457 || stepContains('__open', frames, symbols)
michael@0 1458 || stepContains('NtFlushBuffersFile', frames, symbols)
michael@0 1459 || stepContains('storage:::Statement::ExecuteStep', frames, symbols)
michael@0 1460 || stepContains('__unlink', frames, symbols)
michael@0 1461 || stepContains('fsync', frames, symbols)
michael@0 1462 || stepContains('stat$INODE64', frames, symbols)
michael@0 1463 ;
michael@0 1464 },
michael@0 1465 },
michael@0 1466 ];
michael@0 1467
michael@0 1468 function hasJSFrame(frames, symbols) {
michael@0 1469 for (var i = 0; i < frames.length; i++) {
michael@0 1470 if (symbols[frames[i]].isJSFrame === true) {
michael@0 1471 return true;
michael@0 1472 }
michael@0 1473 }
michael@0 1474 return false;
michael@0 1475 }
michael@0 1476 function findCCEvent(frames, symbols, meta, step) {
michael@0 1477 if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats)
michael@0 1478 return null;
michael@0 1479
michael@0 1480 var time = step.extraInfo.time;
michael@0 1481
michael@0 1482 for (var i = 0; i < meta.gcStats.ccEvents.length; i++) {
michael@0 1483 var ccEvent = meta.gcStats.ccEvents[i];
michael@0 1484 if (ccEvent.start_timestamp <= time && ccEvent.end_timestamp >= time) {
michael@0 1485 //dump("JSON: " + js_beautify(JSON.stringify(ccEvent)) + "\n");
michael@0 1486 return ccEvent;
michael@0 1487 }
michael@0 1488 }
michael@0 1489
michael@0 1490 return null;
michael@0 1491 }
michael@0 1492 function findGCEvent(frames, symbols, meta, step) {
michael@0 1493 if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats)
michael@0 1494 return null;
michael@0 1495
michael@0 1496 var time = step.extraInfo.time;
michael@0 1497
michael@0 1498 for (var i = 0; i < meta.gcStats.gcEvents.length; i++) {
michael@0 1499 var gcEvent = meta.gcStats.gcEvents[i];
michael@0 1500 if (!gcEvent.slices)
michael@0 1501 continue;
michael@0 1502 for (var j = 0; j < gcEvent.slices.length; j++) {
michael@0 1503 var slice = gcEvent.slices[j];
michael@0 1504 if (slice.start_timestamp <= time && slice.end_timestamp >= time) {
michael@0 1505 return gcEvent;
michael@0 1506 }
michael@0 1507 }
michael@0 1508 }
michael@0 1509
michael@0 1510 return null;
michael@0 1511 }
michael@0 1512 function findGCSlice(frames, symbols, meta, step) {
michael@0 1513 if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats)
michael@0 1514 return null;
michael@0 1515
michael@0 1516 var time = step.extraInfo.time;
michael@0 1517
michael@0 1518 for (var i = 0; i < meta.gcStats.gcEvents.length; i++) {
michael@0 1519 var gcEvent = meta.gcStats.gcEvents[i];
michael@0 1520 if (!gcEvent.slices)
michael@0 1521 continue;
michael@0 1522 for (var j = 0; j < gcEvent.slices.length; j++) {
michael@0 1523 var slice = gcEvent.slices[j];
michael@0 1524 if (slice.start_timestamp <= time && slice.end_timestamp >= time) {
michael@0 1525 return slice;
michael@0 1526 }
michael@0 1527 }
michael@0 1528 }
michael@0 1529
michael@0 1530 return null;
michael@0 1531 }
michael@0 1532 function stepContains(substring, frames, symbols) {
michael@0 1533 for (var i = 0; frames && i < frames.length; i++) {
michael@0 1534 if (!(frames[i] in symbols))
michael@0 1535 continue;
michael@0 1536 var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
michael@0 1537 if (frameSym.indexOf(substring) != -1) {
michael@0 1538 return true;
michael@0 1539 }
michael@0 1540 }
michael@0 1541 return false;
michael@0 1542 }
michael@0 1543 function stepContainsRegEx(regex, frames, symbols) {
michael@0 1544 for (var i = 0; frames && i < frames.length; i++) {
michael@0 1545 if (!(frames[i] in symbols))
michael@0 1546 continue;
michael@0 1547 var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
michael@0 1548 if (regex.exec(frameSym)) {
michael@0 1549 return true;
michael@0 1550 }
michael@0 1551 }
michael@0 1552 return false;
michael@0 1553 }
michael@0 1554 function symbolSequence(symbolsOrder, frames, symbols) {
michael@0 1555 var symbolIndex = 0;
michael@0 1556 for (var i = 0; frames && i < frames.length; i++) {
michael@0 1557 if (!(frames[i] in symbols))
michael@0 1558 continue;
michael@0 1559 var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName;
michael@0 1560 var substring = symbolsOrder[symbolIndex];
michael@0 1561 if (frameSym.indexOf(substring) != -1) {
michael@0 1562 symbolIndex++;
michael@0 1563 if (symbolIndex == symbolsOrder.length) {
michael@0 1564 return true;
michael@0 1565 }
michael@0 1566 }
michael@0 1567 }
michael@0 1568 return false;
michael@0 1569 }
michael@0 1570 function firstMatch(array, matchFunction) {
michael@0 1571 for (var i = 0; i < array.length; i++) {
michael@0 1572 if (matchFunction(array[i]))
michael@0 1573 return array[i];
michael@0 1574 }
michael@0 1575 return undefined;
michael@0 1576 }
michael@0 1577
michael@0 1578 function calculateDiagnosticItems(requestID, profileID, meta) {
michael@0 1579 /*
michael@0 1580 if (!histogramData || histogramData.length < 1) {
michael@0 1581 sendFinished(requestID, []);
michael@0 1582 return;
michael@0 1583 }*/
michael@0 1584
michael@0 1585 var profile = gProfiles[profileID];
michael@0 1586 //var symbols = profile.symbols;
michael@0 1587 var symbols = profile.functions;
michael@0 1588 var data = profile.filteredSamples;
michael@0 1589
michael@0 1590 var lastStep = data[data.length-1];
michael@0 1591 var widthSum = data.length;
michael@0 1592 var pendingDiagnosticInfo = null;
michael@0 1593
michael@0 1594 var diagnosticItems = [];
michael@0 1595
michael@0 1596 function finishPendingDiagnostic(endX) {
michael@0 1597 if (!pendingDiagnosticInfo)
michael@0 1598 return;
michael@0 1599
michael@0 1600 var diagnostic = pendingDiagnosticInfo.diagnostic;
michael@0 1601 var currDiagnostic = {
michael@0 1602 x: pendingDiagnosticInfo.x / widthSum,
michael@0 1603 width: (endX - pendingDiagnosticInfo.x) / widthSum,
michael@0 1604 imageFile: pendingDiagnosticInfo.diagnostic.image,
michael@0 1605 title: pendingDiagnosticInfo.diagnostic.title,
michael@0 1606 details: pendingDiagnosticInfo.details,
michael@0 1607 onclickDetails: pendingDiagnosticInfo.onclickDetails
michael@0 1608 };
michael@0 1609
michael@0 1610 if (!currDiagnostic.onclickDetails && diagnostic.bugNumber) {
michael@0 1611 currDiagnostic.onclickDetails = "bug " + diagnostic.bugNumber;
michael@0 1612 }
michael@0 1613
michael@0 1614 diagnosticItems.push(currDiagnostic);
michael@0 1615
michael@0 1616 pendingDiagnosticInfo = null;
michael@0 1617 }
michael@0 1618
michael@0 1619 /*
michael@0 1620 dump("meta: " + meta.gcStats + "\n");
michael@0 1621 if (meta && meta.gcStats) {
michael@0 1622 dump("GC Stats: " + JSON.stringify(meta.gcStats) + "\n");
michael@0 1623 }
michael@0 1624 */
michael@0 1625
michael@0 1626 data.forEach(function diagnoseStep(step, x) {
michael@0 1627 if (step) {
michael@0 1628 var frames = step.frames;
michael@0 1629
michael@0 1630 var diagnostic = firstMatch(diagnosticList, function (diagnostic) {
michael@0 1631 return diagnostic.check(frames, symbols, meta, step);
michael@0 1632 });
michael@0 1633 }
michael@0 1634
michael@0 1635 if (!diagnostic) {
michael@0 1636 finishPendingDiagnostic(x);
michael@0 1637 return;
michael@0 1638 }
michael@0 1639
michael@0 1640 var details = diagnostic.details ? diagnostic.details(frames, symbols, meta, step) : null;
michael@0 1641
michael@0 1642 if (pendingDiagnosticInfo) {
michael@0 1643 // We're already inside a diagnostic range.
michael@0 1644 if (diagnostic == pendingDiagnosticInfo.diagnostic && pendingDiagnosticInfo.details == details) {
michael@0 1645 // We're still inside the same diagnostic.
michael@0 1646 return;
michael@0 1647 }
michael@0 1648
michael@0 1649 // We have left the old diagnostic and found a new one. Finish the old one.
michael@0 1650 finishPendingDiagnostic(x);
michael@0 1651 }
michael@0 1652
michael@0 1653 pendingDiagnosticInfo = {
michael@0 1654 diagnostic: diagnostic,
michael@0 1655 x: x,
michael@0 1656 details: details,
michael@0 1657 onclickDetails: diagnostic.onclickDetails ? diagnostic.onclickDetails(frames, symbols, meta, step) : null
michael@0 1658 };
michael@0 1659 });
michael@0 1660 if (pendingDiagnosticInfo)
michael@0 1661 finishPendingDiagnostic(data.length);
michael@0 1662
michael@0 1663 sendFinished(requestID, diagnosticItems);
michael@0 1664 }

mercurial