Bug 1119061 - Part 2: Add Sync 1.1 -> Sync 1.5 migration telemetry. r=rnewman
authorNick Alexander <nalexander@mozilla.com>
Fri, 16 Jan 2015 17:42:34 -0800
changeset 224213 72103138c055a62adccaf02ea6e005983d5256c5
parent 224212 94d2782392e4749bb63572132ca428c3ca093126
child 224413 35df417b93a79f36e3be6415830716e4d2725c22
push id10871
push usernalexander@mozilla.com
push dateSat, 17 Jan 2015 02:07:30 +0000
treeherderfx-team@72103138c055 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs1119061
milestone38.0a1
Bug 1119061 - Part 2: Add Sync 1.1 -> Sync 1.5 migration telemetry. r=rnewman ======== https://github.com/mozilla-services/android-sync/commit/f7eaef78c18b75f4e03b1f2d897de8de72474399 Author: Nick Alexander <nalexander@mozilla.com> Date: Wed Jan 14 17:36:29 2015 -0800 Bug 1119061 - Part 2: Add Sync 1.1 to Sync 1.5 migration telemetry. ======== https://github.com/mozilla-services/android-sync/commit/e64f9687038a9198a0c67d80d328f08be235311c Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Jan 12 16:30:09 2015 -0800 Bug 1119061 - Part 1: Add TelemetryWrapper. This is cribbed, more or less directly, from the stumbler. ======== https://github.com/mozilla-services/android-sync/commit/51299e74e4e7b9f3228ff163ed2566cca9efcd3a Author: Nick Alexander <nalexander@mozilla.com> Date: Mon Jan 12 17:26:41 2015 -0800 Bug 1119061 - Pre: Clear Firefox Account notifications when Android Account is removed.
mobile/android/base/android-services.mozbuild
mobile/android/base/background/common/telemetry/TelemetryWrapper.java
mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java
mobile/android/base/fxa/receivers/FxAccountDeletedService.java
mobile/android/base/fxa/sync/FxAccountNotificationManager.java
mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
mobile/android/base/sync/MigrationSentinelSyncStage.java
mobile/android/base/sync/telemetry/TelemetryContract.java
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -775,16 +775,17 @@ sync_java_files = [
     'background/common/log/writers/AndroidLogWriter.java',
     'background/common/log/writers/LevelFilteringLogWriter.java',
     'background/common/log/writers/LogWriter.java',
     'background/common/log/writers/PrintLogWriter.java',
     'background/common/log/writers/SimpleTagLogWriter.java',
     'background/common/log/writers/StringLogWriter.java',
     'background/common/log/writers/TagLogWriter.java',
     'background/common/log/writers/ThreadLocalTagLogWriter.java',
+    'background/common/telemetry/TelemetryWrapper.java',
     'background/datareporting/TelemetryRecorder.java',
     'background/db/CursorDumper.java',
     'background/db/Tab.java',
     'background/fxa/FxAccount10AuthDelegate.java',
     'background/fxa/FxAccount10CreateDelegate.java',
     'background/fxa/FxAccount20CreateDelegate.java',
     'background/fxa/FxAccount20LoginDelegate.java',
     'background/fxa/FxAccountAgeLockoutHelper.java',
@@ -1127,16 +1128,17 @@ sync_java_files = [
     'sync/synchronizer/SessionNotBegunException.java',
     'sync/synchronizer/Synchronizer.java',
     'sync/synchronizer/SynchronizerDelegate.java',
     'sync/synchronizer/SynchronizerSession.java',
     'sync/synchronizer/SynchronizerSessionDelegate.java',
     'sync/synchronizer/UnbundleError.java',
     'sync/synchronizer/UnexpectedSessionException.java',
     'sync/SynchronizerConfiguration.java',
+    'sync/telemetry/TelemetryContract.java',
     'sync/ThreadPool.java',
     'sync/UnexpectedJSONException.java',
     'sync/UnknownSynchronizerConfigurationVersionException.java',
     'sync/Utils.java',
     'tokenserver/TokenServerClient.java',
     'tokenserver/TokenServerClientDelegate.java',
     'tokenserver/TokenServerException.java',
     'tokenserver/TokenServerToken.java',
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/background/common/telemetry/TelemetryWrapper.java
@@ -0,0 +1,56 @@
+/* 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/. */
+
+package org.mozilla.gecko.background.common.telemetry;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.mozilla.gecko.background.common.log.Logger;
+
+/**
+ * Android Background Services are normally built into Fennec, but can also be
+ * built as a stand-alone APK for rapid local development. The current Telemetry
+ * implementation is coupled to Gecko, and Background Services should not
+ * interact with Gecko directly. To maintain this independence, Background
+ * Services lazily introspects the relevant Telemetry class from the enclosing
+ * package, warning but otherwise ignoring failures during introspection or
+ * invocation.
+ * <p>
+ * It is possible that Background Services will introspect and invoke the
+ * Telemetry implementation while Gecko is not running. In this case, the Fennec
+ * process itself buffers Telemetry events until such time as they can be
+ * flushed to disk and uploaded. <b>There is no guarantee that all Telemetry
+ * events will be uploaded!</b> Depending on the volume of data and the
+ * application lifecycle, Telemetry events may be dropped.
+ */
+public class TelemetryWrapper {
+  private static final String LOG_TAG = TelemetryWrapper.class.getSimpleName();
+
+  // Marking this volatile maintains thread safety cheaply.
+  private static volatile Method mAddToHistogram;
+
+  public static void addToHistogram(String key, int value) {
+    if (mAddToHistogram == null) {
+      try {
+        final Class<?> telemetry = Class.forName("org.mozilla.gecko.Telemetry");
+        mAddToHistogram = telemetry.getMethod("addToHistogram", String.class, int.class);
+      } catch (ClassNotFoundException e) {
+        Logger.warn(LOG_TAG, "org.mozilla.gecko.Telemetry class found!");
+        return;
+      } catch (NoSuchMethodException e) {
+        Logger.warn(LOG_TAG, "org.mozilla.gecko.Telemetry.addToHistogram(String, int) method not found!");
+        return;
+      }
+    }
+
+    if (mAddToHistogram != null) {
+      try {
+        mAddToHistogram.invoke(null, key, value);
+      } catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
+        Logger.warn(LOG_TAG, "Got exception invoking telemetry!");
+      }
+    }
+  }
+}
--- a/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java
+++ b/mobile/android/base/fxa/activities/FxAccountAbstractUpdateCredentialsActivity.java
@@ -4,29 +4,31 @@
 
 package org.mozilla.gecko.fxa.activities;
 
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.fxa.FxAccountClient10.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.PasswordStretcher;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.tasks.FxAccountSignInTask;
 import org.mozilla.gecko.sync.setup.activities.ActivityUtils;
+import org.mozilla.gecko.sync.telemetry.TelemetryContract;
 
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.AutoCompleteTextView;
 import android.widget.Button;
 import android.widget.EditText;
@@ -145,16 +147,18 @@ public abstract class FxAccountAbstractU
       setResult(RESULT_OK);
 
       // Maybe show success activity.
       final Intent successIntent = makeSuccessIntent(email, result);
       if (successIntent != null) {
         startActivity(successIntent);
       }
       finish();
+
+      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_COMPLETED, 1);
     }
   }
 
   public void updateCredentials(String email, String password) {
     String serverURI = fxAccount.getAccountServerURI();
     Executor executor = Executors.newSingleThreadExecutor();
     FxAccountClient client = new FxAccountClient20(serverURI, executor);
     PasswordStretcher passwordStretcher = makePasswordStretcher(password);
--- a/mobile/android/base/fxa/receivers/FxAccountDeletedService.java
+++ b/mobile/android/base/fxa/receivers/FxAccountDeletedService.java
@@ -1,16 +1,18 @@
 /* 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/. */
 
 package org.mozilla.gecko.fxa.receivers;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.fxa.FxAccountConstants;
+import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
+import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
 import org.mozilla.gecko.sync.config.AccountPickler;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 
 import android.app.IntentService;
 import android.content.Context;
 import android.content.Intent;
 
 /**
@@ -58,16 +60,19 @@ public class FxAccountDeletedService ext
 
     Logger.info(LOG_TAG, "Firefox account named " + accountName + " being removed; " +
         "deleting saved pickle file '" + FxAccountConstants.ACCOUNT_PICKLE_FILENAME + "'.");
     deletePickle(context);
 
     // Delete client database and non-local tabs.
     Logger.info(LOG_TAG, "Deleting the entire clients database and non-local tabs");
     FennecTabsRepository.deleteNonLocalClientsAndTabs(context);
+
+    // Remove any displayed notifications.
+    new FxAccountNotificationManager(FxAccountSyncAdapter.NOTIFICATION_ID).clear(context);
   }
 
   public static void deletePickle(final Context context) {
     try {
       AccountPickler.deletePickle(context, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
     } catch (Exception e) {
       // This should never happen, but we really don't want to die in a background thread.
       Logger.warn(LOG_TAG, "Got exception deleting saved pickle file; ignoring.", e);
--- a/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
+++ b/mobile/android/base/fxa/sync/FxAccountNotificationManager.java
@@ -2,22 +2,24 @@
  * 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/. */
 
 package org.mozilla.gecko.fxa.sync;
 
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.activities.FxAccountFinishMigratingActivity;
 import org.mozilla.gecko.fxa.activities.FxAccountStatusActivity;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.Action;
+import org.mozilla.gecko.sync.telemetry.TelemetryContract;
 
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.support.v4.app.NotificationCompat;
 import android.support.v4.app.NotificationCompat.Builder;
 
@@ -39,16 +41,27 @@ public class FxAccountNotificationManage
   // We're lazy about updating our locale info, because most syncs don't notify.
   private volatile boolean localeUpdated;
 
   public FxAccountNotificationManager(int notificationId) {
     this.notificationId = notificationId;
   }
 
   /**
+   * Remove all Firefox Account related notifications from the notification manager.
+   *
+   * @param context
+   *          Android context.
+   */
+  public void clear(Context context) {
+    final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+    notificationManager.cancel(notificationId);
+  }
+
+  /**
    * Reflect new Firefox Account state to the notification manager: show or hide
    * notifications reflecting the state of a Firefox Account.
    *
    * @param context
    *          Android context.
    * @param fxAccount
    *          Firefox Account to reflect to the notification manager.
    */
@@ -67,16 +80,18 @@ public class FxAccountNotificationManage
       localeUpdated = true;
       BrowserLocaleManager.getInstance().getAndApplyPersistedLocale(context);
     }
 
     final String title;
     final String text;
     final Intent notificationIntent;
     if (action == Action.NeedsFinishMigrating) {
+      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATION_NOTIFICATIONS_OFFERED, 1);
+
       title = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_title);
       text = context.getResources().getString(R.string.fxaccount_sync_finish_migrating_notification_text, state.email);
       notificationIntent = new Intent(context, FxAccountFinishMigratingActivity.class);
     } else {
       title = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_title);
       text = context.getResources().getString(R.string.fxaccount_sync_sign_in_error_notification_text, state.email);
       notificationIntent = new Intent(context, FxAccountStatusActivity.class);
     }
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -62,17 +62,17 @@ import android.os.Bundle;
 import android.os.SystemClock;
 
 public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
   private static final String LOG_TAG = FxAccountSyncAdapter.class.getSimpleName();
 
   public static final String SYNC_EXTRAS_RESPECT_LOCAL_RATE_LIMIT = "respect_local_rate_limit";
   public static final String SYNC_EXTRAS_RESPECT_REMOTE_SERVER_BACKOFF = "respect_remote_server_backoff";
 
