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.

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

mercurial