Bug 868445 - Part 1: allow recording of JSON objects. r=nalexander
authorRichard Newman <rnewman@mozilla.com>
Tue, 04 Jun 2013 17:16:56 -0700
changeset 134130 79088e422daf4d1af974ba219fc01e13ffa245d2
parent 134129 30fe681b27bea80b484fc0b8e70928246eed4f2f
child 134131 530fbc32771b6c5420fa5adfcbdada6c322cc67f
push id29067
push userryanvm@gmail.com
push dateWed, 05 Jun 2013 20:37:20 +0000
treeherdermozilla-inbound@72fbfb2f8e51 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs868445
milestone24.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 868445 - Part 1: allow recording of JSON objects. r=nalexander
mobile/android/base/background/healthreport/Environment.java
mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
mobile/android/base/background/healthreport/HealthReportGenerator.java
mobile/android/base/background/healthreport/HealthReportStorage.java
--- a/mobile/android/base/background/healthreport/Environment.java
+++ b/mobile/android/base/background/healthreport/Environment.java
@@ -236,16 +236,20 @@ public abstract class Environment {
     }
   }
 
   public void setJSONForAddons(byte[] json) throws Exception {
     setJSONForAddons(new String(json, "UTF-8"));
   }
 
   public void setJSONForAddons(String json) throws Exception {
+    if (json == null || "null".equals(json)) {
+      addons = null;
+      return;
+    }
     addons = new JSONObject(json);
   }
 
   public void setJSONForAddons(JSONObject json) {
     addons = json;
   }
 
   /**
--- a/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
+++ b/mobile/android/base/background/healthreport/HealthReportDatabaseStorage.java
@@ -6,16 +6,17 @@ package org.mozilla.gecko.background.hea
 
 import java.io.File;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
+import org.json.JSONObject;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.healthreport.HealthReportStorage.MeasurementFields.FieldSpec;
 
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.ContextWrapper;
 import android.database.Cursor;
 import android.database.SQLException;
@@ -210,17 +211,17 @@ public class HealthReportDatabaseStorage
       // *is* called and still support v8.
       // Instead we check the version code in the HealthReportSQLiteOpenHelper
       // constructor, and only use this workaround if we need to.
       @Override
       public SQLiteDatabase openOrCreateDatabase(String name,
                                                  int mode,
                                                  SQLiteDatabase.CursorFactory factory) {
         final File path = getDatabasePath(name);
-        Logger.info(LOG_TAG, "Opening database through absolute path " + path.getAbsolutePath());
+        Logger.pii(LOG_TAG, "Opening database through absolute path " + path.getAbsolutePath());
         return SQLiteDatabase.openOrCreateDatabase(path, null);
       }
     }
 
     public static String getAbsolutePath(File parent, String name) {
       return parent.getAbsolutePath() + File.separator + name;
     }
 
@@ -228,17 +229,17 @@ public class HealthReportDatabaseStorage
     public HealthReportSQLiteOpenHelper(Context context, File profileDirectory, String name) {
       super(
           (CAN_USE_ABSOLUTE_DB_PATH ? context : new AbsolutePathContext(context, profileDirectory)),
           (CAN_USE_ABSOLUTE_DB_PATH ? getAbsolutePath(profileDirectory, name) : name),
           null,
           CURRENT_VERSION);
 
       if (CAN_USE_ABSOLUTE_DB_PATH) {
-        Logger.info(LOG_TAG, "Opening: " + getAbsolutePath(profileDirectory, name));
+        Logger.pii(LOG_TAG, "Opening: " + getAbsolutePath(profileDirectory, name));
       }
     }
 
     @Override
     public void onCreate(SQLiteDatabase db) {
       db.beginTransaction();
       try {
         db.execSQL("CREATE TABLE addons (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
@@ -1030,16 +1031,21 @@ public class HealthReportDatabaseStorage
       v.put("env", env);
       v.put("field", field);
       v.put("date", day);
       db.insert(table, null, v);
     }
   }
 
   @Override
+  public void recordDailyLast(int env, int day, int field, JSONObject value) {
+    this.recordDailyLast(env, day, field, value == null ? "null" : value.toString(), EVENTS_TEXTUAL);
+  }
+
+  @Override
   public void recordDailyLast(int env, int day, int field, String value) {
     this.recordDailyLast(env, day, field, value, EVENTS_TEXTUAL);
   }
 
   @Override
   public void recordDailyLast(int env, int day, int field, int value) {
     this.recordDailyLast(env, day, field, Integer.valueOf(value), EVENTS_INTEGER);
   }
@@ -1056,16 +1062,21 @@ public class HealthReportDatabaseStorage
     v.put("date", day);
 
     final SQLiteDatabase db = this.helper.getWritableDatabase();
     putValue(v, value);
     db.insert(table, null, v);
   }
 
   @Override
+  public void recordDailyDiscrete(int env, int day, int field, JSONObject value) {
+    this.recordDailyDiscrete(env, day, field, value == null ? "null" : value.toString(), EVENTS_TEXTUAL);
+  }
+
+  @Override
   public void recordDailyDiscrete(int env, int day, int field, String value) {
     this.recordDailyDiscrete(env, day, field, value, EVENTS_TEXTUAL);
   }
 
   @Override
   public void recordDailyDiscrete(int env, int day, int field, int value) {
     this.recordDailyDiscrete(env, day, field, value, EVENTS_INTEGER);
   }
--- a/mobile/android/base/background/healthreport/HealthReportGenerator.java
+++ b/mobile/android/base/background/healthreport/HealthReportGenerator.java
@@ -107,17 +107,17 @@ public class HealthReportGenerator {
       for (int i = 0; i < envs.size(); ++i) {
         Logger.trace(LOG_TAG, "Days environment " + envs.keyAt(i) + ": " + envs.get(envs.keyAt(i)).getHash());
       }
     }
 
     JSONObject days = new JSONObject();
     Cursor cursor = storage.getRawEventsSince(since);
     try {
-      if (!cursor.moveToNext()) {
+      if (!cursor.moveToFirst()) {
         return days;
       }
 
       // A classic walking partition.
       // Columns are "date", "env", "field", "value".
       // Note that we care about the type (integer, string) and kind
       // (last/counter, discrete) of each field.
       // Each field will be accessed once for each date/env pair, so
@@ -181,16 +181,32 @@ public class HealthReportGenerator {
       }
       days.put(HealthReportUtils.getDateStringForDay(lastDate), dateObject);
     } finally {
       cursor.close();
     }
     return days;
   }
 
+  /**
+   * Return the {@link JSONObject} parsed from the provided index of the given
+   * cursor, or {@link JSONObject#NULL} if either SQL <code>NULL</code> or
+   * string <code>"null"</code> is present at that index.
+   */
+  private static Object getJSONAtIndex(Cursor cursor, int index) throws JSONException {
+    if (cursor.isNull(index)) {
+      return JSONObject.NULL;
+    }
+    final String value = cursor.getString(index);
+    if ("null".equals(value)) {
+      return JSONObject.NULL;
+    }
+    return new JSONObject(value);
+  }
+
   protected static void recordMeasurementFromCursor(final Field field,
                                              JSONObject measurement,
                                              Cursor cursor)
                                                            throws JSONException {
     if (field.isDiscreteField()) {
       // Discrete counted. Increment the named counter.
       if (field.isCountedField()) {
         if (!field.isStringField()) {
@@ -200,28 +216,36 @@ public class HealthReportGenerator {
         return;
       }
 
       // Discrete string or integer. Append it.
       if (field.isStringField()) {
         HealthReportUtils.append(measurement, field.fieldName, cursor.getString(3));
         return;
       }
+      if (field.isJSONField()) {
+        HealthReportUtils.append(measurement, field.fieldName, getJSONAtIndex(cursor, 3));
+        return;
+      }
       if (field.isIntegerField()) {
         HealthReportUtils.append(measurement, field.fieldName, cursor.getLong(3));
         return;
       }
       throw new IllegalStateException("Unknown field type: " + field.flags);
     }
 
     // Non-discrete -- must be LAST or COUNTER, so just accumulate the value.
     if (field.isStringField()) {
       measurement.put(field.fieldName, cursor.getString(3));
       return;
     }
+    if (field.isJSONField()) {
+      measurement.put(field.fieldName, getJSONAtIndex(cursor, 3));
+      return;
+    }
     measurement.put(field.fieldName, cursor.getLong(3));
   }
 
   public static JSONObject getEnvironmentsJSON(Environment currentEnvironment,
                                                SparseArray<Environment> envs) throws JSONException {
     JSONObject environments = new JSONObject();
 
     // Always do this, even if it hasn't recorded anything in the DB.
--- a/mobile/android/base/background/healthreport/HealthReportStorage.java
+++ b/mobile/android/base/background/healthreport/HealthReportStorage.java
@@ -1,14 +1,16 @@
 /* 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.healthreport;
 
+import org.json.JSONObject;
+
 import android.database.Cursor;
 import android.util.SparseArray;
 
 /**
  * Abstraction over storage for Firefox Health Report on Android.
  */
 public interface HealthReportStorage {
   // Right now we only care about the name of the field.
@@ -24,30 +26,34 @@ public interface HealthReportStorage {
     Iterable<FieldSpec> getFields();
   }
 
   public abstract class Field {
     protected static final int UNKNOWN_TYPE_OR_FIELD_ID = -1;
 
     protected static final int FLAG_INTEGER  = 1 << 0;
     protected static final int FLAG_STRING   = 1 << 1;
+    protected static final int FLAG_JSON     = 1 << 2;
 
     protected static final int FLAG_DISCRETE = 1 << 8;
     protected static final int FLAG_LAST     = 1 << 9;
     protected static final int FLAG_COUNTER  = 1 << 10;
 
     protected static final int FLAG_COUNTED  = 1 << 14;
 
     public static final int TYPE_INTEGER_DISCRETE = FLAG_INTEGER | FLAG_DISCRETE;
     public static final int TYPE_INTEGER_LAST     = FLAG_INTEGER | FLAG_LAST;
     public static final int TYPE_INTEGER_COUNTER  = FLAG_INTEGER | FLAG_COUNTER;
 
     public static final int TYPE_STRING_DISCRETE  = FLAG_STRING | FLAG_DISCRETE;
     public static final int TYPE_STRING_LAST      = FLAG_STRING | FLAG_LAST;
 
+    public static final int TYPE_JSON_DISCRETE    = FLAG_JSON | FLAG_DISCRETE;
+    public static final int TYPE_JSON_LAST        = FLAG_JSON | FLAG_LAST;
+
     public static final int TYPE_COUNTED_STRING_DISCRETE = FLAG_COUNTED | TYPE_STRING_DISCRETE;
 
     protected int fieldID = UNKNOWN_TYPE_OR_FIELD_ID;
     protected int flags;
 
     protected final String measurementName;
     protected final String measurementVersion;
     protected final String fieldName;
@@ -68,16 +74,24 @@ public interface HealthReportStorage {
     public boolean isIntegerField() {
       return (this.flags & FLAG_INTEGER) > 0;
     }
 
     public boolean isStringField() {
       return (this.flags & FLAG_STRING) > 0;
     }
 
+    public boolean isJSONField() {
+      return (this.flags & FLAG_JSON) > 0;
+    }
+
+    public boolean isStoredAsString() {
+      return (this.flags & (FLAG_JSON | FLAG_STRING)) > 0;
+    }
+
     public boolean isDiscreteField() {
       return (this.flags & FLAG_DISCRETE) > 0;
     }
 
     /**
      * True if the accrued values are intended to be bucket-counted. For strings,
      * each discrete value will name a bucket, with the number of instances per
      * day being the value in the bucket.
@@ -149,18 +163,20 @@ public interface HealthReportStorage {
                         String fieldName);
 
   /**
    * @return a mapping from field IDs to {@link Field} instances, suitable for
    *         use in payload generation.
    */
   public SparseArray<Field> getFieldsByID();
 
+  public void recordDailyLast(int env, int day, int field, JSONObject value);
   public void recordDailyLast(int env, int day, int field, String value);
   public void recordDailyLast(int env, int day, int field, int value);
+  public void recordDailyDiscrete(int env, int day, int field, JSONObject value);
   public void recordDailyDiscrete(int env, int day, int field, String value);
   public void recordDailyDiscrete(int env, int day, int field, int value);
   public void incrementDailyCount(int env, int day, int field, int by);
   public void incrementDailyCount(int env, int day, int field);
 
   /**
    * Obtain a cursor over events that were recorded since <code>time</code>.
    * This cursor exposes 'raw' events, with integer identifiers for values.