Bug 828540 - Part 1: Health Report provider for recording search counts; r=rnewman
authorGregory Szorc <gps@mozilla.com>
Tue, 12 Feb 2013 16:32:43 -0800
changeset 121709 168ec5716059dcacf7ce77114c02d806c2a684e7
parent 121708 8d73c0bba134d1d379e889a9f537d0cbb3ab3615
child 121710 e663ebce39a6fc41c8a5139b2ebb5240631f7c98
push id24303
push userryanvm@gmail.com
push dateWed, 13 Feb 2013 15:19:25 +0000
treeherdermozilla-central@081cf5b0121e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs828540
milestone21.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 828540 - Part 1: Health Report provider for recording search counts; r=rnewman
services/healthreport/HealthReport.jsm
services/healthreport/HealthReportComponents.manifest
services/healthreport/providers.jsm
services/healthreport/tests/xpcshell/test_provider_searches.js
services/healthreport/tests/xpcshell/xpcshell.ini
--- a/services/healthreport/HealthReport.jsm
+++ b/services/healthreport/HealthReport.jsm
@@ -7,16 +7,17 @@
 this.EXPORTED_SYMBOLS = [
   "HealthReporter",
   "AddonsProvider",
   "AppInfoProvider",
   "CrashesProvider",
   "Metrics",
   "PlacesProvider",
   "ProfileMetadataProvider",
+  "SearchesProvider",
   "SessionsProvider",
   "SysInfoProvider",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
 
--- a/services/healthreport/HealthReportComponents.manifest
+++ b/services/healthreport/HealthReportComponents.manifest
@@ -1,9 +1,10 @@
 # Register Firefox Health Report providers.
 category healthreport-js-provider AddonsProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider AppInfoProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider CrashesProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider SysInfoProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider ProfileMetadataProvider resource://gre/modules/HealthReport.jsm
+category healthreport-js-provider SearchesProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider SessionsProvider resource://gre/modules/HealthReport.jsm
 category healthreport-js-provider PlacesProvider resource://gre/modules/HealthReport.jsm
 
--- a/services/healthreport/providers.jsm
+++ b/services/healthreport/providers.jsm
@@ -17,16 +17,17 @@
 #ifndef MERGED_COMPARTMENT
 
 this.EXPORTED_SYMBOLS = [
   "AddonsProvider",
   "AppInfoProvider",
   "CrashDirectoryService",
   "CrashesProvider",
   "PlacesProvider",
+  "SearchesProvider",
   "SessionsProvider",
   "SysInfoProvider",
 ];
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Metrics.jsm");
 
@@ -949,8 +950,99 @@ PlacesProvider.prototype = Object.freeze
     PlacesDBUtils.telemetry(null, function onResult(data) {
       deferred.resolve(data);
     });
 
     return deferred.promise;
   },
 });
 
