Bug 1272354 - (DLC) Download Action: Add telemetry to track download success/failure. r?grisha draft
authorSebastian Kaspari <s.kaspari@gmail.com>
Mon, 03 Apr 2017 16:13:30 +0200
changeset 561411 3b98647a9342809b7303548e3c5c300308a8327e
parent 561410 5e306ad656fb86dc5709bad6260e80b369b2f998
child 561412 9605f6571c857adb2ca77f2fb9013cb12adb7e15
push id53725
push users.kaspari@gmail.com
push dateWed, 12 Apr 2017 16:41:42 +0000
reviewersgrisha
bugs1272354
milestone55.0a1
Bug 1272354 - (DLC) Download Action: Add telemetry to track download success/failure. r?grisha MozReview-Commit-ID: CKDfodx3moX
mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentTelemetry.java
mobile/android/base/moz.build
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
@@ -59,87 +59,99 @@ public class DownloadAction extends Base
             Log.d(LOGTAG, "Downloading: " + content);
 
             File temporaryFile = null;
 
             try {
                 if (!isConnectedToNetwork(context)) {
                     Log.d(LOGTAG, "No connected network available. Postponing download.");
                     // TODO: Reschedule download (bug 1209498)
+                    DownloadContentTelemetry.eventDownloadFailure(content, DownloadContentTelemetry.ERROR_NO_NETWORK);
                     return;
                 }
 
                 if (isActiveNetworkMetered(context)) {
                     Log.d(LOGTAG, "Network is metered. Postponing download.");
                     // TODO: Reschedule download (bug 1209498)
+                    DownloadContentTelemetry.eventDownloadFailure(content, DownloadContentTelemetry.ERROR_NETWORK_METERED);
                     return;
                 }
 
                 File destinationFile = getDestinationFile(context, content);
                 if (destinationFile.exists() && verify(destinationFile, content.getChecksum())) {
                     Log.d(LOGTAG, "Content already exists and is up-to-date.");
                     catalog.markAsDownloaded(content);
+                    DownloadContentTelemetry.eventDownloadSuccess(content);
                     continue;
                 }
 
                 temporaryFile = createTemporaryFile(context, content);
 
                 if (!hasEnoughDiskSpace(content, destinationFile, temporaryFile)) {
                     Log.d(LOGTAG, "Not enough disk space to save content. Skipping download.");
+                    DownloadContentTelemetry.eventDownloadFailure(content, DownloadContentTelemetry.ERROR_DISK_SPACE);
                     continue;
                 }
 
                 // TODO: Check space on disk before downloading content (bug 1220145)
                 final String url = createDownloadURL(content);
 
                 if (!temporaryFile.exists() || temporaryFile.length() < content.getSize()) {
                     download(url, temporaryFile);
                 }
 
                 if (!verify(temporaryFile, content.getDownloadChecksum())) {
                     Log.w(LOGTAG, "Wrong checksum after download, content=" + content.getId());
                     temporaryFile.delete();
+                    DownloadContentTelemetry.eventDownloadFailure(content, DownloadContentTelemetry.ERROR_CHECKSUM);
                     continue;
                 }
 
                 if (!content.isAssetArchive()) {
                     Log.e(LOGTAG, "Downloaded content is not of type 'asset-archive': " + content.getType());
                     temporaryFile.delete();
+                    DownloadContentTelemetry.eventDownloadFailure(content, DownloadContentTelemetry.ERROR_LOGIC);
                     continue;
                 }
 
                 extract(temporaryFile, destinationFile, content.getChecksum());
 
                 catalog.markAsDownloaded(content);
 
+                DownloadContentTelemetry.eventDownloadSuccess(content);
+
                 Log.d(LOGTAG, "Successfully downloaded: " + content);
 
                 if (callback != null) {
                     callback.onContentDownloaded(content);
                 }
 
                 if (temporaryFile != null && temporaryFile.exists()) {
                     temporaryFile.delete();
                 }
             } catch (RecoverableDownloadContentException e) {
                 Log.w(LOGTAG, "Downloading content failed (Recoverable): " + content, e);
 
                 if (e.shouldBeCountedAsFailure()) {
                     catalog.rememberFailure(content, e.getErrorType());
                 }
 
+                DownloadContentTelemetry.eventDownloadFailure(content, e);
+
                 // TODO: Reschedule download (bug 1209498)
             } catch (UnrecoverableDownloadContentException e) {
                 Log.w(LOGTAG, "Downloading content failed (Unrecoverable): " + content, e);
 
                 catalog.markAsPermanentlyFailed(content);
 
                 if (temporaryFile != null && temporaryFile.exists()) {
                     temporaryFile.delete();
                 }
+
+                DownloadContentTelemetry.eventDownloadFailure(content, DownloadContentTelemetry.ERROR_UNRECOVERABLE);
             }
         }
 
         Log.v(LOGTAG, "Done");
     }
 
     protected void download(String source, File temporaryFile)
             throws RecoverableDownloadContentException, UnrecoverableDownloadContentException {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentTelemetry.java
@@ -0,0 +1,92 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; 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/. */
+
+package org.mozilla.gecko.dlc;
+
+
+import android.support.annotation.StringDef;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.mozilla.gecko.Telemetry;
+import org.mozilla.gecko.TelemetryContract;
+import org.mozilla.gecko.dlc.catalog.DownloadContent;
+import org.mozilla.gecko.dlc.BaseAction.RecoverableDownloadContentException;
+
+/**
+ * Helper class to send DLC telemetry events.
+ */
+public class DownloadContentTelemetry {
+    private static final String EXTRA_ACTION = "action";
+    private static final String EXTRA_RESULT = "result";
+    private static final String EXTRA_CONTENT = "content";
+    private static final String EXTRA_ERROR = "error";
+
+    private static final String ACTION_DOWNLOAD = "dlc_download";
+    private static final String ACTION_SYNC = "dlc_sync";
+
+    private static final String RESULT_SUCCESS = "success";
+    private static final String RESULT_FAILURE = "failure";
+
+    @StringDef({ERROR_NO_NETWORK, ERROR_NETWORK_METERED, ERROR_DISK_SPACE, ERROR_CHECKSUM, ERROR_DISK_IO,
+                ERROR_NETWORK_IO, ERROR_MEMORY, ERROR_SERVER, ERROR_LOGIC, ERROR_UNRECOVERABLE})
+    /* package */ @interface Error {}
+    /* package */ static final String ERROR_NO_NETWORK = "no_network";
+    /* package */ static final String ERROR_NETWORK_METERED = "network_metered";
+    /* package */ static final String ERROR_DISK_SPACE = "disk_space";
+    /* package */ static final String ERROR_CHECKSUM = "checksum";
+    /* package */ static final String ERROR_DISK_IO = "io_disk";
+    /* package */ static final String ERROR_NETWORK_IO = "io_network";
+    /* package */ static final String ERROR_MEMORY = "memory";
+    /* package */ static final String ERROR_SERVER = "server";
+    /* package */ static final String ERROR_LOGIC = "logic";
+    /* package */ static final String ERROR_UNRECOVERABLE = "unrecoverable";
+
+    /* package */ static void eventDownloadSuccess(DownloadContent content) {
+        try {
+            final JSONObject extras = new JSONObject();
+            extras.put(EXTRA_ACTION, ACTION_DOWNLOAD);
+            extras.put(EXTRA_RESULT, RESULT_SUCCESS);
+            extras.put(EXTRA_CONTENT, content.getId());
+
+            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.SERVICE, extras.toString());
+        } catch (JSONException e) {
+            throw new AssertionError("Should not happen: Can't build telemetry extra JSON", e);
+        }
+    }
+
+    /* package */ static void eventDownloadFailure(DownloadContent content, RecoverableDownloadContentException e) {
+        switch (e.getErrorType()) {
+            case RecoverableDownloadContentException.DISK_IO:
+                eventDownloadFailure(content, ERROR_DISK_IO);
+                break;
+            case RecoverableDownloadContentException.MEMORY:
+                eventDownloadFailure(content, ERROR_MEMORY);
+                break;
+            case RecoverableDownloadContentException.NETWORK:
+                eventDownloadFailure(content, ERROR_NETWORK_IO);
+                break;
+            case RecoverableDownloadContentException.SERVER:
+                eventDownloadFailure(content, ERROR_SERVER);
+                break;
+            default:
+                throw new AssertionError("Unknown error type: " + e.getErrorType());
+        }
+    }
+
+    /* package */ static void eventDownloadFailure(DownloadContent content, @Error String errorType) {
+        try {
+            final JSONObject extras = new JSONObject();
+            extras.put(EXTRA_ACTION, ACTION_DOWNLOAD);
+            extras.put(EXTRA_RESULT, RESULT_FAILURE);
+            extras.put(EXTRA_CONTENT, content.getId());
+            extras.put(EXTRA_ERROR, errorType);
+
+            Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.SERVICE, extras.toString());
+        } catch (JSONException e) {
+            throw new AssertionError("Should not happen: Can't build telemetry extra JSON", e);
+        }
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -406,16 +406,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'distribution/ReferrerReceiver.java',
     'dlc/BaseAction.java',
     'dlc/catalog/DownloadContent.java',
     'dlc/catalog/DownloadContentBootstrap.java',
     'dlc/catalog/DownloadContentBuilder.java',
     'dlc/catalog/DownloadContentCatalog.java',
     'dlc/DownloadAction.java',
     'dlc/DownloadContentService.java',
+    'dlc/DownloadContentTelemetry.java',
     'dlc/StudyAction.java',
     'dlc/SyncAction.java',
     'dlc/VerifyAction.java',
     'DoorHangerPopup.java',
     'DownloadsIntegration.java',
     'DynamicToolbar.java',
     'EditBookmarkDialog.java',
     'Experiments.java',