summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/TelemetryStopwatch.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/TelemetryStopwatch.jsm')
-rw-r--r--toolkit/components/telemetry/TelemetryStopwatch.jsm335
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);
+ }
+}