Bug 1307227 - Copy over the Gecko Profiler Addon files; r=julienw

This bug's first commit will be a non-working revision.

These files are taken from:

68d5de9c9f

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Greg Tatum 2019-05-31 16:07:09 +00:00
parent 72973df697
commit f59b172f85
5 changed files with 1287 additions and 0 deletions

View File

@ -0,0 +1,306 @@
/* global browser */
const DEFAULT_VIEWER_URL = "https://profiler.firefox.com";
const DEFAULT_WINDOW_LENGTH = 20; // 20sec
const tabToConnectionMap = new Map();
var profilerState;
var profileViewerURL = DEFAULT_VIEWER_URL;
var isMigratedToNewUrl;
function adjustState(newState) {
// Deep clone the object, since this can be called through popup.html,
// which can be unloaded thus leaving this object dead.
newState = JSON.parse(JSON.stringify(newState));
Object.assign(window.profilerState, newState);
browser.storage.local.set({ profilerState: window.profilerState });
}
function makeProfileAvailableToTab(profile, port) {
port.postMessage({ type: "ProfilerConnectToPage", payload: profile });
port.onMessage.addListener(async message => {
if (message.type === "ProfilerGetSymbolTable") {
const { debugName, breakpadId } = message;
try {
const [
addresses,
index,
buffer,
] = await browser.geckoProfiler.getSymbols(debugName, breakpadId);
port.postMessage({
type: "ProfilerGetSymbolTableReply",
status: "success",
result: [addresses, index, buffer],
debugName,
breakpadId,
});
} catch (e) {
port.postMessage({
type: "ProfilerGetSymbolTableReply",
status: "error",
error: `${e}`,
debugName,
breakpadId,
});
}
}
});
}
async function createAndWaitForTab(url) {
const tabPromise = browser.tabs.create({
active: true,
url,
});
return tabPromise;
}
function getProfilePreferablyAsArrayBuffer() {
// This is a compatibility wrapper for Firefox builds from before 1362800
// landed. We can remove it once Nightly switches to 56.
if ("getProfileAsArrayBuffer" in browser.geckoProfiler) {
return browser.geckoProfiler.getProfileAsArrayBuffer();
}
return browser.geckoProfiler.getProfile();
}
async function captureProfile() {
// Pause profiler before we collect the profile, so that we don't capture
// more samples while the parent process waits for subprocess profiles.
await browser.geckoProfiler.pause().catch(() => {});
const profilePromise = getProfilePreferablyAsArrayBuffer().catch(
e => {
console.error(e);
return {};
}
);
const tabOpenPromise = createAndWaitForTab(profileViewerURL + "/from-addon");
try {
const [profile, tab] = await Promise.all([profilePromise, tabOpenPromise]);
const connection = tabToConnectionMap.get(tab.id);
if (connection) {
// If, for instance, it takes a long time to load the profile,
// then our onDOMContentLoaded handler and our runtime.onConnect handler
// have already connected to the page. All we need to do then is
// provide the profile.
makeProfileAvailableToTab(profile, connection.port);
} else {
// If our onDOMContentLoaded handler and our runtime.onConnect handler
// haven't connected to the page, set this so that they'll have a
// profile they can provide once they do.
tabToConnectionMap.set(tab.id, { profile });
}
} catch (e) {
console.error(e);
// const { tab } = await tabOpenPromise;
// TODO data URL doesn't seem to be working. Permissions issue?
// await browser.tabs.update(tab.id, { url: `data:text/html,${encodeURIComponent(e.toString)}` });
}
try {
await browser.geckoProfiler.resume();
} catch (e) {
console.error(e);
}
}
/**
* Not all features are supported on every version of Firefox. Get the list of checked
* features, add a few defaults, and filter for what is actually supported.
*/
function getEnabledFeatures(features, threads) {
const enabledFeatures = Object.keys(features).filter(f => features[f]);
if (threads.length > 0) {
enabledFeatures.push("threads");
}
const supportedFeatures = Object.values(
browser.geckoProfiler.ProfilerFeature
);
return enabledFeatures.filter(feature => supportedFeatures.includes(feature));
}
async function startProfiler() {
const settings = window.profilerState;
const threads = settings.threads.split(",");
const options = {
bufferSize: settings.buffersize,
interval: settings.interval,
features: getEnabledFeatures(settings.features, threads),
threads,
};
if (
browser.geckoProfiler.supports &&
browser.geckoProfiler.supports.WINDOWLENGTH
) {
options.windowLength =
settings.windowLength !== settings.infiniteWindowLength
? settings.windowLength
: 0;
}
await browser.geckoProfiler.start(options);
}
async function stopProfiler() {
await browser.geckoProfiler.stop();
}
/* exported restartProfiler */
async function restartProfiler() {
await stopProfiler();
await startProfiler();
}
(async () => {
const storageResults = await browser.storage.local.get({
profilerState: null,
profileViewerURL: DEFAULT_VIEWER_URL,
// This value is to check whether or not folks have been migrated from
// perf-html.io to profiler.firefox.com
isMigratedToNewUrl: false,
});
// Assign to global variables:
window.profilerState = storageResults.profilerState;
window.profileViewerURL = storageResults.profileViewerURL;
if (!storageResults.isMigratedToNewUrl) {
if (window.profileViewerURL.startsWith("https://perf-html.io")) {
// This user needs to be migrated from perf-html.io to profiler.firefox.com.
// This is only done one time.
window.profileViewerURL = DEFAULT_VIEWER_URL;
}
// Store the fact that this migration check has been done, and optionally update
// the url if it was changed.
await browser.storage.local.set({
isMigratedToNewUrl: true,
profileViewerURL: window.profileViewerURL,
});
}
if (!window.profilerState) {
window.profilerState = {};
const features = {
java: false,
js: true,
leaf: true,
mainthreadio: false,
memory: false,
privacy: false,
responsiveness: true,
screenshots: false,
seqstyle: false,
stackwalk: true,
tasktracer: false,
trackopts: false,
jstracer: false,
};
const platform = await browser.runtime.getPlatformInfo();
switch (platform.os) {
case "mac":
// Screenshots are currently only working on mac.
features.screenshots = true;
break;
case "android":
// Java profiling is only meaningful on android.
features.java = true;
break;
}
adjustState({
isRunning: false,
settingsOpen: false,
buffersize: 10000000, // 90MB
windowLength: DEFAULT_WINDOW_LENGTH,
interval: 1,
features,
threads: "GeckoMain,Compositor",
});
} else if (window.profilerState.windowLength === undefined) {
// We have `windowprofilerState` but no `windowLength`.
// That means we've upgraded the gecko profiler addon from an older version.
// Adding the default window legth in that case.
adjustState({
windowLength: DEFAULT_WINDOW_LENGTH,
});
}
browser.geckoProfiler.onRunning.addListener(isRunning => {
adjustState({ isRunning });
// With "path: null" we'll get the default icon for the browser action, which
// is theme-aware.
// The on state does not need to be theme-aware because we want to highlight
// the icon in blue regardless of whether a dark or a light theme is in use.
browser.browserAction.setIcon({
path: isRunning ? "icons/toolbar_on.png" : null,
});
browser.browserAction.setTitle({
title: isRunning ? "Gecko Profiler (on)" : null,
});
for (const popup of browser.extension.getViews({ type: "popup" })) {
popup.renderState(window.profilerState);
}
});
browser.storage.onChanged.addListener(changes => {
if (changes.profileViewerURL) {
profileViewerURL = changes.profileViewerURL.newValue;
}
});
browser.commands.onCommand.addListener(command => {
if (command === "ToggleProfiler") {
if (window.profilerState.isRunning) {
stopProfiler();
} else {
startProfiler();
}
} else if (command === "CaptureProfile") {
if (window.profilerState.isRunning) {
captureProfile();
}
}
});
browser.runtime.onConnect.addListener(port => {
const tabId = port.sender.tab.id;
const connection = tabToConnectionMap.get(tabId);
if (connection && connection.profile) {
makeProfileAvailableToTab(connection.profile, port);
} else {
tabToConnectionMap.set(tabId, { port });
}
});
browser.tabs.onRemoved.addListener(tabId => {
tabToConnectionMap.delete(tabId);
});
browser.webNavigation.onDOMContentLoaded.addListener(
async ({ frameId, tabId, url }) => {
if (frameId !== 0) {
return;
}
if (url.startsWith(profileViewerURL)) {
browser.tabs.executeScript(tabId, { file: "content.js" });
} else {
// As soon as we navigate away from the profile report, clean
// this up so we don't leak it.
tabToConnectionMap.delete(tabId);
}
}
);
})();