-  protected static final int NOTIFICATION_ID = LOG_TAG.hashCode();
+  public static final int NOTIFICATION_ID = LOG_TAG.hashCode();
 
   // Tracks the last seen storage hostname for backoff purposes.
   private static final String PREF_BACKOFF_STORAGE_HOST = "backoffStorageHost";
 
   // Used to do cheap in-memory rate limiting. Don't sync again if we
   // successfully synced within this duration.
   private static final int MINIMUM_SYNC_DELAY_MILLIS = 15 * 1000;        // 15 seconds.
   private volatile long lastSyncRealtimeMillis;
--- a/mobile/android/base/sync/MigrationSentinelSyncStage.java
+++ b/mobile/android/base/sync/MigrationSentinelSyncStage.java
@@ -2,27 +2,29 @@
  * 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/. */
 
 package org.mozilla.gecko.sync;
 
 import java.net.URISyntaxException;
 
 import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.MigratedFromSync11;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.SyncStorageRecordRequest;
 import org.mozilla.gecko.sync.net.SyncStorageRequestDelegate;
 import org.mozilla.gecko.sync.net.SyncStorageResponse;
 import org.mozilla.gecko.sync.stage.AbstractNonRepositorySyncStage;
 import org.mozilla.gecko.sync.stage.NoSuchStageException;
