Bug 1029031 - Record default search provider in FHR. r=bsmedberg, r=MattN, a=sledru
authorGregory Szorc <gps@mozilla.com>
Wed, 25 Jun 2014 12:44:00 -0700
changeset 207388 98a0b7def57a86c22b71d72b1797892a28bae59e
parent 207387 28c71f97aaf13b0ec103383c8dbe7b8ae355f27a
child 207389 248da3015a51ea8cbf4530b8832c363d8f1de479
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbsmedberg, MattN, sledru
bugs1029031
milestone32.0a2
Bug 1029031 - Record default search provider in FHR. r=bsmedberg, r=MattN, a=sledru
browser/base/content/test/general/browser_urlbar_search_healthreport.js
services/healthreport/docs/dataformat.rst
services/healthreport/providers.jsm
services/healthreport/tests/xpcshell/test_provider_searches.js
--- a/browser/base/content/test/general/browser_urlbar_search_healthreport.js
+++ b/browser/base/content/test/general/browser_urlbar_search_healthreport.js
@@ -55,10 +55,26 @@ add_task(function* test_healthreport_sea
 
   data = yield m.getValues();
   ok(data.days.hasDay(now), "We have a search measurement for today.");
   let day = data.days.getDay(now);
   ok(day.has(field), "Have a search count for the urlbar.");
   let newCount = day.get(field);
   is(newCount, oldCount + 1, "We recorded one new search.");
 
+  // We should record the default search engine if Telemetry is enabled.
+  let oldTelemetry = Services.prefs.getBoolPref("toolkit.telemetry.enabled");
+  Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
+
+  m = provider.getMeasurement("engines", 1);
+  yield provider.collectDailyData();
+  data = yield m.getValues();
+
+  ok(data.days.hasDay(now), "Have engines data when Telemetry is enabled.");
+  day = data.days.getDay(now);
+  ok(day.has("default"), "We have default engine data.");
+  is(day.get("default"), "google", "The default engine is reported properly.");
+
+  // Restore.
+  Services.prefs.setBoolPref("toolkit.telemetry.enabled", oldTelemetry);
+
   gBrowser.removeTab(tab);
 });
--- a/services/healthreport/docs/dataformat.rst
+++ b/services/healthreport/docs/dataformat.rst
@@ -1397,16 +1397,46 @@ Example
 ::
 
     "org.mozilla.searches.counts": {
       "_v": 1,
       "google.searchbar": 3,
       "google.urlbar": 7
     },
 
+org.mozilla.searches.engines
+----------------------------
+
+This measurement contains information about search engines.
+
+Version 1
+^^^^^^^^^
+
+This version debuted with Firefox 31 on desktop. It contains the
+following properties:
+
+default
+   Daily string identifier or name of the default search engine provider.
+
+   This field will only be collected if Telemetry is enabled. If
+   Telemetry is enabled and then later disabled, this field may
+   disappear from future days in the payload.
+
+   The special value ``NONE`` could occur if there is no default search
+   engine.
+
+   The special value ``UNDEFINED`` could occur if a default search
+   engine exists but its identifier could not be determined.
+
+   This field's contents are
+   ``Services.search.defaultEngine.identifier`` (if defined) or
+   ``"other-"`` + ``Services.search.defaultEngine.name`` if not.
+   In other words, search engines without an ``.identifier``
+   are prefixed with ``other-``.
+
 org.mozilla.sync.sync
 ---------------------
 
 This daily measurement contains information about the Sync service.
 
 Values should be recorded for every day FHR measurements occurred.
 
 Version 1
--- a/services/healthreport/providers.jsm
+++ b/services/healthreport/providers.jsm
@@ -50,20 +50,25 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesDBUtils",
                                   "resource://gre/modules/PlacesDBUtils.jsm");
 
 
 const LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_LAST_NUMERIC};
 const LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_LAST_TEXT};
 const DAILY_DISCRETE_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_DISCRETE_NUMERIC};
 const DAILY_LAST_NUMERIC_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_NUMERIC};
