Fri, 16 Jan 2015 18:13:44 +0100
Integrate suggestion from review to improve consistency with existing code.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 const Cu = Components.utils;
9 const PREF_BRANCH = "toolkit.telemetry.";
10 const PREF_ENABLED = PREF_BRANCH + "enabled";
12 this.EXPORTED_SYMBOLS = [
13 "UITelemetry",
14 ];
16 Cu.import("resource://gre/modules/Services.jsm", this);
18 /**
19 * UITelemetry is a helper JSM used to record UI specific telemetry events.
20 *
21 * It implements nsIUITelemetryObserver, defined in nsIAndroidBridge.idl.
22 */
23 this.UITelemetry = {
24 _enabled: undefined,
25 _activeSessions: {},
26 _measurements: [],
28 // Lazily decide whether telemetry is enabled.
29 get enabled() {
30 if (this._enabled !== undefined) {
31 return this._enabled;
32 }
34 // Set an observer to watch for changes at runtime.
35 Services.prefs.addObserver(PREF_ENABLED, this, false);
36 Services.obs.addObserver(this, "profile-before-change", false);
38 // Pick up the current value.
39 try {
40 this._enabled = Services.prefs.getBoolPref(PREF_ENABLED);
41 } catch (e) {
42 this._enabled = false;
43 }
45 return this._enabled;
46 },
48 observe: function(aSubject, aTopic, aData) {
49 if (aTopic == "profile-before-change") {
50 Services.obs.removeObserver(this, "profile-before-change");
51 Services.prefs.removeObserver(PREF_ENABLED, this);
52 this._enabled = undefined;
53 return;
54 }
56 if (aTopic == "nsPref:changed") {
57 switch (aData) {
58 case PREF_ENABLED:
59 let on = Services.prefs.getBoolPref(PREF_ENABLED);
60 this._enabled = on;
62 // Wipe ourselves if we were just disabled.
63 if (!on) {
64 this._activeSessions = {};
65 this._measurements = [];
66 }
67 break;
68 }
69 }
70 },
72 /**
73 * This exists exclusively for testing -- our events are not intended to
74 * be retrieved via an XPCOM interface.
75 */
76 get wrappedJSObject() {
77 return this;
78 },
80 /**
81 * Holds the functions that provide UITelemetry's simple
82 * measurements. Those functions are mapped to unique names,
83 * and should be registered with addSimpleMeasureFunction.
84 */
85 _simpleMeasureFunctions: {},
87 /**
88 * Adds a single event described by a timestamp, an action, and the calling
89 * method.
90 *
91 * Optionally provide a string 'extras', which will be recorded as part of
92 * the event.
93 *
94 * All extant sessions will be recorded by name for each event.
95 */
96 addEvent: function(aAction, aMethod, aTimestamp, aExtras) {
97 if (!this.enabled) {
98 return;
99 }
101 let sessions = Object.keys(this._activeSessions);
102 let aEvent = {
103 type: "event",
104 action: aAction,
105 method: aMethod,
106 sessions: sessions,
107 timestamp: aTimestamp,
108 };
110 if (aExtras) {
111 aEvent.extras = aExtras;
112 }
114 this._recordEvent(aEvent);
115 },
117 /**
118 * Begins tracking a session by storing a timestamp for session start.
119 */
120 startSession: function(aName, aTimestamp) {
121 if (!this.enabled) {
122 return;
123 }
125 if (this._activeSessions[aName]) {
126 // Do not overwrite a previous event start if it already exists.
127 return;
128 }
129 this._activeSessions[aName] = aTimestamp;
130 },
132 /**
133 * Tracks the end of a session with a timestamp.
134 */
135 stopSession: function(aName, aReason, aTimestamp) {
136 if (!this.enabled) {
137 return;
138 }
140 let sessionStart = this._activeSessions[aName];
141 delete this._activeSessions[aName];
143 if (!sessionStart) {
144 return;
145 }
147 let aEvent = {
148 type: "session",
149 name: aName,
150 reason: aReason,
151 start: sessionStart,
152 end: aTimestamp,
153 };
155 this._recordEvent(aEvent);
156 },
158 _recordEvent: function(aEvent) {
159 this._measurements.push(aEvent);
160 },
162 /**
163 * Called by TelemetryPing to populate the simple measurement
164 * blob. This function will iterate over all functions added
165 * via addSimpleMeasureFunction and return an object with the
166 * results of those functions.
167 */
168 getSimpleMeasures: function() {
169 if (!this.enabled) {
170 return {};
171 }
173 let result = {};
174 for (let name in this._simpleMeasureFunctions) {
175 result[name] = this._simpleMeasureFunctions[name]();
176 }
177 return result;
178 },
180 /**
181 * Allows the caller to register functions that will get called
182 * for simple measures during a Telemetry ping. aName is a unique
183 * identifier used as they key for the simple measurement in the
184 * object that getSimpleMeasures returns.
185 *
186 * This function throws an exception if aName already has a function
187 * registered for it.
188 */
189 addSimpleMeasureFunction: function(aName, aFunction) {
190 if (!this.enabled) {
191 return;
192 }
194 if (aName in this._simpleMeasureFunctions) {
195 throw new Error("A simple measurement function is already registered for " + aName);
196 }
198 if (!aFunction || typeof aFunction !== 'function') {
199 throw new Error("addSimpleMeasureFunction called with non-function argument.");
200 }
202 this._simpleMeasureFunctions[aName] = aFunction;
203 },
205 removeSimpleMeasureFunction: function(aName) {
206 delete this._simpleMeasureFunctions[aName];
207 },
209 getUIMeasurements: function() {
210 if (!this.enabled) {
211 return [];
212 }
214 return this._measurements.slice();
215 }
216 };