prefs-presets.sys.mjs (17245B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 // @ts-check 5 6 /** 7 * @typedef {import("perf").PageContext} PageContext 8 * @typedef {import("perf").PerformancePref} PerformancePref 9 * @typedef {import("perf").PrefObserver} PrefObserver 10 * @typedef {import("perf").PrefPostfix} PrefPostfix 11 * @typedef {import("perf").Presets} Presets 12 * @typedef {import("perf").ProfilerViewMode} ProfilerViewMode 13 * @typedef {import("perf").RecordingSettings} RecordingSettings 14 */ 15 16 /** @type {PerformancePref["Entries"]} */ 17 const ENTRIES_PREF = "devtools.performance.recording.entries"; 18 /** @type {PerformancePref["Interval"]} */ 19 const INTERVAL_PREF = "devtools.performance.recording.interval"; 20 /** @type {PerformancePref["Features"]} */ 21 const FEATURES_PREF = "devtools.performance.recording.features"; 22 /** @type {PerformancePref["Threads"]} */ 23 const THREADS_PREF = "devtools.performance.recording.threads"; 24 /** @type {PerformancePref["ObjDirs"]} */ 25 const OBJDIRS_PREF = "devtools.performance.recording.objdirs"; 26 /** @type {PerformancePref["Duration"]} */ 27 const DURATION_PREF = "devtools.performance.recording.duration"; 28 /** @type {PerformancePref["Preset"]} */ 29 const PRESET_PREF = "devtools.performance.recording.preset"; 30 /** @type {PerformancePref["PopupFeatureFlag"]} */ 31 const POPUP_FEATURE_FLAG_PREF = "devtools.performance.popup.feature-flag"; 32 /* This will be used to observe all profiler-related prefs. */ 33 const PREF_PREFIX = "devtools.performance.recording."; 34 35 // The presets that we find in all interfaces are defined here. 36 37 // The property l10nIds contain all FTL l10n IDs for these cases: 38 // - properties in "popup" are used in the popup's select box. 39 // - properties in "devtools" are used in other UIs (about:profiling and devtools panels). 40 // 41 // Properties for both cases have the same values, but because they're not used 42 // in the same way we need to duplicate them. 43 // Their values for the en-US locale are in the files: 44 // devtools/client/locales/en-US/perftools.ftl 45 // browser/locales/en-US/browser/appmenu.ftl 46 // 47 // IMPORTANT NOTE: Please keep the existing profiler presets in sync with their 48 // Fenix counterparts and consider adding any new presets to Fenix: 49 // https://searchfox.org/firefox-main/rev/d87eb30d610a3032111f9ee47441b53927de63d3/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/perf/ProfilerUtils.kt 50 51 /** @type {Presets} */ 52 export const presets = { 53 "web-developer": { 54 entries: 128 * 1024 * 1024, 55 interval: 1, 56 features: ["screenshots", "js", "cpu", "memory"], 57 threads: ["GeckoMain", "Compositor", "Renderer", "DOM Worker"], 58 duration: 0, 59 profilerViewMode: "active-tab", 60 l10nIds: { 61 popup: { 62 label: "profiler-popup-presets-web-developer-label", 63 description: "profiler-popup-presets-web-developer-description", 64 }, 65 devtools: { 66 label: "perftools-presets-web-developer-label", 67 description: "perftools-presets-web-developer-description", 68 }, 69 }, 70 }, 71 "firefox-platform": { 72 entries: 128 * 1024 * 1024, 73 interval: 1, 74 features: [ 75 "screenshots", 76 "js", 77 "stackwalk", 78 "cpu", 79 "java", 80 "processcpu", 81 "memory", 82 ], 83 threads: [ 84 "GeckoMain", 85 "Compositor", 86 "Renderer", 87 "SwComposite", 88 "DOM Worker", 89 ], 90 duration: 0, 91 l10nIds: { 92 popup: { 93 label: "profiler-popup-presets-firefox-label", 94 description: "profiler-popup-presets-firefox-description", 95 }, 96 devtools: { 97 label: "perftools-presets-firefox-label", 98 description: "perftools-presets-firefox-description", 99 }, 100 }, 101 }, 102 graphics: { 103 entries: 128 * 1024 * 1024, 104 interval: 1, 105 features: ["stackwalk", "js", "cpu", "java", "processcpu", "memory"], 106 threads: [ 107 "GeckoMain", 108 "Compositor", 109 "Renderer", 110 "SwComposite", 111 "RenderBackend", 112 "GlyphRasterizer", 113 "SceneBuilder", 114 "WrWorker", 115 "CanvasWorkers", 116 "TextureUpdate", 117 ], 118 duration: 0, 119 l10nIds: { 120 popup: { 121 label: "profiler-popup-presets-graphics-label", 122 description: "profiler-popup-presets-graphics-description", 123 }, 124 devtools: { 125 label: "perftools-presets-graphics-label", 126 description: "perftools-presets-graphics-description", 127 }, 128 }, 129 }, 130 media: { 131 entries: 128 * 1024 * 1024, 132 interval: 1, 133 features: [ 134 "js", 135 "stackwalk", 136 "cpu", 137 "audiocallbacktracing", 138 "ipcmessages", 139 "processcpu", 140 "memory", 141 ], 142 threads: [ 143 "BackgroundThreadPool", 144 "Compositor", 145 "DOM Worker", 146 "GeckoMain", 147 "IPDL Background", 148 "InotifyEventThread", 149 "ModuleProcessThread", 150 "PacerThread", 151 "RemVidChild", 152 "RenderBackend", 153 "Renderer", 154 "Socket Thread", 155 "SwComposite", 156 "TextureUpdate", 157 "audio", 158 "camera", 159 "capture", 160 "cubeb", 161 "decoder", 162 "gmp", 163 "graph", 164 "grph", 165 "media", 166 "webrtc", 167 ], 168 duration: 0, 169 l10nIds: { 170 popup: { 171 label: "profiler-popup-presets-media-label", 172 description: "profiler-popup-presets-media-description2", 173 }, 174 devtools: { 175 label: "perftools-presets-media-label", 176 description: "perftools-presets-media-description2", 177 }, 178 }, 179 }, 180 ml: { 181 entries: 128 * 1024 * 1024, 182 interval: 1, 183 features: ["js", "stackwalk", "cpu", "ipcmessages", "processcpu", "memory"], 184 threads: [ 185 "BackgroundThreadPool", 186 "DOM Worker", 187 "GeckoMain", 188 "IPDL Background", 189 "onnx_worker", 190 ], 191 duration: 0, 192 l10nIds: { 193 popup: { 194 label: "profiler-popup-presets-ml-label", 195 description: "profiler-popup-presets-ml-description", 196 }, 197 devtools: { 198 label: "perftools-presets-ml-label", 199 description: "perftools-presets-ml-description2", 200 }, 201 }, 202 }, 203 networking: { 204 entries: 128 * 1024 * 1024, 205 interval: 1, 206 features: [ 207 "screenshots", 208 "js", 209 "stackwalk", 210 "cpu", 211 "java", 212 "processcpu", 213 "bandwidth", 214 "memory", 215 ], 216 threads: [ 217 "Cache2 I/O", 218 "Compositor", 219 "DNS Resolver", 220 "DOM Worker", 221 "GeckoMain", 222 "Renderer", 223 "Socket Thread", 224 "StreamTrans", 225 "SwComposite", 226 "TRR Background", 227 ], 228 duration: 0, 229 l10nIds: { 230 popup: { 231 label: "profiler-popup-presets-networking-label", 232 description: "profiler-popup-presets-networking-description", 233 }, 234 devtools: { 235 label: "perftools-presets-networking-label", 236 description: "perftools-presets-networking-description", 237 }, 238 }, 239 }, 240 power: { 241 entries: 128 * 1024 * 1024, 242 interval: 10, 243 features: [ 244 "screenshots", 245 "js", 246 "stackwalk", 247 "cpu", 248 "processcpu", 249 "nostacksampling", 250 "ipcmessages", 251 "markersallthreads", 252 "power", 253 "bandwidth", 254 "memory", 255 ], 256 threads: ["GeckoMain", "Renderer"], 257 duration: 0, 258 l10nIds: { 259 popup: { 260 label: "profiler-popup-presets-power-label", 261 description: "profiler-popup-presets-power-description", 262 }, 263 devtools: { 264 label: "perftools-presets-power-label", 265 description: "perftools-presets-power-description", 266 }, 267 }, 268 }, 269 debug: { 270 entries: 128 * 1024 * 1024, 271 interval: 1, 272 features: [ 273 "cpu", 274 "ipcmessages", 275 "js", 276 "markersallthreads", 277 "processcpu", 278 "samplingallthreads", 279 "stackwalk", 280 "unregisteredthreads", 281 "flows", 282 ], 283 threads: ["*"], 284 duration: 0, 285 l10nIds: { 286 popup: { 287 label: "profiler-popup-presets-debug-label", 288 description: "profiler-popup-presets-debug-description", 289 }, 290 devtools: { 291 label: "perftools-presets-debug-label", 292 description: "perftools-presets-debug-description", 293 }, 294 }, 295 }, 296 "web-compat": { 297 entries: 128 * 1024 * 1024, 298 interval: 1, 299 features: ["screenshots", "js", "stackwalk", "nostacksampling", "tracing"], 300 threads: ["GeckoMain", "DOM Worker"], 301 mozLogs: "console: 5, PageMessages: 5", 302 duration: 0, 303 profilerViewMode: "active-tab", 304 l10nIds: { 305 popup: { 306 label: "profiler-popup-presets-web-compat-label", 307 description: "profiler-popup-presets-web-compat-description", 308 }, 309 devtools: { 310 label: "perftools-presets-web-compat-label", 311 description: "perftools-presets-web-compat-description", 312 }, 313 }, 314 }, 315 }; 316 317 /** 318 * @param {string} prefName 319 * @return {string[]} 320 */ 321 function _getArrayOfStringsPref(prefName) { 322 const text = Services.prefs.getCharPref(prefName); 323 return JSON.parse(text); 324 } 325 326 /** 327 * The profiler recording workflow uses two different pref paths. One set of prefs 328 * is stored for local profiling, and another for remote profiling. This function 329 * decides which to use. The remote prefs have ".remote" appended to the end of 330 * their pref names. 331 * 332 * @param {PageContext} pageContext 333 * @returns {PrefPostfix} 334 */ 335 export function getPrefPostfix(pageContext) { 336 switch (pageContext) { 337 case "devtools": 338 case "aboutprofiling": 339 case "aboutlogging": 340 // Don't use any postfix on the prefs. 341 return ""; 342 case "devtools-remote": 343 case "aboutprofiling-remote": 344 return ".remote"; 345 default: { 346 const { UnhandledCaseError } = ChromeUtils.importESModule( 347 "resource://devtools/shared/performance-new/errors.sys.mjs", 348 { global: "contextual" } 349 ); 350 throw new UnhandledCaseError(pageContext, "Page Context"); 351 } 352 } 353 } 354 355 /** 356 * @param {string[]} objdirs 357 */ 358 function setObjdirPrefValue(objdirs) { 359 Services.prefs.setCharPref(OBJDIRS_PREF, JSON.stringify(objdirs)); 360 } 361 362 /** 363 * Before Firefox 92, the objdir lists for local and remote profiling were 364 * stored in separate lists. In Firefox 92 those two prefs were merged into 365 * one. This function performs the migration. 366 */ 367 function migrateObjdirsPrefsIfNeeded() { 368 const OLD_REMOTE_OBJDIRS_PREF = OBJDIRS_PREF + ".remote"; 369 const remoteString = Services.prefs.getCharPref(OLD_REMOTE_OBJDIRS_PREF, ""); 370 if (remoteString === "") { 371 // No migration necessary. 372 return; 373 } 374 375 const remoteList = JSON.parse(remoteString); 376 const localList = _getArrayOfStringsPref(OBJDIRS_PREF); 377 378 // Merge the two lists, eliminating any duplicates. 379 const mergedList = [...new Set(localList.concat(remoteList))]; 380 setObjdirPrefValue(mergedList); 381 Services.prefs.clearUserPref(OLD_REMOTE_OBJDIRS_PREF); 382 } 383 384 /** 385 * @returns {string[]} 386 */ 387 export function getObjdirPrefValue() { 388 migrateObjdirsPrefsIfNeeded(); 389 return _getArrayOfStringsPref(OBJDIRS_PREF); 390 } 391 392 /** 393 * @param {string[]} supportedFeatures 394 * @param {string[]} objdirs 395 * @param {PrefPostfix} prefPostfix 396 * @return {RecordingSettings} 397 */ 398 export function getRecordingSettingsFromPrefs( 399 supportedFeatures, 400 objdirs, 401 prefPostfix 402 ) { 403 // If you add a new preference here, please do not forget to update 404 // `revertRecordingSettings` as well. 405 406 const entries = Services.prefs.getIntPref(ENTRIES_PREF + prefPostfix); 407 const intervalInMicroseconds = Services.prefs.getIntPref( 408 INTERVAL_PREF + prefPostfix 409 ); 410 const interval = intervalInMicroseconds / 1000; 411 const features = _getArrayOfStringsPref(FEATURES_PREF + prefPostfix); 412 const threads = _getArrayOfStringsPref(THREADS_PREF + prefPostfix); 413 const duration = Services.prefs.getIntPref(DURATION_PREF + prefPostfix); 414 415 return { 416 presetName: "custom", 417 entries, 418 interval, 419 // Validate the features before passing them to the profiler. 420 features: features.filter(feature => supportedFeatures.includes(feature)), 421 threads, 422 objdirs, 423 duration, 424 }; 425 } 426 427 /** 428 * @param {PageContext} pageContext 429 * @param {RecordingSettings} prefs 430 */ 431 export function setRecordingSettings(pageContext, prefs) { 432 const prefPostfix = getPrefPostfix(pageContext); 433 Services.prefs.setCharPref(PRESET_PREF + prefPostfix, prefs.presetName); 434 Services.prefs.setIntPref(ENTRIES_PREF + prefPostfix, prefs.entries); 435 // The interval pref stores the value in microseconds for extra precision. 436 const intervalInMicroseconds = prefs.interval * 1000; 437 Services.prefs.setIntPref( 438 INTERVAL_PREF + prefPostfix, 439 intervalInMicroseconds 440 ); 441 Services.prefs.setCharPref( 442 FEATURES_PREF + prefPostfix, 443 JSON.stringify(prefs.features) 444 ); 445 Services.prefs.setCharPref( 446 THREADS_PREF + prefPostfix, 447 JSON.stringify(prefs.threads) 448 ); 449 setObjdirPrefValue(prefs.objdirs); 450 } 451 452 /** 453 * Revert the recording prefs for both local and remote profiling. 454 * 455 * @return {void} 456 */ 457 export function revertRecordingSettings() { 458 for (const prefPostfix of ["", ".remote"]) { 459 Services.prefs.clearUserPref(PRESET_PREF + prefPostfix); 460 Services.prefs.clearUserPref(ENTRIES_PREF + prefPostfix); 461 Services.prefs.clearUserPref(INTERVAL_PREF + prefPostfix); 462 Services.prefs.clearUserPref(FEATURES_PREF + prefPostfix); 463 Services.prefs.clearUserPref(THREADS_PREF + prefPostfix); 464 Services.prefs.clearUserPref(DURATION_PREF + prefPostfix); 465 } 466 Services.prefs.clearUserPref(OBJDIRS_PREF); 467 Services.prefs.clearUserPref(POPUP_FEATURE_FLAG_PREF); 468 } 469 470 /** 471 * Add an observer for the profiler-related preferences. 472 * 473 * @param {PrefObserver} observer 474 * @return {void} 475 */ 476 export function addPrefObserver(observer) { 477 Services.prefs.addObserver(PREF_PREFIX, observer); 478 } 479 480 /** 481 * Removes an observer for the profiler-related preferences. 482 * 483 * @param {PrefObserver} observer 484 * @return {void} 485 */ 486 export function removePrefObserver(observer) { 487 Services.prefs.removeObserver(PREF_PREFIX, observer); 488 } 489 /** 490 * Return the proper view mode for the Firefox Profiler front-end timeline by 491 * looking at the proper preset that is selected. 492 * Return value can be undefined when the preset is unknown or custom. 493 * 494 * @param {PageContext} pageContext 495 * @return {ProfilerViewMode | undefined} 496 */ 497 export function getProfilerViewModeForCurrentPreset(pageContext) { 498 const prefPostfix = getPrefPostfix(pageContext); 499 const presetName = Services.prefs.getCharPref(PRESET_PREF + prefPostfix); 500 501 if (presetName === "custom") { 502 return undefined; 503 } 504 505 const preset = presets[presetName]; 506 if (!preset) { 507 console.error(`Unknown profiler preset was encountered: "${presetName}"`); 508 return undefined; 509 } 510 return preset.profilerViewMode; 511 } 512 513 /** 514 * @param {string} presetName 515 * @param {string[]} supportedFeatures 516 * @param {string[]} objdirs 517 * @return {RecordingSettings | null} 518 */ 519 export function getRecordingSettingsFromPreset( 520 presetName, 521 supportedFeatures, 522 objdirs 523 ) { 524 if (presetName === "custom") { 525 return null; 526 } 527 528 const preset = presets[presetName]; 529 if (!preset) { 530 console.error(`Unknown profiler preset was encountered: "${presetName}"`); 531 return null; 532 } 533 534 return { 535 presetName, 536 entries: preset.entries, 537 interval: preset.interval, 538 // Validate the features before passing them to the profiler. 539 features: preset.features.filter(feature => 540 supportedFeatures.includes(feature) 541 ), 542 threads: preset.threads, 543 mozLogs: preset.mozLogs, 544 objdirs, 545 duration: preset.duration, 546 }; 547 } 548 549 /** 550 * @param {PageContext} pageContext 551 * @param {string[]} supportedFeatures 552 * @returns {RecordingSettings} 553 */ 554 export function getRecordingSettings(pageContext, supportedFeatures) { 555 const objdirs = getObjdirPrefValue(); 556 const prefPostfix = getPrefPostfix(pageContext); 557 const presetName = Services.prefs.getCharPref(PRESET_PREF + prefPostfix); 558 559 // First try to get the values from a preset. If the preset is "custom" or 560 // unrecognized, getRecordingSettingsFromPreset will return null and we will 561 // get the settings from individual prefs instead. 562 return ( 563 getRecordingSettingsFromPreset(presetName, supportedFeatures, objdirs) ?? 564 getRecordingSettingsFromPrefs(supportedFeatures, objdirs, prefPostfix) 565 ); 566 } 567 568 /** 569 * Change the prefs based on a preset. This mechanism is used by the popup to 570 * easily switch between different settings. 571 * 572 * @param {string} presetName 573 * @param {PageContext} pageContext 574 * @param {string[]} supportedFeatures 575 * @return {void} 576 */ 577 export function changePreset(pageContext, presetName, supportedFeatures) { 578 const prefPostfix = getPrefPostfix(pageContext); 579 const objdirs = getObjdirPrefValue(); 580 let recordingSettings = getRecordingSettingsFromPreset( 581 presetName, 582 supportedFeatures, 583 objdirs 584 ); 585 586 if (!recordingSettings) { 587 // No recordingSettings were found for that preset. Most likely this means this 588 // is a custom preset, or it's one that we dont recognize for some reason. 589 // Get the preferences from the individual preference values. 590 Services.prefs.setCharPref(PRESET_PREF + prefPostfix, presetName); 591 recordingSettings = getRecordingSettingsFromPrefs( 592 supportedFeatures, 593 objdirs, 594 prefPostfix 595 ); 596 } 597 598 setRecordingSettings(pageContext, recordingSettings); 599 }