View File

@ -0,0 +1,11 @@
<!-- 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/. -->
<!-- This file was taken from command-screenshot.svg from the Firefox devtools.
- The camera metaphor doesn't really work well, and the fact that we're
- re-using an icon for a completely different use isn't great either. If
- anybody has any ideas for a replacement, I'd love to hear them. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="#0b0b0b">
<path d="M13.6 5H11V3.5C11 2.7 10 2 9.2 2H6.8C6 2 5 2.7 5 3.5V5H2.4C1.6 5 1 5.6 1 6.4v6.5c0 .8.6 1.1 1.4 1.1h11.2c.8 0 1.4-.3 1.4-1.1V6.4c0-.8-.6-1.4-1.4-1.4zm.4 8H2V6h4V3h4v3h3.9l.1 7z"/>
<path d="M8 6.8c-1.3 0-2.4 1.1-2.4 2.4s1.1 2.4 2.4 2.4 2.4-1.1 2.4-2.4c0-1.3-1.1-2.4-2.4-2.4zm0 3.5c-.7 0-1.2-.5-1.2-1.1S7.3 8.1 8 8.1s1.2.5 1.2 1.1-.5 1.1-1.2 1.1z"/>
</svg>

After

Width:  |  Height:  |  Size: 960 B

View File

@ -0,0 +1,407 @@
html {
background: rgb(250,250,250);
font: message-box;
-moz-user-select: none;
cursor: default;
overflow: auto;
}
* {
flex-shrink: 0;
}
body {
margin: 0;
/**
* We had to use `max-width` instead of `width` here because of a bug on Firefox
* that causes a horizontal scrollbar when there is a vertical scrollbar on Linux.
* This is not a problem on platforms that have a floating scrollbar that doesn't
* take additional space, but on platforms that we don't have floating scrollbar,
* it takes some space from the body element and causes horizontal scrollbar to appear.
* See: https://bugzilla.mozilla.org/show_bug.cgi?id=1400279
*/
max-width: 32em;
}
.status-display {
margin: 0;
padding: 10px 4px;
position: relative;
}
.status-display::before {
content: '';
display: inline-block;
width: 17px;
height: 17px;
vertical-align: top;
margin: -2px 3px -20px;
}
:root.status-stopped .status-display:not(.status-display-stopped) { display: none; }
:root.status-running .status-display:not(.status-display-running) { display: none; }
.status-display-stopped {
background: hsl(210, 80%, 85%);
}
.status-display-running {
background: hsl(90, 70%, 70%);
}
.status-display-stopped::before {
background: hsl(210, 70%, 60%);
}
.status-display-running::before {
background: hsl(90, 80%, 40%);
border-radius: 100%;
}
.status-display-button {
position: absolute;
top: 7px;
right: 7px;
padding: 2.5px;
border: 0.5px solid transparent;
border-radius: 2px;
}
.status-display-button:hover {
box-shadow: inset 0 0.5px hsla(0,0%,100%,0.3);
color: black;
}
.status-display-button:hover:active {
box-shadow: inset 0 2px 5px rgba(0,0,0,0.15);
}
.button-start {
border-color: hsl(210, 30%, 60%);
background: rgba(0,0,0,0.08);
color: hsl(210, 80%, 20%);
}
.button-cancel {
border-color: hsl(90, 40%, 50%);
background: rgba(0,0,0,0.08);
color: hsl(90, 80%, 20%);
}
.button-start:hover {
border-color: hsl(90, 40%, 50%);
background: linear-gradient(hsl(90, 70%, 68%), hsl(90, 70%, 58%));
color: hsl(90, 80%, 20%);
}
.button-cancel:hover {
background: linear-gradient(hsl(0, 95%, 68%), hsl(0, 95%, 58%));
border-color: hsl(0, 90%, 30%);
}
.button-start:hover:active {
background: linear-gradient(hsl(90, 70%, 50%), hsl(90, 70%, 45%));
}
.button-cancel:hover:active {
background: linear-gradient(hsl(0, 90%, 50%), hsl(0, 90%, 45%));
}
#button-capture {
margin: 6px 5px 5px;
height: auto;
text-align: left;
padding: 5px 2px;
padding-left: 32px;
border: 1px solid #DDD;
box-shadow: 0 1px rgba(0,0,0,0.3);
border-radius: 5px;
background: linear-gradient(#FAFAFA, #EEE);
display: block;
position: relative;
}
:root.status-stopped #button-capture {
color: GrayText;
}
:root.status-running #button-capture:hover {
background: linear-gradient(#EEE, #DDD);
}
:root.status-running #button-capture:hover:active {
background: #CCC;
border-color: #BBB;
}
#capture-label {
display: flex;
flex-flow: row nowrap;
margin-bottom: 4px;
font-size: 14px;
}
#capture-label::before {
content: '';
position: absolute;
width: 16px;
height: 16px;
background: url(icons/capture-profile-icon.svg);
top: 6px;
left: 10px;
}
:root.status-stopped #capture-label::before {
opacity: 0.5;
}
.keyboard-hint {
font-size: 1rem;
font-weight: normal;
flex: 1;
text-align: center;
}
kbd {
font-size: 10px;
background-color: hsla(0,0%,100%,0.4);
border: 1px solid #CCC;
border-radius: 0.2em;
display: inline-block;
padding: 0.1em 0.35em;
box-shadow: 0 0.1em 0 #BBB;
margin: 0 0.15em;
font-family: inherit;
}
#help-capture {
margin: 0;
}
.info-density {
margin: 4px 12px;
display: grid;
grid-template-columns: 7em auto;
grid-template-rows: auto;
}
.info-density > dt {
grid-column: 1;
padding: 2px 0;
}
.info-density > dd {
grid-column: 2;
margin: 0;
padding: 5px 0 3px;
}
.settings {
background: #FFFFFF;
}
.settings:not(.open) > .settings-content,
.settings:not(.open) > .settings-apply-button-wrapper {
display: none;
}
#settings-label {
margin: 0;
padding: 4px 10px;
font-size: 14px;
font-weight: bold;
background: #EEE;
}
#settings-label:hover {
background: #DDD;
}
#settings-label:hover:active {
background: #CCC;
}
#settings-label::before {
content: '';
display: inline-block;
border-left: 9px solid #BBB;
border-top: 5px solid transparent;
border-bottom: 5px solid transparent;
margin-right: 5px;
}
.settings.open > #settings-label::before {
transform: rotate(90deg);
}
.discrete-level {
display: flex;
height: 100%;
flex-flow: row nowrap;
justify-content: space-between;
}
.discrete-level-notch {
flex: 1;
margin-right: 1px;
border: 1px solid rgba(0,0,0,0.2);
border-radius: 2px;
}
.discrete-level-notch.normal.active {
border-color: hsl(90, 90%, 40%);
background-color: hsla(90, 90%, 40%, 0.5);
}
.discrete-level-notch.warning.active {
border-color: hsl(45, 100%, 49%);
background-color: hsla(45, 100%, 49%, 0.5);
}
.discrete-level-notch.critical.active {
border-color: hsl(0, 90%, 40%);
background-color: hsla(0, 90%, 40%, 0.5);
}
.settings-content {
margin: 0;
padding: 0 10px 18px;
line-height: 22px;
display: grid;
grid-template-columns: 8em auto;
grid-template-rows: auto;
}
.settings-setting-label {
margin: 0;
font-size: 100%;
font-weight: normal;
}
.range-with-value {
display: flex;
flex-flow: row nowrap;
}
body.no-windowlength .settings-setting-label.windowlength,
body.no-windowlength .range-with-value.windowlength {
display: none;
}
.range-input {
margin: 0;
width: 0;
flex: 1;
}
.range-value {
margin-left: 10px;
width: 4em;
white-space: nowrap;
flex-shrink: 0;
}
.settings-textbox {
min-width: 0;
}
.features-list {
margin: 0;
padding: 2px 0 0;
line-height: 20px;
}
.features-list > li {
display: block;
margin: 0;
padding: 0;
}
.settings-apply-button-wrapper {
padding: 4px 10px;
margin: 0;
text-align: right;
bottom: 0;
right: 0;
position: fixed;
width: 100%;
background: #eee;
}
.perf-settings-row {
display: flex;
overflow: hidden;
line-height: 1.8;
}
.perf-settings-row.focused {
background-color: #0074e8;
color: #ffffff;
}
.perf-settings-text-input {
width: 100%;
padding: 4px;
box-sizing: border-box;
}
.perf-settings-text-label {
flex: 1;
}
.perf-settings-details-contents {
padding: 4px;
margin: 0 0 18px;
border: #ededf0 1px solid;
background-color: #f9f9fa;
}
.perf-settings-details {
grid-column-start: 1;
grid-column-end: 3;
}
.perf-settings-summary {
height: 30px;
cursor: default;
-moz-user-select: none;
}
.perf-settings-thread-columns {
margin-bottom: 20px;
display: flex;
line-height: 2;
}
.perf-settings-thread-column {
flex: 1;
}
.perf-settings-checkbox-label {
display: block;
}
.perf-settings-feature-label {
margin: 8px 0;
display: flex;
flex-wrap: wrap;
}
.perf-settings-checkbox {
align-self: flex-start;
}
.perf-settings-feature-title {
margin-left: 20px;
flex: 1 100%;
line-height: 1.6;
}
.perf-settings-feature-name {
color: #0060df;
line-height: 1.6;
}
.perf-settings-subtext {
font-weight: bold;
}

