549 lines
16 KiB
JavaScript
549 lines
16 KiB
JavaScript
/* 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/. */
|
|
// @ts-check
|
|
|
|
/**
|
|
* @typedef {import("perf").PageContext} PageContext
|
|
* @typedef {import("perf").PerformancePref} PerformancePref
|
|
* @typedef {import("perf").PrefObserver} PrefObserver
|
|
* @typedef {import("perf").PrefPostfix} PrefPostfix
|
|
* @typedef {import("perf").Presets} Presets
|
|
* @typedef {import("perf").ProfilerViewMode} ProfilerViewMode
|
|
* @typedef {import("perf").RecordingSettings} RecordingSettings
|
|
*/
|
|
|
|
/** @type {PerformancePref["Entries"]} */
|
|
const ENTRIES_PREF = "devtools.performance.recording.entries";
|
|
/** @type {PerformancePref["Interval"]} */
|
|
const INTERVAL_PREF = "devtools.performance.recording.interval";
|
|
/** @type {PerformancePref["Features"]} */
|
|
const FEATURES_PREF = "devtools.performance.recording.features";
|
|
/** @type {PerformancePref["Threads"]} */
|
|
const THREADS_PREF = "devtools.performance.recording.threads";
|
|
/** @type {PerformancePref["ObjDirs"]} */
|
|
const OBJDIRS_PREF = "devtools.performance.recording.objdirs";
|
|
/** @type {PerformancePref["Duration"]} */
|
|
const DURATION_PREF = "devtools.performance.recording.duration";
|
|
/** @type {PerformancePref["Preset"]} */
|
|
const PRESET_PREF = "devtools.performance.recording.preset";
|
|
/** @type {PerformancePref["PopupFeatureFlag"]} */
|
|
const POPUP_FEATURE_FLAG_PREF = "devtools.performance.popup.feature-flag";
|
|
/* This will be used to observe all profiler-related prefs. */
|
|
const PREF_PREFIX = "devtools.performance.recording.";
|
|
|
|
// The presets that we find in all interfaces are defined here.
|
|
|
|
// The property l10nIds contain all FTL l10n IDs for these cases:
|
|
// - properties in "popup" are used in the popup's select box.
|
|
// - properties in "devtools" are used in other UIs (about:profiling and devtools panels).
|
|
//
|
|
// Properties for both cases have the same values, but because they're not used
|
|
// in the same way we need to duplicate them.
|
|
// Their values for the en-US locale are in the files:
|
|
// devtools/client/locales/en-US/perftools.ftl
|
|
// browser/locales/en-US/browser/appmenu.ftl
|
|
//
|
|
// IMPORTANT NOTE: Please keep the existing profiler presets in sync with their
|
|
// Fenix counterparts and consider adding any new presets to Fenix:
|
|
// https://github.com/mozilla-mobile/firefox-android/blob/1d177e7e78d027e8ab32cedf0fc68316787d7454/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerUtils.kt
|
|
|
|
/** @type {Presets} */
|
|
export const presets = {
|
|
"web-developer": {
|
|
entries: 128 * 1024 * 1024,
|
|
interval: 1,
|
|
features: ["screenshots", "js", "cpu", "memory"],
|
|
threads: ["GeckoMain", "Compositor", "Renderer", "DOM Worker"],
|
|
duration: 0,
|
|
profilerViewMode: "active-tab",
|
|
l10nIds: {
|
|
popup: {
|
|
label: "profiler-popup-presets-web-developer-label",
|
|
description: "profiler-popup-presets-web-developer-description",
|
|
},
|
|
devtools: {
|
|
label: "perftools-presets-web-developer-label",
|
|
description: "perftools-presets-web-developer-description",
|
|
},
|
|
},
|
|
},
|
|
"firefox-platform": {
|
|
entries: 128 * 1024 * 1024,
|
|
interval: 1,
|
|
features: [
|
|
"screenshots",
|
|
"js",
|
|
"stackwalk",
|
|
"cpu",
|
|
"java",
|
|
"processcpu",
|
|
"memory",
|
|
],
|
|
threads: [
|
|
"GeckoMain",
|
|
"Compositor",
|
|
"Renderer",
|
|
"SwComposite",
|
|
"DOM Worker",
|
|
],
|
|
duration: 0,
|
|
l10nIds: {
|
|
popup: {
|
|
label: "profiler-popup-presets-firefox-label",
|
|
description: "profiler-popup-presets-firefox-description",
|
|
},
|
|
devtools: {
|
|
label: "perftools-presets-firefox-label",
|
|
description: "perftools-presets-firefox-description",
|
|
},
|
|
},
|
|
},
|
|
graphics: {
|
|
entries: 128 * 1024 * 1024,
|
|
interval: 1,
|
|
features: ["stackwalk", "js", "cpu", "java", "processcpu", "memory"],
|
|
threads: [
|
|
"GeckoMain",
|
|
"Compositor",
|
|
"Renderer",
|
|
"SwComposite",
|
|
"RenderBackend",
|
|
"GlyphRasterizer",
|
|
"SceneBuilder",
|
|
"WrWorker",
|
|
"CanvasWorkers",
|
|
"TextureUpdate",
|
|
],
|
|
duration: 0,
|
|
l10nIds: {
|
|
popup: {
|
|
label: "profiler-popup-presets-graphics-label",
|
|
description: "profiler-popup-presets-graphics-description",
|
|
},
|
|
devtools: {
|
|
label: "perftools-presets-graphics-label",
|
|
description: "perftools-presets-graphics-description",
|
|
},
|
|
},
|
|
},
|
|
media: {
|
|
entries: 128 * 1024 * 1024,
|
|
interval: 1,
|
|
features: [
|
|
"js",
|
|
"stackwalk",
|
|
"cpu",
|
|
"audiocallbacktracing",
|
|
"ipcmessages",
|
|
"processcpu",
|
|
"memory",
|
|
],
|
|
threads: [
|
|
"BackgroundThreadPool",
|
|
"Compositor",
|
|
"DOM Worker",
|
|
"GeckoMain",
|
|
"IPDL Background",
|
|
"InotifyEventThread",
|
|
"ModuleProcessThread",
|
|
"PacerThread",
|
|
"RemVidChild",
|
|
"RenderBackend",
|
|
"Renderer",
|
|
"Socket Thread",
|
|
"SwComposite",
|
|
"TextureUpdate",
|
|
"audio",
|
|
"camera",
|
|
"capture",
|
|
"cubeb",
|
|
"decoder",
|
|
"gmp",
|
|
"graph",
|
|
"grph",
|
|
"media",
|
|
"webrtc",
|
|
],
|
|
duration: 0,
|
|
l10nIds: {
|
|
popup: {
|
|
label: "profiler-popup-presets-media-label",
|
|
description: "profiler-popup-presets-media-description2",
|
|
},
|
|
devtools: {
|
|
label: "perftools-presets-media-label",
|
|
description: "perftools-presets-media-description2",
|
|
},
|
|
},
|
|
},
|
|
networking: {
|
|
entries: 128 * 1024 * 1024,
|
|
interval: 1,
|
|
features: [
|
|
"screenshots",
|
|
"js",
|
|
"stackwalk",
|
|
"cpu",
|
|
"java",
|
|
"processcpu",
|
|
"bandwidth",
|
|
"memory",
|
|
],
|
|
threads: [
|
|
"Cache2 I/O",
|
|
"Compositor",
|
|
"DNS Resolver",
|
|
"DOM Worker",
|
|
"GeckoMain",
|
|
"Renderer",
|
|
"Socket Thread",
|
|
"StreamTrans",
|
|
"SwComposite",
|
|
"TRR Background",
|
|
],
|
|
duration: 0,
|
|
l10nIds: {
|
|
popup: {
|
|
label: "profiler-popup-presets-networking-label",
|
|
description: "profiler-popup-presets-networking-description",
|
|
},
|
|
devtools: {
|
|
label: "perftools-presets-networking-label",
|
|
description: "perftools-presets-networking-description",
|
|
},
|
|
},
|
|
},
|
|
power: {
|
|
entries: 128 * 1024 * 1024,
|
|
interval: 10,
|
|
features: [
|
|
"screenshots",
|
|
"js",
|
|
"stackwalk",
|
|
"cpu",
|
|
"processcpu",
|
|
"nostacksampling",
|
|
"ipcmessages",
|
|
"markersallthreads",
|
|
"power",
|
|
"bandwidth",
|
|
"memory",
|
|
],
|
|
threads: ["GeckoMain", "Renderer"],
|
|
duration: 0,
|
|
l10nIds: {
|
|
popup: {
|
|
label: "profiler-popup-presets-power-label",
|
|
description: "profiler-popup-presets-power-description",
|
|
},
|
|
devtools: {
|
|
label: "perftools-presets-power-label",
|
|
description: "perftools-presets-power-description",
|
|
},
|
|
},
|
|
},
|
|
debug: {
|
|
entries: 128 * 1024 * 1024,
|
|
interval: 1,
|
|
features: [
|
|
"cpu",
|
|
"ipcmessages",
|
|
"js",
|
|
"markersallthreads",
|
|
"processcpu",
|
|
"samplingallthreads",
|
|
"stackwalk",
|
|
"unregisteredthreads",
|
|
],
|
|
threads: ["*"],
|
|
duration: 0,
|
|
l10nIds: {
|
|
popup: {
|
|
label: "profiler-popup-presets-debug-label",
|
|
description: "profiler-popup-presets-debug-description",
|
|
},
|
|
devtools: {
|
|
label: "perftools-presets-debug-label",
|
|
description: "perftools-presets-debug-description",
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @param {string} prefName
|
|
* @return {string[]}
|
|
*/
|
|
function _getArrayOfStringsPref(prefName) {
|
|
const text = Services.prefs.getCharPref(prefName);
|
|
return JSON.parse(text);
|
|
}
|
|
|
|
/**
|
|
* The profiler recording workflow uses two different pref paths. One set of prefs
|
|
* is stored for local profiling, and another for remote profiling. This function
|
|
* decides which to use. The remote prefs have ".remote" appended to the end of
|
|
* their pref names.
|
|
*
|
|
* @param {PageContext} pageContext
|
|
* @returns {PrefPostfix}
|
|
*/
|
|
export function getPrefPostfix(pageContext) {
|
|
switch (pageContext) {
|
|
case "devtools":
|
|
case "aboutprofiling":
|
|
case "aboutlogging":
|
|
// Don't use any postfix on the prefs.
|
|
return "";
|
|
case "devtools-remote":
|
|
case "aboutprofiling-remote":
|
|
return ".remote";
|
|
default: {
|
|
const { UnhandledCaseError } = ChromeUtils.importESModule(
|
|
"resource://devtools/shared/performance-new/errors.sys.mjs"
|
|
);
|
|
throw new UnhandledCaseError(pageContext, "Page Context");
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {string[]} objdirs
|
|
*/
|
|
function setObjdirPrefValue(objdirs) {
|
|
Services.prefs.setCharPref(OBJDIRS_PREF, JSON.stringify(objdirs));
|
|
}
|
|
|
|
/**
|
|
* Before Firefox 92, the objdir lists for local and remote profiling were
|
|
* stored in separate lists. In Firefox 92 those two prefs were merged into
|
|
* one. This function performs the migration.
|
|
*/
|
|
function migrateObjdirsPrefsIfNeeded() {
|
|
const OLD_REMOTE_OBJDIRS_PREF = OBJDIRS_PREF + ".remote";
|
|
const remoteString = Services.prefs.getCharPref(OLD_REMOTE_OBJDIRS_PREF, "");
|
|
if (remoteString === "") {
|
|
// No migration necessary.
|
|
return;
|
|
}
|
|
|
|
const remoteList = JSON.parse(remoteString);
|
|
const localList = _getArrayOfStringsPref(OBJDIRS_PREF);
|
|
|
|
// Merge the two lists, eliminating any duplicates.
|
|
const mergedList = [...new Set(localList.concat(remoteList))];
|
|
setObjdirPrefValue(mergedList);
|
|
Services.prefs.clearUserPref(OLD_REMOTE_OBJDIRS_PREF);
|
|
}
|
|
|
|
/**
|
|
* @returns {string[]}
|
|
*/
|
|
export function getObjdirPrefValue() {
|
|
migrateObjdirsPrefsIfNeeded();
|
|
return _getArrayOfStringsPref(OBJDIRS_PREF);
|
|
}
|
|
|
|
/**
|
|
* @param {string[]} supportedFeatures
|
|
* @param {string[]} objdirs
|
|
* @param {PrefPostfix} prefPostfix
|
|
* @return {RecordingSettings}
|
|
*/
|
|
export function getRecordingSettingsFromPrefs(
|
|
supportedFeatures,
|
|
objdirs,
|
|
prefPostfix
|
|
) {
|
|
// If you add a new preference here, please do not forget to update
|
|
// `revertRecordingSettings` as well.
|
|
|
|
const entries = Services.prefs.getIntPref(ENTRIES_PREF + prefPostfix);
|
|
const intervalInMicroseconds = Services.prefs.getIntPref(
|
|
INTERVAL_PREF + prefPostfix
|
|
);
|
|
const interval = intervalInMicroseconds / 1000;
|
|
const features = _getArrayOfStringsPref(FEATURES_PREF + prefPostfix);
|
|
const threads = _getArrayOfStringsPref(THREADS_PREF + prefPostfix);
|
|
const duration = Services.prefs.getIntPref(DURATION_PREF + prefPostfix);
|
|
|
|
return {
|
|
presetName: "custom",
|
|
entries,
|
|
interval,
|
|
// Validate the features before passing them to the profiler.
|
|
features: features.filter(feature => supportedFeatures.includes(feature)),
|
|
threads,
|
|
objdirs,
|
|
duration,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {PageContext} pageContext
|
|
* @param {RecordingSettings} prefs
|
|
*/
|
|
export function setRecordingSettings(pageContext, prefs) {
|
|
const prefPostfix = getPrefPostfix(pageContext);
|
|
Services.prefs.setCharPref(PRESET_PREF + prefPostfix, prefs.presetName);
|
|
Services.prefs.setIntPref(ENTRIES_PREF + prefPostfix, prefs.entries);
|
|
// The interval pref stores the value in microseconds for extra precision.
|
|
const intervalInMicroseconds = prefs.interval * 1000;
|
|
Services.prefs.setIntPref(
|
|
INTERVAL_PREF + prefPostfix,
|
|
intervalInMicroseconds
|
|
);
|
|
Services.prefs.setCharPref(
|
|
FEATURES_PREF + prefPostfix,
|
|
JSON.stringify(prefs.features)
|
|
);
|
|
Services.prefs.setCharPref(
|
|
THREADS_PREF + prefPostfix,
|
|
JSON.stringify(prefs.threads)
|
|
);
|
|
setObjdirPrefValue(prefs.objdirs);
|
|
}
|
|
|
|
/**
|
|
* Revert the recording prefs for both local and remote profiling.
|
|
* @return {void}
|
|
*/
|
|
export function revertRecordingSettings() {
|
|
for (const prefPostfix of ["", ".remote"]) {
|
|
Services.prefs.clearUserPref(PRESET_PREF + prefPostfix);
|
|
Services.prefs.clearUserPref(ENTRIES_PREF + prefPostfix);
|
|
Services.prefs.clearUserPref(INTERVAL_PREF + prefPostfix);
|
|
Services.prefs.clearUserPref(FEATURES_PREF + prefPostfix);
|
|
Services.prefs.clearUserPref(THREADS_PREF + prefPostfix);
|
|
Services.prefs.clearUserPref(DURATION_PREF + prefPostfix);
|
|
}
|
|
Services.prefs.clearUserPref(OBJDIRS_PREF);
|
|
Services.prefs.clearUserPref(POPUP_FEATURE_FLAG_PREF);
|
|
}
|
|
|
|
/**
|
|
* Add an observer for the profiler-related preferences.
|
|
* @param {PrefObserver} observer
|
|
* @return {void}
|
|
*/
|
|
export function addPrefObserver(observer) {
|
|
Services.prefs.addObserver(PREF_PREFIX, observer);
|
|
}
|
|
|
|
/**
|
|
* Removes an observer for the profiler-related preferences.
|
|
* @param {PrefObserver} observer
|
|
* @return {void}
|
|
*/
|
|
export function removePrefObserver(observer) {
|
|
Services.prefs.removeObserver(PREF_PREFIX, observer);
|
|
}
|
|
/**
|
|
* Return the proper view mode for the Firefox Profiler front-end timeline by
|
|
* looking at the proper preset that is selected.
|
|
* Return value can be undefined when the preset is unknown or custom.
|
|
* @param {PageContext} pageContext
|
|
* @return {ProfilerViewMode | undefined}
|
|
*/
|
|
export function getProfilerViewModeForCurrentPreset(pageContext) {
|
|
const prefPostfix = getPrefPostfix(pageContext);
|
|
const presetName = Services.prefs.getCharPref(PRESET_PREF + prefPostfix);
|
|
|
|
if (presetName === "custom") {
|
|
return undefined;
|
|
}
|
|
|
|
const preset = presets[presetName];
|
|
if (!preset) {
|
|
console.error(`Unknown profiler preset was encountered: "${presetName}"`);
|
|
return undefined;
|
|
}
|
|
return preset.profilerViewMode;
|
|
}
|
|
|
|
/**
|
|
* @param {string} presetName
|
|
* @param {string[]} supportedFeatures
|
|
* @param {string[]} objdirs
|
|
* @return {RecordingSettings | null}
|
|
*/
|
|
export function getRecordingSettingsFromPreset(
|
|
presetName,
|
|
supportedFeatures,
|
|
objdirs
|
|
) {
|
|
if (presetName === "custom") {
|
|
return null;
|
|
}
|
|
|
|
const preset = presets[presetName];
|
|
if (!preset) {
|
|
console.error(`Unknown profiler preset was encountered: "${presetName}"`);
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
presetName,
|
|
entries: preset.entries,
|
|
interval: preset.interval,
|
|
// Validate the features before passing them to the profiler.
|
|
features: preset.features.filter(feature =>
|
|
supportedFeatures.includes(feature)
|
|
),
|
|
threads: preset.threads,
|
|
objdirs,
|
|
duration: preset.duration,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param {PageContext} pageContext
|
|
* @param {string[]} supportedFeatures
|
|
* @returns {RecordingSettings}
|
|
*/
|
|
export function getRecordingSettings(pageContext, supportedFeatures) {
|
|
const objdirs = getObjdirPrefValue();
|
|
const prefPostfix = getPrefPostfix(pageContext);
|
|
const presetName = Services.prefs.getCharPref(PRESET_PREF + prefPostfix);
|
|
|
|
// First try to get the values from a preset. If the preset is "custom" or
|
|
// unrecognized, getRecordingSettingsFromPreset will return null and we will
|
|
// get the settings from individual prefs instead.
|
|
return (
|
|
getRecordingSettingsFromPreset(presetName, supportedFeatures, objdirs) ??
|
|
getRecordingSettingsFromPrefs(supportedFeatures, objdirs, prefPostfix)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Change the prefs based on a preset. This mechanism is used by the popup to
|
|
* easily switch between different settings.
|
|
* @param {string} presetName
|
|
* @param {PageContext} pageContext
|
|
* @param {string[]} supportedFeatures
|
|
* @return {void}
|
|
*/
|
|
export function changePreset(pageContext, presetName, supportedFeatures) {
|
|
const prefPostfix = getPrefPostfix(pageContext);
|
|
const objdirs = getObjdirPrefValue();
|
|
let recordingSettings = getRecordingSettingsFromPreset(
|
|
presetName,
|
|
supportedFeatures,
|
|
objdirs
|
|
);
|
|
|
|
if (!recordingSettings) {
|
|
// No recordingSettings were found for that preset. Most likely this means this
|
|
// is a custom preset, or it's one that we dont recognize for some reason.
|
|
// Get the preferences from the individual preference values.
|
|
Services.prefs.setCharPref(PRESET_PREF + prefPostfix, presetName);
|
|
recordingSettings = getRecordingSettingsFromPrefs(
|
|
supportedFeatures,
|
|
objdirs,
|
|
prefPostfix
|
|
);
|
|
}
|
|
|
|
setRecordingSettings(pageContext, recordingSettings);
|
|
}
|