Bug 1272354 - (DLC) Download Action: Add telemetry to track download success/failure. r?grisha
MozReview-Commit-ID: CKDfodx3moX
--- 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',