fune/browser/components/extensions/parent/ext-geckoProfiler.js
Markus Stange 7935e9f102 Bug 1509549 - Use ProfilerGetSymbols for geckoProfiler WebExtension symbolication and remove all other sources of symbolication. r=kmag
We will no longer parse Breakpad-generated .sym files, and we will no longer
call out to dump_syms.exe or nm.

This has a number of advantages:
 - It's simpler.
 - It's faster, especially for local builds on Windows.
 - It's more extensible. For example, we can now easily add code to the wasm
   module which consults debugging information in order to obtain filename +
   line information or inlined call stacks.
 - On Macs that don't have the Xcode command line tools installed, Xcode will
   no longer pop up a dialog because we no longer attempt to run 'nm'.

Depends on D13005

Differential Revision: https://phabricator.services.mozilla.com/D13006

--HG--
extra : moz-landing-system : lando
2019-02-07 19:37:21 +00:00

164 lines
5.3 KiB
JavaScript

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
ChromeUtils.defineModuleGetter(this, "ProfilerGetSymbols", "resource://app/modules/ProfilerGetSymbols.jsm");
const PREF_ASYNC_STACK = "javascript.options.asyncstack";
const ASYNC_STACKS_ENABLED = Services.prefs.getBoolPref(PREF_ASYNC_STACK, false);
var {
ExtensionError,
} = ExtensionUtils;
const symbolCache = new Map();
const primeSymbolStore = libs => {
for (const {path, debugName, debugPath, breakpadId} of libs) {
symbolCache.set(`${debugName}/${breakpadId}`, {path, debugPath});
}
};
const isRunningObserver = {
_observers: new Set(),
observe(subject, topic, data) {
switch (topic) {
case "profiler-started":
case "profiler-stopped":
// Call observer(false) or observer(true), but do it through a promise
// so that it's asynchronous.
// We don't want it to be synchronous because of the observer call in
// addObserver, which is asynchronous, and we want to get the ordering
// right.
const isRunningPromise = Promise.resolve(topic === "profiler-started");
for (let observer of this._observers) {
isRunningPromise.then(observer);
}
break;
}
},
_startListening() {
Services.obs.addObserver(this, "profiler-started");
Services.obs.addObserver(this, "profiler-stopped");
},
_stopListening() {
Services.obs.removeObserver(this, "profiler-started");
Services.obs.removeObserver(this, "profiler-stopped");
},
addObserver(observer) {
if (this._observers.size === 0) {
this._startListening();
}
this._observers.add(observer);
observer(Services.profiler.IsActive());
},
removeObserver(observer) {
if (this._observers.delete(observer) && this._observers.size === 0) {
this._stopListening();
}
},
};
this.geckoProfiler = class extends ExtensionAPI {
getAPI(context) {
return {
geckoProfiler: {
async start(options) {
const {bufferSize, windowLength, interval, features, threads} = options;
Services.prefs.setBoolPref(PREF_ASYNC_STACK, false);
if (threads) {
Services.profiler.StartProfiler(bufferSize, interval,
features, features.length,
threads, threads.length,
windowLength);
} else {
Services.profiler.StartProfiler(bufferSize, interval,
features, features.length,
[], 0,
windowLength);
}
},
async stop() {
if (ASYNC_STACKS_ENABLED !== null) {
Services.prefs.setBoolPref(PREF_ASYNC_STACK, ASYNC_STACKS_ENABLED);
}
Services.profiler.StopProfiler();
},
async pause() {
Services.profiler.PauseSampling();
},
async resume() {
Services.profiler.ResumeSampling();
},
async getProfile() {
if (!Services.profiler.IsActive()) {
throw new ExtensionError("The profiler is stopped. " +
"You need to start the profiler before you can capture a profile.");
}
return Services.profiler.getProfileDataAsync();
},
async getProfileAsArrayBuffer() {
if (!Services.profiler.IsActive()) {
throw new ExtensionError("The profiler is stopped. " +
"You need to start the profiler before you can capture a profile.");
}
return Services.profiler.getProfileDataAsArrayBuffer();
},
async getSymbols(debugName, breakpadId) {
if (symbolCache.size === 0) {
primeSymbolStore(Services.profiler.sharedLibraries);
}
const cachedLibInfo = symbolCache.get(`${debugName}/${breakpadId}`);
if (!cachedLibInfo) {
throw new Error(
`The library ${debugName} ${breakpadId} is not in the Services.profiler.sharedLibraries list, ` +
"so the local path for it is not known and symbols for it can not be obtained. " +
"This usually happens if a content process uses a library that's not used in the parent " +
"process - Services.profiler.sharedLibraries only knows about libraries in the parent process.");
}
const {path, debugPath} = cachedLibInfo;
if (!OS.Path.split(path).absolute) {
throw new Error(
`Services.profiler.sharedLibraries did not contain an absolute path for the library ${debugName} ${breakpadId}, ` +
"so symbols for this library can not be obtained.");
}
return ProfilerGetSymbols.getSymbolTable(path, debugPath, breakpadId);
},
onRunning: new EventManager({
context,
name: "geckoProfiler.onRunning",
register: fire => {
isRunningObserver.addObserver(fire.async);
return () => {
isRunningObserver.removeObserver(fire.async);
};
},
}).api(),
},
};
}
};