/* -*- 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/. */ /** * This module contains a global counter to store API call in the current process. */ import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs"; import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; import { DeferredTask } from "resource://gre/modules/DeferredTask.sys.mjs"; const { DefaultMap } = ExtensionUtils; const lazy = {}; XPCOMUtils.defineLazyPreferenceGetter( lazy, "gTimingEnabled", "extensions.webextensions.enablePerformanceCounters", false ); XPCOMUtils.defineLazyPreferenceGetter( lazy, "gTimingMaxAge", "extensions.webextensions.performanceCountersMaxAge", 1000 ); class CounterMap extends DefaultMap { defaultConstructor() { return new DefaultMap(() => ({ duration: 0, calls: 0 })); } flush() { let result = new CounterMap(undefined, this); this.clear(); return result; } merge(other) { for (let [webextId, counters] of other) { for (let [api, counter] of counters) { let current = this.get(webextId).get(api); current.calls += counter.calls; current.duration += counter.duration; } } } } /** * Global Deferred used to send to the parent performance counters * when the counter is in a child. */ var _performanceCountersSender = null; // Pre-definition of the global Counters instance. export var PerformanceCounters = null; function _sendPerformanceCounters(childApiManagerId) { let counters = PerformanceCounters.flush(); // No need to send empty counters. if (counters.size == 0) { return; } let options = { childId: childApiManagerId, counters: counters }; Services.cpmm.sendAsyncMessage("Extension:SendPerformanceCounter", options); } class Counters { constructor() { this.data = new CounterMap(); } /** * Returns true if performance counters are enabled. * * Indirection used so gTimingEnabled is not exposed direcly * in PerformanceCounters -- which would prevent tests to dynamically * change the preference value once PerformanceCounters.jsm is loaded. * * @returns {boolean} */ get enabled() { return lazy.gTimingEnabled; } /** * Returns the counters max age * * Indirection used so gTimingMaxAge is not exposed direcly * in PerformanceCounters -- which would prevent tests to dynamically * change the preference value once PerformanceCounters.jsm is loaded. * * @returns {number} */ get maxAge() { return lazy.gTimingMaxAge; } /** * Stores an execution time. * * @param {string} webExtensionId The web extension id. * @param {string} apiPath The API path. * @param {integer} duration How long the call took. * @param {childApiManagerId} childApiManagerId If executed from a child, its API manager id. */ storeExecutionTime(webExtensionId, apiPath, duration, childApiManagerId) { let apiCounter = this.data.get(webExtensionId).get(apiPath); apiCounter.duration += duration; apiCounter.calls += 1; // Create the global deferred task if we're in a child and // it's the first time. if (childApiManagerId) { if (!_performanceCountersSender) { _performanceCountersSender = new DeferredTask(() => { _sendPerformanceCounters(childApiManagerId); }, this.maxAge); } _performanceCountersSender.arm(); } } /** * Merges another CounterMap into this.data * * Can be used by the main process to merge data received * from the children. * * @param {CounterMap} data The map to merge. */ merge(data) { this.data.merge(data); } /** * Returns the performance counters and purges them. * * @returns {CounterMap} */ flush() { return this.data.flush(); } /** * Returns the performance counters. * * @returns {CounterMap} */ getData() { return this.data; } } PerformanceCounters = new Counters();