1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/toolkit/components/telemetry/TelemetryPing.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1129 @@ 1.4 +/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +"use strict"; 1.10 + 1.11 +const Cc = Components.classes; 1.12 +const Ci = Components.interfaces; 1.13 +const Cr = Components.results; 1.14 +const Cu = Components.utils; 1.15 + 1.16 +Cu.import("resource://gre/modules/debug.js", this); 1.17 +Cu.import("resource://gre/modules/Services.jsm", this); 1.18 +Cu.import("resource://gre/modules/XPCOMUtils.jsm", this); 1.19 +#ifndef MOZ_WIDGET_GONK 1.20 +Cu.import("resource://gre/modules/LightweightThemeManager.jsm", this); 1.21 +#endif 1.22 +Cu.import("resource://gre/modules/ThirdPartyCookieProbe.jsm", this); 1.23 +Cu.import("resource://gre/modules/Promise.jsm", this); 1.24 +Cu.import("resource://gre/modules/Task.jsm", this); 1.25 +Cu.import("resource://gre/modules/AsyncShutdown.jsm", this); 1.26 + 1.27 +// When modifying the payload in incompatible ways, please bump this version number 1.28 +const PAYLOAD_VERSION = 1; 1.29 + 1.30 +// This is the HG changeset of the Histogram.json file, used to associate 1.31 +// submitted ping data with its histogram definition (bug 832007) 1.32 +#expand const HISTOGRAMS_FILE_VERSION = "__HISTOGRAMS_FILE_VERSION__"; 1.33 + 1.34 +const PREF_BRANCH = "toolkit.telemetry."; 1.35 +const PREF_SERVER = PREF_BRANCH + "server"; 1.36 +const PREF_ENABLED = PREF_BRANCH + "enabled"; 1.37 +const PREF_PREVIOUS_BUILDID = PREF_BRANCH + "previousBuildID"; 1.38 + 1.39 +// Do not gather data more than once a minute 1.40 +const TELEMETRY_INTERVAL = 60000; 1.41 +// Delay before intializing telemetry (ms) 1.42 +const TELEMETRY_DELAY = 60000; 1.43 +// Delay before initializing telemetry if we're testing (ms) 1.44 +const TELEMETRY_TEST_DELAY = 100; 1.45 + 1.46 +// Seconds of idle time before pinging. 1.47 +// On idle-daily a gather-telemetry notification is fired, during it probes can 1.48 +// start asynchronous tasks to gather data. On the next idle the data is sent. 1.49 +const IDLE_TIMEOUT_SECONDS = 5 * 60; 1.50 + 1.51 +var gLastMemoryPoll = null; 1.52 + 1.53 +let gWasDebuggerAttached = false; 1.54 + 1.55 +function getLocale() { 1.56 + return Cc["@mozilla.org/chrome/chrome-registry;1"]. 1.57 + getService(Ci.nsIXULChromeRegistry). 1.58 + getSelectedLocale('global'); 1.59 +} 1.60 + 1.61 +XPCOMUtils.defineLazyServiceGetter(this, "Telemetry", 1.62 + "@mozilla.org/base/telemetry;1", 1.63 + "nsITelemetry"); 1.64 +XPCOMUtils.defineLazyServiceGetter(this, "idleService", 1.65 + "@mozilla.org/widget/idleservice;1", 1.66 + "nsIIdleService"); 1.67 +XPCOMUtils.defineLazyModuleGetter(this, "UpdateChannel", 1.68 + "resource://gre/modules/UpdateChannel.jsm"); 1.69 +XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate", 1.70 + "resource://gre/modules/AddonManager.jsm"); 1.71 +XPCOMUtils.defineLazyModuleGetter(this, "TelemetryFile", 1.72 + "resource://gre/modules/TelemetryFile.jsm"); 1.73 +XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", 1.74 + "resource://gre/modules/UITelemetry.jsm"); 1.75 +XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog", 1.76 + "resource://gre/modules/TelemetryLog.jsm"); 1.77 + 1.78 +function generateUUID() { 1.79 + let str = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID().toString(); 1.80 + // strip {} 1.81 + return str.substring(1, str.length - 1); 1.82 +} 1.83 + 1.84 +/** 1.85 + * Read current process I/O counters. 1.86 + */ 1.87 +let processInfo = { 1.88 + _initialized: false, 1.89 + _IO_COUNTERS: null, 1.90 + _kernel32: null, 1.91 + _GetProcessIoCounters: null, 1.92 + _GetCurrentProcess: null, 1.93 + getCounters: function() { 1.94 + let isWindows = ("@mozilla.org/windows-registry-key;1" in Components.classes); 1.95 + if (isWindows) 1.96 + return this.getCounters_Windows(); 1.97 + return null; 1.98 + }, 1.99 + getCounters_Windows: function() { 1.100 + if (!this._initialized){ 1.101 + Cu.import("resource://gre/modules/ctypes.jsm"); 1.102 + this._IO_COUNTERS = new ctypes.StructType("IO_COUNTERS", [ 1.103 + {'readOps': ctypes.unsigned_long_long}, 1.104 + {'writeOps': ctypes.unsigned_long_long}, 1.105 + {'otherOps': ctypes.unsigned_long_long}, 1.106 + {'readBytes': ctypes.unsigned_long_long}, 1.107 + {'writeBytes': ctypes.unsigned_long_long}, 1.108 + {'otherBytes': ctypes.unsigned_long_long} ]); 1.109 + try { 1.110 + this._kernel32 = ctypes.open("Kernel32.dll"); 1.111 + this._GetProcessIoCounters = this._kernel32.declare("GetProcessIoCounters", 1.112 + ctypes.winapi_abi, 1.113 + ctypes.bool, // return 1.114 + ctypes.voidptr_t, // hProcess 1.115 + this._IO_COUNTERS.ptr); // lpIoCounters 1.116 + this._GetCurrentProcess = this._kernel32.declare("GetCurrentProcess", 1.117 + ctypes.winapi_abi, 1.118 + ctypes.voidptr_t); // return 1.119 + this._initialized = true; 1.120 + } catch (err) { 1.121 + return null; 1.122 + } 1.123 + } 1.124 + let io = new this._IO_COUNTERS(); 1.125 + if(!this._GetProcessIoCounters(this._GetCurrentProcess(), io.address())) 1.126 + return null; 1.127 + return [parseInt(io.readBytes), parseInt(io.writeBytes)]; 1.128 + } 1.129 +}; 1.130 + 1.131 +this.EXPORTED_SYMBOLS = ["TelemetryPing"]; 1.132 + 1.133 +this.TelemetryPing = Object.freeze({ 1.134 + /** 1.135 + * Returns the current telemetry payload. 1.136 + * @returns Object 1.137 + */ 1.138 + getPayload: function() { 1.139 + return Impl.getPayload(); 1.140 + }, 1.141 + /** 1.142 + * Save histograms to a file. 1.143 + * Used only for testing purposes. 1.144 + * 1.145 + * @param {nsIFile} aFile The file to load from. 1.146 + */ 1.147 + testSaveHistograms: function(aFile) { 1.148 + return Impl.testSaveHistograms(aFile); 1.149 + }, 1.150 + /** 1.151 + * Collect and store information about startup. 1.152 + */ 1.153 + gatherStartup: function() { 1.154 + return Impl.gatherStartup(); 1.155 + }, 1.156 + /** 1.157 + * Inform the ping which AddOns are installed. 1.158 + * 1.159 + * @param aAddOns - The AddOns. 1.160 + */ 1.161 + setAddOns: function(aAddOns) { 1.162 + return Impl.setAddOns(aAddOns); 1.163 + }, 1.164 + /** 1.165 + * Send a ping to a test server. Used only for testing. 1.166 + * 1.167 + * @param aServer - The server. 1.168 + */ 1.169 + testPing: function(aServer) { 1.170 + return Impl.testPing(aServer); 1.171 + }, 1.172 + /** 1.173 + * Load histograms from a file. 1.174 + * Used only for testing purposes. 1.175 + * 1.176 + * @param aFile - File to load from. 1.177 + */ 1.178 + testLoadHistograms: function(aFile) { 1.179 + return Impl.testLoadHistograms(aFile); 1.180 + }, 1.181 + /** 1.182 + * Returns the path component of the current submission URL. 1.183 + * @returns String 1.184 + */ 1.185 + submissionPath: function() { 1.186 + return Impl.submissionPath(); 1.187 + }, 1.188 + Constants: Object.freeze({ 1.189 + PREF_ENABLED: PREF_ENABLED, 1.190 + PREF_SERVER: PREF_SERVER, 1.191 + PREF_PREVIOUS_BUILDID: PREF_PREVIOUS_BUILDID, 1.192 + }), 1.193 + /** 1.194 + * Used only for testing purposes. 1.195 + */ 1.196 + reset: function() { 1.197 + this.uninstall(); 1.198 + return this.setup(); 1.199 + }, 1.200 + /** 1.201 + * Used only for testing purposes. 1.202 + */ 1.203 + setup: function() { 1.204 + return Impl.setup(true); 1.205 + }, 1.206 + /** 1.207 + * Used only for testing purposes. 1.208 + */ 1.209 + uninstall: function() { 1.210 + try { 1.211 + Impl.uninstall(); 1.212 + } catch (ex) { 1.213 + // Ignore errors 1.214 + } 1.215 + }, 1.216 + /** 1.217 + * Descriptive metadata 1.218 + * 1.219 + * @param reason 1.220 + * The reason for the telemetry ping, this will be included in the 1.221 + * returned metadata, 1.222 + * @return The metadata as a JS object 1.223 + */ 1.224 + getMetadata: function(reason) { 1.225 + return Impl.getMetadata(reason); 1.226 + }, 1.227 + /** 1.228 + * Send a notification. 1.229 + */ 1.230 + observe: function (aSubject, aTopic, aData) { 1.231 + return Impl.observe(aSubject, aTopic, aData); 1.232 + } 1.233 +}); 1.234 + 1.235 +let Impl = { 1.236 + _histograms: {}, 1.237 + _initialized: false, 1.238 + _prevValues: {}, 1.239 + // Generate a unique id once per session so the server can cope with 1.240 + // duplicate submissions. 1.241 + _uuid: generateUUID(), 1.242 + // Regex that matches histograms we care about during startup. 1.243 + // Keep this in sync with gen-histogram-bucket-ranges.py. 1.244 + _startupHistogramRegex: /SQLITE|HTTP|SPDY|CACHE|DNS/, 1.245 + _slowSQLStartup: {}, 1.246 + _prevSession: null, 1.247 + _hasWindowRestoredObserver: false, 1.248 + _hasXulWindowVisibleObserver: false, 1.249 + _startupIO : {}, 1.250 + // The previous build ID, if this is the first run with a new build. 1.251 + // Undefined if this is not the first run, or the previous build ID is unknown. 1.252 + _previousBuildID: undefined, 1.253 + 1.254 + /** 1.255 + * Gets a series of simple measurements (counters). At the moment, this 1.256 + * only returns startup data from nsIAppStartup.getStartupInfo(). 1.257 + * 1.258 + * @return simple measurements as a dictionary. 1.259 + */ 1.260 + getSimpleMeasurements: function getSimpleMeasurements(forSavedSession) { 1.261 + let si = Services.startup.getStartupInfo(); 1.262 + 1.263 + var ret = { 1.264 + // uptime in minutes 1.265 + uptime: Math.round((new Date() - si.process) / 60000) 1.266 + } 1.267 + 1.268 + // Look for app-specific timestamps 1.269 + var appTimestamps = {}; 1.270 + try { 1.271 + let o = {}; 1.272 + Cu.import("resource://gre/modules/TelemetryTimestamps.jsm", o); 1.273 + appTimestamps = o.TelemetryTimestamps.get(); 1.274 + } catch (ex) {} 1.275 + try { 1.276 + ret.addonManager = AddonManagerPrivate.getSimpleMeasures(); 1.277 + } catch (ex) {} 1.278 + try { 1.279 + ret.UITelemetry = UITelemetry.getSimpleMeasures(); 1.280 + } catch (ex) {} 1.281 + 1.282 + if (si.process) { 1.283 + for each (let field in Object.keys(si)) { 1.284 + if (field == "process") 1.285 + continue; 1.286 + ret[field] = si[field] - si.process 1.287 + } 1.288 + 1.289 + for (let p in appTimestamps) { 1.290 + if (!(p in ret) && appTimestamps[p]) 1.291 + ret[p] = appTimestamps[p] - si.process; 1.292 + } 1.293 + } 1.294 + 1.295 + ret.startupInterrupted = Number(Services.startup.interrupted); 1.296 + 1.297 + // Update debuggerAttached flag 1.298 + let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); 1.299 + let isDebuggerAttached = debugService.isDebuggerAttached; 1.300 + gWasDebuggerAttached = gWasDebuggerAttached || isDebuggerAttached; 1.301 + ret.debuggerAttached = Number(gWasDebuggerAttached); 1.302 + 1.303 + ret.js = Cu.getJSEngineTelemetryValue(); 1.304 + 1.305 + let shutdownDuration = Telemetry.lastShutdownDuration; 1.306 + if (shutdownDuration) 1.307 + ret.shutdownDuration = shutdownDuration; 1.308 + 1.309 + let failedProfileLockCount = Telemetry.failedProfileLockCount; 1.310 + if (failedProfileLockCount) 1.311 + ret.failedProfileLockCount = failedProfileLockCount; 1.312 + 1.313 + let maximalNumberOfConcurrentThreads = Telemetry.maximalNumberOfConcurrentThreads; 1.314 + if (maximalNumberOfConcurrentThreads) 1.315 + ret.maximalNumberOfConcurrentThreads = maximalNumberOfConcurrentThreads; 1.316 + 1.317 + for (let ioCounter in this._startupIO) 1.318 + ret[ioCounter] = this._startupIO[ioCounter]; 1.319 + 1.320 + let hasPingBeenSent = false; 1.321 + try { 1.322 + hasPingBeenSent = Telemetry.getHistogramById("TELEMETRY_SUCCESS").snapshot().sum > 0; 1.323 + } catch(e) { 1.324 + } 1.325 + if (!forSavedSession || hasPingBeenSent) { 1.326 + ret.savedPings = TelemetryFile.pingsLoaded; 1.327 + } 1.328 + 1.329 + ret.pingsOverdue = TelemetryFile.pingsOverdue; 1.330 + ret.pingsDiscarded = TelemetryFile.pingsDiscarded; 1.331 + 1.332 + return ret; 1.333 + }, 1.334 + 1.335 + /** 1.336 + * When reflecting a histogram into JS, Telemetry hands us an object 1.337 + * with the following properties: 1.338 + * 1.339 + * - min, max, histogram_type, sum, sum_squares_{lo,hi}: simple integers; 1.340 + * - log_sum, log_sum_squares: doubles; 1.341 + * - counts: array of counts for histogram buckets; 1.342 + * - ranges: array of calculated bucket sizes. 1.343 + * 1.344 + * This format is not straightforward to read and potentially bulky 1.345 + * with lots of zeros in the counts array. Packing histograms makes 1.346 + * raw histograms easier to read and compresses the data a little bit. 1.347 + * 1.348 + * Returns an object: 1.349 + * { range: [min, max], bucket_count: <number of buckets>, 1.350 + * histogram_type: <histogram_type>, sum: <sum>, 1.351 + * sum_squares_lo: <sum_squares_lo>, 1.352 + * sum_squares_hi: <sum_squares_hi>, 1.353 + * log_sum: <log_sum>, log_sum_squares: <log_sum_squares>, 1.354 + * values: { bucket1: count1, bucket2: count2, ... } } 1.355 + */ 1.356 + packHistogram: function packHistogram(hgram) { 1.357 + let r = hgram.ranges;; 1.358 + let c = hgram.counts; 1.359 + let retgram = { 1.360 + range: [r[1], r[r.length - 1]], 1.361 + bucket_count: r.length, 1.362 + histogram_type: hgram.histogram_type, 1.363 + values: {}, 1.364 + sum: hgram.sum 1.365 + }; 1.366 + 1.367 + if (hgram.histogram_type == Telemetry.HISTOGRAM_EXPONENTIAL) { 1.368 + retgram.log_sum = hgram.log_sum; 1.369 + retgram.log_sum_squares = hgram.log_sum_squares; 1.370 + } else { 1.371 + retgram.sum_squares_lo = hgram.sum_squares_lo; 1.372 + retgram.sum_squares_hi = hgram.sum_squares_hi; 1.373 + } 1.374 + 1.375 + let first = true; 1.376 + let last = 0; 1.377 + 1.378 + for (let i = 0; i < c.length; i++) { 1.379 + let value = c[i]; 1.380 + if (!value) 1.381 + continue; 1.382 + 1.383 + // add a lower bound 1.384 + if (i && first) { 1.385 + retgram.values[r[i - 1]] = 0; 1.386 + } 1.387 + first = false; 1.388 + last = i + 1; 1.389 + retgram.values[r[i]] = value; 1.390 + } 1.391 + 1.392 + // add an upper bound 1.393 + if (last && last < c.length) 1.394 + retgram.values[r[last]] = 0; 1.395 + return retgram; 1.396 + }, 1.397 + 1.398 + getHistograms: function getHistograms(hls) { 1.399 + let registered = Telemetry.registeredHistograms([]); 1.400 + let ret = {}; 1.401 + 1.402 + for (let name of registered) { 1.403 + for (let n of [name, "STARTUP_" + name]) { 1.404 + if (n in hls) { 1.405 + ret[n] = this.packHistogram(hls[n]); 1.406 + } 1.407 + } 1.408 + } 1.409 + 1.410 + return ret; 1.411 + }, 1.412 + 1.413 + getAddonHistograms: function getAddonHistograms() { 1.414 + let ahs = Telemetry.addonHistogramSnapshots; 1.415 + let ret = {}; 1.416 + 1.417 + for (let addonName in ahs) { 1.418 + let addonHistograms = ahs[addonName]; 1.419 + let packedHistograms = {}; 1.420 + for (let name in addonHistograms) { 1.421 + packedHistograms[name] = this.packHistogram(addonHistograms[name]); 1.422 + } 1.423 + if (Object.keys(packedHistograms).length != 0) 1.424 + ret[addonName] = packedHistograms; 1.425 + } 1.426 + 1.427 + return ret; 1.428 + }, 1.429 + 1.430 + getThreadHangStats: function getThreadHangStats(stats) { 1.431 + stats.forEach((thread) => { 1.432 + thread.activity = this.packHistogram(thread.activity); 1.433 + thread.hangs.forEach((hang) => { 1.434 + hang.histogram = this.packHistogram(hang.histogram); 1.435 + }); 1.436 + }); 1.437 + return stats; 1.438 + }, 1.439 + 1.440 + /** 1.441 + * Descriptive metadata 1.442 + * 1.443 + * @param reason 1.444 + * The reason for the telemetry ping, this will be included in the 1.445 + * returned metadata, 1.446 + * @return The metadata as a JS object 1.447 + */ 1.448 + getMetadata: function getMetadata(reason) { 1.449 + let ai = Services.appinfo; 1.450 + let ret = { 1.451 + reason: reason, 1.452 + OS: ai.OS, 1.453 + appID: ai.ID, 1.454 + appVersion: ai.version, 1.455 + appName: ai.name, 1.456 + appBuildID: ai.appBuildID, 1.457 + appUpdateChannel: UpdateChannel.get(), 1.458 + platformBuildID: ai.platformBuildID, 1.459 + revision: HISTOGRAMS_FILE_VERSION, 1.460 + locale: getLocale() 1.461 + }; 1.462 + 1.463 + // In order to share profile data, the appName used for Metro Firefox is "Firefox", 1.464 + // (the same as desktop Firefox). We set it to "MetroFirefox" here in order to 1.465 + // differentiate telemetry pings sent by desktop vs. metro Firefox. 1.466 + if(Services.metro && Services.metro.immersive) { 1.467 + ret.appName = "MetroFirefox"; 1.468 + } 1.469 + 1.470 + if (this._previousBuildID) { 1.471 + ret.previousBuildID = this._previousBuildID; 1.472 + } 1.473 + 1.474 + // sysinfo fields are not always available, get what we can. 1.475 + let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2); 1.476 + let fields = ["cpucount", "memsize", "arch", "version", "kernel_version", 1.477 + "device", "manufacturer", "hardware", "tablet", 1.478 + "hasMMX", "hasSSE", "hasSSE2", "hasSSE3", 1.479 + "hasSSSE3", "hasSSE4A", "hasSSE4_1", "hasSSE4_2", 1.480 + "hasEDSP", "hasARMv6", "hasARMv7", "hasNEON", "isWow64", 1.481 + "profileHDDModel", "profileHDDRevision", "binHDDModel", 1.482 + "binHDDRevision", "winHDDModel", "winHDDRevision"]; 1.483 + for each (let field in fields) { 1.484 + let value; 1.485 + try { 1.486 + value = sysInfo.getProperty(field); 1.487 + } catch (e) { 1.488 + continue; 1.489 + } 1.490 + if (field == "memsize") { 1.491 + // Send RAM size in megabytes. Rounding because sysinfo doesn't 1.492 + // always provide RAM in multiples of 1024. 1.493 + value = Math.round(value / 1024 / 1024); 1.494 + } 1.495 + ret[field] = value; 1.496 + } 1.497 + 1.498 + // gfxInfo fields are not always available, get what we can. 1.499 + let gfxInfo = Cc["@mozilla.org/gfx/info;1"].getService(Ci.nsIGfxInfo); 1.500 + let gfxfields = ["adapterDescription", "adapterVendorID", "adapterDeviceID", 1.501 + "adapterRAM", "adapterDriver", "adapterDriverVersion", 1.502 + "adapterDriverDate", "adapterDescription2", 1.503 + "adapterVendorID2", "adapterDeviceID2", "adapterRAM2", 1.504 + "adapterDriver2", "adapterDriverVersion2", 1.505 + "adapterDriverDate2", "isGPU2Active", "D2DEnabled", 1.506 + "DWriteEnabled", "DWriteVersion" 1.507 + ]; 1.508 + 1.509 + if (gfxInfo) { 1.510 + for each (let field in gfxfields) { 1.511 + try { 1.512 + let value = gfxInfo[field]; 1.513 + // bug 940806: We need to do a strict equality comparison here, 1.514 + // otherwise a type conversion will occur and boolean false values 1.515 + // will get filtered out 1.516 + if (value !== "") { 1.517 + ret[field] = value; 1.518 + } 1.519 + } catch (e) { 1.520 + continue 1.521 + } 1.522 + } 1.523 + } 1.524 + 1.525 +#ifndef MOZ_WIDGET_GONK 1.526 + let theme = LightweightThemeManager.currentTheme; 1.527 + if (theme) 1.528 + ret.persona = theme.id; 1.529 +#endif 1.530 + 1.531 + if (this._addons) 1.532 + ret.addons = this._addons; 1.533 + 1.534 + let flashVersion = this.getFlashVersion(); 1.535 + if (flashVersion) 1.536 + ret.flashVersion = flashVersion; 1.537 + 1.538 + try { 1.539 + let scope = {}; 1.540 + Cu.import("resource:///modules/experiments/Experiments.jsm", scope); 1.541 + let experiments = scope.Experiments.instance() 1.542 + let activeExperiment = experiments.getActiveExperimentID(); 1.543 + if (activeExperiment) { 1.544 + ret.activeExperiment = activeExperiment; 1.545 + ret.activeExperimentBranch = experiments.getActiveExperimentBranch(); 1.546 + } 1.547 + } catch(e) { 1.548 + // If this is not Firefox, the import will fail. 1.549 + } 1.550 + 1.551 + return ret; 1.552 + }, 1.553 + 1.554 + /** 1.555 + * Pull values from about:memory into corresponding histograms 1.556 + */ 1.557 + gatherMemory: function gatherMemory() { 1.558 + let mgr; 1.559 + try { 1.560 + mgr = Cc["@mozilla.org/memory-reporter-manager;1"]. 1.561 + getService(Ci.nsIMemoryReporterManager); 1.562 + } catch (e) { 1.563 + // OK to skip memory reporters in xpcshell 1.564 + return; 1.565 + } 1.566 + 1.567 + let histogram = Telemetry.getHistogramById("TELEMETRY_MEMORY_REPORTER_MS"); 1.568 + let startTime = new Date(); 1.569 + 1.570 + // Get memory measurements from distinguished amount attributes. We used 1.571 + // to measure "explicit" too, but it could cause hangs, and the data was 1.572 + // always really noisy anyway. See bug 859657. 1.573 + // 1.574 + // test_TelemetryPing.js relies on some of these histograms being 1.575 + // here. If you remove any of the following histograms from here, you'll 1.576 + // have to modify test_TelemetryPing.js: 1.577 + // 1.578 + // * MEMORY_JS_GC_HEAP, and 1.579 + // * MEMORY_JS_COMPARTMENTS_SYSTEM. 1.580 + // 1.581 + // The distinguished amount attribute names don't match the telemetry id 1.582 + // names in some cases due to a combination of (a) historical reasons, and 1.583 + // (b) the fact that we can't change telemetry id names without breaking 1.584 + // data continuity. 1.585 + // 1.586 + let boundHandleMemoryReport = this.handleMemoryReport.bind(this); 1.587 + function h(id, units, amountName) { 1.588 + try { 1.589 + // If mgr[amountName] throws an exception, just move on -- some amounts 1.590 + // aren't available on all platforms. But if the attribute simply 1.591 + // isn't present, that indicates the distinguished amounts have changed 1.592 + // and this file hasn't been updated appropriately. 1.593 + let amount = mgr[amountName]; 1.594 + NS_ASSERT(amount !== undefined, 1.595 + "telemetry accessed an unknown distinguished amount"); 1.596 + boundHandleMemoryReport(id, units, amount); 1.597 + } catch (e) { 1.598 + }; 1.599 + } 1.600 + let b = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_BYTES, n); 1.601 + let c = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT, n); 1.602 + let cc= (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE, n); 1.603 + let p = (id, n) => h(id, Ci.nsIMemoryReporter.UNITS_PERCENTAGE, n); 1.604 + 1.605 + b("MEMORY_VSIZE", "vsize"); 1.606 + b("MEMORY_VSIZE_MAX_CONTIGUOUS", "vsizeMaxContiguous"); 1.607 + b("MEMORY_RESIDENT", "residentFast"); 1.608 + b("MEMORY_HEAP_ALLOCATED", "heapAllocated"); 1.609 + p("MEMORY_HEAP_COMMITTED_UNUSED_RATIO", "heapOverheadRatio"); 1.610 + b("MEMORY_JS_GC_HEAP", "JSMainRuntimeGCHeap"); 1.611 + b("MEMORY_JS_MAIN_RUNTIME_TEMPORARY_PEAK", "JSMainRuntimeTemporaryPeak"); 1.612 + c("MEMORY_JS_COMPARTMENTS_SYSTEM", "JSMainRuntimeCompartmentsSystem"); 1.613 + c("MEMORY_JS_COMPARTMENTS_USER", "JSMainRuntimeCompartmentsUser"); 1.614 + b("MEMORY_IMAGES_CONTENT_USED_UNCOMPRESSED", "imagesContentUsedUncompressed"); 1.615 + b("MEMORY_STORAGE_SQLITE", "storageSQLite"); 1.616 + cc("MEMORY_EVENTS_VIRTUAL", "lowMemoryEventsVirtual"); 1.617 + cc("MEMORY_EVENTS_PHYSICAL", "lowMemoryEventsPhysical"); 1.618 + c("GHOST_WINDOWS", "ghostWindows"); 1.619 + cc("PAGE_FAULTS_HARD", "pageFaultsHard"); 1.620 + 1.621 + histogram.add(new Date() - startTime); 1.622 + }, 1.623 + 1.624 + handleMemoryReport: function(id, units, amount) { 1.625 + let val; 1.626 + if (units == Ci.nsIMemoryReporter.UNITS_BYTES) { 1.627 + val = Math.floor(amount / 1024); 1.628 + } 1.629 + else if (units == Ci.nsIMemoryReporter.UNITS_PERCENTAGE) { 1.630 + // UNITS_PERCENTAGE amounts are 100x greater than their raw value. 1.631 + val = Math.floor(amount / 100); 1.632 + } 1.633 + else if (units == Ci.nsIMemoryReporter.UNITS_COUNT) { 1.634 + val = amount; 1.635 + } 1.636 + else if (units == Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE) { 1.637 + // If the reporter gives us a cumulative count, we'll report the 1.638 + // difference in its value between now and our previous ping. 1.639 + 1.640 + if (!(id in this._prevValues)) { 1.641 + // If this is the first time we're reading this reporter, store its 1.642 + // current value but don't report it in the telemetry ping, so we 1.643 + // ignore the effect startup had on the reporter. 1.644 + this._prevValues[id] = amount; 1.645 + return; 1.646 + } 1.647 + 1.648 + val = amount - this._prevValues[id]; 1.649 + this._prevValues[id] = amount; 1.650 + } 1.651 + else { 1.652 + NS_ASSERT(false, "Can't handle memory reporter with units " + units); 1.653 + return; 1.654 + } 1.655 + 1.656 + let h = this._histograms[id]; 1.657 + if (!h) { 1.658 + h = Telemetry.getHistogramById(id); 1.659 + this._histograms[id] = h; 1.660 + } 1.661 + h.add(val); 1.662 + }, 1.663 + 1.664 + /** 1.665 + * Return true if we're interested in having a STARTUP_* histogram for 1.666 + * the given histogram name. 1.667 + */ 1.668 + isInterestingStartupHistogram: function isInterestingStartupHistogram(name) { 1.669 + return this._startupHistogramRegex.test(name); 1.670 + }, 1.671 + 1.672 + /** 1.673 + * Make a copy of interesting histograms at startup. 1.674 + */ 1.675 + gatherStartupHistograms: function gatherStartupHistograms() { 1.676 + let info = Telemetry.registeredHistograms([]); 1.677 + let snapshots = Telemetry.histogramSnapshots; 1.678 + for (let name of info) { 1.679 + // Only duplicate histograms with actual data. 1.680 + if (this.isInterestingStartupHistogram(name) && name in snapshots) { 1.681 + Telemetry.histogramFrom("STARTUP_" + name, name); 1.682 + } 1.683 + } 1.684 + }, 1.685 + 1.686 + /** 1.687 + * Get the current session's payload using the provided 1.688 + * simpleMeasurements and info, which are typically obtained by a call 1.689 + * to |this.getSimpleMeasurements| and |this.getMetadata|, 1.690 + * respectively. 1.691 + */ 1.692 + assemblePayloadWithMeasurements: function assemblePayloadWithMeasurements(simpleMeasurements, info) { 1.693 + let payloadObj = { 1.694 + ver: PAYLOAD_VERSION, 1.695 + simpleMeasurements: simpleMeasurements, 1.696 + histograms: this.getHistograms(Telemetry.histogramSnapshots), 1.697 + slowSQL: Telemetry.slowSQL, 1.698 + fileIOReports: Telemetry.fileIOReports, 1.699 + chromeHangs: Telemetry.chromeHangs, 1.700 + threadHangStats: this.getThreadHangStats(Telemetry.threadHangStats), 1.701 + lateWrites: Telemetry.lateWrites, 1.702 + addonHistograms: this.getAddonHistograms(), 1.703 + addonDetails: AddonManagerPrivate.getTelemetryDetails(), 1.704 + UIMeasurements: UITelemetry.getUIMeasurements(), 1.705 + log: TelemetryLog.entries(), 1.706 + info: info 1.707 + }; 1.708 + 1.709 + if (Object.keys(this._slowSQLStartup).length != 0 && 1.710 + (Object.keys(this._slowSQLStartup.mainThread).length || 1.711 + Object.keys(this._slowSQLStartup.otherThreads).length)) { 1.712 + payloadObj.slowSQLStartup = this._slowSQLStartup; 1.713 + } 1.714 + 1.715 + return payloadObj; 1.716 + }, 1.717 + 1.718 + getSessionPayload: function getSessionPayload(reason) { 1.719 + let measurements = this.getSimpleMeasurements(reason == "saved-session"); 1.720 + let info = this.getMetadata(reason); 1.721 + return this.assemblePayloadWithMeasurements(measurements, info); 1.722 + }, 1.723 + 1.724 + assemblePing: function assemblePing(payloadObj, reason) { 1.725 + let slug = this._uuid; 1.726 + return { slug: slug, reason: reason, payload: payloadObj }; 1.727 + }, 1.728 + 1.729 + getSessionPayloadAndSlug: function getSessionPayloadAndSlug(reason) { 1.730 + return this.assemblePing(this.getSessionPayload(reason), reason); 1.731 + }, 1.732 + 1.733 + popPayloads: function popPayloads(reason) { 1.734 + function payloadIter() { 1.735 + if (reason != "overdue-flush") { 1.736 + yield this.getSessionPayloadAndSlug(reason); 1.737 + } 1.738 + let iterator = TelemetryFile.popPendingPings(reason); 1.739 + for (let data of iterator) { 1.740 + yield data; 1.741 + } 1.742 + } 1.743 + 1.744 + let payloadIterWithThis = payloadIter.bind(this); 1.745 + return { __iterator__: payloadIterWithThis }; 1.746 + }, 1.747 + 1.748 + /** 1.749 + * Send data to the server. Record success/send-time in histograms 1.750 + */ 1.751 + send: function send(reason, server) { 1.752 + // populate histograms one last time 1.753 + this.gatherMemory(); 1.754 + return this.sendPingsFromIterator(server, reason, 1.755 + Iterator(this.popPayloads(reason))); 1.756 + }, 1.757 + 1.758 + sendPingsFromIterator: function sendPingsFromIterator(server, reason, i) { 1.759 + let p = [data for (data in i)].map((data) => 1.760 + this.doPing(server, data).then(null, () => TelemetryFile.savePing(data, true))); 1.761 + 1.762 + return Promise.all(p); 1.763 + }, 1.764 + 1.765 + finishPingRequest: function finishPingRequest(success, startTime, ping) { 1.766 + let hping = Telemetry.getHistogramById("TELEMETRY_PING"); 1.767 + let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS"); 1.768 + 1.769 + hsuccess.add(success); 1.770 + hping.add(new Date() - startTime); 1.771 + 1.772 + if (success) { 1.773 + return TelemetryFile.cleanupPingFile(ping); 1.774 + } else { 1.775 + return Promise.resolve(); 1.776 + } 1.777 + }, 1.778 + 1.779 + submissionPath: function submissionPath(ping) { 1.780 + let slug; 1.781 + if (!ping) { 1.782 + slug = this._uuid; 1.783 + } else { 1.784 + let info = ping.payload.info; 1.785 + let pathComponents = [ping.slug, info.reason, info.appName, 1.786 + info.appVersion, info.appUpdateChannel, 1.787 + info.appBuildID]; 1.788 + slug = pathComponents.join("/"); 1.789 + } 1.790 + return "/submit/telemetry/" + slug; 1.791 + }, 1.792 + 1.793 + doPing: function doPing(server, ping) { 1.794 + let deferred = Promise.defer(); 1.795 + let url = server + this.submissionPath(ping); 1.796 + let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] 1.797 + .createInstance(Ci.nsIXMLHttpRequest); 1.798 + request.mozBackgroundRequest = true; 1.799 + request.open("POST", url, true); 1.800 + request.overrideMimeType("text/plain"); 1.801 + request.setRequestHeader("Content-Type", "application/json; charset=UTF-8"); 1.802 + 1.803 + let startTime = new Date(); 1.804 + 1.805 + function handler(success) { 1.806 + return function(event) { 1.807 + this.finishPingRequest(success, startTime, ping).then(() => { 1.808 + if (success) { 1.809 + deferred.resolve(); 1.810 + } else { 1.811 + deferred.reject(event); 1.812 + } 1.813 + }); 1.814 + }; 1.815 + } 1.816 + request.addEventListener("error", handler(false).bind(this), false); 1.817 + request.addEventListener("load", handler(true).bind(this), false); 1.818 + 1.819 + request.setRequestHeader("Content-Encoding", "gzip"); 1.820 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.821 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.822 + converter.charset = "UTF-8"; 1.823 + let utf8Payload = converter.ConvertFromUnicode(JSON.stringify(ping.payload)); 1.824 + utf8Payload += converter.Finish(); 1.825 + let payloadStream = Cc["@mozilla.org/io/string-input-stream;1"] 1.826 + .createInstance(Ci.nsIStringInputStream); 1.827 + payloadStream.data = this.gzipCompressString(utf8Payload); 1.828 + request.send(payloadStream); 1.829 + return deferred.promise; 1.830 + }, 1.831 + 1.832 + gzipCompressString: function gzipCompressString(string) { 1.833 + let observer = { 1.834 + buffer: "", 1.835 + onStreamComplete: function(loader, context, status, length, result) { 1.836 + this.buffer = String.fromCharCode.apply(this, result); 1.837 + } 1.838 + }; 1.839 + 1.840 + let scs = Cc["@mozilla.org/streamConverters;1"] 1.841 + .getService(Ci.nsIStreamConverterService); 1.842 + let listener = Cc["@mozilla.org/network/stream-loader;1"] 1.843 + .createInstance(Ci.nsIStreamLoader); 1.844 + listener.init(observer); 1.845 + let converter = scs.asyncConvertData("uncompressed", "gzip", 1.846 + listener, null); 1.847 + let stringStream = Cc["@mozilla.org/io/string-input-stream;1"] 1.848 + .createInstance(Ci.nsIStringInputStream); 1.849 + stringStream.data = string; 1.850 + converter.onStartRequest(null, null); 1.851 + converter.onDataAvailable(null, null, stringStream, 0, string.length); 1.852 + converter.onStopRequest(null, null, null); 1.853 + return observer.buffer; 1.854 + }, 1.855 + 1.856 + attachObservers: function attachObservers() { 1.857 + if (!this._initialized) 1.858 + return; 1.859 + Services.obs.addObserver(this, "cycle-collector-begin", false); 1.860 + Services.obs.addObserver(this, "idle-daily", false); 1.861 + }, 1.862 + 1.863 + detachObservers: function detachObservers() { 1.864 + if (!this._initialized) 1.865 + return; 1.866 + Services.obs.removeObserver(this, "idle-daily"); 1.867 + Services.obs.removeObserver(this, "cycle-collector-begin"); 1.868 + if (this._isIdleObserver) { 1.869 + idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); 1.870 + this._isIdleObserver = false; 1.871 + } 1.872 + }, 1.873 + 1.874 + /** 1.875 + * Initializes telemetry within a timer. If there is no PREF_SERVER set, don't turn on telemetry. 1.876 + */ 1.877 + setup: function setup(aTesting) { 1.878 + // Initialize some probes that are kept in their own modules 1.879 + this._thirdPartyCookies = new ThirdPartyCookieProbe(); 1.880 + this._thirdPartyCookies.init(); 1.881 + 1.882 + // Record old value and update build ID preference if this is the first 1.883 + // run with a new build ID. 1.884 + let previousBuildID = undefined; 1.885 + try { 1.886 + previousBuildID = Services.prefs.getCharPref(PREF_PREVIOUS_BUILDID); 1.887 + } catch (e) { 1.888 + // Preference was not set. 1.889 + } 1.890 + let thisBuildID = Services.appinfo.appBuildID; 1.891 + // If there is no previousBuildID preference, this._previousBuildID remains 1.892 + // undefined so no value is sent in the telemetry metadata. 1.893 + if (previousBuildID != thisBuildID) { 1.894 + this._previousBuildID = previousBuildID; 1.895 + Services.prefs.setCharPref(PREF_PREVIOUS_BUILDID, thisBuildID); 1.896 + } 1.897 + 1.898 +#ifdef MOZILLA_OFFICIAL 1.899 + if (!Telemetry.canSend) { 1.900 + // We can't send data; no point in initializing observers etc. 1.901 + // Only do this for official builds so that e.g. developer builds 1.902 + // still enable Telemetry based on prefs. 1.903 + Telemetry.canRecord = false; 1.904 + return; 1.905 + } 1.906 +#endif 1.907 + let enabled = false; 1.908 + try { 1.909 + enabled = Services.prefs.getBoolPref(PREF_ENABLED); 1.910 + this._server = Services.prefs.getCharPref(PREF_SERVER); 1.911 + } catch (e) { 1.912 + // Prerequesite prefs aren't set 1.913 + } 1.914 + if (!enabled) { 1.915 + // Turn off local telemetry if telemetry is disabled. 1.916 + // This may change once about:telemetry is added. 1.917 + Telemetry.canRecord = false; 1.918 + return; 1.919 + } 1.920 + 1.921 + AsyncShutdown.sendTelemetry.addBlocker( 1.922 + "Telemetry: shutting down", 1.923 + function condition(){ 1.924 + this.uninstall(); 1.925 + if (Telemetry.canSend) { 1.926 + return this.savePendingPings(); 1.927 + } 1.928 + }.bind(this)); 1.929 + 1.930 + Services.obs.addObserver(this, "sessionstore-windows-restored", false); 1.931 + Services.obs.addObserver(this, "quit-application-granted", false); 1.932 +#ifdef MOZ_WIDGET_ANDROID 1.933 + Services.obs.addObserver(this, "application-background", false); 1.934 +#endif 1.935 + Services.obs.addObserver(this, "xul-window-visible", false); 1.936 + this._hasWindowRestoredObserver = true; 1.937 + this._hasXulWindowVisibleObserver = true; 1.938 + 1.939 + // Delay full telemetry initialization to give the browser time to 1.940 + // run various late initializers. Otherwise our gathered memory 1.941 + // footprint and other numbers would be too optimistic. 1.942 + this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.943 + let deferred = Promise.defer(); 1.944 + 1.945 + function timerCallback() { 1.946 + Task.spawn(function*(){ 1.947 + this._initialized = true; 1.948 + 1.949 + yield TelemetryFile.loadSavedPings(); 1.950 + // If we have any TelemetryPings lying around, we'll be aggressive 1.951 + // and try to send them all off ASAP. 1.952 + if (TelemetryFile.pingsOverdue > 0) { 1.953 + // It doesn't really matter what we pass to this.send as a reason, 1.954 + // since it's never sent to the server. All that this.send does with 1.955 + // the reason is check to make sure it's not a test-ping. 1.956 + yield this.send("overdue-flush", this._server); 1.957 + } 1.958 + 1.959 + this.attachObservers(); 1.960 + this.gatherMemory(); 1.961 + 1.962 + Telemetry.asyncFetchTelemetryData(function () {}); 1.963 + delete this._timer; 1.964 + deferred.resolve(); 1.965 + }.bind(this)); 1.966 + } 1.967 + 1.968 + this._timer.initWithCallback(timerCallback.bind(this), 1.969 + aTesting ? TELEMETRY_TEST_DELAY : TELEMETRY_DELAY, 1.970 + Ci.nsITimer.TYPE_ONE_SHOT); 1.971 + return deferred.promise; 1.972 + }, 1.973 + 1.974 + testLoadHistograms: function testLoadHistograms(file) { 1.975 + return TelemetryFile.testLoadHistograms(file); 1.976 + }, 1.977 + 1.978 + getFlashVersion: function getFlashVersion() { 1.979 + let host = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); 1.980 + let tags = host.getPluginTags(); 1.981 + 1.982 + for (let i = 0; i < tags.length; i++) { 1.983 + if (tags[i].name == "Shockwave Flash") 1.984 + return tags[i].version; 1.985 + } 1.986 + 1.987 + return null; 1.988 + }, 1.989 + 1.990 + savePendingPings: function savePendingPings() { 1.991 + let sessionPing = this.getSessionPayloadAndSlug("saved-session"); 1.992 + return TelemetryFile.savePendingPings(sessionPing); 1.993 + }, 1.994 + 1.995 + testSaveHistograms: function testSaveHistograms(file) { 1.996 + return TelemetryFile.savePingToFile(this.getSessionPayloadAndSlug("saved-session"), 1.997 + file.path, true); 1.998 + }, 1.999 + 1.1000 + /** 1.1001 + * Remove observers to avoid leaks 1.1002 + */ 1.1003 + uninstall: function uninstall() { 1.1004 + this.detachObservers(); 1.1005 + if (this._hasWindowRestoredObserver) { 1.1006 + Services.obs.removeObserver(this, "sessionstore-windows-restored"); 1.1007 + this._hasWindowRestoredObserver = false; 1.1008 + } 1.1009 + if (this._hasXulWindowVisibleObserver) { 1.1010 + Services.obs.removeObserver(this, "xul-window-visible"); 1.1011 + this._hasXulWindowVisibleObserver = false; 1.1012 + } 1.1013 + Services.obs.removeObserver(this, "quit-application-granted"); 1.1014 +#ifdef MOZ_WIDGET_ANDROID 1.1015 + Services.obs.removeObserver(this, "application-background", false); 1.1016 +#endif 1.1017 + }, 1.1018 + 1.1019 + getPayload: function getPayload() { 1.1020 + // This function returns the current Telemetry payload to the caller. 1.1021 + // We only gather startup info once. 1.1022 + if (Object.keys(this._slowSQLStartup).length == 0) { 1.1023 + this.gatherStartupHistograms(); 1.1024 + this._slowSQLStartup = Telemetry.slowSQL; 1.1025 + } 1.1026 + this.gatherMemory(); 1.1027 + return this.getSessionPayload("gather-payload"); 1.1028 + }, 1.1029 + 1.1030 + gatherStartup: function gatherStartup() { 1.1031 + let counters = processInfo.getCounters(); 1.1032 + if (counters) { 1.1033 + [this._startupIO.startupSessionRestoreReadBytes, 1.1034 + this._startupIO.startupSessionRestoreWriteBytes] = counters; 1.1035 + } 1.1036 + this.gatherStartupHistograms(); 1.1037 + this._slowSQLStartup = Telemetry.slowSQL; 1.1038 + }, 1.1039 + 1.1040 + setAddOns: function setAddOns(aAddOns) { 1.1041 + this._addons = aAddOns; 1.1042 + }, 1.1043 + 1.1044 + sendIdlePing: function sendIdlePing(aTest, aServer) { 1.1045 + if (this._isIdleObserver) { 1.1046 + idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS); 1.1047 + this._isIdleObserver = false; 1.1048 + } 1.1049 + if (aTest) { 1.1050 + return this.send("test-ping", aServer); 1.1051 + } else if (Telemetry.canSend) { 1.1052 + return this.send("idle-daily", aServer); 1.1053 + } 1.1054 + }, 1.1055 + 1.1056 + testPing: function testPing(server) { 1.1057 + return this.sendIdlePing(true, server); 1.1058 + }, 1.1059 + 1.1060 + /** 1.1061 + * This observer drives telemetry. 1.1062 + */ 1.1063 + observe: function (aSubject, aTopic, aData) { 1.1064 + switch (aTopic) { 1.1065 + case "profile-after-change": 1.1066 + return this.setup(); 1.1067 + case "cycle-collector-begin": 1.1068 + let now = new Date(); 1.1069 + if (!gLastMemoryPoll 1.1070 + || (TELEMETRY_INTERVAL <= now - gLastMemoryPoll)) { 1.1071 + gLastMemoryPoll = now; 1.1072 + this.gatherMemory(); 1.1073 + } 1.1074 + break; 1.1075 + case "xul-window-visible": 1.1076 + Services.obs.removeObserver(this, "xul-window-visible"); 1.1077 + this._hasXulWindowVisibleObserver = false; 1.1078 + var counters = processInfo.getCounters(); 1.1079 + if (counters) { 1.1080 + [this._startupIO.startupWindowVisibleReadBytes, 1.1081 + this._startupIO.startupWindowVisibleWriteBytes] = counters; 1.1082 + } 1.1083 + break; 1.1084 + case "sessionstore-windows-restored": 1.1085 + Services.obs.removeObserver(this, "sessionstore-windows-restored"); 1.1086 + this._hasWindowRestoredObserver = false; 1.1087 + // Check whether debugger was attached during startup 1.1088 + let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); 1.1089 + gWasDebuggerAttached = debugService.isDebuggerAttached; 1.1090 + this.gatherStartup(); 1.1091 + break; 1.1092 + case "idle-daily": 1.1093 + // Enqueue to main-thread, otherwise components may be inited by the 1.1094 + // idle-daily category and miss the gather-telemetry notification. 1.1095 + Services.tm.mainThread.dispatch((function() { 1.1096 + // Notify that data should be gathered now, since ping will happen soon. 1.1097 + Services.obs.notifyObservers(null, "gather-telemetry", null); 1.1098 + // The ping happens at the first idle of length IDLE_TIMEOUT_SECONDS. 1.1099 + idleService.addIdleObserver(this, IDLE_TIMEOUT_SECONDS); 1.1100 + this._isIdleObserver = true; 1.1101 + }).bind(this), Ci.nsIThread.DISPATCH_NORMAL); 1.1102 + break; 1.1103 + case "idle": 1.1104 + this.sendIdlePing(false, this._server); 1.1105 + break; 1.1106 + 1.1107 +#ifdef MOZ_WIDGET_ANDROID 1.1108 + // On Android, we can get killed without warning once we are in the background, 1.1109 + // but we may also submit data and/or come back into the foreground without getting 1.1110 + // killed. To deal with this, we save the current session data to file when we are 1.1111 + // put into the background. This handles the following post-backgrounding scenarios: 1.1112 + // 1) We are killed immediately. In this case the current session data (which we 1.1113 + // save to a file) will be loaded and submitted on a future run. 1.1114 + // 2) We submit the data while in the background, and then are killed. In this case 1.1115 + // the file that we saved will be deleted by the usual process in 1.1116 + // finishPingRequest after it is submitted. 1.1117 + // 3) We submit the data, and then come back into the foreground. Same as case (2). 1.1118 + // 4) We do not submit the data, but come back into the foreground. In this case 1.1119 + // we have the option of either deleting the file that we saved (since we will either 1.1120 + // send the live data while in the foreground, or create the file again on the next 1.1121 + // backgrounding), or not (in which case we will delete it on submit, or overwrite 1.1122 + // it on the next backgrounding). Not deleting it is faster, so that's what we do. 1.1123 + case "application-background": 1.1124 + if (Telemetry.canSend) { 1.1125 + let ping = this.getSessionPayloadAndSlug("saved-session"); 1.1126 + TelemetryFile.savePing(ping, true); 1.1127 + } 1.1128 + break; 1.1129 +#endif 1.1130 + } 1.1131 + }, 1.1132 +};