+const DAILY_LAST_TEXT_FIELD = {type: Metrics.Storage.FIELD_DAILY_LAST_TEXT};
 const DAILY_COUNTER_FIELD = {type: Metrics.Storage.FIELD_DAILY_COUNTER};
 
 const TELEMETRY_PREF = "toolkit.telemetry.enabled";
 
+function isTelemetryEnabled(prefs) {
+  return prefs.get(TELEMETRY_PREF, false);
+}
+
 /**
  * Represents basic application state.
  *
  * This is roughly a union of nsIXULAppInfo, nsIXULRuntime, with a few extra
  * pieces thrown in.
  */
 function AppInfoMeasurement() {
   Metrics.Measurement.call(this);
@@ -348,17 +353,17 @@ AppInfoProvider.prototype = Object.freez
 
     // FUTURE this should be retrieved periodically or at upload time.
     yield this._recordIsTelemetryEnabled(m);
     yield this._recordIsBlocklistEnabled(m);
     yield this._recordDefaultBrowser(m);
   },
 
   _recordIsTelemetryEnabled: function (m) {
-    let enabled = TELEMETRY_PREF && this._prefs.get(TELEMETRY_PREF, false);
+    let enabled = isTelemetryEnabled(this._prefs);
     this._log.debug("Recording telemetry enabled (" + TELEMETRY_PREF + "): " + enabled);
     yield m.setDailyLastNumeric("isTelemetryEnabled", enabled ? 1 : 0);
   },
 
   _recordIsBlocklistEnabled: function (m) {
     let enabled = this._prefs.get("extensions.blocklist.enabled", false);
     this._log.debug("Recording blocklist enabled: " + enabled);
     yield m.setDailyLastNumeric("isBlocklistEnabled", enabled ? 1 : 0);
@@ -1277,42 +1282,90 @@ SearchCountMeasurement3.prototype = Obje
     }
     if (engine.identifier) {
       return engine.identifier;
     }
     return "other-" + engine.name;
   },
 });
 
