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