diff options
Diffstat (limited to 'toolkit/components/telemetry/UITelemetry.jsm')
-rw-r--r-- | toolkit/components/telemetry/UITelemetry.jsm | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/UITelemetry.jsm b/toolkit/components/telemetry/UITelemetry.jsm new file mode 100644 index 0000000000..bd7a34b725 --- /dev/null +++ b/toolkit/components/telemetry/UITelemetry.jsm @@ -0,0 +1,235 @@ +/* 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/. */ + +"use strict"; + +const Cu = Components.utils; + +const PREF_BRANCH = "toolkit.telemetry."; +const PREF_ENABLED = PREF_BRANCH + "enabled"; + +this.EXPORTED_SYMBOLS = [ + "UITelemetry", +]; + +Cu.import("resource://gre/modules/Services.jsm", this); + +/** + * UITelemetry is a helper JSM used to record UI specific telemetry events. + * + * It implements nsIUITelemetryObserver, defined in nsIAndroidBridge.idl. + */ +this.UITelemetry = { + _enabled: undefined, + _activeSessions: {}, + _measurements: [], + + // Lazily decide whether telemetry is enabled. + get enabled() { + if (this._enabled !== undefined) { + return this._enabled; + } + + // Set an observer to watch for changes at runtime. + Services.prefs.addObserver(PREF_ENABLED, this, false); + Services.obs.addObserver(this, "profile-before-change", false); + + // Pick up the current value. + try { + this._enabled = Services.prefs.getBoolPref(PREF_ENABLED); + } catch (e) { + this._enabled = false; + } + + return this._enabled; + }, + + observe: function(aSubject, aTopic, aData) { + if (aTopic == "profile-before-change") { + Services.obs.removeObserver(this, "profile-before-change"); + Services.prefs.removeObserver(PREF_ENABLED, this); + this._enabled = undefined; + return; + } + + if (aTopic == "nsPref:changed") { + switch (aData) { + case PREF_ENABLED: + let on = Services.prefs.getBoolPref(PREF_ENABLED); + this._enabled = on; + + // Wipe ourselves if we were just disabled. + if (!on) { + this._activeSessions = {}; + this._measurements = []; + } + break; + } + } + }, + + /** + * This exists exclusively for testing -- our events are not intended to + * be retrieved via an XPCOM interface. + */ + get wrappedJSObject() { + return this; + }, + + /** + * Holds the functions that provide UITelemetry's simple + * measurements. Those functions are mapped to unique names, + * and should be registered with addSimpleMeasureFunction. + */ + _simpleMeasureFunctions: {}, + + /** + * A hack to generate the relative timestamp from start when we don't have + * access to the Java timer. + * XXX: Bug 1007647 - Support realtime and/or uptime in JavaScript. + */ + uptimeMillis: function() { + return Date.now() - Services.startup.getStartupInfo().process; + }, + + /** + * Adds a single event described by a timestamp, an action, and the calling + * method. + * + * Optionally provide a string 'extras', which will be recorded as part of + * the event. + * + * All extant sessions will be recorded by name for each event. + */ + addEvent: function(aAction, aMethod, aTimestamp, aExtras) { + if (!this.enabled) { + return; + } + + let sessions = Object.keys(this._activeSessions); + let aEvent = { + type: "event", + action: aAction, + method: aMethod, + sessions: sessions, + timestamp: (aTimestamp == undefined) ? this.uptimeMillis() : aTimestamp, + }; + + if (aExtras) { + aEvent.extras = aExtras; + } + + this._recordEvent(aEvent); + }, + + /** + * Begins tracking a session by storing a timestamp for session start. + */ + startSession: function(aName, aTimestamp) { + if (!this.enabled) { + return; + } + + if (this._activeSessions[aName]) { + // Do not overwrite a previous event start if it already exists. + return; + } + this._activeSessions[aName] = (aTimestamp == undefined) ? this.uptimeMillis() : aTimestamp; + }, + + /** + * Tracks the end of a session with a timestamp. + */ + stopSession: function(aName, aReason, aTimestamp) { + if (!this.enabled) { + return; + } + + let sessionStart = this._activeSessions[aName]; + delete this._activeSessions[aName]; + + if (!sessionStart) { + return; + } + + let aEvent = { + type: "session", + name: aName, + reason: aReason, + start: sessionStart, + end: (aTimestamp == undefined) ? this.uptimeMillis() : aTimestamp, + }; + + this._recordEvent(aEvent); + }, + + _recordEvent: function(aEvent) { + this._measurements.push(aEvent); + }, + + /** + * Called by TelemetrySession to populate the simple measurement + * blob. This function will iterate over all functions added + * via addSimpleMeasureFunction and return an object with the + * results of those functions. + */ + getSimpleMeasures: function() { + if (!this.enabled) { + return {}; + } + + let result = {}; + for (let name in this._simpleMeasureFunctions) { + result[name] = this._simpleMeasureFunctions[name](); + } + return result; + }, + + /** + * Allows the caller to register functions that will get called + * for simple measures during a Telemetry ping. aName is a unique + * identifier used as they key for the simple measurement in the + * object that getSimpleMeasures returns. + * + * This function throws an exception if aName already has a function + * registered for it. + */ + addSimpleMeasureFunction: function(aName, aFunction) { + if (!this.enabled) { + return; + } + + if (aName in this._simpleMeasureFunctions) { + throw new Error("A simple measurement function is already registered for " + aName); + } + + if (!aFunction || typeof aFunction !== 'function') { + throw new Error("addSimpleMeasureFunction called with non-function argument."); + } + + this._simpleMeasureFunctions[aName] = aFunction; + }, + + removeSimpleMeasureFunction: function(aName) { + delete this._simpleMeasureFunctions[aName]; + }, + + /** + * Called by TelemetrySession to populate the UI measurement + * blob. + * + * Optionally clears the set of measurements based on aClear. + */ + getUIMeasurements: function(aClear) { + if (!this.enabled) { + return []; + } + + let measurements = this._measurements.slice(); + if (aClear) { + this._measurements = []; + } + return measurements; + } +}; |