1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/telemetry/UITelemetry.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,216 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const Cu = Components.utils; 1.11 + 1.12 +const PREF_BRANCH = "toolkit.telemetry."; 1.13 +const PREF_ENABLED = PREF_BRANCH + "enabled"; 1.14 + 1.15 +this.EXPORTED_SYMBOLS = [ 1.16 + "UITelemetry", 1.17 +]; 1.18 + 1.19 +Cu.import("resource://gre/modules/Services.jsm", this); 1.20 + 1.21 +/** 1.22 + * UITelemetry is a helper JSM used to record UI specific telemetry events. 1.23 + * 1.24 + * It implements nsIUITelemetryObserver, defined in nsIAndroidBridge.idl. 1.25 + */ 1.26 +this.UITelemetry = { 1.27 + _enabled: undefined, 1.28 + _activeSessions: {}, 1.29 + _measurements: [], 1.30 + 1.31 + // Lazily decide whether telemetry is enabled. 1.32 + get enabled() { 1.33 + if (this._enabled !== undefined) { 1.34 + return this._enabled; 1.35 + } 1.36 + 1.37 + // Set an observer to watch for changes at runtime. 1.38 + Services.prefs.addObserver(PREF_ENABLED, this, false); 1.39 + Services.obs.addObserver(this, "profile-before-change", false); 1.40 + 1.41 + // Pick up the current value. 1.42 + try { 1.43 + this._enabled = Services.prefs.getBoolPref(PREF_ENABLED); 1.44 + } catch (e) { 1.45 + this._enabled = false; 1.46 + } 1.47 + 1.48 + return this._enabled; 1.49 + }, 1.50 + 1.51 + observe: function(aSubject, aTopic, aData) { 1.52 + if (aTopic == "profile-before-change") { 1.53 + Services.obs.removeObserver(this, "profile-before-change"); 1.54 + Services.prefs.removeObserver(PREF_ENABLED, this); 1.55 + this._enabled = undefined; 1.56 + return; 1.57 + } 1.58 + 1.59 + if (aTopic == "nsPref:changed") { 1.60 + switch (aData) { 1.61 + case PREF_ENABLED: 1.62 + let on = Services.prefs.getBoolPref(PREF_ENABLED); 1.63 + this._enabled = on; 1.64 + 1.65 + // Wipe ourselves if we were just disabled. 1.66 + if (!on) { 1.67 + this._activeSessions = {}; 1.68 + this._measurements = []; 1.69 + } 1.70 + break; 1.71 + } 1.72 + } 1.73 + }, 1.74 + 1.75 + /** 1.76 + * This exists exclusively for testing -- our events are not intended to 1.77 + * be retrieved via an XPCOM interface. 1.78 + */ 1.79 + get wrappedJSObject() { 1.80 + return this; 1.81 + }, 1.82 + 1.83 + /** 1.84 + * Holds the functions that provide UITelemetry's simple 1.85 + * measurements. Those functions are mapped to unique names, 1.86 + * and should be registered with addSimpleMeasureFunction. 1.87 + */ 1.88 + _simpleMeasureFunctions: {}, 1.89 + 1.90 + /** 1.91 + * Adds a single event described by a timestamp, an action, and the calling 1.92 + * method. 1.93 + * 1.94 + * Optionally provide a string 'extras', which will be recorded as part of 1.95 + * the event. 1.96 + * 1.97 + * All extant sessions will be recorded by name for each event. 1.98 + */ 1.99 + addEvent: function(aAction, aMethod, aTimestamp, aExtras) { 1.100 + if (!this.enabled) { 1.101 + return; 1.102 + } 1.103 + 1.104 + let sessions = Object.keys(this._activeSessions); 1.105 + let aEvent = { 1.106 + type: "event", 1.107 + action: aAction, 1.108 + method: aMethod, 1.109 + sessions: sessions, 1.110 + timestamp: aTimestamp, 1.111 + }; 1.112 + 1.113 + if (aExtras) { 1.114 + aEvent.extras = aExtras; 1.115 + } 1.116 + 1.117 + this._recordEvent(aEvent); 1.118 + }, 1.119 + 1.120 + /** 1.121 + * Begins tracking a session by storing a timestamp for session start. 1.122 + */ 1.123 + startSession: function(aName, aTimestamp) { 1.124 + if (!this.enabled) { 1.125 + return; 1.126 + } 1.127 + 1.128 + if (this._activeSessions[aName]) { 1.129 + // Do not overwrite a previous event start if it already exists. 1.130 + return; 1.131 + } 1.132 + this._activeSessions[aName] = aTimestamp; 1.133 + }, 1.134 + 1.135 + /** 1.136 + * Tracks the end of a session with a timestamp. 1.137 + */ 1.138 + stopSession: function(aName, aReason, aTimestamp) { 1.139 + if (!this.enabled) { 1.140 + return; 1.141 + } 1.142 + 1.143 + let sessionStart = this._activeSessions[aName]; 1.144 + delete this._activeSessions[aName]; 1.145 + 1.146 + if (!sessionStart) { 1.147 + return; 1.148 + } 1.149 + 1.150 + let aEvent = { 1.151 + type: "session", 1.152 + name: aName, 1.153 + reason: aReason, 1.154 + start: sessionStart, 1.155 + end: aTimestamp, 1.156 + }; 1.157 + 1.158 + this._recordEvent(aEvent); 1.159 + }, 1.160 + 1.161 + _recordEvent: function(aEvent) { 1.162 + this._measurements.push(aEvent); 1.163 + }, 1.164 + 1.165 + /** 1.166 + * Called by TelemetryPing to populate the simple measurement 1.167 + * blob. This function will iterate over all functions added 1.168 + * via addSimpleMeasureFunction and return an object with the 1.169 + * results of those functions. 1.170 + */ 1.171 + getSimpleMeasures: function() { 1.172 + if (!this.enabled) { 1.173 + return {}; 1.174 + } 1.175 + 1.176 + let result = {}; 1.177 + for (let name in this._simpleMeasureFunctions) { 1.178 + result[name] = this._simpleMeasureFunctions[name](); 1.179 + } 1.180 + return result; 1.181 + }, 1.182 + 1.183 + /** 1.184 + * Allows the caller to register functions that will get called 1.185 + * for simple measures during a Telemetry ping. aName is a unique 1.186 + * identifier used as they key for the simple measurement in the 1.187 + * object that getSimpleMeasures returns. 1.188 + * 1.189 + * This function throws an exception if aName already has a function 1.190 + * registered for it. 1.191 + */ 1.192 + addSimpleMeasureFunction: function(aName, aFunction) { 1.193 + if (!this.enabled) { 1.194 + return; 1.195 + } 1.196 + 1.197 + if (aName in this._simpleMeasureFunctions) { 1.198 + throw new Error("A simple measurement function is already registered for " + aName); 1.199 + } 1.200 + 1.201 + if (!aFunction || typeof aFunction !== 'function') { 1.202 + throw new Error("addSimpleMeasureFunction called with non-function argument."); 1.203 + } 1.204 + 1.205 + this._simpleMeasureFunctions[aName] = aFunction; 1.206 + }, 1.207 + 1.208 + removeSimpleMeasureFunction: function(aName) { 1.209 + delete this._simpleMeasureFunctions[aName]; 1.210 + }, 1.211 + 1.212 + getUIMeasurements: function() { 1.213 + if (!this.enabled) { 1.214 + return []; 1.215 + } 1.216 + 1.217 + return this._measurements.slice(); 1.218 + } 1.219 +};