Bug 1120379 - Send a ping deletion message to the server when FHR is deactivated. r=gfritzsche
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Thu, 28 May 2015 08:47:00 +0200
changeset 279721 fbf0fdd5facef147113166594959274f82f4a6c8
parent 279720 cc9acd02073ce768db4eb7d5124f5d7199ade689
child 279722 113c09b2fd55d1330266543530bd917b0cb52e70
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgfritzsche
bugs1120379
milestone41.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1120379 - Send a ping deletion message to the server when FHR is deactivated. r=gfritzsche
toolkit/components/telemetry/TelemetryController.jsm
toolkit/components/telemetry/TelemetrySend.jsm
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -49,16 +49,17 @@ const PING_FORMAT_VERSION = 4;
 
 // Delay before intializing telemetry (ms)
 const TELEMETRY_DELAY = 60000;
 // Delay before initializing telemetry if we're testing (ms)
 const TELEMETRY_TEST_DELAY = 100;
 
 // Ping types.
 const PING_TYPE_MAIN = "main";
+const PING_TYPE_DELETION = "deletion";
 
 // Session ping reasons.
 const REASON_GATHER_PAYLOAD = "gather-payload";
 const REASON_GATHER_SUBSESSION_PAYLOAD = "gather-subsession-payload";
 
 XPCOMUtils.defineLazyModuleGetter(this, "ClientID",
                                   "resource://gre/modules/ClientID.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
@@ -138,16 +139,17 @@ this.TelemetryController = Object.freeze
   initLogging: function() {
     configureLogging();
   },
   /**
    * Used only for testing purposes.
    */
   reset: function() {
     Impl._clientID = null;
+    Impl._detachObservers();
     TelemetryStorage.reset();
     TelemetrySend.reset();
 
     return this.setup();
   },
   /**
    * Used only for testing purposes.
    */
@@ -648,16 +650,18 @@ let Impl = {
       this._sessionRecorder.onStartup();
     }
 
     if (!this.enableTelemetryRecording()) {
       this._log.config("setupChromeProcess - Telemetry recording is disabled, skipping Chrome process setup.");
       return Promise.resolve();
     }
 
+    this._attachObservers();
+
     // For very short session durations, we may never load the client
     // id from disk.
     // We try to cache it in prefs to avoid this, even though this may
     // lead to some stale client ids.
     this._clientID = Preferences.get(PREF_CACHED_CLIENTID, null);
 
     // Delay full telemetry initialization to give the browser time to
     // run various late initializers. Otherwise our gathered memory
@@ -712,16 +716,17 @@ let Impl = {
 
   // Do proper shutdown waiting and cleanup.
   _cleanupOnShutdown: Task.async(function*() {
     if (!this._initialized) {
       return;
     }
 
     Preferences.ignore(PREF_BRANCH_LOG, configureLogging);
+    this._detachObservers();
 
     // Now do an orderly shutdown.
     try {
       // Stop any ping sending.
       yield TelemetrySend.shutdown();
 
       // First wait for clients processing shutdown.
       yield this._shutdownBarrier.wait();
@@ -800,16 +805,47 @@ let Impl = {
       initStarted: this._initStarted,
       haveDelayedInitTask: !!this._delayedInitTask,
       shutdownBarrier: this._shutdownBarrier.state,
       connectionsBarrier: this._connectionsBarrier.state,
     };
   },
 
   /**
+   * Called whenever the FHR Upload preference changes (e.g. when user disables FHR from
+   * the preferences panel), this triggers sending the deletion ping.
+   */
+  _onUploadPrefChange: function() {
+    const uploadEnabled = Preferences.get(PREF_FHR_UPLOAD_ENABLED, false);
+    if (uploadEnabled) {
+      // There's nothing we should do if we are enabling upload.
+      return;
+    }
+    // Send the deletion ping.
+    this._log.trace("_onUploadPrefChange - Sending deletion ping.");
+    this.submitExternalPing(PING_TYPE_DELETION, {}, { addClientId: true });
+  },
+
+  _attachObservers: function() {
+    if (IS_UNIFIED_TELEMETRY) {
+      // Watch the FHR upload setting to trigger deletion pings.
+      Preferences.observe(PREF_FHR_UPLOAD_ENABLED, this._onUploadPrefChange, this);
+    }
+  },
+
+  /**
+   * Remove the preference observer to avoid leaks.
+   */
+  _detachObservers: function() {
+    if (IS_UNIFIED_TELEMETRY) {
+      Preferences.ignore(PREF_FHR_UPLOAD_ENABLED, this._onUploadPrefChange, this);
+    }
+  },
+
+  /**
    * Allows waiting for TelemetryControllers delayed initialization to complete.
    * This will complete before TelemetryController is shutting down.
    * @return {Promise} Resolved when delayed TelemetryController initialization completed.
    */
   promiseInitialized: function() {
     return this._delayedInitTaskDeferred.promise;
   },
 
--- a/toolkit/components/telemetry/TelemetrySend.jsm
+++ b/toolkit/components/telemetry/TelemetrySend.jsm
@@ -49,16 +49,18 @@ const TOPIC_IDLE_DAILY = "idle-daily";
 const TOPIC_QUIT_APPLICATION = "quit-application";
 
 // Whether the FHR/Telemetry unification features are enabled.
 // Changing this pref requires a restart.
 const IS_UNIFIED_TELEMETRY = Preferences.get(PREF_UNIFIED, false);
 
 const PING_FORMAT_VERSION = 4;
 
+const PING_TYPE_DELETION = "deletion";
+
 // We try to spread "midnight" pings out over this interval.
 const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * 60 * 1000;
 // We delay sending "midnight" pings on this client by this interval.
 const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS;
 
 // Timeout after which we consider a ping submission failed.
 const PING_SUBMIT_TIMEOUT_MS = 2 * 60 * 1000;
 
@@ -88,16 +90,25 @@ let Policy = {
 /**
  * Determine if the ping has the new v4 ping format or the legacy v2 one or earlier.
  */
 function isV4PingFormat(aPing) {
   return ("id" in aPing) && ("application" in aPing) &&
          ("version" in aPing) && (aPing.version >= 2);
 }
 
+/**
+ * Check if the provided ping is a deletion ping.
+ * @param {Object} aPing The ping to check.
+ * @return {Boolean} True if the ping is a deletion ping, false otherwise.
+ */
+function isDeletionPing(aPing) {
+  return isV4PingFormat(aPing) && (aPing.type == PING_TYPE_DELETION);
+}
+
 function tomorrow(date) {
   let d = new Date(date);
   d.setDate(d.getDate() + 1);
   return d;
 }
 
 /**
  * @return {String} This returns a string with the gzip compressed data.
@@ -381,17 +392,17 @@ let TelemetrySendImpl = {
     switch(topic) {
     case TOPIC_IDLE_DAILY:
       this._sendPersistedPings();
       break;
     }
   },
 
   submitPing: function(ping) {
-    if (!this._canSend()) {
+    if (!this._canSend(ping)) {
       this._log.trace("submitPing - Telemetry is not allowed to send pings.");
       return Promise.resolve();
     }
 
     // Check if we can send pings now.
     const now = Policy.now();
     const nextPingSendTime = this._getNextPingSendTime(now);
     const throttled = (nextPingSendTime > now.getTime());
@@ -561,17 +572,17 @@ let TelemetrySendImpl = {
       }
     }
 
     let slug = pathComponents.join("/");
     return "/submit/telemetry/" + slug;
   },
 
   _doPing: function(ping, id, isPersisted) {
-    if (!this._canSend()) {
+    if (!this._canSend(ping)) {
       // We can't send the pings to the server, so don't try to.
       this._log.trace("_doPing - Sending is disabled.");
       return Promise.resolve();
     }
 
     this._log.trace("_doPing - server: " + this._server + ", persisted: " + isPersisted +
                     ", id: " + id);
     const isNewPing = isV4PingFormat(ping);
@@ -665,30 +676,36 @@ let TelemetrySendImpl = {
     startTime = new Date();
     request.send(payloadStream);
 
     return deferred.promise;
   },
 
   /**
    * Check if pings can be sent to the server. If FHR is not allowed to upload,
-   * pings are not sent to the server (Telemetry is a sub-feature of FHR).
+   * pings are not sent to the server (Telemetry is a sub-feature of FHR). If trying
+   * to send a deletion ping, don't block it.
    * If unified telemetry is off, don't send pings if Telemetry is disabled.
    *
+   * @param {Object} [ping=null] A ping to be checked.
    * @return {Boolean} True if pings can be send to the servers, false otherwise.
    */
-  _canSend: function() {
+  _canSend: function(ping = null) {
     // We only send pings from official builds, but allow overriding this for tests.
     if (!Telemetry.isOfficialTelemetry && !this._testMode) {
       return false;
     }
 
     // With unified Telemetry, the FHR upload setting controls whether we can send pings.
     // The Telemetry pref enables sending extended data sets instead.
     if (IS_UNIFIED_TELEMETRY) {
+      // Deletion pings are sent even if the upload is disabled.
+      if (ping && isDeletionPing(ping)) {
+        return true;
+      }
       return Preferences.get(PREF_FHR_UPLOAD_ENABLED, false);
     }
 
     // Without unified Telemetry, the Telemetry enabled pref controls ping sending.
     return Preferences.get(PREF_TELEMETRY_ENABLED, false);
   },
 
   _reschedulePingSendTimer: function(timestamp) {