Wed, 31 Dec 2014 06:09:35 +0100
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]);
1002 }
1003 }
1005 function FocusedFrameSampleFilter(focusedSymbol) {
1006 this._focusedSymbol = focusedSymbol;
1007 }
1008 FocusedFrameSampleFilter.prototype = {
1009 filter: function FocusedFrameSampleFilter_filter(samples, symbols, functions, useFunctions) {
1010 return filterBySymbol(samples, this._focusedSymbol);
1011 }
1012 };
1014 function FocusedCallstackPrefixSampleFilter(focusedCallstack, appliesToJS) {
1015 this._focusedCallstackPrefix = focusedCallstack;
1016 this._appliesToJS = appliesToJS;
1017 }
1018 FocusedCallstackPrefixSampleFilter.prototype = {
1019 filter: function FocusedCallstackPrefixSampleFilter_filter(samples, symbols, functions, useFunctions) {
1020 return filterByCallstackPrefix(samples, symbols, functions, this._focusedCallstackPrefix, this._appliesToJS, useFunctions);
1021 }
1022 };
1024 function FocusedCallstackPostfixSampleFilter(focusedCallstack, appliesToJS) {
1025 this._focusedCallstackPostfix = focusedCallstack;
1026 this._appliesToJS = appliesToJS;
1027 }
1028 FocusedCallstackPostfixSampleFilter.prototype = {
1029 filter: function FocusedCallstackPostfixSampleFilter_filter(samples, symbols, functions, useFunctions) {
1030 return filterByCallstackPostfix(samples, symbols, functions, this._focusedCallstackPostfix, this._appliesToJS, useFunctions);
1031 }
1032 };
1034 function RangeSampleFilter(start, end) {
1035 this._start = start;
1036 this._end = end;
1037 }
1038 RangeSampleFilter.prototype = {
1039 filter: function RangeSampleFilter_filter(samples, symbols, functions) {
1040 return samples.slice(this._start, this._end);
1041 }
1042 }
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");
1059 }
1060 })
1061 }
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);
1073 }
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");
1079 }
1080 }
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);
1087 }
1088 if (filters.javascriptOnly) {
1089 samples = chargeNonJSToCallers(samples, symbols, functions, filters.mergeFunctions);
1090 }
1092 gProfiles[profileID].filterSettings = filters;
1093 gProfiles[profileID].filteredSamples = samples;
1094 sendFinishedInChunks(requestID, samples, 40000,
1095 function (sample) { return sample ? sample.frames.length : 1; });
1096 }
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);
1108 }
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)";
1121 }
1123 return "rgb(0,0,0)";
1124 }
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;
1136 }
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;
1151 }
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;
1196 }
1197 histogramData.push(currHistogramData);
1198 nextX += 1;
1199 }
1200 }
1201 sendFinished(requestID, { histogramData: histogramData, frameStart: frameStart, widthSum: Math.ceil(nextX) });
1202 }
1204 var diagnosticList = [
1205 // *************** Known bugs first (highest priority)
1206 {
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)
1218 ;
1219 },
1220 },
1222 {
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)
1230 ;
1231 },
1232 },
1233 {
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)
1240 ;
1241 },
1242 },
1243 {
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)
1250 ;
1251 },
1252 },
1253 {
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)
1260 ;
1261 },
1262 },
1263 {
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))
1270 ;
1271 },
1272 },
1273 {
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)
1279 ;
1280 },
1281 },
1282 {
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)
1288 ;
1289 },
1290 },
1291 {
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)
1297 ;
1298 },
1299 },
1301 {
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)
1308 ;
1309 },
1310 },
1312 {
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)
1323 ;
1324 },
1325 },
1327 {
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)
1334 ;
1335 },
1336 },
1338 // **************** General issues
1339 {
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)
1345 ;
1346 },
1347 },
1349 {
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;
1361 }
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";
1371 }
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;
1380 }
1381 },
1382 },
1383 {
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;
1391 }
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;
1400 }
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;
1409 }
1410 },
1411 },
1412 {
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
1421 ;
1422 },
1423 },
1424 {
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
1431 ;
1432 },
1433 },
1434 {
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)
1442 ;
1443 },
1444 },
1445 {
1446 image: "text.png",
1447 title: "Font Loading",
1448 check: function(frames, symbols, meta) {
1449 return stepContains('gfxFontGroup::BuildFontList', frames, symbols);
1450 },
1451 },
1452 {
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)
1463 ;
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;
1472 }
1473 }
1474 return false;
1475 }
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;
1487 }
1488 }
1490 return null;
1491 }
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;
1506 }
1507 }
1508 }
1510 return null;
1511 }
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;
1526 }
1527 }
1528 }
1530 return null;
1531 }
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;
1539 }
1540 }
1541 return false;
1542 }
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;
1550 }
1551 }
1552 return false;
1553 }
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;
1565 }
1566 }
1567 }
1568 return false;
1569 }
1570 function firstMatch(array, matchFunction) {
1571 for (var i = 0; i < array.length; i++) {
1572 if (matchFunction(array[i]))
1573 return array[i];
1574 }
1575 return undefined;
1576 }
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;
1612 }
1614 diagnosticItems.push(currDiagnostic);
1616 pendingDiagnosticInfo = null;
1617 }
1619 /*
1620 dump("meta: " + meta.gcStats + "\n");
1621 if (meta && meta.gcStats) {
1622 dump("GC Stats: " + JSON.stringify(meta.gcStats) + "\n");
1623 }
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 });
1633 }
1635 if (!diagnostic) {
1636 finishPendingDiagnostic(x);
1637 return;
1638 }
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;
1647 }
1649 // We have left the old diagnostic and found a new one. Finish the old one.
1650 finishPendingDiagnostic(x);
1651 }
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);
1664 }