Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const PREF_ASYNC_STACK = "javascript.options.asyncstack";
const ASYNC_STACKS_ENABLED = Services.prefs.getBoolPref(
PREF_ASYNC_STACK,
false
);
var { ExtensionError } = ExtensionUtils;
ChromeUtils.defineLazyGetter(this, "symbolicationService", () => {
let { createLocalSymbolicationService } = ChromeUtils.importESModule(
"resource://devtools/client/performance-new/shared/symbolication.sys.mjs"
);
return createLocalSymbolicationService(Services.profiler.sharedLibraries, []);
});
const isRunningObserver = {
_observers: new Set(),
observe(subject, topic) {
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,
threads,
0,
windowLength
);
} else {
Services.profiler.StartProfiler(
bufferSize,
interval,
features,
[],
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.Pause();
},
async resume() {
Services.profiler.Resume();
},
async dumpProfileToFile(fileName) {
if (!Services.profiler.IsActive()) {
throw new ExtensionError(
"The profiler is stopped. " +
"You need to start the profiler before you can capture a profile."
);
}
if (fileName.includes("\\") || fileName.includes("/")) {
throw new ExtensionError("Path cannot contain a subdirectory.");
}
let dirPath = PathUtils.join(PathUtils.profileDir, "profiler");
let filePath = PathUtils.join(dirPath, fileName);
try {
await IOUtils.makeDirectory(dirPath);
await Services.profiler.dumpProfileToFileAsync(filePath);
} catch (e) {
Cu.reportError(e);
throw new ExtensionError(`Dumping profile to ${filePath} failed.`);
}
},
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 getProfileAsGzippedArrayBuffer() {
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.getProfileDataAsGzippedArrayBuffer();
},
async getSymbols(debugName, breakpadId) {
return symbolicationService.getSymbolTable(debugName, breakpadId);
},
onRunning: new EventManager({
context,
name: "geckoProfiler.onRunning",
register: fire => {
isRunningObserver.addObserver(fire.async);
return () => {
isRunningObserver.removeObserver(fire.async);
};
},
}).api(),
},
};
}
};