+function SearchEnginesMeasurement1() {
+  Metrics.Measurement.call(this);
+}
+
+SearchEnginesMeasurement1.prototype = Object.freeze({
+  __proto__: Metrics.Measurement.prototype,
+
+  name: "engines",
+  version: 1,
+
+  fields: {
+    default: DAILY_LAST_TEXT_FIELD,
+  },
+});
+
 this.SearchesProvider = function () {
   Metrics.Provider.call(this);
+
+  this._prefs = new Preferences({defaultBranch: null});
 };
 
 this.SearchesProvider.prototype = Object.freeze({
   __proto__: Metrics.Provider.prototype,
 
   name: "org.mozilla.searches",
   measurementTypes: [
     SearchCountMeasurement1,
     SearchCountMeasurement2,
     SearchCountMeasurement3,
+    SearchEnginesMeasurement1,
   ],
 
   /**
    * Initialize the search service before our measurements are touched.
    */
   preInit: function (storage) {
     // Initialize search service.
     let deferred = Promise.defer();
     Services.search.init(function onInitComplete () {
       deferred.resolve();
     });
     return deferred.promise;
   },
 
+  collectDailyData: function () {
+    return this.storage.enqueueTransaction(function getDaily() {
+      // We currently only record this if Telemetry is enabled.
+      if (!isTelemetryEnabled(this._prefs)) {
+        return;
+      }
+
+      let m = this.getMeasurement(SearchEnginesMeasurement1.prototype.name,
+                                  SearchEnginesMeasurement1.prototype.version);
+
+      let engine;
+      try {
+        engine = Services.search.defaultEngine;
+      } catch (e) {}
+      let name;
+
+      if (!engine) {
+        name = "NONE";
+      } else if (engine.identifier) {
+        name = engine.identifier;
+      } else if (engine.name) {
+        name = "other-" + engine.name;
+      } else {
+        name = "UNDEFINED";
+      }
+
+      yield m.setDailyLastText("default", name);
+    }.bind(this));
+  },
+
   /**
    * Record that a search occurred.
    *
    * @param engine
    *        (nsISearchEngine) The search engine used.
    * @param source
    *        (string) Where the search was initiated from. Must be one of the
    *        SearchCountMeasurement2.SOURCES values.
--- a/services/healthreport/tests/xpcshell/test_provider_searches.js
+++ b/services/healthreport/tests/xpcshell/test_provider_searches.js
@@ -1,16 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const {utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Metrics.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
 let bsp = Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
 
 const DEFAULT_ENGINES = [
   {name: "Amazon.com",    identifier: "amazondotcom"},
   {name: "Bing",          identifier: "bing"},
   {name: "Google",        identifier: "google"},
   {name: "Yahoo",         identifier: "yahoo"},
   {name: "Foobar Search", identifier: "foobar"},
@@ -36,17 +37,17 @@ function run_test() {
 }
 
 add_test(function test_constructor() {
   let provider = new SearchesProvider();
 
   run_next_test();
 });
 
-add_task(function test_record() {
+add_task(function* test_record() {
   let storage = yield Metrics.Storage("record");
   let provider = new MockSearchesProvider();
 
   yield provider.init(storage);
 
   let now = new Date();
 
   // Record searches for all but one of our defaults, and one engine that's
@@ -99,17 +100,17 @@ add_task(function test_record() {
   for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) {
     let field = identifier + "." + source;
     do_check_true(day.has(field));
   }
 
   yield storage.close();
 });
 
-add_task(function test_includes_other_fields() {
+add_task(function* test_includes_other_fields() {
   let storage = yield Metrics.Storage("includes_other_fields");
   let provider = new MockSearchesProvider();
 
   yield provider.init(storage);
   let m = provider.getMeasurement("counts", 3);
 
   // Register a search against a provider that isn't live in this session.
   let id = yield m.storage.registerField(m.id, "test.searchbar",
@@ -129,8 +130,55 @@ add_task(function test_includes_other_fi
   let data = yield provider.storage.getMeasurementValues(m.id);
   let serializer = m.serializer(m.SERIALIZE_JSON);
   let formatted = serializer.daily(data.days.getDay(now));
   do_check_true(testField in formatted);
   do_check_eq(formatted[testField], 1);
 
   yield storage.close();
 });
+
+add_task(function* test_default_search_engine() {
+  let storage = yield Metrics.Storage("default_search_engine");
+  let provider = new SearchesProvider();
+  yield provider.init(storage);
+
+  let m = provider.getMeasurement("engines", 1);
+
+  // Ensure no collection if Telemetry not enabled.
+  Services.prefs.setBoolPref("toolkit.telemetry.enabled", false);
+
+  let now = new Date();
+  yield provider.collectDailyData();
+
+  let data = yield m.getValues();
+  Assert.equal(data.days.hasDay(now), false);
+
+  // Now enable telemetry and ensure we populate.
+  Services.prefs.setBoolPref("toolkit.telemetry.enabled", true);
+
+  yield provider.collectDailyData();
+  data = yield m.getValues();
+  Assert.ok(data.days.hasDay(now));
+
+  let day = data.days.getDay(now);
+  Assert.equal(day.size, 1);
+  Assert.ok(day.has("default"));
+
+  // test environment doesn't have a default engine.
+  Assert.equal(day.get("default"), "NONE");
+
+  Services.search.addEngineWithDetails("testdefault",
+                                       "http://localhost/icon.png",
+                                       null,
+                                       "test description",
+                                       "GET",
+                                       "http://localhost/search/%s");
+  let engine1 = Services.search.getEngineByName("testdefault");
+  Assert.ok(engine1);
+  Services.search.defaultEngine = engine1;
+
+  yield provider.collectDailyData();
+  data = yield m.getValues();
+  Assert.equal(data.days.getDay(now).get("default"), "other-testdefault");
+
+  yield storage.close();
+});