View File

@ -0,0 +1,178 @@
<!DOCTYPE html>
<html class="status-running">
<head>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="popup.css">
</head>
<body>
<p class="status-display status-display-running">
The profiler is recording.
<input type="button" class="status-display-button button-cancel" value="Discard &amp; Stop">
</p>
<p class="status-display status-display-stopped">
The profiler is stopped.
<input type="button" class="status-display-button button-start" value="Start">
</p>
<button id="button-capture">
<strong id="capture-label">Capture Profile <span class="keyboard-hint"><kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>2</kbd></span></strong>
<p id="help-capture">Capture the current contents of the profile buffer and open the profile in a new tab.</p>
</button>
<dl class="info-density">
<dt>Overhead:</dt>
<dd>
<div class="discrete-level">
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch normal active"></span>
<span class="discrete-level-notch warning active"></span>
<span class="discrete-level-notch warning active"></span>
<span class="discrete-level-notch warning active"></span>
<span class="discrete-level-notch warning active"></span>
<span class="discrete-level-notch warning inactive"></span>
<span class="discrete-level-notch warning inactive"></span>
<span class="discrete-level-notch warning inactive"></span>
<span class="discrete-level-notch critical inactive"></span>
<span class="discrete-level-notch critical inactive"></span>
<span class="discrete-level-notch critical inactive"></span>
<span class="discrete-level-notch critical inactive"></span>
<span class="discrete-level-notch critical inactive"></span>
<span class="discrete-level-notch critical inactive"></span>
</div>
</dd>
</dl>
<section class="settings">
<h1 id="settings-label">Settings</h1>
<section class="settings-content">
<h1 class="settings-setting-label">Interval:</h1>
<span class="range-with-value">
<input type="range" class="range-input interval-range" min="0" max="100">
<span class="range-value interval-value">1 ms</span>
</span>
<h1 class="settings-setting-label windowlength">Window length:</h1>
<span class="range-with-value windowlength">
<input type="range" class="range-input windowlength-range" min="0" max="100">
<span class="range-value windowlength-value">20 sec</span>
</span>
<h1 class="settings-setting-label">Buffer size:</h1>
<span class="range-with-value">
<input type="range" class="range-input buffersize-range" min="0" max="100">
<span class="range-value buffersize-value">90 MB</span>
</span>
<div class="perf-settings-details">
<summary class="perf-settings-summary" id="perf-settings-threads-summary">Threads:</summary>
<div class="perf-settings-details-contents">
<div class="perf-settings-thread-columns">
<div class="perf-settings-thread-column"><label class="perf-settings-checkbox-label" title="The main processes for both the parent process, and content processes"><input
class="perf-settings-checkbox" id="perf-settings-thread-checkbox-gecko-main" type="checkbox" value="GeckoMain" />GeckoMain</label><label
class="perf-settings-checkbox-label" title="Composites together different painted elements on the page."><input
class="perf-settings-checkbox" id="perf-settings-thread-checkbox-compositor" type="checkbox" value="Compositor" />Compositor</label><label
class="perf-settings-checkbox-label" title="This handle both web workers and service workers"><input class="perf-settings-checkbox"
id="perf-settings-thread-checkbox-dom-worker" type="checkbox" value="DOM Worker" />DOM Worker</label><label
class="perf-settings-checkbox-label" title="When WebRender is enabled, the thread that executes OpenGL calls"><input
class="perf-settings-checkbox" id="perf-settings-thread-checkbox-renderer" type="checkbox" value="Renderer" />Renderer</label></div>
<div class="perf-settings-thread-column"><label class="perf-settings-checkbox-label" title="The WebRender RenderBackend thread"><input
class="perf-settings-checkbox" id="perf-settings-thread-checkbox-render-backend" type="checkbox" value="RenderBackend" />RenderBackend</label><label
class="perf-settings-checkbox-label" title="When off-main-thread painting is enabled, the thread on which painting happens"><input
class="perf-settings-checkbox" id="perf-settings-thread-checkbox-paint-worker" type="checkbox" value="PaintWorker" />PaintWorker</label><label
class="perf-settings-checkbox-label" title="Style computation is split into multiple threads"><input class="perf-settings-checkbox"
id="perf-settings-thread-checkbox-style-thread" type="checkbox" value="StyleThread" />StyleThread</label><label
class="perf-settings-checkbox-label" title="The thread where networking code runs any blocking socket calls"><input
class="perf-settings-checkbox" id="perf-settings-thread-checkbox-socket-thread" type="checkbox" value="Socket Thread" />Socket
Thread</label></div>
<div class="perf-settings-thread-column"><label class="perf-settings-checkbox-label" title="TODO"><input class="perf-settings-checkbox"
id="perf-settings-thread-checkbox-stream-trans" type="checkbox" value="StreamTrans" />StreamTrans</label><label
class="perf-settings-checkbox-label" title="Image decoding threads"><input class="perf-settings-checkbox"
id="perf-settings-thread-checkbox-img-decoder" type="checkbox" value="ImgDecoder" />ImgDecoder</label><label
class="perf-settings-checkbox-label" title="DNS resolution happens on this thread"><input class="perf-settings-checkbox"
id="perf-settings-thread-checkbox-dns-resolver" type="checkbox" value="DNS Resolver" />DNS Resolver</label></div>
</div>
<div class="perf-settings-row"><label class="perf-settings-text-label" title="These thread names are a comma separated list that is used to enable profiling of the threads in the profiler. The name needs to be only a partial match of the thread name to be included. It is whitespace sensitive.">
<div>Add custom threads by name:</div><input class="perf-settings-text-input" id="perf-settings-thread-text"
type="text" value="GeckoMain,Compositor" />
</label></div>
</div>
</div>
<div class="perf-settings-details">
<summary class="perf-settings-summary" id="perf-settings-features-summary">Features:</summary>
<div class="perf-settings-details-contents">
<label class="perf-settings-checkbox-label perf-settings-feature-label"><input
class="perf-settings-checkbox" id="perf-settings-feature-checkbox-stackwalk" type="checkbox" value="stackwalk" />
<div class="perf-settings-feature-name">Native Stacks</div>
<div class="perf-settings-feature-title">Record native stacks (C++ and Rust). This is not available on all
platforms.<span class="perf-settings-subtext"> (Recommended on by default.)</span></div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-js" type="checkbox" value="js" />
<div class="perf-settings-feature-name">JavaScript</div>
<div class="perf-settings-feature-title">Record JavaScript stack information, and interleave it with native
stacks.<span class="perf-settings-subtext"> (Recommended on by default.)</span></div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label" id="java"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-java" type="checkbox" value="java" />
<div class="perf-settings-feature-name">Java</div>
<div class="perf-settings-feature-title">Profile Java code.</div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-responsiveness" type="checkbox" value="responsiveness" />
<div class="perf-settings-feature-name">Responsiveness</div>
<div class="perf-settings-feature-title">Collect thread responsiveness information.<span class="perf-settings-subtext">
(Recommended on by default.)</span></div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-leaf" type="checkbox" value="leaf" />
<div class="perf-settings-feature-name">Native Leaf Stack</div>
<div class="perf-settings-feature-title">Record the native memory address of the leaf-most stack. This could be
useful on platforms that do not support stack walking.<span class="perf-settings-subtext"> (Recommended on by
default.)</span></div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-screenshots" type="checkbox" value="screenshots" />
<div class="perf-settings-feature-name">Screenshots</div>
<div class="perf-settings-feature-title">Capture screenshots of browser windows.</div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-mainthreadio" type="checkbox" value="mainthreadio" />
<div class="perf-settings-feature-name">Main Thread IO</div>
<div class="perf-settings-feature-title">Record main thread I/O markers.</div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-memory" type="checkbox" value="memory" />
<div class="perf-settings-feature-name">Memory</div>
<div class="perf-settings-feature-title">Add memory measurements to the samples, this includes resident set
size (RSS) and unique set size (USS).</div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-privacy" type="checkbox" value="privacy" />
<div class="perf-settings-feature-name">Privacy</div>
<div class="perf-settings-feature-title">Remove some potentially user-identifiable information.</div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-seqstyle" type="checkbox" value="seqstyle" />
<div class="perf-settings-feature-name">Sequential Styling</div>
<div class="perf-settings-feature-title">Disable parallel traversal in styling.</div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-trackopts" type="checkbox" value="trackopts" />
<div class="perf-settings-feature-name">JIT Optimizations</div>
<div class="perf-settings-feature-title">Track JIT optimizations in the JS engine.</div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-tasktracer" type="checkbox" value="tasktracer" />
<div class="perf-settings-feature-name">TaskTracer</div>
<div class="perf-settings-feature-title">Enable TaskTracer (Experimental, requires custom build.)</div>
</label><label class="perf-settings-checkbox-label perf-settings-feature-label"><input class="perf-settings-checkbox"
id="perf-settings-feature-checkbox-jstracer" type="checkbox" value="jstracer" />
<div class="perf-settings-feature-name">JSTracer</div>
<div class="perf-settings-feature-title">Trace JS engine (Experimental, requires custom build.)</div>
</label>
</div>
</div>
</section>
<section class="settings-apply-button-wrapper"><input type="button" class="settings-apply-button" value="Apply (Restart Profiler)"></section>
</section>
<script src="popup.js"></script>
</body>
</html>

