From ec1a47fdf5169d3af9412c1a14eb187f8f2d0bbf Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Fri, 2 Dec 2022 17:04:17 +0000 Subject: [PATCH] Bug 1797284 - Teach about:logging to start the Firefox Profiler, with the appropriate profiler preset for a particular logging preset. r=julienw,mossop,flod This has some provision to continue working if the tab is closed or reloaded, but it's not fool proof. Eventually we might want to move this to a service, but it's already very useful as it is. Differential Revision: https://phabricator.services.mozilla.com/D160213 --- .../client/performance-new/@types/perf.d.ts | 1 + .../performance-new/popup/background.jsm.js | 1 + toolkit/content/aboutLogging.html | 39 ++-- toolkit/content/aboutLogging.js | 210 +++++++++++++++--- .../en-US/toolkit/about/aboutLogging.ftl | 15 ++ toolkit/themes/shared/aboutLogging.css | 4 + 6 files changed, 226 insertions(+), 44 deletions(-) diff --git a/devtools/client/performance-new/@types/perf.d.ts b/devtools/client/performance-new/@types/perf.d.ts index eff161587e4c..c2aa69cd5a5c 100644 --- a/devtools/client/performance-new/@types/perf.d.ts +++ b/devtools/client/performance-new/@types/perf.d.ts @@ -110,6 +110,7 @@ export type RecordingState = export type PageContext = | "devtools" | "devtools-remote" + | "aboutlogging" | "aboutprofiling" | "aboutprofiling-remote"; diff --git a/devtools/client/performance-new/popup/background.jsm.js b/devtools/client/performance-new/popup/background.jsm.js index fe03cbc852bd..3c05ac4731a4 100644 --- a/devtools/client/performance-new/popup/background.jsm.js +++ b/devtools/client/performance-new/popup/background.jsm.js @@ -452,6 +452,7 @@ function getPrefPostfix(pageContext) { switch (pageContext) { case "devtools": case "aboutprofiling": + case "aboutlogging": // Don't use any postfix on the prefs. return ""; case "devtools-remote": diff --git a/toolkit/content/aboutLogging.html b/toolkit/content/aboutLogging.html index beb685886d5e..348b70f5470a 100644 --- a/toolkit/content/aboutLogging.html +++ b/toolkit/content/aboutLogging.html @@ -19,8 +19,7 @@

- - +

@@ -44,23 +43,35 @@
-
+

+
+ + +
+
+ +
- - +
- - - - +
+
+ + +
+
+ + + + +
+
-
-

- -

-
+

+ +

