Bug 1017778 - Telemetry probe for home provider database errors. r=margaret, a=lsblakk
authorRichard Newman <rnewman@mozilla.com>
Mon, 02 Jun 2014 14:17:36 -0700
changeset 200478 cf4871067d2abf62534498c864b55bdf78412992
parent 200477 83b7e03162f26f49a1a9281dfff15a4ae52fb563
child 200479 fc83bcd4ed6d243c4b1e65ad1800d78dc6b47965
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmargaret, lsblakk
bugs1017778
milestone31.0a2
Bug 1017778 - Telemetry probe for home provider database errors. r=margaret, a=lsblakk
mobile/android/base/db/FormHistoryProvider.java
mobile/android/base/db/HomeProvider.java
mobile/android/base/db/PasswordsProvider.java
mobile/android/base/db/SQLiteBridgeContentProvider.java
toolkit/components/telemetry/Histograms.json
--- a/mobile/android/base/db/FormHistoryProvider.java
+++ b/mobile/android/base/db/FormHistoryProvider.java
@@ -1,16 +1,17 @@
 /* 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.db;
 
 import java.lang.IllegalArgumentException;
 import java.util.HashMap;
+
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.db.BrowserContract.FormHistory;
 import org.mozilla.gecko.db.BrowserContract.DeletedFormHistory;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.sqlite.SQLiteBridge;
 import org.mozilla.gecko.sync.Utils;
 
@@ -28,16 +29,17 @@ public class FormHistoryProvider extends
     private static final int DELETED_FORM_HISTORY = 101;
 
     private static final UriMatcher URI_MATCHER;
 
 
     // This should be kept in sync with the db version in toolkit/components/satchel/nsFormHistory.js
     private static int DB_VERSION = 4;
     private static String DB_FILENAME = "formhistory.sqlite";
+    private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_FORMS";
 
     private static final String WHERE_GUID_IS_NULL = BrowserContract.DeletedFormHistory.GUID + " IS NULL";
     private static final String WHERE_GUID_IS_VALUE = BrowserContract.DeletedFormHistory.GUID + " = ?";
 
     private static final String LOG_TAG = "FormHistoryProvider";
 
     static {
         URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
@@ -149,12 +151,17 @@ public class FormHistoryProvider extends
     public void onPostQuery(Cursor cursor, Uri uri, SQLiteBridge db) { }
 
     @Override
     protected String getDBName(){
         return DB_FILENAME;
     }
 
     @Override
+    protected String getTelemetryPrefix() {
+        return TELEMETRY_TAG;
+    }
+
+    @Override
     protected int getDBVersion(){
         return DB_VERSION;
     }
 }
--- a/mobile/android/base/db/HomeProvider.java
+++ b/mobile/android/base/db/HomeProvider.java
@@ -24,16 +24,17 @@ import android.net.Uri;
 import android.util.Log;
 
 public class HomeProvider extends SQLiteBridgeContentProvider {
     private static final String LOGTAG = "GeckoHomeProvider";
 
     // This should be kept in sync with the db version in mobile/android/modules/HomeProvider.jsm
     private static int DB_VERSION = 2;
     private static String DB_FILENAME = "home.sqlite";
+    private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_HOME";
 
     private static final String TABLE_ITEMS = "items";
 
     // Endpoint to return static fake data.
     static final int ITEMS_FAKE = 100;
     static final int ITEMS = 101;
     static final int ITEMS_ID = 102;
 
@@ -140,16 +141,21 @@ public class HomeProvider extends SQLite
      */
 
     @Override
     protected String getDBName(){
         return DB_FILENAME;
     }
 
     @Override
