Bug 718067 - Part 3: Add MetricsCollector; r=rnewman
authorGregory Szorc <gps@mozilla.com>
Mon, 05 Nov 2012 13:45:35 -0800
changeset 113822 9abaed8040943cd0ea1f4bff29b4bb9cf72f6db4
parent 113821 1a386fa321b2a67b3363d4b62f6c60d7ef288f2f
child 113823 bea765d36cad520b2d6298c1e67d060fd3168fb5
push id23890
push userryanvm@gmail.com
push dateWed, 21 Nov 2012 02:43:32 +0000
treeherdermozilla-central@4f19e7fd8bea [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrnewman
bugs718067
milestone20.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 718067 - Part 3: Add MetricsCollector; r=rnewman
services/metrics/Makefile.in
services/metrics/collector.jsm
services/metrics/modules-testing/mocks.jsm
services/metrics/tests/xpcshell/test_load_modules.js
services/metrics/tests/xpcshell/test_metrics_collection_result.js
services/metrics/tests/xpcshell/test_metrics_collector.js
services/metrics/tests/xpcshell/xpcshell.ini
--- a/services/metrics/Makefile.in
+++ b/services/metrics/Makefile.in
@@ -5,16 +5,17 @@
 DEPTH     = @DEPTH@
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 modules := \
+  collector.jsm \
   dataprovider.jsm \
   $(NULL)
 
 testing_modules := \
   mocks.jsm \
   $(NULL)
 
 TEST_DIRS += tests
new file mode 100644
--- /dev/null
+++ b/services/metrics/collector.jsm
@@ -0,0 +1,140 @@
+/* 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/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["MetricsCollector"];
+
+const {utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/commonjs/promise/core.js");
+Cu.import("resource://services-common/log4moz.js");
+Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
+
+
+/**
+ * Handles and coordinates the collection of metrics data from providers.
+ *
+ * This provides an interface for managing `MetricsProvider` instances. It
+ * provides APIs for bulk collection of data.
+ */
+this.MetricsCollector = function MetricsCollector() {
+  this._log = Log4Moz.repository.getLogger("Metrics.MetricsCollector");
+
+  this._providers = [];
+  this.collectionResults = new Map();
+}
+
+MetricsCollector.prototype = {
+  /**
+   * Registers a `MetricsProvider` with this collector.
+   *
+   * Once a `MetricsProvider` is registered, data will be collected from it
+   * whenever we collect data.
+   *
+   * @param provider
+   *        (MetricsProvider) The provider instance to register.
+   */
+  registerProvider: function registerProvider(provider) {
+    if (!(provider instanceof MetricsProvider)) {
+      throw new Error("argument must be a MetricsProvider instance.");
+    }
+
+    for (let p of this._providers) {
+      if (p.provider == provider) {
+        return;
+      }
+    }
+
+    this._providers.push({
+      provider: provider,
+      constantsCollected: false,
+    });
+  },
+
+  /**
+   * Collects all constant measurements from all providers.
+   *
+   * Returns a Promise that will be fulfilled once all data providers have
+   * provided their constant data. A side-effect of this promise fulfillment
+   * is that the collector is populated with the obtained collection results.
+   * The resolved value to the promise is this `MetricsCollector` instance.
+   */
+  collectConstantMeasurements: function collectConstantMeasurements() {
+    let promises = [];
+
+    for (let provider of this._providers) {
+      if (provider.constantsCollected) {
+        this._log.trace("Provider has already provided constant data: " +
+                        provider.name);
+        continue;
+      }
+
+      let result = provider.provider.collectConstantMeasurements();
+      if (!result) {
+        this._log.trace("Provider does not provide constant data: " +
+                        provider.name);
+        continue;
+      }
+
+      // Chain an invisible promise that updates state.
+      let promise = result.onFinished(function onFinished(result) {
+        provider.constantsCollected = true;
+
+        return Promise.resolve(result);
+      });
+
+      promises.push(promise);
+    }
+
+    return this._handleCollectionPromises(promises);
+  },
+
+  /**
+   * Handles promises returned by the collect* functions.
+   *
+   * This consumes the data resolved by the promises and returns a new promise
+   * that will be resolved once all promises have been resolved.
+   */
+  _handleCollectionPromises: function _handleCollectionPromises(promises) {
+    if (!promises.length) {
+      return Promise.resolve(this);
+    }
+
+    let deferred = Promise.defer();
+    let finishedCount = 0;
+
+    let onResult = function onResult(result) {
+      try {
+        this._log.debug("Got result for " + result.name);
+
+        if (this.collectionResults.has(result.name)) {
+          this.collectionResults.get(result.name).aggregate(result);
+        } else {
+          this.collectionResults.set(result.name, result);
+        }
+      } finally {
+        finishedCount++;
+        if (finishedCount >= promises.length) {
+          deferred.resolve(this);
+        }
+      }
+    }.bind(this);
+
+    let onError = function onError(error) {
+      this._log.warn("Error when handling result: " +
+                     CommonUtils.exceptionStr(error));
+      deferred.reject(error);
+    }.bind(this);
+
+    for (let promise of promises) {
+      promise.then(onResult, onError);
+    }
+
+    return deferred.promise;
+  },
+};
+
+Object.freeze(MetricsCollector.prototype);
+
--- a/services/metrics/modules-testing/mocks.jsm
+++ b/services/metrics/modules-testing/mocks.jsm
@@ -31,21 +31,24 @@ DummyMeasurement.prototype = {
   },
 };
 
 
 this.DummyProvider = function DummyProvider(name="DummyProvider") {
   MetricsProvider.call(this, name);
 
   this.constantMeasurementName = "DummyMeasurement";
+  this.collectConstantCount = 0;
 }
 DummyProvider.prototype = {
   __proto__: MetricsProvider.prototype,
 
   collectConstantMeasurements: function collectConstantMeasurements() {
+    this.collectConstantCount++;
+
     let result = this.createResult();
     result.expectMeasurement(this.constantMeasurementName);
     result.addMeasurement(new DummyMeasurement(this.constantMeasurementName));
 
     result.setValue(this.constantMeasurementName, "string", "foo");
     result.setValue(this.constantMeasurementName, "uint32", 24);
 
     result.finish();
--- a/services/metrics/tests/xpcshell/test_load_modules.js
+++ b/services/metrics/tests/xpcshell/test_load_modules.js
@@ -1,14 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const modules = [
+  "collector.jsm",
   "dataprovider.jsm",
 ];
 
 const test_modules = [
   "mocks.jsm",
 ];
 
 function run_test() {
--- a/services/metrics/tests/xpcshell/test_metrics_collection_result.js
+++ b/services/metrics/tests/xpcshell/test_metrics_collection_result.js
@@ -27,44 +27,44 @@ add_test(function test_constructor() {
     do_check_true(failed);
   }
 
   run_next_test();
 });
 
 add_test(function test_expected_measurements() {
   let result = new MetricsCollectionResult("foo");
-  do_check_eq(result.missingMeasurements.size(), 0);
+  do_check_eq(result.missingMeasurements.size0);
 
   result.expectMeasurement("foo");
   result.expectMeasurement("bar");
-  do_check_eq(result.missingMeasurements.size(), 2);
+  do_check_eq(result.missingMeasurements.size, 2);
   do_check_true(result.missingMeasurements.has("foo"));
   do_check_true(result.missingMeasurements.has("bar"));
 
   run_next_test();
 });
 
 add_test(function test_missing_measurements() {
   let result = new MetricsCollectionResult("foo");
 
   let missing = result.missingMeasurements;
-  do_check_eq(missing.size(), 0);
+  do_check_eq(missing.size, 0);
 
   result.expectMeasurement("DummyMeasurement");
   result.expectMeasurement("b");
 
   missing = result.missingMeasurements;
-  do_check_eq(missing.size(), 2);
+  do_check_eq(missing.size, 2);
   do_check_true(missing.has("DummyMeasurement"));
   do_check_true(missing.has("b"));
 
   result.addMeasurement(new DummyMeasurement());
   missing = result.missingMeasurements;
-  do_check_eq(missing.size(), 1);
+  do_check_eq(missing.size, 1);
   do_check_true(missing.has("b"));
 
   run_next_test();
 });
 
 add_test(function test_add_measurement() {
   let result = new MetricsCollectionResult("add_measurement");
 
@@ -77,17 +77,17 @@ add_test(function test_add_measurement()
   } finally {
     do_check_true(failed);
     failed = false;
   }
 
   result.expectMeasurement("foo");
   result.addMeasurement(new DummyMeasurement("foo"));
 
-  do_check_eq(result.measurements.size(), 1);
+  do_check_eq(result.measurements.size, 1);
   do_check_true(result.measurements.has("foo"));
 
   run_next_test();
 });
 
 add_test(function test_set_value() {
   let result = new MetricsCollectionResult("set_value");
   result.expectMeasurement("DummyMeasurement");
@@ -163,24 +163,24 @@ add_test(function test_aggregate_side_ef
   result1.addMeasurement(new DummyMeasurement("dummy1"));
   result1.setValue("dummy1", "invalid", "invalid");
 
   result2.addMeasurement(new DummyMeasurement("dummy2"));
   result2.setValue("dummy2", "another", "invalid");
 
   result1.aggregate(result2);
 
-  do_check_eq(result1.expectedMeasurements.size(), 4);
+  do_check_eq(result1.expectedMeasurements.size, 4);
   do_check_true(result1.expectedMeasurements.has("bar"));
 
-  do_check_eq(result1.measurements.size(), 2);
+  do_check_eq(result1.measurements.size, 2);
   do_check_true(result1.measurements.has("dummy1"));
   do_check_true(result1.measurements.has("dummy2"));
 
-  do_check_eq(result1.missingMeasurements.size(), 2);
+  do_check_eq(result1.missingMeasurements.size, 2);
   do_check_true(result1.missingMeasurements.has("bar"));
 
   do_check_eq(result1.errors.length, 2);
 
   run_next_test();
 });
 
 add_test(function test_json() {
new file mode 100644
--- /dev/null
+++ b/services/metrics/tests/xpcshell/test_metrics_collector.js
@@ -0,0 +1,124 @@
+/* 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/services/metrics/collector.jsm");
+Cu.import("resource://gre/modules/services/metrics/dataprovider.jsm");
+Cu.import("resource://testing-common/services/metrics/mocks.jsm");
+
+
+function run_test() {
+  run_next_test();
+};
+
+add_test(function test_constructor() {
+  let collector = new MetricsCollector();
+
+  run_next_test();
+});
+
+add_test(function test_register_provider() {
+  let collector = new MetricsCollector();
+  let dummy = new DummyProvider();
+
+  collector.registerProvider(dummy);
+  do_check_eq(collector._providers.length, 1);
+  collector.registerProvider(dummy);
+  do_check_eq(collector._providers.length, 1);
+
+  let failed = false;
+  try {
+    collector.registerProvider({});
+  } catch (ex) {
+    do_check_true(ex.message.startsWith("argument must be a MetricsProvider"));
+    failed = true;
+  } finally {
+    do_check_true(failed);
+    failed = false;
+  }
+
+  run_next_test();
+});
+
+add_test(function test_collect_constant_measurements() {
+  let collector = new MetricsCollector();
+  let provider = new DummyProvider();
+  collector.registerProvider(provider);
+
+  do_check_eq(provider.collectConstantCount, 0);
+
+  collector.collectConstantMeasurements().then(function onResult() {
+    do_check_eq(provider.collectConstantCount, 1);
+    do_check_eq(collector.collectionResults.size, 1);
+    do_check_true(collector.collectionResults.has("DummyProvider"));
+
+    let result = collector.collectionResults.get("DummyProvider");
+    do_check_true(result instanceof MetricsCollectionResult);
+
+    do_check_true(collector._providers[0].constantsCollected);
+
+    run_next_test();
+  });
+});
+
+add_test(function test_collect_constant_onetime() {
+  let collector = new MetricsCollector();
+  let provider = new DummyProvider();
+  collector.registerProvider(provider);
+
+  collector.collectConstantMeasurements().then(function onResult() {
+    do_check_eq(provider.collectConstantCount, 1);
+
+    collector.collectConstantMeasurements().then(function onResult() {
+      do_check_eq(provider.collectConstantCount, 1);
+
+      run_next_test();
+    });
+  });
+});
+
+add_test(function test_collect_multiple() {
+  let collector = new MetricsCollector();
+
+  for (let i = 0; i < 10; i++) {
+    collector.registerProvider(new DummyProvider("provider" + i));
+  }
+
+  do_check_eq(collector._providers.length, 10);
+
+  collector.collectConstantMeasurements().then(function onResult(innerCollector) {
+    do_check_eq(collector, innerCollector);
+    do_check_eq(collector.collectionResults.size, 10);
+
+    run_next_test();
+  });
+});
+
+add_test(function test_collect_aggregate() {
+  let collector = new MetricsCollector();
+
+  let dummy1 = new DummyProvider();
+  dummy1.constantMeasurementName = "measurement1";
+
+  let dummy2 = new DummyProvider();
+  dummy2.constantMeasurementName = "measurement2";
+
+  collector.registerProvider(dummy1);
+  collector.registerProvider(dummy2);
+  do_check_eq(collector._providers.length, 2);
+
+  collector.collectConstantMeasurements().then(function onResult() {
+    do_check_eq(collector.collectionResults.size, 1);
+
+    let measurements = collector.collectionResults.get("DummyProvider").measurements;
+    do_check_eq(measurements.size, 2);
+    do_check_true(measurements.has("measurement1"));
+    do_check_true(measurements.has("measurement2"));
+
+    run_next_test();
+  });
+});
+
--- a/services/metrics/tests/xpcshell/xpcshell.ini
+++ b/services/metrics/tests/xpcshell/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head = head.js
 tail =
 
 [test_load_modules.js]
 [test_metrics_collection_result.js]
 [test_metrics_measurement.js]
 [test_metrics_provider.js]
+[test_metrics_collector.js]