1 /* 2 * This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, you can obtain one at http://mozilla.org/MPL/2.0/. 5 */ 6 7 package org.mozilla.gecko.telemetry; 8 9 import android.content.Context; 10 import android.support.annotation.WorkerThread; 11 import android.util.Log; 12 import org.mozilla.gecko.telemetry.pingbuilders.TelemetryCorePingBuilder; 13 import org.mozilla.gecko.telemetry.schedulers.TelemetryUploadScheduler; 14 import org.mozilla.gecko.telemetry.schedulers.TelemetryUploadAllPingsImmediatelyScheduler; 15 import org.mozilla.gecko.telemetry.stores.TelemetryJSONFilePingStore; 16 import org.mozilla.gecko.telemetry.stores.TelemetryPingStore; 17 import org.mozilla.gecko.util.ThreadUtils; 18 19 import java.io.File; 20 import java.io.IOException; 21 22 /** 23 * The entry-point for Java-based telemetry. This class handles: 24 * * Initializing the Stores & Schedulers. 25 * * Queueing upload requests for a given ping. 26 * 27 * To test Telemetry , see {@link TelemetryConstants} & 28 * https://wiki.mozilla.org/Mobile/Fennec/Android/Java_telemetry. 29 * 30 * The full architecture is: 31 * 32 * Fennec -(PingBuilder)-> Dispatcher -2-> Scheduler -> UploadService 33 * | 1 | 34 * Store <-------------------------- 35 * 36 * The store acts as a single store of truth and contains a list of all 37 * pings waiting to be uploaded. The dispatcher will queue a ping to upload 38 * by writing it to the store. Later, the UploadService will try to upload 39 * this queued ping by reading directly from the store. 40 * 41 * To implement a new ping type, you should: 42 * 1) Implement a {@link org.mozilla.gecko.telemetry.pingbuilders.TelemetryPingBuilder} for your ping type. 43 * 2) Re-use a ping store in .../stores/ or implement a new one: {@link TelemetryPingStore}. The 44 * type of store may be affected by robustness requirements (e.g. do you have data in addition to 45 * pings that need to be atomically updated when a ping is stored?) and performance requirements. 46 * 3) Re-use an upload scheduler in .../schedulers/ or implement a new one: {@link TelemetryUploadScheduler}. 47 * 4) Initialize your Store & (if new) Scheduler in the constructor of this class 48 * 5) Add a queuePingForUpload method for your PingBuilder class (see 49 * {@link #queuePingForUpload(Context, TelemetryCorePingBuilder)}) 50 * 6) In Fennec, where you want to store a ping and attempt upload, create a PingBuilder and 51 * pass it to the new queuePingForUpload method. 52 */ 53 public class TelemetryDispatcher { 54 private static final String LOGTAG = "Gecko" + TelemetryDispatcher.class.getSimpleName(); 55 56 private static final String STORE_CONTAINER_DIR_NAME = "telemetry_java"; 57 private static final String CORE_STORE_DIR_NAME = "core"; 58 59 private final TelemetryJSONFilePingStore coreStore; 60 61 private final TelemetryUploadAllPingsImmediatelyScheduler uploadAllPingsImmediatelyScheduler; 62 63 @WorkerThread // via TelemetryJSONFilePingStore TelemetryDispatcher(final String profilePath, final String profileName)64 public TelemetryDispatcher(final String profilePath, final String profileName) { 65 final String storePath = profilePath + File.separator + STORE_CONTAINER_DIR_NAME; 66 67 // There are measurements in the core ping (e.g. seq #) that would ideally be atomically updated 68 // when the ping is stored. However, for simplicity, we use the json store and accept the possible 69 // loss of data (see bug 1243585 comment 16+ for more). 70 coreStore = new TelemetryJSONFilePingStore(new File(storePath, CORE_STORE_DIR_NAME), profileName); 71 72 uploadAllPingsImmediatelyScheduler = new TelemetryUploadAllPingsImmediatelyScheduler(); 73 } 74 queuePingForUpload(final Context context, final TelemetryPing ping, final TelemetryPingStore store, final TelemetryUploadScheduler scheduler)75 private void queuePingForUpload(final Context context, final TelemetryPing ping, final TelemetryPingStore store, 76 final TelemetryUploadScheduler scheduler) { 77 final QueuePingRunnable runnable = new QueuePingRunnable(context, ping, store, scheduler); 78 ThreadUtils.postToBackgroundThread(runnable); // TODO: Investigate how busy this thread is. See if we want another. 79 } 80 81 /** 82 * Queues the given ping for upload and potentially schedules upload. This method can be called from any thread. 83 */ queuePingForUpload(final Context context, final TelemetryCorePingBuilder pingBuilder)84 public void queuePingForUpload(final Context context, final TelemetryCorePingBuilder pingBuilder) { 85 final TelemetryPing ping = pingBuilder.build(); 86 queuePingForUpload(context, ping, coreStore, uploadAllPingsImmediatelyScheduler); 87 } 88 89 private static class QueuePingRunnable implements Runnable { 90 private final Context applicationContext; 91 private final TelemetryPing ping; 92 private final TelemetryPingStore store; 93 private final TelemetryUploadScheduler scheduler; 94 QueuePingRunnable(final Context context, final TelemetryPing ping, final TelemetryPingStore store, final TelemetryUploadScheduler scheduler)95 public QueuePingRunnable(final Context context, final TelemetryPing ping, final TelemetryPingStore store, 96 final TelemetryUploadScheduler scheduler) { 97 this.applicationContext = context.getApplicationContext(); 98 this.ping = ping; 99 this.store = store; 100 this.scheduler = scheduler; 101 } 102 103 @Override run()104 public void run() { 105 // We block while storing the ping so the scheduled upload is guaranteed to have the newly-stored value. 106 try { 107 store.storePing(ping); 108 } catch (final IOException e) { 109 // Don't log exception to avoid leaking profile path. 110 Log.e(LOGTAG, "Unable to write ping to disk. Continuing with upload attempt"); 111 } 112 113 if (scheduler.isReadyToUpload(store)) { 114 scheduler.scheduleUpload(applicationContext, store); 115 } 116 } 117 } 118 } 119