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
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 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]