diff --git a/toolkit/content/aboutLogging.js b/toolkit/content/aboutLogging.js index 90720279767f..1b9fd6ff5833 100644 --- a/toolkit/content/aboutLogging.js +++ b/toolkit/content/aboutLogging.js @@ -3,6 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); const gDashboard = Cc["@mozilla.org/network/dashboard;1"].getService( Ci.nsIDashboard ); @@ -10,8 +13,36 @@ const gDirServ = Cc["@mozilla.org/file/directory_service;1"].getService( Ci.nsIDirectoryServiceProvider ); -const $ = document.querySelector.bind(document); +const { ProfilerMenuButton } = ChromeUtils.import( + "resource://devtools/client/performance-new/popup/menu-button.jsm.js" +); +const { CustomizableUI } = ChromeUtils.import( + "resource:///modules/CustomizableUI.jsm" +); +XPCOMUtils.defineLazyGetter(this, "ProfilerPopupBackground", function() { + return ChromeUtils.import( + "resource://devtools/client/performance-new/popup/background.jsm.js" + ); +}); + +const $ = document.querySelector.bind(document); +const $$ = document.querySelectorAll.bind(document); + +/** + * All the information associated with a logging presets: + * - `modules` is the list of log modules and option, the same that would have + * been set as a MOZ_LOG environment variable + * - l10nIds.label and l10nIds.description are the Ids of the strings that + * appear in the dropdown selector, and a one-liner describing the purpose of + * a particular logging preset + * - profilerPreset is the name of a Firefox Profiler preset [1]. In general, + * the profiler preset will have the correct set of threads for a particular + * logging preset, so that all logging statements are recorded in the profile + * as markers. + * + * [1]: The keys of https://searchfox.org/mozilla-central/rev/88f285c5163f73abd209d4f73cfa476660351982/devtools/client/performance-new/popup/background.jsm.js#119 + */ const gLoggingPresets = { networking: { modules: @@ -20,6 +51,7 @@ const gLoggingPresets = { label: "about-logging-preset-networking-label", description: "about-logging-preset-networking-description", }, + profilerPreset: "networking", }, "media-playback": { modules: @@ -28,6 +60,7 @@ const gLoggingPresets = { label: "about-logging-preset-media-playback-label", description: "about-logging-preset-media-playback-description", }, + profilerPreset: "media", }, custom: { modules: "", @@ -38,6 +71,12 @@ const gLoggingPresets = { }, }; +const gLoggingSettings = { + loggingOutputType: "profiler", + running: false, + loggingPreset: "", +}; + function populatePresets() { let dropdown = $("#logging-preset-dropdown"); for (let presetName in gLoggingPresets) { @@ -46,6 +85,9 @@ function populatePresets() { document.l10n.setAttributes(option, preset.l10nIds.label); option.value = presetName; dropdown.appendChild(option); + if (option.value === gLoggingSettings.loggingPreset) { + option.setAttribute("selected", true); + } } function setPresetAndDescription(preset) { @@ -53,22 +95,44 @@ function populatePresets() { $("#logging-preset-description"), gLoggingPresets[preset].l10nIds.description ); + gLoggingSettings.loggingPreset = preset; } dropdown.onchange = function() { - $("#log-modules").value = gLoggingPresets[dropdown.value].modules; + // When switching to custom, leave the existing module list, to allow + // editing. + if (dropdown.value != "custom") { + $("#log-modules").value = gLoggingPresets[dropdown.value].modules; + } setPresetAndDescription(dropdown.value); - Services.prefs.setCharPref("logging.config.preset", dropdown.value); setLogModules(); + Services.prefs.setCharPref("logging.config.preset", dropdown.value); }; $("#log-modules").value = gLoggingPresets[dropdown.value].modules; setPresetAndDescription(dropdown.value); + // When changing the list switch to custom. $("#log-modules").oninput = e => { dropdown.value = "custom"; }; } +function updateLoggingOutputType(profilerOutputType) { + gLoggingSettings.loggingOutputType = profilerOutputType; + + if (gLoggingSettings.loggingOutputType === "profiler") { + // hide options related to file output for clarity + $("#log-file-configuration").hidden = true; + } else if (gLoggingSettings.loggingOutputType === "file") { + $("#log-file-configuration").hidden = false; + } + + Services.prefs.setCharPref( + "logging.config.output_type", + gLoggingSettings.loggingOutputType + ); +} + let gInited = false; function init() { if (gInited) { @@ -77,28 +141,56 @@ function init() { gInited = true; gDashboard.enableLogging = true; - let setLogButton = document.getElementById("set-log-file-button"); + populatePresets(); + + let setLogButton = $("#set-log-file-button"); setLogButton.addEventListener("click", setLogFile); - let setModulesButton = document.getElementById("set-log-modules-button"); + let setModulesButton = $("#set-log-modules-button"); setModulesButton.addEventListener("click", setLogModules); - let startLoggingButton = document.getElementById("start-logging-button"); - startLoggingButton.addEventListener("click", startLogging); + let toggleLoggingButton = $("#toggle-logging-button"); + toggleLoggingButton.addEventListener("click", startStopLogging); - let stopLoggingButton = document.getElementById("stop-logging-button"); - stopLoggingButton.addEventListener("click", stopLogging); + $$("input[type=radio]").forEach(radio => { + radio.onchange = e => { + updateLoggingOutputType(e.target.value); + }; + }); + + try { + let loggingOutputType = Services.prefs.getCharPref( + "logging.config.output_type" + ); + if (loggingOutputType.length) { + updateLoggingOutputType(loggingOutputType); + } + } catch { + updateLoggingOutputType("profiler"); + } + + try { + let loggingPreset = Services.prefs.getCharPref("logging.config.preset"); + gLoggingSettings.loggingPreset = loggingPreset; + } catch {} + + try { + let running = Services.prefs.getBoolPref("logging.config.running"); + gLoggingSettings.running = running; + $("#toggle-logging-button").setAttribute( + "data-l10n-id", + `about-logging-${gLoggingSettings.running ? "stop" : "start"}-logging` + ); + } catch {} try { let file = gDirServ.getFile("TmpD", {}); file.append("log.txt"); - document.getElementById("log-file").value = file.path; + $("#log-file").value = file.path; } catch (e) { console.error(e); } - populatePresets(); - // Update the value of the log file. updateLogFile(); @@ -108,9 +200,8 @@ function init() { // If we can't set the file and the modules at runtime, // the start and stop buttons wouldn't really do anything. if (setLogButton.disabled || setModulesButton.disabled) { - document.querySelector("#buttons-disabled").hidden = false; - startLoggingButton.disabled = true; - stopLoggingButton.disabled = true; + $("#buttons-disabled").hidden = false; + toggleLoggingButton.disabled = true; } } @@ -120,8 +211,8 @@ function updateLogFile() { // Try to get the environment variable for the log file logPath = Services.env.get("MOZ_LOG_FILE") || Services.env.get("NSPR_LOG_FILE"); - let currentLogFile = document.getElementById("current-log-file"); - let setLogFileButton = document.getElementById("set-log-file-button"); + let currentLogFile = $("#current-log-file"); + let setLogFileButton = $("#set-log-file-button"); // If the log file was set from an env var, we disable the ability to set it // at runtime. @@ -135,15 +226,15 @@ function updateLogFile() { try { let file = gDirServ.getFile("TmpD", {}); file.append("log.txt"); - document.getElementById("log-file").value = file.path; + $("#log-file").value = file.path; } catch (e) { console.error(e); } // Fall back to the temp dir - currentLogFile.innerText = document.getElementById("log-file").value; + currentLogFile.innerText = $("#log-file").value; } - let openLogFileButton = document.getElementById("open-log-file-button"); + let openLogFileButton = $("#open-log-file-button"); openLogFileButton.disabled = true; if (currentLogFile.innerText.length) { @@ -165,8 +256,8 @@ function updateLogModules() { Services.env.get("MOZ_LOG") || Services.env.get("MOZ_LOG_MODULES") || Services.env.get("NSPR_LOG_MODULES"); - let currentLogModules = document.getElementById("current-log-modules"); - let setLogModulesButton = document.getElementById("set-log-modules-button"); + let currentLogModules = $("#current-log-modules"); + let setLogModulesButton = $("#set-log-modules-button"); if (logModules.length) { currentLogModules.innerText = logModules; // If the log modules are set by an environment variable at startup, do not @@ -220,12 +311,12 @@ function updateLogModules() { } function setLogFile() { - let setLogButton = document.getElementById("set-log-file-button"); + let setLogButton = $("#set-log-file-button"); if (setLogButton.disabled) { // There's no point trying since it wouldn't work anyway. return; } - let logFile = document.getElementById("log-file").value.trim(); + let logFile = $("#log-file").value.trim(); Services.prefs.setCharPref("logging.config.LOG_FILE", logFile); updateLogFile(); } @@ -244,13 +335,13 @@ function clearLogModules() { } function setLogModules() { - let setLogModulesButton = document.getElementById("set-log-modules-button"); + let setLogModulesButton = $("#set-log-modules-button"); if (setLogModulesButton.disabled) { // The modules were set via env var, so we shouldn't try to change them. return; } - let modules = document.getElementById("log-modules").value.trim(); + let modules = $("#log-modules").value.trim(); // Clear previously set log modules. clearLogModules(); @@ -280,16 +371,75 @@ function setLogModules() { updateLogModules(); } +function isLogging() { + try { + return Services.prefs.getBoolPref("logging.config.running"); + } catch { + return false; + } +} + +function startStopLogging() { + if (isLogging()) { + document.l10n.setAttributes( + $("#toggle-logging-button"), + "about-logging-start-logging" + ); + stopLogging(); + } else { + document.l10n.setAttributes( + $("#toggle-logging-button"), + "about-logging-stop-logging" + ); + startLogging(); + } +} + function startLogging() { - setLogFile(); setLogModules(); + if (gLoggingSettings.loggingOutputType === "profiler") { + if (gLoggingSettings.loggingPreset != "custom") { + // Change the preset before starting the profiler, so that the + // underlying profiler code picks up the right configuration. + const profilerPreset = + gLoggingPresets[gLoggingSettings.loggingPreset].profilerPreset; + const supportedFeatures = Services.profiler.GetFeatures(); + ProfilerPopupBackground.changePreset( + "aboutlogging", + profilerPreset, + supportedFeatures + ); + } + // Force displaying the profiler button in the navbar if not preset, so + // that there is a visual indication profiling is in progress. + if (!ProfilerMenuButton.isInNavbar()) { + // Ensure the widget is enabled. + Services.prefs.setBoolPref( + "devtools.performance.popup.feature-flag", + true + ); + // Enable the profiler menu button. + ProfilerMenuButton.addToNavbar(); + // Dispatch the change event manually, so that the shortcuts will also be + // added. + CustomizableUI.dispatchToolboxEvent("customizationchange"); + } + ProfilerPopupBackground.startProfiler("aboutlogging"); + } else { + setLogFile(); + } + Services.prefs.setBoolPref("logging.config.running", true); } function stopLogging() { + if (gLoggingSettings.loggingOutputType === "profiler") { + ProfilerPopupBackground.captureProfile("aboutlogging"); + } else { + Services.prefs.clearUserPref("logging.config.LOG_FILE"); + updateLogFile(); + } + Services.prefs.setBoolPref("logging.config.running", false); clearLogModules(); - // clear the log file as well - Services.prefs.clearUserPref("logging.config.LOG_FILE"); - updateLogFile(); } // We use the pageshow event instead of onload. This is needed because sometimes diff --git a/toolkit/locales/en-US/toolkit/about/aboutLogging.ftl b/toolkit/locales/en-US/toolkit/about/aboutLogging.ftl index 48b4813eeba6..802e3b282e66 100644 --- a/toolkit/locales/en-US/toolkit/about/aboutLogging.ftl +++ b/toolkit/locales/en-US/toolkit/about/aboutLogging.ftl @@ -2,6 +2,20 @@ # 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/. +## The following feature name must be treated as a brand. +## +## They cannot be: +## - Transliterated. +## - Translated. +## +## Declension should be avoided where possible, leaving the original +## brand unaltered in prominent UI positions. +## +## For further details, consult: +## https://mozilla-l10n.github.io/styleguides/mozilla_general/#brands-copyright-and-trademark + +-profiler-brand-name = Firefox Profiler + # This is the title of the page about-logging-title = About Logging about-logging-page-title = Logging manager @@ -22,6 +36,7 @@ about-logging-log-modules-selection = Log module selection about-logging-new-log-modules = New log modules: about-logging-logging-output-selection = Logging output about-logging-logging-to-file = Logging to a file +about-logging-logging-to-profiler = Logging to the { -profiler-brand-name } about-logging-no-log-modules = None about-logging-logging-preset-selector-text = Logging preset: diff --git a/toolkit/themes/shared/aboutLogging.css b/toolkit/themes/shared/aboutLogging.css index 7a776ec36079..cc888db0ab3d 100644 --- a/toolkit/themes/shared/aboutLogging.css +++ b/toolkit/themes/shared/aboutLogging.css @@ -31,6 +31,10 @@ div.page-section > div { padding-bottom: 2em; } +div.page-section > div.radio-entry { + padding-bottom: 0; +} + #current-log-modules, #no-log-modules { font-family: monospace;