Bug 723561. Create telemetry stopwatch helper to easily store and retrieve timestamp data. r=gavin
authorFelipe Gomes <felipc@gmail.com>
Wed, 08 Feb 2012 14:36:01 -0800
changeset 86467 2ca8d6ac63bc
parent 86466 0071109aa94e
child 86468 cd44c3d677ef
push id22021
push userbmo@edmorley.co.uk
push date2012-02-09 17:55 +0000
treeherdermozilla-central@7b1ae3535886 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin
bugs723561
milestone13.0a1
Bug 723561. Create telemetry stopwatch helper to easily store and retrieve timestamp data. r=gavin
toolkit/components/telemetry/Makefile.in
toolkit/components/telemetry/TelemetryStopwatch.jsm
toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js
toolkit/components/telemetry/tests/unit/xpcshell.ini
--- a/toolkit/components/telemetry/Makefile.in
+++ b/toolkit/components/telemetry/Makefile.in
@@ -65,16 +65,20 @@ XPIDLSRCS = \
   nsITelemetry.idl \
   $(NULL)
 
 EXTRA_COMPONENTS = \
   TelemetryPing.manifest \
   TelemetryPing.js \
   $(NULL)
 
+EXTRA_JS_MODULES = \
+  TelemetryStopwatch.jsm \
+  $(NULL)
+
 CPPSRCS = \
   Telemetry.cpp \
   $(NULL)
 
 EXTRA_DSO_LDOPTS += \
   $(MOZ_COMPONENT_LIBS) \
   $(MOZ_JS_LIBS) \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/TelemetryStopwatch.jsm
