Bug 1116810 - Part 3: Stumbler java code for telemetry. r=vng
authorGarvan Keeley <gkeeley@mozilla.com>
Tue, 06 Jan 2015 19:31:00 -0500
changeset 248543 6ba69cd7598b63dd58546e6e9dd825c915704315
parent 248542 db1367a5cc7c37396db4240930035e3aba95b736
child 248544 b336e92729390e5c5daa9f728d1a917f767fbe12
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersvng
bugs1116810
milestone37.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 1116810 - Part 3: Stumbler java code for telemetry. r=vng
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/AppGlobals.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageContract.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/PersistedStats.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/GPSScanner.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/utils/TelemetryWrapper.java
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/AppGlobals.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/AppGlobals.java
@@ -62,10 +62,21 @@ public class AppGlobals {
         if (name.length() > maxLen) {
             name = name.substring(name.length() - maxLen, name.length());
         }
         return LOG_PREFIX + name;
     }
 
     public static final String ACTION_TEST_SETTING_ENABLED = "stumbler-test-setting-enabled";
     public static final String ACTION_TEST_SETTING_DISABLED = "stumbler-test-setting-disabled";
+
+    // Histogram values
+    public static final String TELEMETRY_TIME_BETWEEN_UPLOADS_SEC = "STUMBLER_TIME_BETWEEN_UPLOADS_SEC";
+    public static final String TELEMETRY_BYTES_UPLOADED_PER_SEC = "STUMBLER_VOLUME_BYTES_UPLOADED_PER_SEC";
+    public static final String TELEMETRY_TIME_BETWEEN_STARTS_SEC = "STUMBLER_TIME_BETWEEN_START_SEC";
+    public static final String TELEMETRY_BYTES_PER_UPLOAD = "STUMBLER_UPLOAD_BYTES";
+    public static final String TELEMETRY_OBSERVATIONS_PER_UPLOAD = "STUMBLER_UPLOAD_OBSERVATION_COUNT";
+    public static final String TELEMETRY_CELLS_PER_UPLOAD = "STUMBLER_UPLOAD_CELL_COUNT";
+    public static final String TELEMETRY_WIFIS_PER_UPLOAD = "STUMBLER_UPLOAD_WIFI_AP_COUNT";
+    public static final String TELEMETRY_OBSERVATIONS_PER_DAY = "STUMBLER_OBSERVATIONS_PER_DAY";
+    public static final String TELEMETRY_TIME_BETWEEN_RECEIVED_LOCATIONS_SEC = "STUMBLER_TIME_BETWEEN_RECEIVED_LOCATIONS_SEC";
 }
 
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageContract.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageContract.java
@@ -16,19 +16,20 @@ public final class DataStorageContract {
         public static final String CELL = "cell";
         public static final String WIFI = "wifi";
         public static final String CELL_COUNT = "cell_count";
         public static final String WIFI_COUNT = "wifi_count";
     }
 
     public static class Stats {
         public static final String KEY_VERSION = "version_code";
-        public static final int VERSION_CODE = 1;
+        public static final int VERSION_CODE = 2;
         public static final String KEY_BYTES_SENT = "bytes_sent";
         public static final String KEY_LAST_UPLOAD_TIME = "last_upload_time";
         public static final String KEY_OBSERVATIONS_SENT = "observations_sent";
         public static final String KEY_WIFIS_SENT = "wifis_sent";
         public static final String KEY_CELLS_SENT = "cells_sent";
+        public static final String KEY_OBSERVATIONS_PER_DAY = "obs_per_day";
     }
 
     private DataStorageContract() {
     }
 }
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
@@ -64,16 +64,17 @@ public class DataStorageManager {
     private final StorageIsEmptyTracker mTracker;
 
     private static DataStorageManager sInstance;
 
     private ReportBatch mCurrentReportsSendBuffer;
     private ReportBatchIterator mReportBatchIterator;
     private final ReportFileList mFileList;
     private Timer mFlushMemoryBuffersToDiskTimer;
+    private final PersistedStats mPersistedOnDiskUploadStats;
 
     static final String SEP_REPORT_COUNT = "-r";
     static final String SEP_WIFI_COUNT = "-w";
     static final String SEP_CELL_COUNT = "-c";
     static final String SEP_TIME_MS = "-t";
     static final String FILENAME_PREFIX = "reports";
     static final String MEMORY_BUFFER_NAME = "in memory send buffer";
 
@@ -237,16 +238,17 @@ public class DataStorageManager {
         final String baseDir = getStorageDir(c);
         mStatsFile = new File(baseDir, "upload_stats.ini");
         mReportsDir = new File(baseDir + "/reports");
         if (!mReportsDir.exists()) {
             mReportsDir.mkdirs();
         }
         mFileList = new ReportFileList();
         mFileList.update(mReportsDir);
+        mPersistedOnDiskUploadStats = new PersistedStats(baseDir);
     }
 
     public synchronized int getMaxWeeksStored() {
         return mMaxWeeksStored;
     }
 
     private static byte[] readFile(File file) throws IOException {
         final RandomAccessFile f = new RandomAccessFile(file, "r");
@@ -443,70 +445,29 @@ public class DataStorageManager {
                     } catch (IOException ex) {
                         Log.e(LOG_TAG, "mFlushMemoryBuffersToDiskTimer exception" + ex);
                     }
                 }
             }, kMillis);
         }
     }
 
