|
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 |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 /** |
|
6 * Telemetry. |
|
7 * |
|
8 * To add metrics for a tool: |
|
9 * |
|
10 * 1. Create boolean, flag and exponential entries in |
|
11 * toolkit/components/telemetry/Histograms.json. Each type is optional but it |
|
12 * is best if all three can be included. |
|
13 * |
|
14 * 2. Add your chart entries to browser/devtools/shared/telemetry.js |
|
15 * (Telemetry.prototype._histograms): |
|
16 * mytoolname: { |
|
17 * histogram: "DEVTOOLS_MYTOOLNAME_OPENED_BOOLEAN", |
|
18 * userHistogram: "DEVTOOLS_MYTOOLNAME_OPENED_PER_USER_FLAG", |
|
19 * timerHistogram: "DEVTOOLS_MYTOOLNAME_TIME_ACTIVE_SECONDS" |
|
20 * }, |
|
21 * |
|
22 * 3. Include this module at the top of your tool. Use: |
|
23 * let Telemetry = require("devtools/shared/telemetry") |
|
24 * |
|
25 * 4. Create a telemetry instance in your tool's constructor: |
|
26 * this._telemetry = new Telemetry(); |
|
27 * |
|
28 * 5. When your tool is opened call: |
|
29 * this._telemetry.toolOpened("mytoolname"); |
|
30 * |
|
31 * 6. When your tool is closed call: |
|
32 * this._telemetry.toolClosed("mytoolname"); |
|
33 * |
|
34 * Note: |
|
35 * You can view telemetry stats for your local Firefox instance via |
|
36 * about:telemetry. |
|
37 * |
|
38 * You can view telemetry stats for large groups of Firefox users at |
|
39 * metrics.mozilla.com. |
|
40 */ |
|
41 |
|
42 const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version"; |
|
43 |
|
44 this.Telemetry = function() { |
|
45 // Bind pretty much all functions so that callers do not need to. |
|
46 this.toolOpened = this.toolOpened.bind(this); |
|
47 this.toolClosed = this.toolClosed.bind(this); |
|
48 this.log = this.log.bind(this); |
|
49 this.logOncePerBrowserVersion = this.logOncePerBrowserVersion.bind(this); |
|
50 this.destroy = this.destroy.bind(this); |
|
51 |
|
52 this._timers = new Map(); |
|
53 }; |
|
54 |
|
55 module.exports = Telemetry; |
|
56 |
|
57 let {Cc, Ci, Cu} = require("chrome"); |
|
58 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); |
|
59 let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); |
|
60 |
|
61 Telemetry.prototype = { |
|
62 _histograms: { |
|
63 toolbox: { |
|
64 timerHistogram: "DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS" |
|
65 }, |
|
66 options: { |
|
67 histogram: "DEVTOOLS_OPTIONS_OPENED_BOOLEAN", |
|
68 userHistogram: "DEVTOOLS_OPTIONS_OPENED_PER_USER_FLAG", |
|
69 timerHistogram: "DEVTOOLS_OPTIONS_TIME_ACTIVE_SECONDS" |
|
70 }, |
|
71 webconsole: { |
|
72 histogram: "DEVTOOLS_WEBCONSOLE_OPENED_BOOLEAN", |
|
73 userHistogram: "DEVTOOLS_WEBCONSOLE_OPENED_PER_USER_FLAG", |
|
74 timerHistogram: "DEVTOOLS_WEBCONSOLE_TIME_ACTIVE_SECONDS" |
|
75 }, |
|
76 browserconsole: { |
|
77 histogram: "DEVTOOLS_BROWSERCONSOLE_OPENED_BOOLEAN", |
|
78 userHistogram: "DEVTOOLS_BROWSERCONSOLE_OPENED_PER_USER_FLAG", |
|
79 timerHistogram: "DEVTOOLS_BROWSERCONSOLE_TIME_ACTIVE_SECONDS" |
|
80 }, |
|
81 inspector: { |
|
82 histogram: "DEVTOOLS_INSPECTOR_OPENED_BOOLEAN", |
|
83 userHistogram: "DEVTOOLS_INSPECTOR_OPENED_PER_USER_FLAG", |
|
84 timerHistogram: "DEVTOOLS_INSPECTOR_TIME_ACTIVE_SECONDS" |
|
85 }, |
|
86 ruleview: { |
|
87 histogram: "DEVTOOLS_RULEVIEW_OPENED_BOOLEAN", |
|
88 userHistogram: "DEVTOOLS_RULEVIEW_OPENED_PER_USER_FLAG", |
|
89 timerHistogram: "DEVTOOLS_RULEVIEW_TIME_ACTIVE_SECONDS" |
|
90 }, |
|
91 computedview: { |
|
92 histogram: "DEVTOOLS_COMPUTEDVIEW_OPENED_BOOLEAN", |
|
93 userHistogram: "DEVTOOLS_COMPUTEDVIEW_OPENED_PER_USER_FLAG", |
|
94 timerHistogram: "DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS" |
|
95 }, |
|
96 layoutview: { |
|
97 histogram: "DEVTOOLS_LAYOUTVIEW_OPENED_BOOLEAN", |
|
98 userHistogram: "DEVTOOLS_LAYOUTVIEW_OPENED_PER_USER_FLAG", |
|
99 timerHistogram: "DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS" |
|
100 }, |
|
101 fontinspector: { |
|
102 histogram: "DEVTOOLS_FONTINSPECTOR_OPENED_BOOLEAN", |
|
103 userHistogram: "DEVTOOLS_FONTINSPECTOR_OPENED_PER_USER_FLAG", |
|
104 timerHistogram: "DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS" |
|
105 }, |
|
106 jsdebugger: { |
|
107 histogram: "DEVTOOLS_JSDEBUGGER_OPENED_BOOLEAN", |
|
108 userHistogram: "DEVTOOLS_JSDEBUGGER_OPENED_PER_USER_FLAG", |
|
109 timerHistogram: "DEVTOOLS_JSDEBUGGER_TIME_ACTIVE_SECONDS" |
|
110 }, |
|
111 jsbrowserdebugger: { |
|
112 histogram: "DEVTOOLS_JSBROWSERDEBUGGER_OPENED_BOOLEAN", |
|
113 userHistogram: "DEVTOOLS_JSBROWSERDEBUGGER_OPENED_PER_USER_FLAG", |
|
114 timerHistogram: "DEVTOOLS_JSBROWSERDEBUGGER_TIME_ACTIVE_SECONDS" |
|
115 }, |
|
116 styleeditor: { |
|
117 histogram: "DEVTOOLS_STYLEEDITOR_OPENED_BOOLEAN", |
|
118 userHistogram: "DEVTOOLS_STYLEEDITOR_OPENED_PER_USER_FLAG", |
|
119 timerHistogram: "DEVTOOLS_STYLEEDITOR_TIME_ACTIVE_SECONDS" |
|
120 }, |
|
121 shadereditor: { |
|
122 histogram: "DEVTOOLS_SHADEREDITOR_OPENED_BOOLEAN", |
|
123 userHistogram: "DEVTOOLS_SHADEREDITOR_OPENED_PER_USER_FLAG", |
|
124 timerHistogram: "DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS" |
|
125 }, |
|
126 jsprofiler: { |
|
127 histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN", |
|
128 userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG", |
|
129 timerHistogram: "DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS" |
|
130 }, |
|
131 netmonitor: { |
|
132 histogram: "DEVTOOLS_NETMONITOR_OPENED_BOOLEAN", |
|
133 userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG", |
|
134 timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS" |
|
135 }, |
|
136 tilt: { |
|
137 histogram: "DEVTOOLS_TILT_OPENED_BOOLEAN", |
|
138 userHistogram: "DEVTOOLS_TILT_OPENED_PER_USER_FLAG", |
|
139 timerHistogram: "DEVTOOLS_TILT_TIME_ACTIVE_SECONDS" |
|
140 }, |
|
141 paintflashing: { |
|
142 histogram: "DEVTOOLS_PAINTFLASHING_OPENED_BOOLEAN", |
|
143 userHistogram: "DEVTOOLS_PAINTFLASHING_OPENED_PER_USER_FLAG", |
|
144 timerHistogram: "DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS" |
|
145 }, |
|
146 scratchpad: { |
|
147 histogram: "DEVTOOLS_SCRATCHPAD_OPENED_BOOLEAN", |
|
148 userHistogram: "DEVTOOLS_SCRATCHPAD_OPENED_PER_USER_FLAG", |
|
149 timerHistogram: "DEVTOOLS_SCRATCHPAD_TIME_ACTIVE_SECONDS" |
|
150 }, |
|
151 responsive: { |
|
152 histogram: "DEVTOOLS_RESPONSIVE_OPENED_BOOLEAN", |
|
153 userHistogram: "DEVTOOLS_RESPONSIVE_OPENED_PER_USER_FLAG", |
|
154 timerHistogram: "DEVTOOLS_RESPONSIVE_TIME_ACTIVE_SECONDS" |
|
155 }, |
|
156 developertoolbar: { |
|
157 histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_BOOLEAN", |
|
158 userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG", |
|
159 timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS" |
|
160 }, |
|
161 custom: { |
|
162 histogram: "DEVTOOLS_CUSTOM_OPENED_BOOLEAN", |
|
163 userHistogram: "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG", |
|
164 timerHistogram: "DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS" |
|
165 } |
|
166 }, |
|
167 |
|
168 /** |
|
169 * Add an entry to a histogram. |
|
170 * |
|
171 * @param {String} id |
|
172 * Used to look up the relevant histogram ID and log true to that |
|
173 * histogram. |
|
174 */ |
|
175 toolOpened: function(id) { |
|
176 let charts = this._histograms[id] || this._histograms.custom; |
|
177 |
|
178 if (charts.histogram) { |
|
179 this.log(charts.histogram, true); |
|
180 } |
|
181 if (charts.userHistogram) { |
|
182 this.logOncePerBrowserVersion(charts.userHistogram, true); |
|
183 } |
|
184 if (charts.timerHistogram) { |
|
185 this._timers.set(charts.timerHistogram, new Date()); |
|
186 } |
|
187 }, |
|
188 |
|
189 toolClosed: function(id) { |
|
190 let charts = this._histograms[id]; |
|
191 |
|
192 if (!charts || !charts.timerHistogram) { |
|
193 return; |
|
194 } |
|
195 |
|
196 let startTime = this._timers.get(charts.timerHistogram); |
|
197 |
|
198 if (startTime) { |
|
199 let time = (new Date() - startTime) / 1000; |
|
200 this.log(charts.timerHistogram, time); |
|
201 this._timers.delete(charts.timerHistogram); |
|
202 } |
|
203 }, |
|
204 |
|
205 /** |
|
206 * Log a value to a histogram. |
|
207 * |
|
208 * @param {String} histogramId |
|
209 * Histogram in which the data is to be stored. |
|
210 * @param value |
|
211 * Value to store. |
|
212 */ |
|
213 log: function(histogramId, value) { |
|
214 if (histogramId) { |
|
215 let histogram; |
|
216 |
|
217 try { |
|
218 let histogram = Services.telemetry.getHistogramById(histogramId); |
|
219 histogram.add(value); |
|
220 } catch(e) { |
|
221 dump("Warning: An attempt was made to write to the " + histogramId + |
|
222 " histogram, which is not defined in Histograms.json\n"); |
|
223 } |
|
224 } |
|
225 }, |
|
226 |
|
227 /** |
|
228 * Log info about usage once per browser version. This allows us to discover |
|
229 * how many individual users are using our tools for each browser version. |
|
230 * |
|
231 * @param {String} perUserHistogram |
|
232 * Histogram in which the data is to be stored. |
|
233 */ |
|
234 logOncePerBrowserVersion: function(perUserHistogram, value) { |
|
235 let currentVersion = appInfo.version; |
|
236 let latest = Services.prefs.getCharPref(TOOLS_OPENED_PREF); |
|
237 let latestObj = JSON.parse(latest); |
|
238 |
|
239 let lastVersionHistogramUpdated = latestObj[perUserHistogram]; |
|
240 |
|
241 if (typeof lastVersionHistogramUpdated == "undefined" || |
|
242 lastVersionHistogramUpdated !== currentVersion) { |
|
243 latestObj[perUserHistogram] = currentVersion; |
|
244 latest = JSON.stringify(latestObj); |
|
245 Services.prefs.setCharPref(TOOLS_OPENED_PREF, latest); |
|
246 this.log(perUserHistogram, value); |
|
247 } |
|
248 }, |
|
249 |
|
250 destroy: function() { |
|
251 for (let [histogram, time] of this._timers) { |
|
252 time = (new Date() - time) / 1000; |
|
253 |
|
254 this.log(histogram, time); |
|
255 this._timers.delete(histogram); |
|
256 } |
|
257 } |
|
258 }; |
|
259 |
|
260 XPCOMUtils.defineLazyGetter(this, "appInfo", function() { |
|
261 return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); |
|
262 }); |