@@ -0,0 +1,100 @@
+/* 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/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+let EXPORTED_SYMBOLS = ["TelemetryStopwatch"];
+
+let Telemetry = Cc["@mozilla.org/base/telemetry;1"]
+                  .getService(Ci.nsITelemetry);
+
+// simpleTimers are directly associated with a histogram
+// name. objectTimers are associated with an object _and_
+// a histogram name.
+let simpleTimers = {};
+let objectTimers = new WeakMap();
+
+let TelemetryStopwatch = {
+  /**
+   * Starts a timer associated with a telemetry histogram. The timer can be
+   * directly associated with a histogram, or with a pair of a histogram and
+   * an object.
+   *
+   * @param aHistogram a string which must be a valid histogram name
+   *                   from TelemetryHistograms.h
+   *
+   * @param aObj Optional parameter. If specified, the timer is associated
+   *             with this object, meaning that multiple timers for a same
+   *             histogram may be run concurrently, as long as they are
+   *             associated with different objects.
+   *
+   * @return true if the timer was successfully started, false otherwise. If a
+   *         timer already exists, it can't be started again, and the existing
+   *         one will be cleared in order to avoid measurements errors.
+   */
+  start: function(aHistogram, aObj) {
+    if (!validTypes(aHistogram, aObj))
+      return false;
+
+    let timers;
+    if (aObj) {
+      timers = objectTimers.get(aObj, {});
+      objectTimers.set(aObj, timers);
+    } else {
+      timers = simpleTimers;
+    }
+
+    if (timers.hasOwnProperty(aHistogram)) {
+      delete timers[aHistogram];
+      Cu.reportError("TelemetryStopwatch: key was already initialized");
+      return false;
+    }
+
+    timers[aHistogram] = Date.now();
+    return true;
+  },
+
+  /**
+   * Stops the timer associated with the given histogram (and object),
+   * calculates the time delta between start and finish, and adds the value
+   * to the histogram.
+   *
+   * @param aHistogram a string which must be a valid histogram name
+   *                   from TelemetryHistograms.h. If an invalid name
+   *                   is given, the function will throw.
+   *
+   * @param aObj Optional parameter which associates the histogram timer with
+   *             the given object.
+   *
+   * @return true if the timer was succesfully stopped and the data was
+   *         added to the histogram, false otherwise.
+   */
+  finish: function(aHistogram, aObj) {
+    if (!validTypes(aHistogram, aObj))
+      return false;
+
+    let timers = aObj
+                 ? objectTimers.get(aObj, {})
+                 : simpleTimers;
+
+    let start = timers[aHistogram];
+    delete timers[aHistogram];
+
+    if (start) {
+      let delta = Date.now() - start;
+      let histogram = Telemetry.getHistogramById(aHistogram);
+      histogram.add(delta);
+      return true;
+    }
+
+    return false;
+  }
+}
+
+function validTypes(aKey, aObj) {
+  return (typeof aKey == "string") &&
+         (aObj === undefined || typeof aObj == "object");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryStopwatch.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const Telemetry = Cc["@mozilla.org/base/telemetry;1"]
+                  .getService(Ci.nsITelemetry);
+
+let tmpScope = {};
+Cu.import("resource:///modules/TelemetryStopwatch.jsm", tmpScope);
+let TelemetryStopwatch = tmpScope.TelemetryStopwatch;
+
+// We can't create a histogram here since the ones created with
+// newHistogram are not seen by getHistogramById that the module uses.
+const HIST_NAME = "TELEMETRY_PING";
+const HIST_NAME2 = "RANGE_CHECKSUM_ERRORS";
+
+let refObj = {}, refObj2 = {};
+
+let originalCount1, originalCount2;
+
+function run_test() {
+  let histogram = Telemetry.getHistogramById(HIST_NAME);
+  let snapshot = histogram.snapshot();
+  originalCount1 = snapshot.counts.reduce(function (a,b) a += b);
+
+  histogram = Telemetry.getHistogramById(HIST_NAME2);
+  snapshot = histogram.snapshot();
+  originalCount2 = snapshot.counts.reduce(function (a,b) a += b);
+
+  do_check_false(TelemetryStopwatch.start(3));
+  do_check_false(TelemetryStopwatch.start({}));
+  do_check_false(TelemetryStopwatch.start("", 3));
+  do_check_false(TelemetryStopwatch.start("", ""));
+  do_check_false(TelemetryStopwatch.start({}, {}));
+
+  do_check_true(TelemetryStopwatch.start("mark1"));
+  do_check_true(TelemetryStopwatch.start("mark2"));
+
+  do_check_true(TelemetryStopwatch.start("mark1", refObj));
+  do_check_true(TelemetryStopwatch.start("mark2", refObj));
+
+  // Same timer can't be re-started before being stopped
+  do_check_false(TelemetryStopwatch.start("mark1"));
+  do_check_false(TelemetryStopwatch.start("mark1", refObj));
+
+  // Can't stop a timer that was accidentaly started twice
+  do_check_false(TelemetryStopwatch.finish("mark1"));
+  do_check_false(TelemetryStopwatch.finish("mark1", refObj));
+
+  do_check_true(TelemetryStopwatch.start("NON-EXISTENT_HISTOGRAM"));
+  try {
+    TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM");
+    do_throw("Non-existent histogram name should throw an error.");
+  } catch (e) {}
+
+  do_check_true(TelemetryStopwatch.start("NON-EXISTENT_HISTOGRAM", refObj));
+  try {
+    TelemetryStopwatch.finish("NON-EXISTENT_HISTOGRAM", refObj);
+    do_throw("Non-existent histogram name should throw an error.");
+  } catch (e) {}
+
+  do_check_true(TelemetryStopwatch.start(HIST_NAME));
+  do_check_true(TelemetryStopwatch.start(HIST_NAME2));
+  do_check_true(TelemetryStopwatch.start(HIST_NAME, refObj));
+  do_check_true(TelemetryStopwatch.start(HIST_NAME2, refObj));
+  do_check_true(TelemetryStopwatch.start(HIST_NAME, refObj2));
+  do_check_true(TelemetryStopwatch.start(HIST_NAME2, refObj2));
+
+  do_check_true(TelemetryStopwatch.finish(HIST_NAME));
+  do_check_true(TelemetryStopwatch.finish(HIST_NAME2));
+  do_check_true(TelemetryStopwatch.finish(HIST_NAME, refObj));
+  do_check_true(TelemetryStopwatch.finish(HIST_NAME2, refObj));
+  do_check_true(TelemetryStopwatch.finish(HIST_NAME, refObj2));
+  do_check_true(TelemetryStopwatch.finish(HIST_NAME2, refObj2));
+
+  // Verify that TS.finish deleted the timers
+  do_check_false(TelemetryStopwatch.finish(HIST_NAME));
+  do_check_false(TelemetryStopwatch.finish(HIST_NAME, refObj));
+
+  // Verify that they can be used again
+  do_check_true(TelemetryStopwatch.start(HIST_NAME));
+  do_check_true(TelemetryStopwatch.start(HIST_NAME, refObj));
+  do_check_true(TelemetryStopwatch.finish(HIST_NAME));
+  do_check_true(TelemetryStopwatch.finish(HIST_NAME, refObj));
+
+
+  do_check_false(TelemetryStopwatch.finish("unknown-mark")); // Unknown marker
+  do_check_false(TelemetryStopwatch.finish("unknown-mark", {})); // Unknown object
+  do_check_false(TelemetryStopwatch.finish(HIST_NAME, {})); // Known mark on unknown object
+
+  finishTest();
+}
+
+function finishTest() {
+  let histogram = Telemetry.getHistogramById(HIST_NAME);
+  let snapshot = histogram.snapshot();
+  let newCount = snapshot.counts.reduce(function (a,b) a += b);
+
+  do_check_eq(newCount - originalCount1, 5, "The correct number of histograms were added for histogram 1.");
+
+  histogram = Telemetry.getHistogramById(HIST_NAME2);
+  snapshot = histogram.snapshot();
+  newCount = snapshot.counts.reduce(function (a,b) a += b);
+
+  do_check_eq(newCount - originalCount2, 3, "The correct number of histograms were added for histogram 2.");
+}
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -2,8 +2,9 @@
 head = 
 tail = 
 
 [test_nsITelemetry.js]
 [test_TelemetryPing.js]
 # Bug 676989: test fails consistently on Android
 # fail-if = os == "android"
 [test_TelemetryPing_idle.js]
+[test_TelemetryStopwatch.js]