|
1 /* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 "use strict"; |
|
7 |
|
8 const Cc = Components.classes; |
|
9 const Ci = Components.interfaces; |
|
10 const Cr = Components.results; |
|
11 const Cu = Components.utils; |
|
12 |
|
13 Cu.import("resource://gre/modules/debug.js", this); |
|
14 Cu.import("resource://gre/modules/Services.jsm", this); |
|
15 Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); |
|
16 #ifndef MOZ_WIDGET_GONK |
|
17 Cu.import("resource://gre/modules/LightweightThemeManager.jsm", this); |
|
18 #endif |
|
19 Cu.import("resource://gre/modules/ThirdPartyCookieProbe.jsm", this); |
|
20 Cu.import("resource://gre/modules/Promise.jsm", this); |
|
21 Cu.import("resource://gre/modules/Task.jsm", this); |
|
22 Cu.import("resource://gre/modules/AsyncShutdown.jsm", this); |
|
23 |
|
24 // When modifying the payload in incompatible ways, please bump this version number |
|
25 const PAYLOAD_VERSION = 1; |
|
26 |
|
27 // This is the HG changeset of the Histogram.json file, used to associate |
|
28 // submitted ping data with its histogram definition (bug 832007) |
|
29 #expand const HISTOGRAMS_FILE_VERSION = "__HISTOGRAMS_FILE_VERSION__"; |
|
30 |
|
31 const PREF_BRANCH = "toolkit.telemetry."; |
|
32 const PREF_SERVER = PREF_BRANCH + "server"; |
|
33 const PREF_ENABLED = PREF_BRANCH + "enabled"; |
|
34 const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID"; |
|
35 |
|
36 // Do not gather data more than once a minute |
|
37 const TELEMETRY_INTERVAL = 60000; |
|
38 // Delay before intializing telemetry (ms) |
|
39 const TELEMETRY_DELAY = 60000; |
|
40 // Delay before initializing telemetry if we're testing (ms) |
|
41 const TELEMETRY_TEST_DELAY = 100; |
|
42 |
|
43 // Seconds of idle time before pinging. |
|
44 // On idle-daily a gather-telemetry notification is fired, during it probes can |
|
45 // start asynchronous tasks to gather data. On the next idle the data is sent. |
|
46 const IDLE_TIMEOUT_SECONDS = 5 * 60; |
|
47 |
|
48 var gLastMemoryPoll = null; |
|
49 |
|
50 let gWasDebuggerAttached = false; |
|
51 |
|
52 function getLocale() { |
|
53 return Cc["@mozilla.org/chrome/chrome-registry;1"]. |
|
54 getService(Ci.nsIXULChromeRegistry). |
|
55 getSelectedLocale('global'); |
|
56 } |
|
57 |
|
58 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", |
|
59 "@mozilla.org/base/telemetry;1", |
|
60 "nsITelemetry"); |
|
61 XPCOMUtils.defineLazyServiceGetter(this, "idleService", |
|
62 "@mozilla.org/widget/idleservice;1", |
|
63 "nsIIdleService"); |
|
64 XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", |
|
65 "resource://gre/modules/UpdateChannel.jsm"); |
|
66 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", |
|
67 "resource://gre/modules/AddonManager.jsm"); |
|
68 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryFile", |
|
69 "resource://gre/modules/TelemetryFile.jsm"); |
|
70 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", |
|
71 "resource://gre/modules/UITelemetry.jsm"); |
|
72 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog", |
|
73 "resource://gre/modules/TelemetryLog.jsm"); |
|
74 |
|
75 function generateUUID() { |
|
76 let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); |
|
77 // strip {} |
|
78 return str.substring(1, str.length - 1); |
|
79 } |
|
80 |
|
81 /** |
|
82 * Read current process I/O counters. |
|
83 */ |
|
84 let processInfo = { |
|
85 _initialized: false, |
|
86 _IO_COUNTERS: null, |
|
87 _kernel32: null, |
|
88 _GetProcessIoCounters: null, |
|
89 _GetCurrentProcess: null, |
|
90 getCounters: function() { |
|
91 let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes); |
|
92 if (isWindows) |
|
93 return this.getCounters_Windows(); |
|
94 return null; |
|
95 }, |
|
96 getCounters_Windows: function() { |
|
97 if (!this._initialized){ |
|
98 Cu.import("resource://gre/modules/ctypes.jsm"); |
|
99 this._IO_COUNTERS = new ctypes.StructType("IO_COUNTERS", [ |
|
100 {'readOps': ctypes.unsigned_long_long}, |
|
101 {'writeOps': ctypes.unsigned_long_long}, |
|
102 {'otherOps': ctypes.unsigned_long_long}, |
|
103 {'readBytes': ctypes.unsigned_long_long}, |
|
104 {'writeBytes': ctypes.unsigned_long_long}, |
|
105 {'otherBytes': ctypes.unsigned_long_long} ]); |
|
106 try { |
|
107 this._kernel32 = ctypes.open("Kernel32.dll"); |
|
108 this._GetProcessIoCounters = this._kernel32.declare("GetProcessIoCounters", |
|
109 ctypes.winapi_abi, |
|
110 ctypes.bool, // return |
|
111 ctypes.voidptr_t, // hProcess |
|
112 this._IO_COUNTERS.ptr); // lpIoCounters |
|
113 this._GetCurrentProcess = this._kernel32.declare("GetCurrentProcess", |
|
114 ctypes.winapi_abi, |
|
115 ctypes.voidptr_t); // return |
|
116 this._initialized = true; |
|
117 } catch (err) { |
|
118 return null; |
|
119 } |
|
120 } |
|
121 let io = new this._IO_COUNTERS(); |
|
122 if(!this._GetProcessIoCounters(this._GetCurrentProcess(), io.address())) |
|
123 return null; |
|
124 return [parseInt(io.readBytes), parseInt(io.writeBytes)]; |
|
125 } |
|
126 }; |
|
127 |
|
128 this.EXPORTED_SYMBOLS = ["TelemetryPing"]; |
|
129 |
|
130 this.TelemetryPing = Object.freeze({ |
|
131 /** |
|
132 * Returns the current telemetry payload. |
|
133 * @returns Object |
|
134 */ |
|
135 getPayload: function() { |
|
136 return Impl.getPayload(); |
|
137 }, |
|
138 /** |
|
139 * Save histograms to a file. |
|
140 * Used only for testing purposes. |
|
141 * |
|
142 * @param {nsIFile} aFile The file to load from. |
|
143 */ |
|
144 testSaveHistograms: function(aFile) { |
|
145 return Impl.testSaveHistograms(aFile); |
|
146 }, |
|
147 /** |
|
148 * Collect and store information about startup. |
|
149 */ |
|
150 gatherStartup: function() { |
|
151 return Impl.gatherStartup(); |
|
152 }, |
|
153 /** |
|
154 * Inform the ping which AddOns are installed. |
|
155 * |
|
156 * @param aAddOns - The AddOns. |
|
157 */ |
|
158 setAddOns: function(aAddOns) { |
|
159 return Impl.setAddOns(aAddOns); |
|
160 }, |
|
161 /** |
|
162 * Send a ping to a test server. Used only for testing. |
|
163 * |
|
164 * @param aServer - The server. |
|
165 */ |
|
166 testPing: function(aServer) { |
|
167 return Impl.testPing(aServer); |
|
168 }, |
|
169 /** |
|
170 * Load histograms from a file. |
|
171 * Used only for testing purposes. |
|
172 * |
|
173 * @param aFile - File to load from. |
|
174 */ |
|
175 testLoadHistograms: function(aFile) { |
|
176 return Impl.testLoadHistograms(aFile); |
|
177 }, |
|
178 /** |
|
179 * Returns the path component of the current submission URL. |
|
180 * @returns String |
|
181 */ |
|
182 submissionPath: function() { |
|
183 return Impl.submissionPath(); |
|
184 }, |
|
185 Constants: Object.freeze({ |
|
186 PREF_ENABLED: PREF_ENABLED, |
|
187 PREF_SERVER: PREF_SERVER, |
|
188 PREF_PREVIOUS_BUILDID: PREF_PREVIOUS_BUILDID, |
|
189 }), |
|
190 /** |
|
191 * Used only for testing purposes. |
|
192 */ |
|
193 reset: function() { |
|
194 this.uninstall(); |
|
195 return this.setup(); |
|
196 }, |
|
197 /** |
|
198 * Used only for testing purposes. |
|
199 */ |
|
200 setup: function() { |
|
201 return Impl.setup(true); |
|
202 }, |
|
203 /** |
|
204 * Used only for testing purposes. |
|
205 */ |
|
206 uninstall: function() { |
|
207 try { |
|
208 Impl.uninstall(); |
|
209 } catch (ex) { |
|
210 // Ignore errors |
|
211 } |
|
212 }, |
|
213 /** |
|
214 * Descriptive metadata |
|
215 * |
|
216 * @param reason |
|
217 * The reason for the telemetry ping, this will be included in the |
|
218 * returned metadata, |
|
219 * @return The metadata as a JS object |
|
220 */ |
|
221 getMetadata: function(reason) { |
|
222 return Impl.getMetadata(reason); |
|
223 }, |
|
224 /** |
|
225 * Send a notification. |
|
226 */ |
|
227 observe: function (aSubject, aTopic, aData) { |
|
228 return Impl.observe(aSubject, aTopic, aData); |
|
229 } |
|
230 }); |
|
231 |
|
232 let Impl = { |
|
233 _histograms: {}, |
|
234 _initialized: false, |
|
235 _prevValues: {}, |
|
236 // Generate a unique id once per session so the server can cope with |
|
237 // duplicate submissions. |
|
238 _uuid: generateUUID(), |
|
239 // Regex that matches histograms we care about during startup. |
|
240 // Keep this in sync with gen-histogram-bucket-ranges.py. |
|
241 _startupHistogramRegex: /SQLITE|HTTP|SPDY|CACHE|DNS/, |
|
242 _slowSQLStartup: {}, |
|
243 _prevSession: null, |
|
244 _hasWindowRestoredObserver: false, |
|
245 _hasXulWindowVisibleObserver: false, |
|
246 _startupIO : {}, |
|
247 // The previous build ID, if this is the first run with a new build. |
|
248 // Undefined if this is not the first run, or the previous build ID is unknown. |
|
249 _previousBuildID: undefined, |
|
250 |
|
251 /** |
|
252 * Gets a series of simple measurements (counters). At the moment, this |
|
253 * only returns startup data from nsIAppStartup.getStartupInfo(). |
|
254 * |
|
255 * @return simple measurements as a dictionary. |
|
256 */ |
|
257 getSimpleMeasurements: function getSimpleMeasurements(forSavedSession) { |
|
258 let si = Services.startup.getStartupInfo(); |
|
259 |
|
260 var ret = { |
|
261 // uptime in minutes |
|
262 uptime: Math.round((new Date() - si.process) / 60000) |
|
263 } |
|
264 |
|
265 // Look for app-specific timestamps |
|
266 var appTimestamps = {}; |
|
267 try { |
|
268 let o = {}; |
|
269 Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", o); |
|
270 appTimestamps = o.TelemetryTimestamps.get(); |
|
271 } catch (ex) {} |
|
272 try { |
|
273 ret.addonManager = AddonManagerPrivate.getSimpleMeasures(); |
|
274 } catch (ex) {} |
|
275 try { |
|
276 ret.UITelemetry = UITelemetry.getSimpleMeasures(); |
|
277 } catch (ex) {} |
|
278 |
|
279 if (si.process) { |
|
280 for each (let field in Object.keys(si)) { |
|
281 if (field == "process") |
|
282 continue; |
|
283 ret[field] = si[field] - si.process |
|
284 } |
|
285 |
|
286 for (let p in appTimestamps) { |
|
287 if (!(p in ret) && appTimestamps[p]) |
|
288 ret[p] = appTimestamps[p] - si.process; |
|
289 } |
|
290 } |
|
291 |
|
292 ret.startupInterrupted = Number(Services.startup.interrupted); |
|
293 |
|
294 // Update debuggerAttached flag |
|
295 let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); |
|
296 let isDebuggerAttached = debugService.isDebuggerAttached; |
|
297 gWasDebuggerAttached = gWasDebuggerAttached || isDebuggerAttached; |
|
298 ret.debuggerAttached = Number(gWasDebuggerAttached); |
|
299 |
|
300 ret.js = Cu.getJSEngineTelemetryValue(); |
|
301 |
|
302 let shutdownDuration = Telemetry.lastShutdownDuration; |
|
303 if (shutdownDuration) |
|
304 ret.shutdownDuration = shutdownDuration; |
|
305 |
|
306 let failedProfileLockCount = Telemetry.failedProfileLockCount; |
|
307 if (failedProfileLockCount) |
|
308 ret.failedProfileLockCount = failedProfileLockCount; |
|
309 |
|
310 let maximalNumberOfConcurrentThreads = Telemetry.maximalNumberOfConcurrentThreads; |
|
311 if (maximalNumberOfConcurrentThreads) |
|
312 ret.maximalNumberOfConcurrentThreads = maximalNumberOfConcurrentThreads; |
|
313 |
|
314 for (let ioCounter in this._startupIO) |
|
315 ret[ioCounter] = this._startupIO[ioCounter]; |
|
316 |
|
317 let hasPingBeenSent = false; |
|
318 try { |
|
319 hasPingBeenSent = Telemetry.getHistogramById("TELEMETRY_SUCCESS").snapshot().sum > 0; |
|
320 } catch(e) { |
|
321 } |
|
322 if (!forSavedSession || hasPingBeenSent) { |
|
323 ret.savedPings = TelemetryFile.pingsLoaded; |
|
324 } |
|
325 |
|
326 ret.pingsOverdue = TelemetryFile.pingsOverdue; |
|
327 ret.pingsDiscarded = TelemetryFile.pingsDiscarded; |
|
328 |
|
329 return ret; |
|
330 }, |
|
331 |
|
332 /** |
|
333 * When reflecting a histogram into JS, Telemetry hands us an object |
|
334 * with the following properties: |
|
335 * |
|
336 * - min, max, histogram_type, sum, sum_squares_{lo,hi}: simple integers; |
|
337 * - log_sum, log_sum_squares: doubles; |
|
338 * - counts: array of counts for histogram buckets; |
|
339 * - ranges: array of calculated bucket sizes. |
|
340 * |
|
341 * This format is not straightforward to read and potentially bulky |
|
342 * with lots of zeros in the counts array. Packing histograms makes |
|
343 * raw histograms easier to read and compresses the data a little bit. |
|
344 * |
|
345 * Returns an object: |
|
346 * { range: [min, max], bucket_count: <number of buckets>, |
|
347 * histogram_type: <histogram_type>, sum: <sum>, |
|
348 * sum_squares_lo: <sum_squares_lo>, |
|
349 * sum_squares_hi: <sum_squares_hi>, |
|
350 * log_sum: <log_sum>, log_sum_squares: <log_sum_squares>, |
|
351 * values: { bucket1: count1, bucket2: count2, ... } } |
|
352 */ |
|
353 packHistogram: function packHistogram(hgram) { |
|
354 let r = hgram.ranges;; |
|
355 let c = hgram.counts; |
|
356 let retgram = { |
|
357 range: [r[1], r[r.length - 1]], |
|
358 bucket_count: r.length, |
|
359 histogram_type: hgram.histogram_type, |
|
360 values: {}, |
|
361 sum: hgram.sum |
|
362 }; |
|
363 |
|
364 if (hgram.histogram_type == Telemetry.HISTOGRAM_EXPONENTIAL) { |
|
365 retgram.log_sum = hgram.log_sum; |
|
366 retgram.log_sum_squares = hgram.log_sum_squares; |
|
367 } else { |
|
368 retgram.sum_squares_lo = hgram.sum_squares_lo; |
|
369 retgram.sum_squares_hi = hgram.sum_squares_hi; |
|
370 } |
|
371 |
|
372 let first = true; |
|
373 let last = 0; |
|
374 |
|
375 for (let i = 0; i < c.length; i++) { |
|
376 let value = c[i]; |
|
377 if (!value) |
|
378 continue; |
|
379 |
|
380 // add a lower bound |
|
381 if (i && first) { |
|
382 retgram.values[r[i - 1]] = 0; |
|
383 } |
|
384 first = false; |
|
385 last = i + 1; |
|
386 retgram.values[r[i]] = value; |
|
387 } |
|
388 |
|
389 // add an upper bound |
|
390 if (last && last < c.length) |
|
391 retgram.values[r[last]] = 0; |
|
392 return retgram; |
|
393 }, |
|
394 |
|
395 getHistograms: function getHistograms(hls) { |
|
396 let registered = Telemetry.registeredHistograms([]); |
|
397 let ret = {}; |
|
398 |
|
399 for (let name of registered) { |
|
400 for (let n of [name, "STARTUP_" + name]) { |
|
401 if (n in hls) { |
|
402 ret[n] = this.packHistogram(hls[n]); |
|
403 } |
|
404 } |
|
405 } |
|
406 |
|
407 return ret; |
|
408 }, |
|
409 |
|
410 getAddonHistograms: function getAddonHistograms() { |
|
411 let ahs = Telemetry.addonHistogramSnapshots; |
|
412 let ret = {}; |
|
413 |
|
414 for (let addonName in ahs) { |
|
415 let addonHistograms = ahs[addonName]; |
|
416 let packedHistograms = {}; |
|
417 for (let name in addonHistograms) { |
|
418 packedHistograms[name] = this.packHistogram(addonHistograms[name]); |
|
419 } |
|
420 if (Object.keys(packedHistograms).length != 0) |
|
421 ret[addonName] = packedHistograms; |
|
422 } |
|
423 |
|
424 return ret; |
|
425 }, |
|
426 |
|
427 getThreadHangStats: function getThreadHangStats(stats) { |
|
428 stats.forEach((thread) => { |
|
429 thread.activity = this.packHistogram(thread.activity); |
|
430 thread.hangs.forEach((hang) => { |
|
431 hang.histogram = this.packHistogram(hang.histogram); |
|
432 }); |
|
433 }); |
|
434 return stats; |
|
435 }, |
|
436 |
|
437 /** |
|
438 * Descriptive metadata |
|
439 * |
|
440 * @param reason |
|
441 * The reason for the telemetry ping, this will be included in the |
|
442 * returned metadata, |
|
443 * @return The metadata as a JS object |
|
444 */ |
|
445 getMetadata: function getMetadata(reason) { |
|
446 let ai = Services.appinfo; |
|
447 let ret = { |
|
448 reason: reason, |
|
449 OS: ai.OS, |
|
450 appID: ai.ID, |
|
451 appVersion: ai.version, |
|
452 appName: ai.name, |
|
453 appBuildID: ai.appBuildID, |
|
454 appUpdateChannel: UpdateChannel.get(), |
|
455 platformBuildID: ai.platformBuildID, |
|
456 revision: HISTOGRAMS_FILE_VERSION, |
|
457 locale: getLocale() |
|
458 }; |
|
459 |
|
460 // In order to share profile data, the appName used for Metro Firefox is "Firefox", |
|
461 // (the same as desktop Firefox). We set it to "MetroFirefox" here in order to |
|
462 // differentiate telemetry pings sent by desktop vs. metro Firefox. |
|
463 if(Services.metro && Services.metro.immersive) { |
|
464 ret.appName = "MetroFirefox"; |
|
465 } |
|
466 |
|
467 if (this._previousBuildID) { |
|
468 ret.previousBuildID = this._previousBuildID; |
|
469 } |
|
470 |
|
471 // sysinfo fields are not always available, get what we can. |
|
472 let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); |
|
473 let fields = ["cpucount", "memsize", "arch", "version", "kernel_version", |
|
474 "device", "manufacturer", "hardware", "tablet", |
|
475 "hasMMX", "hasSSE", "hasSSE2", "hasSSE3", |
|
476 "hasSSSE3", "hasSSE4A", "hasSSE4_1", "hasSSE4_2", |
|
477 "hasEDSP", "hasARMv6", "hasARMv7", "hasNEON", "isWow64", |
|
478 "profileHDDModel", "profileHDDRevision", "binHDDModel", |
|
479 "binHDDRevision", "winHDDModel", "winHDDRevision"]; |
|
480 for each (let field in fields) { |
|
481 let value; |
|
482 try { |
|
483 value = sysInfo.getProperty(field); |
|
484 } catch (e) { |
|
485 continue; |
|
486 } |
|
487 if (field == "memsize") { |
|
488 // Send RAM size in megabytes. Rounding because sysinfo doesn't |
|
489 // always provide RAM in multiples of 1024. |
|
490 value = Math.round(value / 1024 / 1024); |
|
491 } |
|
492 ret[field] = value; |
|
493 } |
|
494 |
|
495 // gfxInfo fields are not always available, get what we can. |
|
496 let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); |
|
497 let gfxfields = ["adapterDescription", "adapterVendorID", "adapterDeviceID", |
|
498 "adapterRAM", "adapterDriver", "adapterDriverVersion", |
|
499 "adapterDriverDate", "adapterDescription2", |
|
500 "adapterVendorID2", "adapterDeviceID2", "adapterRAM2", |
|
501 "adapterDriver2", "adapterDriverVersion2", |
|
502 "adapterDriverDate2", "isGPU2Active", "D2DEnabled", |
|
503 "DWriteEnabled", "DWriteVersion" |
|
504 ]; |
|
505 |
|
506 if (gfxInfo) { |
|
507 for each (let field in gfxfields) { |
|
508 try { |
|
509 let value = gfxInfo[field]; |
|
510 // bug 940806: We need to do a strict equality comparison here, |
|
511 // otherwise a type conversion will occur and boolean false values |
|
512 // will get filtered out |
|
513 if (value !== "") { |
|
514 ret[field] = value; |
|
515 } |
|
516 } catch (e) { |
|
517 continue |
|
518 } |
|
519 } |
|
520 } |
|
521 |
|
522 #ifndef MOZ_WIDGET_GONK |
|
523 let theme = LightweightThemeManager.currentTheme; |
|
524 if (theme) |
|
525 ret.persona = theme.id; |
|
526 #endif |
|
527 |
|
528 if (this._addons) |
|
529 ret.addons = this._addons; |
|
530 |
|
531 let flashVersion = this.getFlashVersion(); |
|
532 if (flashVersion) |
|
533 ret.flashVersion = flashVersion; |
|
534 |
|
535 try { |
|
536 let scope = {}; |
|
537 Cu.import("resource:///modules/experiments/Experiments.jsm", scope); |
|
538 let experiments = scope.Experiments.instance() |
|
539 let activeExperiment = experiments.getActiveExperimentID(); |
|
540 if (activeExperiment) { |
|
541 ret.activeExperiment = activeExperiment; |
|
542 ret.activeExperimentBranch = experiments.getActiveExperimentBranch(); |
|
543 } |
|
544 } catch(e) { |
|
545 // If this is not Firefox, the import will fail. |
|
546 } |
|
547 |
|
548 return ret; |
|
549 }, |
|
550 |
|
551 /** |
|
552 * Pull values from about:memory into corresponding histograms |
|
553 */ |
|
554 gatherMemory: function gatherMemory() { |
|
555 let mgr; |
|
556 try { |
|
557 mgr = Cc["@mozilla.org/memory-reporter-manager;1"]. |
|
558 getService(Ci.nsIMemoryReporterManager); |
|
559 } catch (e) { |
|
560 // OK to skip memory reporters in xpcshell |
|
561 return; |
|
562 } |
|
563 |
|
564 let histogram = Telemetry.getHistogramById("TELEMETRY_MEMORY_REPORTER_MS"); |
|
565 let startTime = new Date(); |
|
566 |
|
567 // Get memory measurements from distinguished amount attributes. We used |
|
568 // to measure "explicit" too, but it could cause hangs, and the data was |
|
569 // always really noisy anyway. See bug 859657. |
|
570 // |
|
571 // test_TelemetryPing.js relies on some of these histograms being |
|
572 // here. If you remove any of the following histograms from here, you'll |
|
573 // have to modify test_TelemetryPing.js: |
|
574 // |
|
575 // * MEMORY_JS_GC_HEAP, and |
|
576 // * MEMORY_JS_COMPARTMENTS_SYSTEM. |
|
577 // |
|
578 // The distinguished amount attribute names don't match the telemetry id |
|
579 // names in some cases due to a combination of (a) historical reasons, and |
|
580 // (b) the fact that we can't change telemetry id names without breaking |
|
581 // data continuity. |
|
582 // |
|
583 let boundHandleMemoryReport = this.handleMemoryReport.bind(this); |
|
584 function h(id, units, amountName) { |
|
585 try { |
|
586 // If mgr[amountName] throws an exception, just move on -- some amounts |
|
587 // aren't available on all platforms. But if the attribute simply |
|
588 // isn't present, that indicates the distinguished amounts have changed |
|
589 // and this file hasn't been updated appropriately. |
|
590 let amount = mgr[amountName]; |
|
591 NS_ASSERT(amount !== undefined, |
|
592 "telemetry accessed an unknown distinguished amount"); |
|
593 boundHandleMemoryReport(id, units, amount); |
|
594 } catch (e) { |
|
595 }; |
|
596 } |
|
597 let b = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_BYTES, n); |
|
598 let c = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT, n); |
|
599 let cc= (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE, n); |
|
600 let p = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_PERCENTAGE, n); |
|
601 |
|
602 b("MEMORY_VSIZE", "vsize"); |
|
603 b("MEMORY_VSIZE_MAX_CONTIGUOUS", "vsizeMaxContiguous"); |
|
604 b("MEMORY_RESIDENT", "residentFast"); |
|
605 b("MEMORY_HEAP_ALLOCATED", "heapAllocated"); |
|
606 p("MEMORY_HEAP_COMMITTED_UNUSED_RATIO", "heapOverheadRatio"); |
|
607 b("MEMORY_JS_GC_HEAP", "JSMainRuntimeGCHeap"); |
|
608 b("MEMORY_JS_MAIN_RUNTIME_TEMPORARY_PEAK", "JSMainRuntimeTemporaryPeak"); |
|
609 c("MEMORY_JS_COMPARTMENTS_SYSTEM", "JSMainRuntimeCompartmentsSystem"); |
|
610 c("MEMORY_JS_COMPARTMENTS_USER", "JSMainRuntimeCompartmentsUser"); |
|
611 b("MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED", "imagesContentUsedUncompressed"); |
|
612 b("MEMORY_STORAGE_SQLITE", "storageSQLite"); |
|
613 cc("MEMORY_EVENTS_VIRTUAL", "lowMemoryEventsVirtual"); |
|
614 cc("MEMORY_EVENTS_PHYSICAL", "lowMemoryEventsPhysical"); |
|
615 c("GHOST_WINDOWS", "ghostWindows"); |
|
616 cc("PAGE_FAULTS_HARD", "pageFaultsHard"); |
|
617 |
|
618 histogram.add(new Date() - startTime); |
|
619 }, |
|
620 |
|
621 handleMemoryReport: function(id, units, amount) { |
|
622 let val; |
|
623 if (units == Ci.nsIMemoryReporter.UNITS_BYTES) { |
|
624 val = Math.floor(amount / 1024); |
|
625 } |
|
626 else if (units == Ci.nsIMemoryReporter.UNITS_PERCENTAGE) { |
|
627 // UNITS_PERCENTAGE amounts are 100x greater than their raw value. |
|
628 val = Math.floor(amount / 100); |
|
629 } |
|
630 else if (units == Ci.nsIMemoryReporter.UNITS_COUNT) { |
|
631 val = amount; |
|
632 } |
|
633 else if (units == Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE) { |
|
634 // If the reporter gives us a cumulative count, we'll report the |
|
635 // difference in its value between now and our previous ping. |
|
636 |
|
637 if (!(id in this._prevValues)) { |
|
638 // If this is the first time we're reading this reporter, store its |
|
639 // current value but don't report it in the telemetry ping, so we |
|
640 // ignore the effect startup had on the reporter. |
|
641 this._prevValues[id] = amount; |
|
642 return; |
|
643 } |
|
644 |
|
645 val = amount - this._prevValues[id]; |
|
646 this._prevValues[id] = amount; |
|
647 } |
|
648 else { |
|
649 NS_ASSERT(false, "Can't handle memory reporter with units " + units); |
|
650 return; |
|
651 } |
|
652 |
|
653 let h = this._histograms[id]; |
|
654 if (!h) { |
|
655 h = Telemetry.getHistogramById(id); |
|
656 this._histograms[id] = h; |
|
657 } |
|
658 h.add(val); |
|
659 }, |
|
660 |
|
661 /** |
|
662 * Return true if we're interested in having a STARTUP_* histogram for |
|
663 * the given histogram name. |
|
664 */ |
|
665 isInterestingStartupHistogram: function isInterestingStartupHistogram(name) { |
|
666 return this._startupHistogramRegex.test(name); |
|
667 }, |
|
668 |
|
669 /** |
|
670 * Make a copy of interesting histograms at startup. |
|
671 */ |
|
672 gatherStartupHistograms: function gatherStartupHistograms() { |
|
673 let info = Telemetry.registeredHistograms([]); |
|
674 let snapshots = Telemetry.histogramSnapshots; |
|
675 for (let name of info) { |
|
676 // Only duplicate histograms with actual data. |
|
677 if (this.isInterestingStartupHistogram(name) && name in snapshots) { |
|
678 Telemetry.histogramFrom("STARTUP_" + name, name); |
|
679 } |
|
680 } |
|
681 }, |
|
682 |
|
683 /** |
|
684 * Get the current session's payload using the provided |
|
685 * simpleMeasurements and info, which are typically obtained by a call |
|
686 * to |this.getSimpleMeasurements| and |this.getMetadata|, |
|
687 * respectively. |
|
688 */ |
|
689 assemblePayloadWithMeasurements: function assemblePayloadWithMeasurements(simpleMeasurements, info) { |
|
690 let payloadObj = { |
|
691 ver: PAYLOAD_VERSION, |
|
692 simpleMeasurements: simpleMeasurements, |
|
693 histograms: this.getHistograms(Telemetry.histogramSnapshots), |
|
694 slowSQL: Telemetry.slowSQL, |
|
695 fileIOReports: Telemetry.fileIOReports, |
|
696 chromeHangs: Telemetry.chromeHangs, |
|
697 threadHangStats: this.getThreadHangStats(Telemetry.threadHangStats), |
|
698 lateWrites: Telemetry.lateWrites, |
|
699 addonHistograms: this.getAddonHistograms(), |
|
700 addonDetails: AddonManagerPrivate.getTelemetryDetails(), |
|
701 UIMeasurements: UITelemetry.getUIMeasurements(), |
|
702 log: TelemetryLog.entries(), |
|
703 info: info |
|
704 }; |
|
705 |
|
706 if (Object.keys(this._slowSQLStartup).length != 0 && |
|
707 (Object.keys(this._slowSQLStartup.mainThread).length || |
|
708 Object.keys(this._slowSQLStartup.otherThreads).length)) { |
|
709 payloadObj.slowSQLStartup = this._slowSQLStartup; |
|
710 } |
|
711 |
|
712 return payloadObj; |
|
713 }, |
|
714 |
|
715 getSessionPayload: function getSessionPayload(reason) { |
|
716 let measurements = this.getSimpleMeasurements(reason == "saved-session"); |
|
717 let info = this.getMetadata(reason); |
|
718 return this.assemblePayloadWithMeasurements(measurements, info); |
|
719 }, |
|
720 |
|
721 assemblePing: function assemblePing(payloadObj, reason) { |
|
722 let slug = this._uuid; |
|
723 return { slug: slug, reason: reason, payload: payloadObj }; |
|
724 }, |
|
725 |
|
726 getSessionPayloadAndSlug: function getSessionPayloadAndSlug(reason) { |
|
727 return this.assemblePing(this.getSessionPayload(reason), reason); |
|
728 }, |
|
729 |
|
730 popPayloads: function popPayloads(reason) { |
|
731 function payloadIter() { |
|
732 if (reason != "overdue-flush") { |
|
733 yield this.getSessionPayloadAndSlug(reason); |
|
734 } |
|
735 let iterator = TelemetryFile.popPendingPings(reason); |
|
736 for (let data of iterator) { |
|
737 yield data; |
|
738 } |
|
739 } |
|
740 |
|
741 let payloadIterWithThis = payloadIter.bind(this); |
|
742 return { __iterator__: payloadIterWithThis }; |
|
743 }, |
|
744 |
|
745 /** |
|
746 * Send data to the server. Record success/send-time in histograms |
|
747 */ |
|
748 send: function send(reason, server) { |
|
749 // populate histograms one last time |
|
750 this.gatherMemory(); |
|
751 return this.sendPingsFromIterator(server, reason, |
|
752 Iterator(this.popPayloads(reason))); |
|
753 }, |
|
754 |
|
755 sendPingsFromIterator: function sendPingsFromIterator(server, reason, i) { |
|
756 let p = [data for (data in i)].map((data) => |
|
757 this.doPing(server, data).then(null, () => TelemetryFile.savePing(data, true))); |
|
758 |
|
759 return Promise.all(p); |
|
760 }, |
|
761 |
|
762 finishPingRequest: function finishPingRequest(success, startTime, ping) { |
|
763 let hping = Telemetry.getHistogramById("TELEMETRY_PING"); |
|
764 let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS"); |
|
765 |
|
766 hsuccess.add(success); |
|
767 hping.add(new Date() - startTime); |
|
768 |
|
769 if (success) { |
|
770 return TelemetryFile.cleanupPingFile(ping); |
|
771 } else { |
|
772 return Promise.resolve(); |
|
773 } |
|
774 }, |
|
775 |
|
776 submissionPath: function submissionPath(ping) { |
|
777 let slug; |
|
778 if (!ping) { |
|
779 slug = this._uuid; |
|
780 } else { |
|
781 let info = ping.payload.info; |
|
782 let pathComponents = [ping.slug, info.reason, info.appName, |
|
783 info.appVersion, info.appUpdateChannel, |
|
784 info.appBuildID]; |
|
785 slug = pathComponents.join("/"); |
|
786 } |
|
787 return "/submit/telemetry/" + slug; |
|
788 }, |
|
789 |
|
790 doPing: function doPing(server, ping) { |
|
791 let deferred = Promise.defer(); |
|
792 let url = server + this.submissionPath(ping); |
|
793 let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] |
|
794 .createInstance(Ci.nsIXMLHttpRequest); |
|
795 request.mozBackgroundRequest = true; |
|
796 request.open("POST", url, true); |
|
797 request.overrideMimeType("text/plain"); |
|
798 request.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); |
|
799 |
|
800 let startTime = new Date(); |
|
801 |
|
802 function handler(success) { |
|
803 return function(event) { |
|
804 this.finishPingRequest(success, startTime, ping).then(() => { |
|
805 if (success) { |
|
806 deferred.resolve(); |
|
807 } else { |
|
808 deferred.reject(event); |
|
809 } |
|
810 }); |
|
811 }; |
|
812 } |
|
813 request.addEventListener("error", handler(false).bind(this), false); |
|
814 request.addEventListener("load", handler(true).bind(this), false); |
|
815 |
|
816 request.setRequestHeader("Content-Encoding", "gzip"); |
|
817 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] |
|
818 .createInstance(Ci.nsIScriptableUnicodeConverter); |
|
819 converter.charset = "UTF-8"; |
|
820 let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(ping.payload)); |
|
821 utf8Payload += converter.Finish(); |
|
822 let payloadStream = Cc["@mozilla.org/io/string-input-stream;1"] |
|
823 .createInstance(Ci.nsIStringInputStream); |
|
824 payloadStream.data = this.gzipCompressString(utf8Payload); |
|
825 request.send(payloadStream); |
|
826 return deferred.promise; |
|
827 }, |
|
828 |
|
829 gzipCompressString: function gzipCompressString(string) { |
|
830 let observer = { |
|
831 buffer: "", |
|
832 onStreamComplete: function(loader, context, status, length, result) { |
|
833 this.buffer = String.fromCharCode.apply(this, result); |
|
834 } |
|
835 }; |
|
836 |
|
837 let scs = Cc["@mozilla.org/streamConverters;1"] |
|
838 .getService(Ci.nsIStreamConverterService); |
|
839 let listener = Cc["@mozilla.org/network/stream-loader;1"] |
|
840 .createInstance(Ci.nsIStreamLoader); |
|
841 listener.init(observer); |
|
842 let converter = scs.asyncConvertData("uncompressed", "gzip", |
|
843 listener, null); |
|
844 let stringStream = Cc["@mozilla.org/io/string-input-stream;1"] |
|
845 .createInstance(Ci.nsIStringInputStream); |
|
846 stringStream.data = string; |
|
847 converter.onStartRequest(null, null); |
|
848 converter.onDataAvailable(null, null, stringStream, 0, string.length); |
|
849 converter.onStopRequest(null, null, null); |
|
850 return observer.buffer; |
|
851 }, |
|
852 |
|
853 attachObservers: function attachObservers() { |
|
854 if (!this._initialized) |
|
855 return; |
|
856 Services.obs.addObserver(this, "cycle-collector-begin", false); |
|
857 Services.obs.addObserver(this, "idle-daily", false); |
|
858 }, |
|
859 |
|
860 detachObservers: function detachObservers() { |
|
861 if (!this._initialized) |
|
862 return; |
|
863 Services.obs.removeObserver(this, "idle-daily"); |
|
864 Services.obs.removeObserver(this, "cycle-collector-begin"); |
|
865 if (this._isIdleObserver) { |
|
866 idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); |
|
867 this._isIdleObserver = false; |
|
868 } |
|
869 }, |
|
870 |
|
871 /** |
|
872 * Initializes telemetry within a timer. If there is no PREF_SERVER set, don't turn on telemetry. |
|
873 */ |
|
874 setup: function setup(aTesting) { |
|
875 // Initialize some probes that are kept in their own modules |
|
876 this._thirdPartyCookies = new ThirdPartyCookieProbe(); |
|
877 this._thirdPartyCookies.init(); |
|
878 |
|
879 // Record old value and update build ID preference if this is the first |
|
880 // run with a new build ID. |
|
881 let previousBuildID = undefined; |
|
882 try { |
|
883 previousBuildID = Services.prefs.getCharPref(PREF_PREVIOUS_BUILDID); |
|
884 } catch (e) { |
|
885 // Preference was not set. |
|
886 } |
|
887 let thisBuildID = Services.appinfo.appBuildID; |
|
888 // If there is no previousBuildID preference, this._previousBuildID remains |
|
889 // undefined so no value is sent in the telemetry metadata. |
|
890 if (previousBuildID != thisBuildID) { |
|
891 this._previousBuildID = previousBuildID; |
|
892 Services.prefs.setCharPref(PREF_PREVIOUS_BUILDID, thisBuildID); |
|
893 } |
|
894 |
|
895 #ifdef MOZILLA_OFFICIAL |
|
896 if (!Telemetry.canSend) { |
|
897 // We can't send data; no point in initializing observers etc. |
|
898 // Only do this for official builds so that e.g. developer builds |
|
899 // still enable Telemetry based on prefs. |
|
900 Telemetry.canRecord = false; |
|
901 return; |
|
902 } |
|
903 #endif |
|
904 let enabled = false; |
|
905 try { |
|
906 enabled = Services.prefs.getBoolPref(PREF_ENABLED); |
|
907 this._server = Services.prefs.getCharPref(PREF_SERVER); |
|
908 } catch (e) { |
|
909 // Prerequesite prefs aren't set |
|
910 } |
|
911 if (!enabled) { |
|
912 // Turn off local telemetry if telemetry is disabled. |
|
913 // This may change once about:telemetry is added. |
|
914 Telemetry.canRecord = false; |
|
915 return; |
|
916 } |
|
917 |
|
918 AsyncShutdown.sendTelemetry.addBlocker( |
|
919 "Telemetry: shutting down", |
|
920 function condition(){ |
|
921 this.uninstall(); |
|
922 if (Telemetry.canSend) { |
|
923 return this.savePendingPings(); |
|
924 } |
|
925 }.bind(this)); |
|
926 |
|
927 Services.obs.addObserver(this, "sessionstore-windows-restored", false); |
|
928 Services.obs.addObserver(this, "quit-application-granted", false); |
|
929 #ifdef MOZ_WIDGET_ANDROID |
|
930 Services.obs.addObserver(this, "application-background", false); |
|
931 #endif |
|
932 Services.obs.addObserver(this, "xul-window-visible", false); |
|
933 this._hasWindowRestoredObserver = true; |
|
934 this._hasXulWindowVisibleObserver = true; |
|
935 |
|
936 // Delay full telemetry initialization to give the browser time to |
|
937 // run various late initializers. Otherwise our gathered memory |
|
938 // footprint and other numbers would be too optimistic. |
|
939 this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
|
940 let deferred = Promise.defer(); |
|
941 |
|
942 function timerCallback() { |
|
943 Task.spawn(function*(){ |
|
944 this._initialized = true; |
|
945 |
|
946 yield TelemetryFile.loadSavedPings(); |
|
947 // If we have any TelemetryPings lying around, we'll be aggressive |
|
948 // and try to send them all off ASAP. |
|
949 if (TelemetryFile.pingsOverdue > 0) { |
|
950 // It doesn't really matter what we pass to this.send as a reason, |
|
951 // since it's never sent to the server. All that this.send does with |
|
952 // the reason is check to make sure it's not a test-ping. |
|
953 yield this.send("overdue-flush", this._server); |
|
954 } |
|
955 |
|
956 this.attachObservers(); |
|
957 this.gatherMemory(); |
|
958 |
|
959 Telemetry.asyncFetchTelemetryData(function () {}); |
|
960 delete this._timer; |
|
961 deferred.resolve(); |
|
962 }.bind(this)); |
|
963 } |
|
964 |
|
965 this._timer.initWithCallback(timerCallback.bind(this), |
|
966 aTesting ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY, |
|
967 Ci.nsITimer.TYPE_ONE_SHOT); |
|
968 return deferred.promise; |
|
969 }, |
|
970 |
|
971 testLoadHistograms: function testLoadHistograms(file) { |
|
972 return TelemetryFile.testLoadHistograms(file); |
|
973 }, |
|
974 |
|
975 getFlashVersion: function getFlashVersion() { |
|
976 let host = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
|
977 let tags = host.getPluginTags(); |
|
978 |
|
979 for (let i = 0; i < tags.length; i++) { |
|
980 if (tags[i].name == "Shockwave Flash") |
|
981 return tags[i].version; |
|
982 } |
|
983 |
|
984 return null; |
|
985 }, |
|
986 |
|
987 savePendingPings: function savePendingPings() { |
|
988 let sessionPing = this.getSessionPayloadAndSlug("saved-session"); |
|
989 return TelemetryFile.savePendingPings(sessionPing); |
|
990 }, |
|
991 |
|
992 testSaveHistograms: function testSaveHistograms(file) { |
|
993 return TelemetryFile.savePingToFile(this.getSessionPayloadAndSlug("saved-session"), |
|
994 file.path, true); |
|
995 }, |
|
996 |
|
997 /** |
|
998 * Remove observers to avoid leaks |
|
999 */ |
|
1000 uninstall: function uninstall() { |
|
1001 this.detachObservers(); |
|
1002 if (this._hasWindowRestoredObserver) { |
|
1003 Services.obs.removeObserver(this, "sessionstore-windows-restored"); |
|
1004 this._hasWindowRestoredObserver = false; |
|
1005 } |
|
1006 if (this._hasXulWindowVisibleObserver) { |
|
1007 Services.obs.removeObserver(this, "xul-window-visible"); |
|
1008 this._hasXulWindowVisibleObserver = false; |
|
1009 } |
|
1010 Services.obs.removeObserver(this, "quit-application-granted"); |
|
1011 #ifdef MOZ_WIDGET_ANDROID |
|
1012 Services.obs.removeObserver(this, "application-background", false); |
|
1013 #endif |
|
1014 }, |
|
1015 |
|
1016 getPayload: function getPayload() { |
|
1017 // This function returns the current Telemetry payload to the caller. |
|
1018 // We only gather startup info once. |
|
1019 if (Object.keys(this._slowSQLStartup).length == 0) { |
|
1020 this.gatherStartupHistograms(); |
|
1021 this._slowSQLStartup = Telemetry.slowSQL; |
|
1022 } |
|
1023 this.gatherMemory(); |
|
1024 return this.getSessionPayload("gather-payload"); |
|
1025 }, |
|
1026 |
|
1027 gatherStartup: function gatherStartup() { |
|
1028 let counters = processInfo.getCounters(); |
|
1029 if (counters) { |
|
1030 [this._startupIO.startupSessionRestoreReadBytes, |
|
1031 this._startupIO.startupSessionRestoreWriteBytes] = counters; |
|
1032 } |
|
1033 this.gatherStartupHistograms(); |
|
1034 this._slowSQLStartup = Telemetry.slowSQL; |
|
1035 }, |
|
1036 |
|
1037 setAddOns: function setAddOns(aAddOns) { |
|
1038 this._addons = aAddOns; |
|
1039 }, |
|
1040 |
|
1041 sendIdlePing: function sendIdlePing(aTest, aServer) { |
|
1042 if (this._isIdleObserver) { |
|
1043 idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); |
|
1044 this._isIdleObserver = false; |
|
1045 } |
|
1046 if (aTest) { |
|
1047 return this.send("test-ping", aServer); |
|
1048 } else if (Telemetry.canSend) { |
|
1049 return this.send("idle-daily", aServer); |
|
1050 } |
|
1051 }, |
|
1052 |
|
1053 testPing: function testPing(server) { |
|
1054 return this.sendIdlePing(true, server); |
|
1055 }, |
|
1056 |
|
1057 /** |
|
1058 * This observer drives telemetry. |
|
1059 */ |
|
1060 observe: function (aSubject, aTopic, aData) { |
|
1061 switch (aTopic) { |
|
1062 case "profile-after-change": |
|
1063 return this.setup(); |
|
1064 case "cycle-collector-begin": |
|
1065 let now = new Date(); |
|
1066 if (!gLastMemoryPoll |
|
1067 || (TELEMETRY_INTERVAL <= now - gLastMemoryPoll)) { |
|
1068 gLastMemoryPoll = now; |
|
1069 this.gatherMemory(); |
|
1070 } |
|
1071 break; |
|
1072 case "xul-window-visible": |
|
1073 Services.obs.removeObserver(this, "xul-window-visible"); |
|
1074 this._hasXulWindowVisibleObserver = false; |
|
1075 var counters = processInfo.getCounters(); |
|
1076 if (counters) { |
|
1077 [this._startupIO.startupWindowVisibleReadBytes, |
|
1078 this._startupIO.startupWindowVisibleWriteBytes] = counters; |
|
1079 } |
|
1080 break; |
|
1081 case "sessionstore-windows-restored": |
|
1082 Services.obs.removeObserver(this, "sessionstore-windows-restored"); |
|
1083 this._hasWindowRestoredObserver = false; |
|
1084 // Check whether debugger was attached during startup |
|
1085 let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); |
|
1086 gWasDebuggerAttached = debugService.isDebuggerAttached; |
|
1087 this.gatherStartup(); |
|
1088 break; |
|
1089 case "idle-daily": |
|
1090 // Enqueue to main-thread, otherwise components may be inited by the |
|
1091 // idle-daily category and miss the gather-telemetry notification. |
|
1092 Services.tm.mainThread.dispatch((function() { |
|
1093 // Notify that data should be gathered now, since ping will happen soon. |
|
1094 Services.obs.notifyObservers(null, "gather-telemetry", null); |
|
1095 // The ping happens at the first idle of length IDLE_TIMEOUT_SECONDS. |
|
1096 idleService.addIdleObserver(this, IDLE_TIMEOUT_SECONDS); |
|
1097 this._isIdleObserver = true; |
|
1098 }).bind(this), Ci.nsIThread.DISPATCH_NORMAL); |
|
1099 break; |
|
1100 case "idle": |
|
1101 this.sendIdlePing(false, this._server); |
|
1102 break; |
|
1103 |
|
1104 #ifdef MOZ_WIDGET_ANDROID |
|
1105 // On Android, we can get killed without warning once we are in the background, |
|
1106 // but we may also submit data and/or come back into the foreground without getting |
|
1107 // killed. To deal with this, we save the current session data to file when we are |
|
1108 // put into the background. This handles the following post-backgrounding scenarios: |
|
1109 // 1) We are killed immediately. In this case the current session data (which we |
|
1110 // save to a file) will be loaded and submitted on a future run. |
|
1111 // 2) We submit the data while in the background, and then are killed. In this case |
|
1112 // the file that we saved will be deleted by the usual process in |
|
1113 // finishPingRequest after it is submitted. |
|
1114 // 3) We submit the data, and then come back into the foreground. Same as case (2). |
|
1115 // 4) We do not submit the data, but come back into the foreground. In this case |
|
1116 // we have the option of either deleting the file that we saved (since we will either |
|
1117 // send the live data while in the foreground, or create the file again on the next |
|
1118 // backgrounding), or not (in which case we will delete it on submit, or overwrite |
|
1119 // it on the next backgrounding). Not deleting it is faster, so that's what we do. |
|
1120 case "application-background": |
|
1121 if (Telemetry.canSend) { |
|
1122 let ping = this.getSessionPayloadAndSlug("saved-session"); |
|
1123 TelemetryFile.savePing(ping, true); |
|
1124 } |
|
1125 break; |
|
1126 #endif |
|
1127 } |
|
1128 }, |
|
1129 }; |