-    public synchronized Properties readSyncStats() throws IOException {
-        if (!mStatsFile.exists()) {
-            return new Properties();
-        }
-
-        final FileInputStream input = new FileInputStream(mStatsFile);
-        try {
-            final Properties props = new Properties();
-            props.load(input);
-            return props;
-        } finally {
-            input.close();
-        }
-    }
-
-    public synchronized void incrementSyncStats(long bytesSent, long reports, long cells, long wifis) throws IOException {
-        if (reports + cells + wifis < 1) {
-            return;
-        }
-
-        final Properties properties = readSyncStats();
-        final long time = System.currentTimeMillis();
-        writeSyncStats(time,
-            Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_BYTES_SENT, "0")) + bytesSent,
-            Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_OBSERVATIONS_SENT, "0")) + reports,
-            Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_CELLS_SENT, "0")) + cells,
-            Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_WIFIS_SENT, "0")) + wifis);
-    }
-
-    public void writeSyncStats(long time, long bytesSent, long totalObs, long totalCells, long totalWifis) throws IOException {
-        final FileOutputStream out = new FileOutputStream(mStatsFile);
-        try {
-            final Properties props = new Properties();
-            props.setProperty(DataStorageContract.Stats.KEY_LAST_UPLOAD_TIME, String.valueOf(time));
-            props.setProperty(DataStorageContract.Stats.KEY_BYTES_SENT, String.valueOf(bytesSent));
-            props.setProperty(DataStorageContract.Stats.KEY_OBSERVATIONS_SENT, String.valueOf(totalObs));
-            props.setProperty(DataStorageContract.Stats.KEY_CELLS_SENT, String.valueOf(totalCells));
-            props.setProperty(DataStorageContract.Stats.KEY_WIFIS_SENT, String.valueOf(totalWifis));
-            props.setProperty(DataStorageContract.Stats.KEY_VERSION, String.valueOf(DataStorageContract.Stats.VERSION_CODE));
-            props.store(out, null);
-        } finally {
-            out.close();
-        }
-    }
-
     public synchronized void deleteAll() {
         if (mFileList.mFiles == null) {
             return;
         }
 
         for (File f : mFileList.mFiles) {
             f.delete();
         }
         mFileList.update(mReportsDir);
     }
 
     private void notifyStorageIsEmpty(boolean isEmpty) {
         if (mTracker != null) {
             mTracker.notifyStorageStateEmpty(isEmpty);
         }
     }
