diff options
Diffstat (limited to 'toolkit/components/telemetry/TelemetryStopwatch.jsm')
-rw-r--r-- | toolkit/components/telemetry/TelemetryStopwatch.jsm | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/TelemetryStopwatch.jsm b/toolkit/components/telemetry/TelemetryStopwatch.jsm new file mode 100644 index 000000000..ab6c6eafb --- /dev/null +++ b/toolkit/components/telemetry/TelemetryStopwatch.jsm @@ -0,0 +1,335 @@ +/* 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/. */ + +const Cc = Components.classes; +const Ci = Components.interfaces; +const Cu = Components.utils; + +this.EXPORTED_SYMBOLS = ["TelemetryStopwatch"]; + +Cu.import("resource://gre/modules/Log.jsm", this); +var Telemetry = Cc["@mozilla.org/base/telemetry;1"] + .getService(Ci.nsITelemetry); + +// Weak map does not allow using null objects as keys. These objects are used +// as 'null' placeholders. +const NULL_OBJECT = {}; +const NULL_KEY = {}; + +/** + * Timers is a variation of a Map used for storing information about running + * Stopwatches. Timers has the following data structure: + * + * { + * "HISTOGRAM_NAME": WeakMap { + * Object || NULL_OBJECT: Map { + * "KEY" || NULL_KEY: startTime + * ... + * } + * ... + * } + * ... + * } + * + * + * @example + * // Stores current time for a keyed histogram "PLAYING_WITH_CUTE_ANIMALS". + * Timers.put("PLAYING_WITH_CUTE_ANIMALS", null, "CATS", Date.now()); + * + * @example + * // Returns information about a simple Stopwatch. + * let startTime = Timers.get("PLAYING_WITH_CUTE_ANIMALS", null, "CATS"); + */ +let Timers = { + _timers: new Map(), + + _validTypes: function(histogram, obj, key) { + let nonEmptyString = value => { + return typeof value === "string" && value !== "" && value.length > 0; + }; + return nonEmptyString(histogram) && + typeof obj == "object" && + (key === NULL_KEY || nonEmptyString(key)); + }, + + get: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if (!this.has(histogram, obj, key)) { + return null; + } + + return this._timers.get(histogram).get(obj).get(key); + }, + + put: function(histogram, obj, key, startTime) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if (!this._validTypes(histogram, obj, key)) { + return false; + } + + let objectMap = this._timers.get(histogram) || new WeakMap(); + let keyedInfo = objectMap.get(obj) || new Map(); + keyedInfo.set(key, startTime); + objectMap.set(obj, keyedInfo); + this._timers.set(histogram, objectMap); + return true; + }, + + has: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + return this._timers.has(histogram) && + this._timers.get(histogram).has(obj) && + this._timers.get(histogram).get(obj).has(key); + }, + + delete: function(histogram, obj, key) { + key = key === null ? NULL_KEY : key; + obj = obj || NULL_OBJECT; + + if (!this.has(histogram, obj, key)) { + return false; + } + let objectMap = this._timers.get(histogram); + let keyedInfo = objectMap.get(obj); + if (keyedInfo.size > 1) { + keyedInfo.delete(key); + return true; + } + objectMap.delete(obj); + // NOTE: + // We never delete empty objecMaps from this._timers because there is no + // nice solution for tracking the number of objects in a WeakMap. + // WeakMap is not enumerable, so we can't deterministically say when it's + // empty. We accept that trade-off here, given that entries for short-lived + // objects will go away when they are no longer referenced + return true; + } +}; + +this.TelemetryStopwatch = { + /** + * Starts a timer associated with a telemetry histogram. The timer can be + * directly associated with a histogram, or with a pair of a histogram and + * an object. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers for the same histogram may be run + * concurrently, as long as they are associated with + * different objects. + * + * @returns {Boolean} True if the timer was successfully started, false + * otherwise. If a timer already exists, it can't be + * started again, and the existing one will be cleared in + * order to avoid measurements errors. + */ + start: function(aHistogram, aObj) { + return TelemetryStopwatchImpl.start(aHistogram, aObj, null); + }, + + /** + * Deletes the timer associated with a telemetry histogram. The timer can be + * directly associated with a histogram, or with a pair of a histogram and + * an object. Important: Only use this method when a legitimate cancellation + * should be done. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers or a same histogram may be run concurrently, + * as long as they are associated with different + * objects. + * + * @returns {Boolean} True if the timer exist and it was cleared, False + * otherwise. + */ + cancel: function(aHistogram, aObj) { + return TelemetryStopwatchImpl.cancel(aHistogram, aObj, null); + }, + + /** + * Returns the elapsed time for a particular stopwatch. Primarily for + * debugging purposes. Must be called prior to finish. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * If an invalid name is given, the function will + * throw. + * + * @param (Object) aObj - Optional parameter which associates the histogram + * timer with the given object. + * + * @returns {Integer} time in milliseconds or -1 if the stopwatch was not + * found. + */ + timeElapsed: function(aHistogram, aObj) { + return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, null); + }, + + /** + * Stops the timer associated with the given histogram (and object), + * calculates the time delta between start and finish, and adds the value + * to the histogram. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {Object} aObj - Optional parameter which associates the histogram + * timer with the given object. + * + * @returns {Boolean} True if the timer was succesfully stopped and the data + * was added to the histogram, False otherwise. + */ + finish: function(aHistogram, aObj) { + return TelemetryStopwatchImpl.finish(aHistogram, aObj, null); + }, + + /** + * Starts a timer associated with a keyed telemetry histogram. The timer can + * be directly associated with a histogram and its key. Similarly to + * @see{TelemetryStopwatch.stat} the histogram and its key can be associated + * with an object. Each key may have multiple associated objects and each + * object can be associated with multiple keys. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer is + * associated with this object, meaning that multiple + * timers for the same histogram may be run + * concurrently,as long as they are associated with + * different objects. + * + * @returns {Boolean} True if the timer was successfully started, false + * otherwise. If a timer already exists, it can't be + * started again, and the existing one will be cleared in + * order to avoid measurements errors. + */ + startKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.start(aHistogram, aObj, aKey); + }, + + /** + * Deletes the timer associated with a keyed histogram. Important: Only use + * this method when a legitimate cancellation should be done. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer + * associated with this object is deleted. + * + * @return {Boolean} True if the timer exist and it was cleared, False + * otherwise. + */ + cancelKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.cancel(aHistogram, aObj, aKey); + }, + + /** + * Returns the elapsed time for a particular stopwatch. Primarily for + * debugging purposes. Must be called prior to finish. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - Optional parameter. If specified, the timer + * associated with this object is used to calculate + * the elapsed time. + * + * @return {Integer} time in milliseconds or -1 if the stopwatch was not + * found. + */ + timeElapsedKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.timeElapsed(aHistogram, aObj, aKey); + }, + + /** + * Stops the timer associated with the given keyed histogram (and object), + * calculates the time delta between start and finish, and adds the value + * to the keyed histogram. + * + * @param {String} aHistogram - a string which must be a valid histogram name. + * + * @param {String} aKey - a string which must be a valid histgram key. + * + * @param {Object} aObj - optional parameter which associates the histogram + * timer with the given object. + * + * @returns {Boolean} True if the timer was succesfully stopped and the data + * was added to the histogram, False otherwise. + */ + finishKeyed: function(aHistogram, aKey, aObj) { + return TelemetryStopwatchImpl.finish(aHistogram, aObj, aKey); + } +}; + +this.TelemetryStopwatchImpl = { + start: function(histogram, object, key) { + if (Timers.has(histogram, object, key)) { + Timers.delete(histogram, object, key); + Cu.reportError(`TelemetryStopwatch: key "${histogram}" was already ` + + "initialized"); + return false; + } + + return Timers.put(histogram, object, key, Components.utils.now()); + }, + + cancel: function (histogram, object, key) { + return Timers.delete(histogram, object, key); + }, + + timeElapsed: function(histogram, object, key) { + let startTime = Timers.get(histogram, object, key); + if (startTime === null) { + Cu.reportError("TelemetryStopwatch: requesting elapsed time for " + + `nonexisting stopwatch. Histogram: "${histogram}", ` + + `key: "${key}"`); + return -1; + } + + try { + let delta = Components.utils.now() - startTime + return Math.round(delta); + } catch (e) { + Cu.reportError("TelemetryStopwatch: failed to calculate elapsed time " + + `for Histogram: "${histogram}", key: "${key}", ` + + `exception: ${Log.exceptionStr(e)}`); + return -1; + } + }, + + finish: function(histogram, object, key) { + let delta = this.timeElapsed(histogram, object, key); + if (delta == -1) { + return false; + } + + try { + if (key) { + Telemetry.getKeyedHistogramById(histogram).add(key, delta); + } else { + Telemetry.getHistogramById(histogram).add(delta); + } + } catch (e) { + Cu.reportError("TelemetryStopwatch: failed to update the Histogram " + + `"${histogram}", using key: "${key}", ` + + `exception: ${Log.exceptionStr(e)}`); + return false; + } + + return Timers.delete(histogram, object, key); + } +} |