+import org.mozilla.gecko.sync.telemetry.TelemetryContract;
 
 /**
  * The purpose of this class is to talk to a Sync 1.1 server and check
  * for a Firefox Accounts migration sentinel.
  *
  * If one is found, a Firefox Account is created, and the existing
  * Firefox Sync account disabled (or deleted).
  */
@@ -147,27 +149,29 @@ public class MigrationSentinelSyncStage 
         onMigrated();
       } else {
         onError(null, "Could not add Android account.");
       }
     }
 
     private void onMigrated() {
       Logger.info(LOG_TAG, "Account migrated!");
+      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_SUCCEEDED, 1);
       session.config.persistLastMigrationSentinelCheckTimestamp(fetchTimestamp);
       session.abort(null, "Account migrated.");
     }
 
     private void onCompletedUneventfully() {
       session.config.persistLastMigrationSentinelCheckTimestamp(fetchTimestamp);
       session.advance();
     }
 
     private void onError(Exception ex, String reason) {
       Logger.info(LOG_TAG, "Could not migrate: " + reason, ex);
+      TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATIONS_FAILED, 1);
       session.abort(ex, reason);
     }
 
     public void check() {
       final String url = session.config.storageURL() + META_FXA_CREDENTIALS;
       try {
         final SyncStorageRecordRequest request = new SyncStorageRecordRequest(url);
         request.delegate = new SyncStorageRequestDelegate() {
@@ -176,16 +180,19 @@ public class MigrationSentinelSyncStage 
           public String ifUnmodifiedSince() {
             return null;
           }
 
           @Override
           public void handleRequestSuccess(SyncStorageResponse response) {
             Logger.info(LOG_TAG, "Found " + META_FXA_CREDENTIALS + " record; attempting migration.");
             setTimestamp(response.normalizedWeaveTimestamp());
+
+            TelemetryWrapper.addToHistogram(TelemetryContract.SYNC11_MIGRATION_SENTINELS_SEEN, 1);
+
             try {
               final ExtendedJSONObject body = response.jsonObjectBody();
               final CryptoRecord cryptoRecord = CryptoRecord.fromJSONRecord(body);
               migrate(cryptoRecord);
             } catch (Exception e) {
               onError(e, "Unable to parse credential response.");
             }
           }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/sync/telemetry/TelemetryContract.java
@@ -0,0 +1,48 @@
+/* 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/. */
+
+package org.mozilla.gecko.sync.telemetry;
+
+public class TelemetryContract {
+  /**
+   * We are a Sync 1.1 (legacy) client, and we downloaded a migration sentinel.
+   */
+  public static final String SYNC11_MIGRATION_SENTINELS_SEEN = "FENNEC_SYNC11_MIGRATION_SENTINELS_SEEN";
+
+  /**
+   * We are a Sync 1.1 (legacy) client and we have downloaded a migration
+   * sentinel, but there was an error creating a Firefox Account from that
+   * sentinel.
+   * <p>
+   * We have logged the error and are ignoring that sentinel.
+   */
+  public static final String SYNC11_MIGRATIONS_FAILED = "FENNEC_SYNC11_MIGRATIONS_FAILED";
+
+  /**
+   * We are a Sync 1.1 (legacy) client and we have downloaded a migration
+   * sentinel, and there was no reported error creating a Firefox Account from
+   * that sentinel.
+   * <p>
+   * We have created a Firefox Account corresponding to the sentinel and have
+   * queued the existing Old Sync account for removal.
+   */
+  public static final String SYNC11_MIGRATIONS_SUCCEEDED = "FENNEC_SYNC11_MIGRATIONS_SUCCEEDED";
+
+  /**
+   * We are (now) a Sync 1.5 (Firefox Accounts-based) client that migrated from
+   * Sync 1.1. We have presented the user the "complete upgrade" notification.
+   * <p>
+   * We will offer every time a sync is triggered, including when a notification
+   * is already pending.
+   */
+  public static final String SYNC11_MIGRATION_NOTIFICATIONS_OFFERED = "FENNEC_SYNC11_MIGRATION_NOTIFICATIONS_OFFERED";
+
+  /**
+   * We are (now) a Sync 1.5 (Firefox Accounts-based) client that migrated from
+   * Sync 1.1. We have presented the user the "complete upgrade" notification
+   * and they have successfully completed the upgrade process by entering their
+   * Firefox Account credentials.
+   */
+  public static final String SYNC11_MIGRATIONS_COMPLETED = "FENNEC_SYNC11_MIGRATIONS_COMPLETED";
+}