View File

@ -0,0 +1,385 @@
/* global browser */
const intervalScale = makeExponentialScale(0.01, 100);
const buffersizeScale = makeExponentialScale(10000, 100000000);
// Window Length accepts a numerical value between 1-N. We also need to put an
// infinite number at the end of the window length slider. Therefore, the max
// value pretends like it's infinite in the slider.
// The maximum value of window length is 300 seconds. For that reason, we are
// treating 400 as infinity.
const infiniteWindowLength = 400;
const windowLengthScale = makeExponentialScale(1, infiniteWindowLength);
const PROFILE_ENTRY_SIZE = 9; // sizeof(double) + sizeof(char), http://searchfox.org/mozilla-central/rev/e8835f52eff29772a57dca7bcc86a9a312a23729/tools/profiler/core/ProfileEntry.h#73
const featurePrefix = "perf-settings-feature-checkbox-";
const features = [
"java",
"js",
"leaf",
"mainthreadio",
"memory",
"privacy",
"responsiveness",
"screenshots",
"seqstyle",
"stackwalk",
"tasktracer",
"jstracer",
"trackopts",
];
const threadPrefix = "perf-settings-thread-checkbox-";
// A map of element ID suffixes to their corresponding profile thread name, for
// creating ID -> name mappings, e.g.`$threadPrefix}dom-worker` - DOM Worker.
const threadMap = {
compositor: "Compositor",
"dns-resolver": "DNS Resolver",
"dom-worker": "DOM Worker",
"gecko-main": "GeckoMain",
"img-decoder": "ImgDecoder",
"paint-worker": "PaintWorker",
"render-backend": "RenderBackend",
renderer: "Renderer",
"socket-thread": "Socket Thread",
"stream-trans": "StreamTrans",
"style-thread": "StyleThread",
};
function renderState(state) {
const { isRunning, settingsOpen, interval, buffersize, windowLength } = state;
document.documentElement.classList.toggle("status-running", isRunning);
document.documentElement.classList.toggle("status-stopped", !isRunning);
document.querySelector(".settings").classList.toggle("open", settingsOpen);
document.querySelector(".interval-value").textContent = `${interval} ms`;
document.querySelector(".buffersize-value").textContent = prettyBytes(
buffersize * PROFILE_ENTRY_SIZE
);
document.querySelector(".windowlength-value").textContent = windowLength ===
infiniteWindowLength
? ``
: `${windowLength} sec`;
const overhead = calculateOverhead(state);
const overheadDiscreteContainer = document.querySelector(".discrete-level");
for (let i = 0; i < overheadDiscreteContainer.children.length; i++) {
const discreteLevelNotch = overheadDiscreteContainer.children[i];
const isActive =
i <=
Math.round(overhead * (overheadDiscreteContainer.children.length - 1));
discreteLevelNotch.classList.toggle("active", isActive);
discreteLevelNotch.classList.toggle("inactive", !isActive);
}
renderControls(state);
}
function renderControls(state) {
document.querySelector(".interval-range").value =
intervalScale.fromValueToFraction(state.interval) * 100;
document.querySelector(".buffersize-range").value =
buffersizeScale.fromValueToFraction(state.buffersize) * 100;
document.querySelector(".windowlength-range").value =
windowLengthScale.fromValueToFraction(state.windowLength) * 100;
for (let name of features) {
document.getElementById(featurePrefix + name).value = state[name];
}
for (let name in threadMap) {
document.getElementById(
threadPrefix + name
).checked = state.threads.includes(threadMap[name]);
}
document.querySelector("#perf-settings-thread-text").value = state.threads;
}
function clamp(val, min, max) {
return Math.max(min, Math.min(max, val));
}
function lerp(frac, rangeStart, rangeEnd) {
return (1 - frac) * rangeStart + frac * rangeEnd;
}
function scaleRangeWithClamping(
val,
sourceRangeStart,
sourceRangeEnd,
destRangeStart,
destRangeEnd
) {
const frac = clamp(
(val - sourceRangeStart) / (sourceRangeEnd - sourceRangeStart),
0,
1
);
return lerp(frac, destRangeStart, destRangeEnd);
}
function calculateOverhead(state) {
const overheadFromSampling =
scaleRangeWithClamping(
Math.log(state.interval),
Math.log(0.05),
Math.log(1),
1,
0
) +
scaleRangeWithClamping(
Math.log(state.interval),
Math.log(1),
Math.log(100),
0.1,
0
);
const overheadFromBuffersize = scaleRangeWithClamping(
Math.log(state.buffersize),
Math.log(10),
Math.log(1000000),
0,
0.1
);
const overheadFromStackwalk = state.stackwalk ? 0.05 : 0;
const overheadFromResponsiveness = state.responsiveness ? 0.05 : 0;
const overheadFromJavaScrpt = state.js ? 0.05 : 0;
const overheadFromSeqStyle = state.seqstyle ? 0.05 : 0;
const overheadFromTaskTracer = state.tasktracer ? 0.05 : 0;
const overheadFromJSTracer = state.jstracer ? 0.05 : 0;
return clamp(
overheadFromSampling +
overheadFromBuffersize +
overheadFromStackwalk +
overheadFromResponsiveness +
overheadFromJavaScrpt +
overheadFromSeqStyle +
overheadFromTaskTracer +
overheadFromJSTracer,
0,
1
);
}
function getBackground() {
return browser.runtime.getBackgroundPage();
}
document.querySelector(".button-start").addEventListener("click", async () => {
const background = await getBackground();
await background.startProfiler();
background.adjustState({ isRunning: true });
renderState(background.profilerState);
});
document.querySelector(".button-cancel").addEventListener("click", async () => {
const background = await getBackground();
await background.stopProfiler();
background.adjustState({ isRunning: false });
renderState(background.profilerState);
});
document
.querySelector("#button-capture")
.addEventListener("click", async () => {
if (document.documentElement.classList.contains("status-running")) {
const background = await getBackground();
await background.captureProfile();
window.close();
}
});
document
.querySelector("#settings-label")
.addEventListener("click", async () => {
const background = await getBackground();
background.adjustState({
settingsOpen: !background.profilerState.settingsOpen,
});
renderState(background.profilerState);
});
document.querySelector(".interval-range").addEventListener("input", async e => {
const background = await getBackground();
const frac = e.target.value / 100;
background.adjustState({
interval: intervalScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.profilerState);
});
document
.querySelector(".buffersize-range")
.addEventListener("input", async e => {
const background = await getBackground();
const frac = e.target.value / 100;
background.adjustState({
buffersize: buffersizeScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.profilerState);
});
document
.querySelector(".windowlength-range")
.addEventListener("input", async e => {
const background = await getBackground();
const frac = e.target.value / 100;
background.adjustState({
windowLength: windowLengthScale.fromFractionToSingleDigitValue(frac),
});
renderState(background.profilerState);
});
window.onload = async () => {
if (
!browser.geckoProfiler.supports ||
!browser.geckoProfiler.supports.WINDOWLENGTH
) {
document.body.classList.add("no-windowlength");
}
// Letting the background script know how the infiniteWindowLength is represented.
const background = await getBackground();
background.adjustState({
infiniteWindowLength,
});
};
/**
* This helper initializes and adds listeners to the features checkboxes that
* will adjust the profiler state when changed.
*/
async function setupFeatureCheckbox(name) {
const platform = await browser.runtime.getPlatformInfo();
// Java profiling is only meaningful on android.
if (name == "java") {
if (platform.os !== "android") {
document.querySelector("#java").style.display = "none";
return;
}
}
const checkbox = document.querySelector(`#${featurePrefix}${name}`);
const background = await getBackground();
checkbox.checked = background.profilerState.features[name];
checkbox.addEventListener("change", async e => {
const features = Object.assign({}, background.profilerState.features);
features[name] = e.target.checked;
background.adjustState({ features });
renderState(background.profilerState);
});
}
/**
* This helper initializes and adds listeners to the threads checkboxes that
* will adjust the profiler state when changed.
*/
async function setupThreadCheckbox(name) {
const checkbox = document.querySelector(`#${threadPrefix}${name}`);
const background = await getBackground();
checkbox.checked = background.profilerState.threads.includes(threadMap[name]);
checkbox.addEventListener("change", async e => {
let threads = background.profilerState.threads;
if (e.target.checked) {
threads += "," + e.target.value;
} else {
threads = threadTextToList(threads)
.filter(thread => thread !== e.target.value)
.join(",");
}
background.adjustState({ threads });
renderState(background.profilerState);
});
}
/**
* Clean up the thread list string into a list of values.
* @param string threads, comma separated values.
* @return Array list of thread names
*/
function threadTextToList(threads) {
return (
threads
// Split on commas
.split(",")
// Clean up any extraneous whitespace
.map(string => string.trim())
// Filter out any blank strings
.filter(string => string)
);
}
for (const name of features) {
setupFeatureCheckbox(name);
}
for (const name in threadMap) {
setupThreadCheckbox(name);
}
document
.querySelector("#perf-settings-thread-text")
.addEventListener("change", async e => {
const background = await getBackground();
background.adjustState({ threads: e.target.value });
renderState(background.profilerState);
});
document
.querySelector(".settings-apply-button")
.addEventListener("click", async () => {
(await getBackground()).restartProfiler();
});
function makeExponentialScale(rangeStart, rangeEnd) {
const startExp = Math.log(rangeStart);
const endExp = Math.log(rangeEnd);
const fromFractionToValue = frac =>
Math.exp((1 - frac) * startExp + frac * endExp);
const fromValueToFraction = value =>
(Math.log(value) - startExp) / (endExp - startExp);
const fromFractionToSingleDigitValue = frac => {
return +fromFractionToValue(frac).toPrecision(1);
};
return {
fromFractionToValue,
fromValueToFraction,
fromFractionToSingleDigitValue,
};
}
const prettyBytes = (function(module) {
"use strict";
const UNITS = ["B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
module.exports = num => {
if (!Number.isFinite(num)) {
throw new TypeError(
`Expected a finite number, got ${typeof num}: ${num}`
);
}
const neg = num < 0;
if (neg) {
num = -num;
}
if (num < 1) {
return (neg ? "-" : "") + num + " B";
}
const exponent = Math.min(
Math.floor(Math.log(num) / Math.log(1000)),
UNITS.length - 1
);
const numStr = Number((num / Math.pow(1000, exponent)).toPrecision(3));
const unit = UNITS[exponent];
return (neg ? "-" : "") + numStr + " " + unit;
};
return module;
})({}).exports;
getBackground().then(background => renderState(background.profilerState));