+
+    public synchronized void incrementSyncStats(long bytesSent, long reports, long cells, long wifis) throws IOException {
+        mPersistedOnDiskUploadStats.incrementSyncStats(bytesSent, reports, cells, wifis);
+    }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/PersistedStats.java
@@ -0,0 +1,99 @@
+/* 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.mozstumbler.service.stumblerthread.datahandling;
+
+import org.mozilla.mozstumbler.service.AppGlobals;
+import org.mozilla.mozstumbler.service.utils.TelemetryWrapper;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+
+class PersistedStats {
+    private final File mStatsFile;
+
+    public PersistedStats(String baseDir) {
+        mStatsFile = new File(baseDir, "upload_stats.ini");
+    }
+
+    public synchronized Properties readSyncStats() throws IOException {
+        if (!mStatsFile.exists()) {
+            return new Properties();
+        }
+
+        final FileInputStream input = new FileInputStream(mStatsFile);
+        try {
+            final Properties props = new Properties();
+            props.load(input);
+            return props;
+        } finally {
+            input.close();
+        }
+    }
+
+    public synchronized void incrementSyncStats(long bytesSent, long reports, long cells, long wifis) throws IOException {
+        if (reports + cells + wifis < 1) {
+            return;
+        }
+
+        final Properties properties = readSyncStats();
+        final long time = System.currentTimeMillis();
+        final long lastUploadTime = Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_LAST_UPLOAD_TIME, "0"));
+        final long storedObsPerDay = Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_OBSERVATIONS_PER_DAY, "0"));
+        long observationsToday = reports;
+        if (lastUploadTime > 0) {
+            long dayLastUploaded = TimeUnit.MILLISECONDS.toDays(lastUploadTime);
+            long dayDiff = TimeUnit.MILLISECONDS.toDays(time) - dayLastUploaded;
+            if (dayDiff > 0) {
+                // send value of store obs per day
+                TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_OBSERVATIONS_PER_DAY,
+                        Long.valueOf(storedObsPerDay / dayDiff).intValue());
+            } else {
+                observationsToday += storedObsPerDay;
+            }
+        }
+
+        writeSyncStats(time,
+            Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_BYTES_SENT, "0")) + bytesSent,
+            Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_OBSERVATIONS_SENT, "0")) + reports,
+            Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_CELLS_SENT, "0")) + cells,
+            Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_WIFIS_SENT, "0")) + wifis,
+            observationsToday);
+
+
+        final long lastUploadMs = Long.parseLong(properties.getProperty(DataStorageContract.Stats.KEY_LAST_UPLOAD_TIME, "0"));
+        final int timeDiffSec = Long.valueOf((time - lastUploadMs) / 1000).intValue();
+        if (lastUploadMs > 0 && timeDiffSec > 0) {
+            TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_TIME_BETWEEN_UPLOADS_SEC, timeDiffSec);
+            TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_BYTES_UPLOADED_PER_SEC, Long.valueOf(bytesSent).intValue() / timeDiffSec);
+        }
+        TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_BYTES_PER_UPLOAD, Long.valueOf(bytesSent).intValue());
+        TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_OBSERVATIONS_PER_UPLOAD, Long.valueOf(reports).intValue());
+        TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_WIFIS_PER_UPLOAD, Long.valueOf(wifis).intValue());
+        TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_CELLS_PER_UPLOAD, Long.valueOf(cells).intValue());
+    }
+
+    public synchronized void writeSyncStats(long time, long bytesSent, long totalObs,
+                                            long totalCells, long totalWifis, long obsPerDay) throws IOException {
+        final FileOutputStream out = new FileOutputStream(mStatsFile);
+        try {
+            final Properties props = new Properties();
+            props.setProperty(DataStorageContract.Stats.KEY_LAST_UPLOAD_TIME, String.valueOf(time));
+            props.setProperty(DataStorageContract.Stats.KEY_BYTES_SENT, String.valueOf(bytesSent));
+            props.setProperty(DataStorageContract.Stats.KEY_OBSERVATIONS_SENT, String.valueOf(totalObs));
+            props.setProperty(DataStorageContract.Stats.KEY_CELLS_SENT, String.valueOf(totalCells));
+            props.setProperty(DataStorageContract.Stats.KEY_WIFIS_SENT, String.valueOf(totalWifis));
+            props.setProperty(DataStorageContract.Stats.KEY_VERSION, String.valueOf(DataStorageContract.Stats.VERSION_CODE));
+            props.setProperty(DataStorageContract.Stats.KEY_OBSERVATIONS_PER_DAY, String.valueOf(obsPerDay));
+            props.store(out, null);
+        } finally {
+            out.close();
+        }
+    }
+
+}
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/GPSScanner.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/GPSScanner.java
@@ -10,20 +10,20 @@ import android.location.GpsSatellite;
 import android.location.GpsStatus;
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.location.LocationProvider;
 import android.os.Bundle;
 import android.support.v4.content.LocalBroadcastManager;
 import android.util.Log;
-
 import org.mozilla.mozstumbler.service.AppGlobals;
 import org.mozilla.mozstumbler.service.AppGlobals.ActiveOrPassiveStumbling;
 import org.mozilla.mozstumbler.service.Prefs;
+import org.mozilla.mozstumbler.service.utils.TelemetryWrapper;
 
 import java.text.SimpleDateFormat;
 import java.util.Date;
 
 public class GPSScanner implements LocationListener {
     public static final String ACTION_BASE = AppGlobals.ACTION_NAMESPACE + ".GPSScanner.";
     public static final String ACTION_GPS_UPDATED = ACTION_BASE + "GPS_UPDATED";
     public static final String ACTION_ARG_TIME = AppGlobals.ACTION_ARG_TIME;
@@ -43,17 +43,17 @@ public class GPSScanner implements Locat
 
     private final LocationBlockList mBlockList = new LocationBlockList();
     private final Context mContext;
     private GpsStatus.Listener mGPSListener;
     private int mLocationCount;
     private Location mLocation = new Location("internal");
     private boolean mAutoGeofencing;
     private boolean mIsPassiveMode;
-
+    private long mTelemetry_lastStartedMs;
     private final ScanManager mScanManager;
 
     public GPSScanner(Context context, ScanManager scanManager) {
         mContext = context;
         mScanManager = scanManager;
     }
 
     public void start(final ActiveOrPassiveStumbling stumblingMode) {
@@ -77,19 +77,23 @@ public class GPSScanner implements Locat
     }
 
     private void startPassiveMode() {
         LocationManager locationManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
         if (!isGpsAvailable(locationManager)) {
             return;
         }
 
-        locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER,
-                                               0,
-                                               0, this);
+        locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
+
+        final int timeDiffSec = Long.valueOf((System.currentTimeMillis() - mTelemetry_lastStartedMs) / 1000).intValue();
+        if (mTelemetry_lastStartedMs > 0 && timeDiffSec > 0) {
+            TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_TIME_BETWEEN_STARTS_SEC, timeDiffSec);
+        }
+        mTelemetry_lastStartedMs = System.currentTimeMillis();
     }
 
     private void startActiveMode() {
         LocationManager lm = getLocationManager();
         if (!isGpsAvailable(lm)) {
             return;
         }
 
@@ -190,23 +194,24 @@ public class GPSScanner implements Locat
 
         String provider = location.getProvider();
         if (!provider.toLowerCase().contains("gps")) {
             Log.d(LOG_TAG, "Discard fused/network location.");
             // only interested in GPS locations
             return;
         }
 
+        final long timeDeltaMs = location.getTime() - mLocation.getTime();
+
         // Seem to get greater likelihood of non-fused location with higher update freq.
         // Check dist and time threshold here, not set on the listener.
         if (mIsPassiveMode) {
-            final long timeDelta = location.getTime() - mLocation.getTime();
             final boolean hasMoved = location.distanceTo(mLocation) > PASSIVE_GPS_MOVEMENT_MIN_DELTA_M;
 
-            if (timeDelta < PASSIVE_GPS_MIN_UPDATE_FREQ_MS || !hasMoved) {
+            if (timeDeltaMs < PASSIVE_GPS_MIN_UPDATE_FREQ_MS || !hasMoved) {
                 return;
             }
         }
 
         Date date = new Date(location.getTime());
         SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
         String time = formatter.format(date);
         logMsg += String.format("%s Coord: %.4f,%.4f, Acc: %.0f, Speed: %.0f, Alt: %.0f, Bearing: %.1f", time, location.getLatitude(),
@@ -223,16 +228,21 @@ public class GPSScanner implements Locat
         if (!mAutoGeofencing) {
             reportNewLocationReceived(location);
         }
         mLocationCount++;
 
         if (mIsPassiveMode) {
             mScanManager.newPassiveGpsLocation();
         }
+
+        if (timeDeltaMs > 0) {
+            TelemetryWrapper.addToHistogram(AppGlobals.TELEMETRY_TIME_BETWEEN_RECEIVED_LOCATIONS_SEC,
+                    Long.valueOf(timeDeltaMs).intValue() / 1000);
+        }
     }
 
     @Override
     public void onProviderDisabled(String provider) {
         if (LocationManager.GPS_PROVIDER.equals(provider)) {
             reportLocationLost();
         }
     }
new file mode 100644
--- /dev/null
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/utils/TelemetryWrapper.java
@@ -0,0 +1,35 @@
+package org.mozilla.mozstumbler.service.utils;
+
+import android.util.Log;
+import org.mozilla.mozstumbler.service.AppGlobals;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class TelemetryWrapper {
+    private static final String LOG_TAG = AppGlobals.makeLogTag(TelemetryWrapper.class.getSimpleName());
+    private static Method mAddToHistogram;
+
+    public static void addToHistogram(String key, int value) {
+        if (mAddToHistogram == null) {
+            try {
+                Class<?> telemetry = Class.forName("org.mozilla.gecko.Telemetry");
+                mAddToHistogram = telemetry.getMethod("addToHistogram", String.class, int.class);
+            } catch (ClassNotFoundException e) {
+                Log.d(LOG_TAG, "Class not found!");
+                return;
+            } catch (NoSuchMethodException e) {
+                Log.d(LOG_TAG, "Method not found!");
+                return;
+            }
+        }
+
+        if (mAddToHistogram != null) {
+            try {
+                mAddToHistogram.invoke(null, key, value);
+            }
+            catch (IllegalArgumentException | InvocationTargetException | IllegalAccessException e) {
+                Log.d(LOG_TAG, "Got exception invoking.");
+            }
+        }
+    }
+}