Bug 1120372 - Introduce the "update" ping. r=bsmedberg,gfritzsche,mhowell
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Wed, 12 Jul 2017 12:12:18 +0200
changeset 420123 ac1b19c47aac763afed9a137fe6ff0ee8d2a7186
parent 420122 e53245c9e2a8855baa5b7fb5f5d5bcb0565874e9
child 420124 ca779a5aa93fecf65a56647492208f3a17fd215c
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, gfritzsche, mhowell
bugs1120372
milestone56.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 1120372 - Introduce the "update" ping. r=bsmedberg,gfritzsche,mhowell This ping will be sent as soon as an update is ready to be applied, after it's downloaded. This is currently enabled but protected behind the 'toolkit.telemetry.updatePing.enabled' preference. MozReview-Commit-ID: 4TuM0e5MzBl
browser/app/profile/firefox.js
toolkit/components/telemetry/TelemetryController.jsm
toolkit/components/telemetry/TelemetryUtils.jsm
toolkit/components/telemetry/UpdatePing.jsm
toolkit/components/telemetry/docs/concepts/pings.rst
toolkit/components/telemetry/docs/data/update-ping.rst
toolkit/components/telemetry/docs/internals/preferences.rst
toolkit/components/telemetry/moz.build
toolkit/mozapps/update/tests/browser/browser.ini
toolkit/mozapps/update/tests/browser/browser_TelemetryUpdatePing.js
toolkit/mozapps/update/tests/moz.build
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1511,16 +1511,18 @@ pref("browser.translation.engine", "bing
 
 // Telemetry settings.
 // Determines if Telemetry pings can be archived locally.
 pref("toolkit.telemetry.archive.enabled", true);
 // Enables sending the shutdown ping when Firefox shuts down.
 pref("toolkit.telemetry.shutdownPingSender.enabled", true);
 // Enables sending the 'new-profile' ping on new profiles.
 pref("toolkit.telemetry.newProfilePing.enabled", true);
+// Enables sending 'update' pings on Firefox updates.
+pref("toolkit.telemetry.updatePing.enabled", true);
 
 // Telemetry experiments settings.
 pref("experiments.enabled", true);
 pref("experiments.manifest.fetchIntervalSeconds", 86400);
 pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%");
 // Whether experiments are supported by the current application profile.
 pref("experiments.supported", true);
 
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -71,16 +71,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySession",
                                   "resource://gre/modules/TelemetrySession.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                   "resource://gre/modules/TelemetrySend.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryReportingPolicy",
                                   "resource://gre/modules/TelemetryReportingPolicy.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryModules",
                                   "resource://gre/modules/TelemetryModules.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdatePing",
+                                  "resource://gre/modules/UpdatePing.jsm");
 
 /**
  * Setup Telemetry logging. This function also gets called when loggin related
  * preferences change.
  */
 var gLogger = null;
 var gLogAppenderDump = null;
 function configureLogging() {
@@ -690,16 +692,20 @@ var Impl = {
     TelemetrySend.earlyInit();
 
     // 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 = ClientID.getCachedClientID();
 
+    // Init the update ping telemetry as early as possible. This won't have
+    // an impact on startup.
+    UpdatePing.earlyInit();
+
     // Delay full telemetry initialization to give the browser time to
     // run various late initializers. Otherwise our gathered memory
     // footprint and other numbers would be too optimistic.
     this._delayedInitTaskDeferred = PromiseUtils.defer();
     this._delayedInitTask = new DeferredTask(async () => {
       try {
         // TODO: This should probably happen after all the delayed init here.
         this._initialized = true;
@@ -776,16 +782,18 @@ var Impl = {
     this._detachObservers();
 
     // Now do an orderly shutdown.
     try {
       if (this._delayedNewPingTask) {
         await this._delayedNewPingTask.finalize();
       }
 
+      UpdatePing.shutdown();
+
       // Stop the datachoices infobar display.
       TelemetryReportingPolicy.shutdown();
       TelemetryEnvironment.shutdown();
 
       // Stop any ping sending.
       await TelemetrySend.shutdown();
 
       await TelemetrySession.shutdown();
--- a/toolkit/components/telemetry/TelemetryUtils.jsm
+++ b/toolkit/components/telemetry/TelemetryUtils.jsm
@@ -29,16 +29,17 @@ this.TelemetryUtils = {
     ArchiveEnabled: "toolkit.telemetry.archive.enabled",
     CachedClientId: "toolkit.telemetry.cachedClientID",
     FirstRun: "toolkit.telemetry.reportingpolicy.firstRun",
     OverrideOfficialCheck: "toolkit.telemetry.send.overrideOfficialCheck",
     Server: "toolkit.telemetry.server",
     ShutdownPingSender: "toolkit.telemetry.shutdownPingSender.enabled",
     TelemetryEnabled: "toolkit.telemetry.enabled",
     Unified: "toolkit.telemetry.unified",
+    UpdatePing: "toolkit.telemetry.updatePing.enabled",
     NewProfilePingEnabled: "toolkit.telemetry.newProfilePing.enabled",
     NewProfilePingDelay: "toolkit.telemetry.newProfilePing.delay",
     PreviousBuildID: "toolkit.telemetry.previousBuildID",
 
     // Log Preferences
     LogLevel: "toolkit.telemetry.log.level",
     LogDump: "toolkit.telemetry.log.dump",
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/UpdatePing.jsm
@@ -0,0 +1,104 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm", this);
+Cu.import("resource://gre/modules/Preferences.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
+                                  "resource://gre/modules/TelemetryController.jsm");
+
+const LOGGER_NAME = "Toolkit.Telemetry";
+const PING_TYPE = "update";
+const UPDATE_DOWNLOADED_TOPIC = "update-downloaded";
+
+this.EXPORTED_SYMBOLS = ["UpdatePing"];
+
+/**
+ * This module is responsible for listening to all the relevant update
+ * signals, gathering the needed information and assembling the "update"
+ * ping.
+ */
+this.UpdatePing = {
+  earlyInit() {
+    this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, "UpdatePing::");
+    this._enabled = Preferences.get(TelemetryUtils.Preferences.UpdatePing, false);
+
+    this._log.trace("init - enabled: " + this._enabled);
+
+    if (!this._enabled) {
+      return;
+    }
+
+    Services.obs.addObserver(this, UPDATE_DOWNLOADED_TOPIC);
+  },
+
+  /**
+   * Generate an "update" ping with reason "ready" and dispatch it
+   * to the Telemetry system.
+   *
+   * @param {String} aUpdateState The state of the downloaded patch. See
+   *        nsIUpdateService.idl for a list of possible values.
+   */
+  _handleUpdateReady(aUpdateState) {
+    const ALLOWED_STATES = [
+      "applied", "applied-service", "pending", "pending-service", "pending-elevate"
+    ];
+    if (!ALLOWED_STATES.includes(aUpdateState)) {
+      this._log.trace("Unexpected update state: " + aUpdateState);
+      return;
+    }
+
+    // Get the information about the update we're going to apply from the
+    // update manager.
+    let updateManager =
+      Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
+    if (!updateManager || !updateManager.activeUpdate) {
+      this._log.trace("Cannot get the update manager or no update is currently active.");
+      return;
+    }
+
+    let update = updateManager.activeUpdate;
+
+    const payload = {
+      reason: "ready",
+      targetChannel: update.channel,
+      targetVersion: update.appVersion,
+      targetBuildId: update.buildID,
+    };
+
+    const options = {
+      addClientId: true,
+      addEnvironment: true,
+      usePingSender: true,
+    };
+
+    TelemetryController.submitExternalPing(PING_TYPE, payload, options)
+                       .catch(e => this._log.error("_handleUpdateReady - failed to submit update ping", e));
+  },
+
+  /**
+   * The notifications handler.
+   */
+  observe(aSubject, aTopic, aData) {
+    this._log.trace("observe - aTopic: " + aTopic);
+    if (aTopic == UPDATE_DOWNLOADED_TOPIC) {
+      this._handleUpdateReady(aData);
+    }
+  },
+
+  shutdown() {
+    if (!this._enabled) {
+      return;
+    }
+    Services.obs.removeObserver(this, UPDATE_DOWNLOADED_TOPIC);
+  },
+};
--- a/toolkit/components/telemetry/docs/concepts/pings.rst
+++ b/toolkit/components/telemetry/docs/concepts/pings.rst
@@ -22,11 +22,11 @@ We send Telemetry with different ping ty
 
 Pings sent from code that ships with Firefox are listed in the :doc:`data documentation <../data/index>`.
 
 Important examples are:
 
 * :doc:`main <../data/main-ping>` - contains the information collected by Telemetry (Histograms, hang stacks, ...)
 * :doc:`saved-session <../data/main-ping>` - has the same format as a main ping, but it contains the *"classic"* Telemetry payload with measurements covering the whole browser session. This is only a separate type to make storage of saved-session easier server-side. This is temporary and will be removed soon.
 * :doc:`crash <../data/crash-ping>` - a ping that is captured and sent after Firefox crashes.
-* :doc:`new-profile <../data/new-profile-ping>` - sent on the first run of a new profile
-* ``upgrade`` - *planned* - sent right after an upgrade
-* :doc:`deletion <../data/deletion-ping>` - sent when FHR upload is disabled, requesting deletion of the data associated with this user
+* :doc:`new-profile <../data/new-profile-ping>` - sent on the first run of a new profile.
+* :doc:`update <../data/update-ping>` - sent right after an update is downloaded.
+* :doc:`deletion <../data/deletion-ping>` - sent when FHR upload is disabled, requesting deletion of the data associated with this user.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/data/update-ping.rst
@@ -0,0 +1,52 @@
+
+"update" ping
+==================
+
+This opt-out ping is sent from Firefox Desktop when a browser update is ready to be applied. There is a
+plan to send this ping after an update is successfully applied and the work will happen in `bug 1380256 <https://bugzilla.mozilla.org/show_bug.cgi?id=1380256>`_.
+
+Structure:
+
+.. code-block:: js
+
+    {
+      type: "update",
+      ... common ping data
+      clientId: <UUID>,
+      environment: { ... },
+      payload: {
+        reason: <string>, // "ready"
+        targetChannel: <string>, // "nightly"
+        targetVersion: <string>, // "56.01a"
+        targetBuildId: <string>, // "20080811053724"
+      }
+    }
+
+payload.reason
+--------------
+This field only supports the value ``ready``, meaning that the ping was generated after an update was downloaded
+and marked as ready to be processed. For *non-staged* updates this happens as soon as the download
+finishes and is verified while for *staged* updates this happens before the staging step is started.
+
+payload.targetChannel
+-----------------------
+The Firefox channel the update was fetched from (only valid for pings with reason "ready").
+
+payload.targetVersion
+-----------------------
+The Firefox version the browser is updating to. Follows the same format a application.version (only valid for pings with reason "ready").
+
+payload.targetBuildId
+-----------------------
+The Firefox build id the browser is updating to. Follows the same format a application.buildId (only valid for pings with reason "ready").
+
+Expected behaviours
+-------------------
+The following is a list of conditions and expected behaviours for the ``update`` ping:
+
+- **The ping is generated once every time an update is downloaded, after it was verified:**
+
+  - *for users who saw the privacy policy*, the ``update`` ping is sent immediately;
+  - *for users who did not see the privacy policy*, the ``update`` ping is saved to disk and after the policy is displayed.
+- **If the download of the update retries or other fallback occur**: the ``update`` ping will not be generated
+  multiple times, but only one time once the download is complete and verified.
--- a/toolkit/components/telemetry/docs/internals/preferences.rst
+++ b/toolkit/components/telemetry/docs/internals/preferences.rst
@@ -57,16 +57,20 @@ Preferences
 ``toolkit.telemetry.newProfilePing.enabled``
 
   Enable the :doc:`../data/new-profile` ping on new profiles.
 
 ``toolkit.telemetry.newProfilePing.delay``
 
   Controls the delay after which the :doc:`../data/new-profile` is sent on new profiles.
 
+``toolkit.telemetry.updatePing.enabled``
+
+  Enable the :doc:`../data/update-ping` on browser updates.
+
 Data-choices notification
 -------------------------
 
 ``toolkit.telemetry.reportingpolicy.firstRun``
 
   This preference is not present until the first run. After, its value is set to false. This is used to show the infobar with a more aggressive timeout if it wasn't shown yet.
 
 ``datareporting.policy.firstRunURL``
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -90,16 +90,17 @@ EXTRA_JS_MODULES += [
     'TelemetrySend.jsm',
     'TelemetrySession.jsm',
     'TelemetryStopwatch.jsm',
     'TelemetryStorage.jsm',
     'TelemetryTimestamps.jsm',
     'TelemetryUtils.jsm',
     'ThirdPartyCookieProbe.jsm',
     'UITelemetry.jsm',
+    'UpdatePing.jsm',
 ]
 
 TESTING_JS_MODULES += [
   'tests/unit/TelemetryArchiveTesting.jsm',
 ]
 
 PYTHON_UNITTEST_MANIFESTS += [
     'tests/python/python.ini',
--- a/toolkit/mozapps/update/tests/browser/browser.ini
+++ b/toolkit/mozapps/update/tests/browser/browser.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 tags = appupdate
 support-files =
   head.js
   downloadPage.html
   testConstants.js
 
+[browser_TelemetryUpdatePing.js]
 [browser_updatesBackgroundWindow.js]
 [browser_updatesBackgroundWindowFailures.js]
 [browser_updatesBasicPrompt.js]
 skip-if = asan
 reason = Bug 1168003
 [browser_updatesBasicPromptNoStaging.js]
 [browser_updatesCantApply.js]
 skip-if = os != 'win'
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_TelemetryUpdatePing.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+Cu.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
+
+/**
+ * Please note that this is really a Telemetry test, not an
+ * "update UI" test like the rest of the tests in this directory.
+ * This test does not live in toolkit/components/telemetry/tests to prevent
+ * duplicating the code for all the test dependencies. Unfortunately, due
+ * to a limitation in the build system, we were not able to simply reference
+ * the dependencies as "support-files" in the test manifest.
+ */
+add_task(async function testUpdatePingReady() {
+  SpecialPowers.pushPrefEnv({set: [
+    [PREF_APP_UPDATE_STAGING_ENABLED, false],
+    [PREF_APP_UPDATE_AUTO, false]
+  ]});
+
+  let updateParams = "promptWaitTime=0";
+
+  let archiveChecker = new TelemetryArchiveTesting.Checker();
+  await archiveChecker.promiseInit();
+
+  // Trigger an "update" ping by downloading and applying an update.
+  await runUpdateTest(updateParams, 1, [
+    {
+      notificationId: "update-available",
+      button: "button",
+      beforeClick() {
+        checkWhatsNewLink("update-available-whats-new");
+      }
+    },
+    {
+      notificationId: "update-restart",
+      button: "secondarybutton",
+      cleanup() {
+        AppMenuNotifications.removeNotification(/.*/);
+      }
+    },
+  ]);
+
+  // We cannot control when the ping will be generated/archived after we trigger
+  // an update, so let's make sure to have one before moving on with validation.
+  let updatePing;
+  await BrowserTestUtils.waitForCondition(async function() {
+    // Check that the ping made it into the Telemetry archive.
+    // The test data is defined in ../data/sharedUpdateXML.js
+    updatePing = await archiveChecker.promiseFindPing("update", [
+        [["payload", "reason"], "ready"],
+        [["payload", "targetBuildId"], "20080811053724"]
+      ]);
+    return !!updatePing;
+  }, "Make sure the ping is generated before trying to validate it.", 500, 100);
+
+  ok(updatePing, "The 'update' ping must be correctly sent.");
+
+  // We don't know the exact value for the other fields, so just check
+  // that they're available.
+  for (let f of ["targetVersion", "targetChannel"]) {
+    ok(f in updatePing.payload,
+       `${f} must be available in the update ping payload.`);
+    ok(typeof(updatePing.payload[f]) == "string",
+       `${f} must have the correct format.`);
+  }
+
+  // Also make sure that the ping contains both a client id and an
+  // environment section.
+  ok("clientId" in updatePing, "The update ping must report a client id.");
+  ok("environment" in updatePing, "The update ping must report the environment.");
+});
--- a/toolkit/mozapps/update/tests/moz.build
+++ b/toolkit/mozapps/update/tests/moz.build
@@ -107,8 +107,11 @@ FINAL_TARGET_FILES += [
     'data/sharedUpdateXML.js',
     'data/simple.mar',
     'data/wrong_product_channel.mar',
     'data/xpcshellUtilsAUS.js',
     'TestAUSReadStrings1.ini',
     'TestAUSReadStrings2.ini',
     'TestAUSReadStrings3.ini',
 ]
+
+with Files("browser/browser_TelemetryUpdatePing.js"):
+    BUG_COMPONENT = ("Toolkit", "Telemetry")