+
+/**
+ * Records search counts per day per engine and where search initiated.
+ */
+function SearchCountMeasurement() {
+  Metrics.Measurement.call(this);
+}
+
+SearchCountMeasurement.prototype = Object.freeze({
+  __proto__: Metrics.Measurement.prototype,
+
+  name: "counts",
+  version: 1,
+
+  // If an engine is removed from this list, it may not be reported any more.
+  // Verify side-effects are sane before removing an entry.
+  PARTNER_ENGINES: [
+    "amazon.com",
+    "bing",
+    "google",
+    "yahoo",
+  ],
+
+  SOURCES: [
+    "abouthome",
+    "contextmenu",
+    "searchbar",
+    "urlbar",
+  ],
+
+  configureStorage: function () {
+    // We only record searches for search engines that have partner
+    // agreements with Mozilla.
+    let engines = this.PARTNER_ENGINES.concat("other");
+
+    let promise;
+
+    // While this creates a large number of fields, storage is sparse and there
+    // will be no overhead for fields that aren't used in a given day.
+    for (let engine of engines) {
+      for (let source of this.SOURCES) {
+        promise = this.registerStorageField(engine + "." + source,
+                                            this.storage.FIELD_DAILY_COUNTER);
+      }
+    }
+
+    return promise;
+  },
+});
+
+this.SearchesProvider = function () {
+  Metrics.Provider.call(this);
+};
+
+this.SearchesProvider.prototype = Object.freeze({
+  __proto__: Metrics.Provider.prototype,
+
+  name: "org.mozilla.searches",
+  measurementTypes: [SearchCountMeasurement],
+
+  /**
+   * Record that a search occurred.
+   *
+   * @param engine
+   *        (string) The search engine used. If the search engine is unknown,
+   *        the search will be attributed to "other".
+   * @param source
+   *        (string) Where the search was initiated from. Must be one of the
+   *        SearchCountMeasurement.SOURCES values.
+   *
+   * @return Promise<>
+   *         The promise is resolved when the storage operation completes.
+   */
+  recordSearch: function (engine, source) {
+    let m = this.getMeasurement("counts", 1);
+
+    if (m.SOURCES.indexOf(source) == -1) {
+      throw new Error("Unknown source for search: " + source);
+    }
+
+    let normalizedEngine = engine.toLowerCase();
+    if (m.PARTNER_ENGINES.indexOf(normalizedEngine) == -1) {
+      normalizedEngine = "other";
+    }
+
+    return this.enqueueStorageOperation(function recordSearch() {
+      return m.incrementDailyCounter(normalizedEngine + "." + source);
+    });
+  },
+});
+
new file mode 100644
--- /dev/null
+++ b/services/healthreport/tests/xpcshell/test_provider_searches.js
@@ -0,0 +1,75 @@
+/* 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/healthreport/providers.jsm");
+
+
+function run_test() {
+  run_next_test();
+}
+
+add_test(function test_constructor() {
+  let provider = new SearchesProvider();
+
+  run_next_test();
+});
+
+add_task(function test_record() {
+  let storage = yield Metrics.Storage("record");
+  let provider = new SearchesProvider();
+
+  yield provider.init(storage);
+
+  const ENGINES = [
+    "amazon.com",
+    "bing",
+    "google",
+    "yahoo",
+    "foobar",
+  ];
+
+  let now = new Date();
+
+  for (let engine of ENGINES) {
+    yield provider.recordSearch(engine, "abouthome");
+    yield provider.recordSearch(engine, "contextmenu");
+    yield provider.recordSearch(engine, "searchbar");
+    yield provider.recordSearch(engine, "urlbar");
+  }
+
+  // Invalid sources should throw.
+  let errored = false;
+  try {
+    yield provider.recordSearch("google", "bad source");
+  } catch (ex) {
+    errored = true;
+  } finally {
+    do_check_true(errored);
+  }
+
+  let m = provider.getMeasurement("counts", 1);
+  let data = yield m.getValues();
+  do_check_eq(data.days.size, 1);
+  do_check_true(data.days.hasDay(now));
+
+  let day = data.days.getDay(now);
+  for (let engine of ENGINES) {
+    if (engine == "foobar") {
+      engine = "other";
+    }
+
+    for (let source of ["abouthome", "contextmenu", "searchbar", "urlbar"]) {
+      let field = engine + "." + source;
+      do_check_true(day.has(field));
+      do_check_eq(day.get(field), 1);
+    }
+  }
+
+  yield storage.close();
+});
+
--- a/services/healthreport/tests/xpcshell/xpcshell.ini
+++ b/services/healthreport/tests/xpcshell/xpcshell.ini
@@ -4,11 +4,12 @@ tail =
 
 [test_load_modules.js]
 [test_profile.js]
 [test_healthreporter.js]
 [test_provider_addons.js]
 [test_provider_appinfo.js]
 [test_provider_crashes.js]
 [test_provider_places.js]
+[test_provider_searches.js]
 [test_provider_sysinfo.js]
 [test_provider_sessions.js]