+    protected String getTelemetryPrefix() {
+        return TELEMETRY_TAG;
+    }
+
+    @Override
     protected int getDBVersion(){
         return DB_VERSION;
     }
 
     @Override
     public String getTable(Uri uri) {
         final int match = URI_MATCHER.match(uri);
         switch (match) {
--- a/mobile/android/base/db/PasswordsProvider.java
+++ b/mobile/android/base/db/PasswordsProvider.java
@@ -24,16 +24,18 @@ import android.database.Cursor;
 import android.net.Uri;
 import android.text.TextUtils;
 import android.util.Log;
 
 public class PasswordsProvider extends SQLiteBridgeContentProvider {
     static final String TABLE_PASSWORDS = "moz_logins";
     static final String TABLE_DELETED_PASSWORDS = "moz_deleted_logins";
 
+    private static final String TELEMETRY_TAG = "SQLITEBRIDGE_PROVIDER_PASSWORDS";
+
     private static final int PASSWORDS = 100;
     private static final int DELETED_PASSWORDS = 101;
 
     static final String DEFAULT_PASSWORDS_SORT_ORDER = Passwords.HOSTNAME + " ASC";
     static final String DEFAULT_DELETED_PASSWORDS_SORT_ORDER = DeletedPasswords.TIME_DELETED + " ASC";
 
     private static final UriMatcher URI_MATCHER;
 
@@ -87,16 +89,21 @@ public class PasswordsProvider extends S
     }
 
     @Override
     protected String getDBName(){
         return DB_FILENAME;
     }
 
     @Override
+    protected String getTelemetryPrefix() {
+        return TELEMETRY_TAG;
+    }
+
+    @Override
     protected int getDBVersion(){
         return DB_VERSION;
     }
 
     @Override
     public String getType(Uri uri) {
         final int match = URI_MATCHER.match(uri);
 
--- a/mobile/android/base/db/SQLiteBridgeContentProvider.java
+++ b/mobile/android/base/db/SQLiteBridgeContentProvider.java
@@ -1,24 +1,24 @@
 /* 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.db;
 
 import java.io.File;
 import java.util.HashMap;
-import java.util.Collection;
-import java.util.Iterator;
+
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoThread;
-import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.mozglue.GeckoLoader;
 import org.mozilla.gecko.sqlite.SQLiteBridge;
 import org.mozilla.gecko.sqlite.SQLiteBridgeException;
+
 import android.content.ContentProvider;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.text.TextUtils;
 import android.util.Log;
@@ -30,24 +30,62 @@ import android.util.Log;
  *
  *  public abstract String getTable(Uri uri);
  *  public abstract String getSortOrder(Uri uri, String aRequested);
  *  public abstract void setupDefaults(Uri uri, ContentValues values);
  *  public abstract void initGecko();
  */
 
 public abstract class SQLiteBridgeContentProvider extends ContentProvider {
+    private static final String ERROR_MESSAGE_DATABASE_IS_LOCKED = "Can't step statement: (5) database is locked";
+
     private HashMap<String, SQLiteBridge> mDatabasePerProfile;
     protected Context mContext = null;
     private final String mLogTag;
 
     protected SQLiteBridgeContentProvider(String logTag) {
         mLogTag = logTag;
     }
 
+    /**
+     * Subclasses must override this to allow error reporting code to compose
+     * the correct histogram name.
+     *
+     * Ensure that you define the new histograms if you define a new class!
+     */
+    protected abstract String getTelemetryPrefix();
+
+    /**
+     * Errors are recorded in telemetry using an enumerated histogram.
+     *
+     * <https://developer.mozilla.org/en-US/docs/Mozilla/Performance/
+     * Adding_a_new_Telemetry_probe#Choosing_a_Histogram_Type>
+     *
+     * These are the allowable enumeration values. Keep these in sync with the
+     * histogram definition!
+     *
+     */
+    private static enum TelemetryErrorOp {
+        BULKINSERT (0),
+        DELETE     (1),
+        INSERT     (2),
+        QUERY      (3),
+        UPDATE     (4);
+
+        private final int bucket;
+
+        TelemetryErrorOp(final int bucket) {
+            this.bucket = bucket;
+        }
+
+        public int getBucket() {
+            return bucket;
+        }
+    }
+
     @Override
     public void shutdown() {
         if (mDatabasePerProfile == null) {
             return;
         }
 
         synchronized (this) {
             for (SQLiteBridge bridge : mDatabasePerProfile.values()){
@@ -250,17 +288,17 @@ public abstract class SQLiteBridgeConten
         final SQLiteBridge db = getDatabase(uri);
         if (db == null) {
             return deleted;
         }
 
         try {
             deleted = db.delete(getTable(uri), selection, selectionArgs);
         } catch (SQLiteBridgeException ex) {
-            Log.e(mLogTag, "Error deleting record", ex);
+            reportError(ex, TelemetryErrorOp.DELETE);
             throw ex;
         }
 
         return deleted;
     }
 
     @Override
     public Uri insert(Uri uri, ContentValues values) {
@@ -286,17 +324,17 @@ public abstract class SQLiteBridgeConten
             // so we put it inside this transaction
             onPreInsert(values, uri, db);
             id = db.insert(getTable(uri), null, values);
 
             if (useTransaction) {
                 db.setTransactionSuccessful();
             }
         } catch (SQLiteBridgeException ex) {
-            Log.e(mLogTag, "Error inserting in db", ex);
+            reportError(ex, TelemetryErrorOp.INSERT);
             throw ex;
         } finally {
             if (useTransaction) {
                 db.endTransaction();
             }
         }
 
         return ContentUris.withAppendedId(uri, id);
@@ -307,33 +345,32 @@ public abstract class SQLiteBridgeConten
         final SQLiteBridge db = getDatabase(uri);
         // If we can not get a SQLiteBridge instance, its likely that the database
         // has not been set up and Gecko is not running. We return 0 and expect
         // callers to try again later
         if (db == null) {
             return 0;
         }
 
-        long id = -1;
         int rowsAdded = 0;
 
         String table = getTable(uri);
 
         try {
             db.beginTransaction();
             for (ContentValues initialValues : allValues) {
                 ContentValues values = new ContentValues(initialValues);
                 setupDefaults(uri, values);
                 onPreInsert(values, uri, db);
-                id = db.insert(table, null, values);
+                db.insert(table, null, values);
                 rowsAdded++;
             }
             db.setTransactionSuccessful();
         } catch (SQLiteBridgeException ex) {
-            Log.e(mLogTag, "Error inserting in db", ex);
+            reportError(ex, TelemetryErrorOp.BULKINSERT);
             throw ex;
         } finally {
             db.endTransaction();
         }
 
         if (rowsAdded > 0) {
             final boolean shouldSyncToNetwork = !isCallerSync(uri);
             mContext.getContentResolver().notifyChange(uri, null, shouldSyncToNetwork);
@@ -355,17 +392,17 @@ public abstract class SQLiteBridgeConten
             return updated;
         }
 
         onPreUpdate(values, uri, db);
 
         try {
             updated = db.update(getTable(uri), values, selection, selectionArgs);
         } catch (SQLiteBridgeException ex) {
-            Log.e(mLogTag, "Error updating table", ex);
+            reportError(ex, TelemetryErrorOp.UPDATE);
             throw ex;
         }
 
         return updated;
     }
 
     @Override
     public Cursor query(Uri uri, String[] projection, String selection,
@@ -381,23 +418,42 @@ public abstract class SQLiteBridgeConten
         }
 
         sortOrder = getSortOrder(uri, sortOrder);
 
         try {
             cursor = db.query(getTable(uri), projection, selection, selectionArgs, null, null, sortOrder, null);
             onPostQuery(cursor, uri, db);
         } catch (SQLiteBridgeException ex) {
-            Log.e(mLogTag, "Error querying database", ex);
+            reportError(ex, TelemetryErrorOp.QUERY);
             throw ex;
         }
 
         return cursor;
     }
 
+    private String getHistogram(SQLiteBridgeException e) {
+        // If you add values here, make sure to update
+        // toolkit/components/telemetry/Histograms.json.
+        if (ERROR_MESSAGE_DATABASE_IS_LOCKED.equals(e.getMessage())) {
+            return getTelemetryPrefix() + "_LOCKED";
+        }
+        return null;
+    }
+
+    protected void reportError(SQLiteBridgeException e, TelemetryErrorOp op) {
+        Log.e(mLogTag, "Error in database " + op.name(), e);
+        final String histogram = getHistogram(e);
+        if (histogram == null) {
+            return;
+        }
+
+        Telemetry.HistogramAdd(histogram, op.getBucket());
+    }
+
     protected abstract String getDBName();
 
     protected abstract int getDBVersion();
 
     protected abstract String getTable(Uri uri);
 
     protected abstract String getSortOrder(Uri uri, String aRequested);
 
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5729,16 +5729,34 @@
   "NETWORK_CACHE_V1_HIT_TIME_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "10000",
     "n_buckets": 50,
     "extended_statistics_ok": true,
     "description": "Time spent to open an existing cache entry"
   },
+  "SQLITEBRIDGE_PROVIDER_PASSWORDS_LOCKED": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": "10",
+    "description": "The number of errors using the PasswordsProvider due to a locked DB."
+  },
+  "SQLITEBRIDGE_PROVIDER_FORMS_LOCKED": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": "10",
+    "description": "The number of errors using the FormHistoryProvider due to a locked DB."
+  },
+  "SQLITEBRIDGE_PROVIDER_HOME_LOCKED": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": "10",
+    "description": "The number of errors using the HomeProvider due to a locked DB."
+  },
   "SSL_TLS12_INTOLERANCE_REASON_PRE": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 64,
     "description": "detected symptom of TLS 1.2 intolerance, before considering historical info"
   },
   "SSL_TLS12_INTOLERANCE_REASON_POST": {
     "expires_in_version": "never",