toolkit/components/telemetry/TelemetryPing.jsm

changeset 0
6474c204b198
     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 +};

mercurial