-
-
Save codehag/466a069158099cd28b244b94377f4d20 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; | |
| Components.utils.import("resource://gre/modules/osfile.jsm"); | |
| Components.utils.import("resource://gre/modules/Services.jsm"); | |
| var done = false; | |
| let gPaths = arguments.filter(a => !a.startsWith("-")); | |
| if (!gPaths.length) { | |
| gPaths = []; | |
| } | |
| function getMarkerTime(thread, name) { | |
| for (let [,,marker] of thread.markers.data) { | |
| if (!marker || !marker.name) continue; | |
| if (marker.name == name) | |
| return { | |
| start: marker.startTime, | |
| end: marker.endTime, | |
| }; | |
| } | |
| throw "marker not found: "+marker; | |
| } | |
| const decoder = new TextDecoder(); | |
| async function readProfile(path) { | |
| const data = decoder.decode(await OS.File.read(path)); | |
| print("parsed " + path); | |
| return JSON.parse(data); | |
| } | |
| /** EDIT THIS LINE **/ | |
| const markerName = "custom.jsdebugger.stepOut.DAMP"; | |
| (async function() { | |
| let currentDirectory = await OS.File.getCurrentDirectory(); | |
| let paths = gPaths.map(a => currentDirectory + "/" + a); | |
| if (!paths.length) { | |
| print("error, no path provided"); | |
| done = true; | |
| return; | |
| } | |
| if (paths.length != 2) { | |
| print("need exactly two profiles, got:" + paths.length); | |
| done = true | |
| return; | |
| } | |
| let json = await readProfile(paths[0]); | |
| let json2 = await readProfile(paths[1]); | |
| json.threads = json.threads.filter(t => t.name == "GeckoMain"); | |
| json2.threads = json2.threads.filter(t => t.name == "GeckoMain"); | |
| json.threads.push(json2.threads[0]); | |
| for (let thread of json.threads) { | |
| let { start, end } = getMarkerTime(thread, markerName); | |
| let samples = thread.samples; | |
| let timeCol = samples.schema.time; | |
| samples.data = samples.data.filter(s => { | |
| let t = s[timeCol]; | |
| return t >= start && t <= end; | |
| }); | |
| for (let d of samples.data) | |
| d[timeCol] -= start; | |
| thread.registerTime = 0; | |
| let markers = thread.markers; | |
| timeCol = markers.schema.time; | |
| markers.data = markers.data.filter(m => m[timeCol] >= start); | |
| for (let m of markers.data) | |
| m[timeCol] -= start; | |
| } | |
| json.processes = []; | |
| let threads = json.threads; | |
| threads[0].name = "First profile"; | |
| threads[1].name = "Second profile"; | |
| threads.push(JSON.parse(JSON.stringify(threads[0]))); | |
| threads[2].name = "Only first"; | |
| threads.push(JSON.parse(JSON.stringify(threads[1]))); | |
| threads[3].name = "Only second"; | |
| let onlyMax = threads[2]; | |
| let onlyMin = threads[3]; | |
| function getJSStack(thread, sample) { | |
| let result = []; | |
| let stackCol = thread.samples.schema.stack; | |
| let stackPrefixCol = thread.stackTable.schema.prefix; | |
| let stackFrameCol = thread.stackTable.schema.frame; | |
| let frameLocationCol = thread.frameTable.schema.location; | |
| let frameImplementationCol = thread.frameTable.schema.implementation; | |
| let stackId = sample[stackCol]; | |
| while (stackId !== null) { | |
| let stack = thread.stackTable.data[stackId]; | |
| let frame = thread.frameTable.data[stack[stackFrameCol]]; | |
| if (typeof frame[frameImplementationCol] == "number") | |
| result.push(thread.stringTable[frame[frameLocationCol]]); | |
| stackId = stack[stackPrefixCol]; | |
| } | |
| if (result.length > 10) | |
| result = result.slice(0, 10); | |
| return JSON.stringify(result); | |
| } | |
| function getStack(thread, sample) { | |
| let result = []; | |
| let jsresult = []; | |
| let stackCol = thread.samples.schema.stack; | |
| let stackPrefixCol = thread.stackTable.schema.prefix; | |
| let stackFrameCol = thread.stackTable.schema.frame; | |
| let frameLocationCol = thread.frameTable.schema.location; | |
| let frameImplementationCol = thread.frameTable.schema.implementation; | |
| let stackId = sample[stackCol]; | |
| while (stackId !== null) { | |
| let stack = thread.stackTable.data[stackId]; | |
| let frame = thread.frameTable.data[stack[stackFrameCol]]; | |
| let line = thread.stringTable[frame[frameLocationCol]]; | |
| if (line.includes(".js") || line.includes(".xml")) | |
| jsresult.push(line); | |
| result.push(line); | |
| stackId = stack[stackPrefixCol]; | |
| } | |
| if (jsresult.length > 2) { | |
| result = jsresult; | |
| // print(jsresult.toSource()); | |
| } | |
| if (result.length > 10) | |
| result = result.slice(0, 10); | |
| return JSON.stringify(result).replace(/0x[0-9a-f]{8}/g, "address"); | |
| } | |
| function getStackMap(thread) { | |
| let stackMap = new Map(); | |
| for (let sample of thread.samples.data) { | |
| let stack = getStack(thread, sample); | |
| stackMap.set(stack, (stackMap.get(stack) || 0) + 1); | |
| } | |
| return stackMap; | |
| } | |
| let maxMap = getStackMap(onlyMax); | |
| let minMap = getStackMap(onlyMin); | |
| print("maxMap.size = " + maxMap.size); | |
| print("minMap.size = " + minMap.size); | |
| print("onlyMax samples before: " + onlyMax.samples.data.length); | |
| onlyMax.samples.data = onlyMax.samples.data.filter(sample => { | |
| let stack = getStack(onlyMax, sample); | |
| let count = minMap.get(stack); | |
| if (count) { | |
| if (count == 1) | |
| minMap.delete(stack); | |
| else | |
| minMap.set(stack, count - 1); | |
| } | |
| return !count; | |
| }); | |
| print("onlyMax samples after: " + onlyMax.samples.data.length); | |
| print("onlyMin samples before: " + onlyMin.samples.data.length); | |
| onlyMin.samples.data = onlyMin.samples.data.filter(sample => { | |
| let stack = getStack(onlyMin, sample); | |
| let count = maxMap.get(stack); | |
| if (count) { | |
| if (count == 1) | |
| maxMap.delete(stack); | |
| else | |
| maxMap.set(stack, count - 1); | |
| } | |
| return !count; | |
| }); | |
| print("onlyMin samples after: " + onlyMin.samples.data.length); | |
| /* | |
| for (let [stack, count] of maxMap) { | |
| print(stack); | |
| } | |
| */ | |
| await OS.File.writeAtomic("merged-profile.json", (new TextEncoder()).encode(JSON.stringify(json))); | |
| done = true; | |
| })().catch(e => { | |
| dump(e + "\n"); | |
| done = true; | |
| }); | |
| // Spin an event loop. | |
| (() => { | |
| var thread = Components.classes["@mozilla.org/thread-manager;1"] | |
| .getService().currentThread; | |
| while (!done) | |
| thread.processNextEvent(true); | |
| // get rid of any pending event | |
| while (thread.hasPendingEvents()) | |
| thread.processNextEvent(true); | |
| })(); |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Alex's comment:
Florian did an experimental script to help comparing two profiles, I tweaked it to accomodate my usecase and I think that's a very good starting point that help prototype how we could compare stacks and frames!
Here is my fork, it is run like this:
$OBJDIR/dist/bin/run-mozilla.sh $OBJDIR/dist/bin/xpcshell compare-profile-alex.js p1.profile p2.profileYou can omit run-mozilla.sh thing if you are not on linux. The two profiles as arguments have to be Talos profile fetched directly from Talos, like this talos zip file.
This script does three things:
All the credits go to florian, his original script supports profile fetched from perf-html server rather than the one from talos and accept a folder as argument. It will filter the slowest and the fastest profile and compare them. It also contains hardcoded markers strings in it you will want to modify.