Bug 1098374 - Telemetry: Stop all monkey patching in devtools telemetry tests r?yulia draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Tue, 01 May 2018 18:06:14 +0100
changeset 791423 6eff48b09b437e1f8f2b3de9a99fbbd511c88647
parent 791409 8994f35fe5fc89f4e8f4e09579a6962f8a4a3e65
child 791456 d867f2c3b4eae3a487880baae9e3c0ed45a3ce79
push id108808
push userbmo:mratcliffe@mozilla.com
push dateFri, 04 May 2018 10:46:22 +0000
reviewersyulia
bugs1098374
milestone61.0a1
Bug 1098374 - Telemetry: Stop all monkey patching in devtools telemetry tests r?yulia Changes and notes: - Created `devtools/client/shared/test/telemetry-test-helpers.js`, which contains test helpers to aid in creating and running telemetry tests. - Removed any telemetry monkeypatching as it is not dependable and no longer needed (there is some left in GCLI but the test is now disabled because we are removing GCLI soon anyhow). - Because `telemetry-test-helpers.js` is imported by `shared-head.js` I have had to make it available everywhere that shared-head.js is used. - All telemetry tests have been rewritten to use the new helper. - shared-head.js cannot be imported by tests inside `devtools/client/performance/test/` because perf have custom `once` and `waitFor` implementations that act differently from the ones inside `shared-head.js`. This means I had to import the telemetry helpers into `devtools/client/performance/test/head.js` - Created `devtools/client/shared/test/browser_telemetry_misc.js` to be sure to catch `DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER` (we catch a few others to be thorough). - Disabled `browser_inspector_menu-02-copy-items.js`, which was failing to test some expired scalars. I also corrected the way the scalars are logged because it was completely wrong. MozReview-Commit-ID: JjQEGM6hT61
devtools/client/aboutdebugging/test/browser.ini
devtools/client/accessibility/test/browser.ini
devtools/client/application/test/browser.ini
devtools/client/canvasdebugger/test/browser.ini
devtools/client/commandline/test/browser.ini
devtools/client/commandline/test/head.js
devtools/client/debugger/new/test/mochitest/browser.ini
devtools/client/debugger/test/mochitest/browser.ini
devtools/client/debugger/test/mochitest/browser2.ini
devtools/client/dom/test/browser.ini
devtools/client/framework/test/browser.ini
devtools/client/framework/test/browser_toolbox_hosts_telemetry.js
devtools/client/framework/test/head.js
devtools/client/inspector/animation-old/test/browser.ini
devtools/client/inspector/animation/test/browser.ini
devtools/client/inspector/boxmodel/test/browser.ini
devtools/client/inspector/computed/test/browser.ini
devtools/client/inspector/extensions/test/browser.ini
devtools/client/inspector/fonts/test/browser.ini
devtools/client/inspector/grids/test/browser.ini
devtools/client/inspector/grids/test/browser_grids_number-of-css-grids-telemetry.js
devtools/client/inspector/grids/test/head.js
devtools/client/inspector/inspector.js
devtools/client/inspector/markup/test/browser.ini
devtools/client/inspector/rules/test/browser.ini
devtools/client/inspector/shared/test/browser.ini
devtools/client/inspector/test/browser.ini
devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
devtools/client/jsonview/test/browser.ini
devtools/client/memory/test/browser/browser.ini
devtools/client/netmonitor/src/har/test/browser.ini
devtools/client/netmonitor/test/browser.ini
devtools/client/performance/test/browser.ini
devtools/client/performance/test/browser_perf-telemetry-01.js
devtools/client/performance/test/browser_perf-telemetry-02.js
devtools/client/performance/test/browser_perf-telemetry-03.js
devtools/client/performance/test/browser_perf-telemetry-04.js
devtools/client/performance/test/head.js
devtools/client/responsive.html/test/browser/browser.ini
devtools/client/responsive.html/test/browser/browser_telemetry_activate_rdm.js
devtools/client/shadereditor/test/browser.ini
devtools/client/shared/components/test/browser/browser.ini
devtools/client/shared/telemetry.js
devtools/client/shared/test/browser.ini
devtools/client/shared/test/browser_telemetry_button_eyedropper.js
devtools/client/shared/test/browser_telemetry_button_paintflashing.js
devtools/client/shared/test/browser_telemetry_button_responsive.js
devtools/client/shared/test/browser_telemetry_button_scratchpad.js
devtools/client/shared/test/browser_telemetry_misc.js
devtools/client/shared/test/browser_telemetry_sidebar.js
devtools/client/shared/test/browser_telemetry_toolbox.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_inspector.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js
devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js
devtools/client/shared/test/head.js
devtools/client/shared/test/shared-head.js
devtools/client/shared/test/telemetry-test-helpers.js
devtools/client/sourceeditor/test/browser.ini
devtools/client/storage/test/browser.ini
devtools/client/styleeditor/test/browser.ini
devtools/client/webaudioeditor/test/browser.ini
devtools/client/webconsole/test/fixtures/stub-generators/browser.ini
devtools/client/webconsole/test/mochitest/browser.ini
devtools/server/tests/browser/browser.ini
devtools/shared/tests/browser/browser.ini
--- a/devtools/client/aboutdebugging/test/browser.ini
+++ b/devtools/client/aboutdebugging/test/browser.ini
@@ -15,16 +15,17 @@ support-files =
   service-workers/delay-sw.js
   service-workers/empty-sw.html
   service-workers/empty-sw.js
   service-workers/fetch-sw.html
   service-workers/fetch-sw.js
   service-workers/push-sw.html
   service-workers/push-sw.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_addons_debug_bootstrapped.js]
 skip-if = coverage # Bug 1387827
 [browser_addons_debug_info.js]
 [browser_addons_debug_webextension.js]
 tags = webextensions
 [browser_addons_debug_webextension_inspector.js]
 tags = webextensions
--- a/devtools/client/accessibility/test/browser.ini
+++ b/devtools/client/accessibility/test/browser.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_accessibility_context_menu_browser.js]
 [browser_accessibility_context_menu_inspector.js]
 [browser_accessibility_mutations.js]
 [browser_accessibility_reload.js]
 [browser_accessibility_sidebar.js]
 [browser_accessibility_tree.js]
 [browser_accessibility_tree_nagivation.js]
--- a/devtools/client/application/test/browser.ini
+++ b/devtools/client/application/test/browser.ini
@@ -4,12 +4,13 @@ subsuite = devtools
 support-files =
   head.js
   service-workers/dynamic-registration.html
   service-workers/empty-sw.js
   service-workers/scope-page.html
   service-workers/simple.html
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_application_panel_list-domain-workers.js]
 [browser_application_panel_list-several-workers.js]
 [browser_application_panel_list-single-worker.js]
--- a/devtools/client/canvasdebugger/test/browser.ini
+++ b/devtools/client/canvasdebugger/test/browser.ini
@@ -12,16 +12,17 @@ support-files =
   doc_simple-canvas-transparent.html
   doc_webgl-bindings.html
   doc_webgl-enum.html
   doc_webgl-drawArrays.html
   doc_webgl-drawElements.html
   head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_canvas-actor-test-01.js]
 [browser_canvas-actor-test-02.js]
 [browser_canvas-actor-test-03.js]
 [browser_canvas-actor-test-04.js]
 [browser_canvas-actor-test-05.js]
 [browser_canvas-actor-test-06.js]
 [browser_canvas-actor-test-07.js]
--- a/devtools/client/commandline/test/browser.ini
+++ b/devtools/client/commandline/test/browser.ini
@@ -117,13 +117,14 @@ skip-if = true # Bug 1093205 - Test does
 [browser_gcli_keyboard5.js]
 [browser_gcli_menu.js]
 [browser_gcli_node.js]
 [browser_gcli_resource.js]
 [browser_gcli_short.js]
 [browser_gcli_spell.js]
 [browser_gcli_split.js]
 [browser_gcli_telemetry.js]
+skip-if = true # Disabling: Monkeypatches telemetry and GCLI is soon to be removed.
 [browser_gcli_tokenize.js]
 [browser_gcli_tooltip.js]
 skip-if = true # Bug 1093205 - Test does not run in Firefox due to missing terminal
 [browser_gcli_types.js]
 [browser_gcli_union.js]
--- a/devtools/client/commandline/test/head.js
+++ b/devtools/client/commandline/test/head.js
@@ -9,16 +9,19 @@
 
 const TEST_BASE_HTTP = "http://example.com/browser/devtools/client/commandline/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/devtools/client/commandline/test/";
 
 var { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 var flags = require("devtools/shared/flags");
 var { Task } = require("devtools/shared/task");
 
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/telemetry-test-helpers.js", this);
+
 // Import the GCLI test helper
 var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this, "UTF-8");
 
 function whenDelayedStartupFinished(aWindow, aCallback) {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     if (aWindow == aSubject) {
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 skip-if = (os == 'linux' && debug && bits == 32)
 support-files =
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   examples/babel/polyfill-bundle.js
   examples/babel/fixtures/eval-source-maps/output.js
   examples/babel/fixtures/eval-source-maps/output.js.map
   examples/babel/fixtures/for-of/output.js
   examples/babel/fixtures/for-of/output.js.map
   examples/babel/fixtures/line-start-bindings-es6/output.js
   examples/babel/fixtures/line-start-bindings-es6/output.js.map
   examples/babel/fixtures/shadowed-vars/output.js
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -127,16 +127,17 @@ support-files =
   doc_WorkerActor.attach-tab2.html
   doc_WorkerActor.attachThread-tab.html
   head.js
   sjs_post-page.sjs
   sjs_random-javascript.sjs
   testactors.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
 uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_addon-modules.js]
 skip-if = e10s # TODO
 tags = addons
 [browser_dbg_addon-modules-unpacked.js]
--- a/devtools/client/debugger/test/mochitest/browser2.ini
+++ b/devtools/client/debugger/test/mochitest/browser2.ini
@@ -127,16 +127,17 @@ support-files =
   doc_WorkerActor.attach-tab2.html
   doc_WorkerActor.attachThread-tab.html
   head.js
   sjs_post-page.sjs
   sjs_random-javascript.sjs
   testactors.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_dbg_no-dangling-breakpoints.js]
 uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_no-page-sources.js]
 uses-unsafe-cpows = true
 skip-if = e10s && debug
 [browser_dbg_on-pause-highlight.js]
--- a/devtools/client/dom/test/browser.ini
+++ b/devtools/client/dom/test/browser.ini
@@ -2,12 +2,13 @@
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   page_array.html
   page_basic.html
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_dom_array.js]
 [browser_dom_basic.js]
 [browser_dom_refresh.js]
--- a/devtools/client/framework/test/browser.ini
+++ b/devtools/client/framework/test/browser.ini
@@ -42,16 +42,17 @@ support-files =
   serviceworker.js
   sjs_code_reload.sjs
   sjs_code_bundle_reload_map.sjs
   test_browser_toolbox_debugger.js
   !/devtools/client/debugger/new/test/mochitest/head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_browser_toolbox.js]
 skip-if = coverage # Bug 1387827
 [browser_browser_toolbox_debugger.js]
 skip-if = os == 'win' || debug # Bug 1282269, 1448084
 [browser_devtools_api.js]
 [browser_devtools_api_destroy.js]
 [browser_dynamic_tool_enabling.js]
--- a/devtools/client/framework/test/browser_toolbox_hosts_telemetry.js
+++ b/devtools/client/framework/test/browser_toolbox_hosts_telemetry.js
@@ -1,50 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* import-globals-from head.js */
+
 "use strict";
 
 const {Toolbox} = require("devtools/client/framework/toolbox");
 const {SIDE, BOTTOM, WINDOW} = Toolbox.HostType;
 
 const URL = "data:text/html;charset=utf8,browser_toolbox_hosts_telemetry.js";
 
-function getHostHistogram() {
-  return Services.telemetry.getHistogramById("DEVTOOLS_TOOLBOX_HOST");
-}
-
 add_task(async function() {
-  // Reset it to make counting easier
-  getHostHistogram().clear();
+  startTelemetry();
 
   info("Create a test tab and open the toolbox");
   let tab = await addTab(URL);
   let target = TargetFactory.forTab(tab);
   let toolbox = await gDevTools.showToolbox(target, "webconsole");
 
   await changeToolboxHost(toolbox);
   await checkResults();
-  await toolbox.destroy();
-
-  toolbox = target = null;
-  gBrowser.removeCurrentTab();
-
-  // Cleanup
-  getHostHistogram().clear();
 });
 
 async function changeToolboxHost(toolbox) {
   info("Switch toolbox host");
   await toolbox.switchHost(SIDE);
   await toolbox.switchHost(WINDOW);
   await toolbox.switchHost(BOTTOM);
   await toolbox.switchHost(SIDE);
   await toolbox.switchHost(WINDOW);
   await toolbox.switchHost(BOTTOM);
 }
 
 function checkResults() {
-  let counts = getHostHistogram().snapshot().counts;
-  is(counts[0], 3, "Toolbox HostType bottom has 3 successful entries");
-  is(counts[1], 2, "Toolbox HostType side has 2 successful entries");
-  is(counts[2], 2, "Toolbox HostType window has 2 successful entries");
+  // Check for:
+  //   - 3 "bottom" entries.
+  //   - 2 "side" entries.
+  //   - 2 "window" entries.
+  checkTelemetry("DEVTOOLS_TOOLBOX_HOST", "", [3, 2, 2, 0, 0, 0, 0, 0, 0, 0], "array");
 }
--- a/devtools/client/framework/test/head.js
+++ b/devtools/client/framework/test/head.js
@@ -1,14 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /* import-globals-from ../../shared/test/shared-head.js */
+/* import-globals-from ../../shared/test/telemetry-test-helpers.js */
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this);
 
 const EventEmitter = require("devtools/shared/event-emitter");
 
 function toggleAllTools(state) {
   for (let [, tool] of gDevTools._tools) {
--- a/devtools/client/inspector/animation-old/test/browser.ini
+++ b/devtools/client/inspector/animation-old/test/browser.ini
@@ -19,16 +19,17 @@ support-files =
   doc_multiple_property_types.html
   doc_timing_combination_animation.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
 [browser_animation_animated_properties_displayed.js]
 [browser_animation_animated_properties_for_delayed_starttime_animations.js]
 [browser_animation_animated_properties_path.js]
 [browser_animation_animated_properties_progress_indicator.js]
 [browser_animation_click_selects_animation.js]
--- a/devtools/client/inspector/animation/test/browser.ini
+++ b/devtools/client/inspector/animation/test/browser.ini
@@ -8,16 +8,17 @@ support-files =
   doc_multi_keyframes.html
   doc_multi_timings.html
   doc_simple_animation.html
   head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
 [browser_animation_animated-property-list.js]
 [browser_animation_animated-property-list_unchanged-items.js]
 [browser_animation_animated-property-name.js]
 [browser_animation_animation-detail_close-button.js]
 [browser_animation_animation-detail_title.js]
--- a/devtools/client/inspector/boxmodel/test/browser.ini
+++ b/devtools/client/inspector/boxmodel/test/browser.ini
@@ -4,16 +4,17 @@ subsuite = devtools
 support-files =
   doc_boxmodel_iframe1.html
   doc_boxmodel_iframe2.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_boxmodel.js]
 [browser_boxmodel_edit-position-visible-position-change.js]
 [browser_boxmodel_editablemodel.js]
 [browser_boxmodel_editablemodel_allproperties.js]
 disabled=too many intermittent failures (bug 1009322)
--- a/devtools/client/inspector/computed/test/browser.ini
+++ b/devtools/client/inspector/computed/test/browser.ini
@@ -9,16 +9,17 @@ support-files =
   doc_sourcemaps.css.map
   doc_sourcemaps.html
   doc_sourcemaps.scss
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_computed_browser-styles.js]
 [browser_computed_cycle_color.js]
 [browser_computed_getNodeInfo.js]
 [browser_computed_keybindings_01.js]
 [browser_computed_keybindings_02.js]
--- a/devtools/client/inspector/extensions/test/browser.ini
+++ b/devtools/client/inspector/extensions/test/browser.ini
@@ -3,12 +3,13 @@ tags = devtools
 subsuite = devtools
 support-files =
   head.js
   head_devtools_inspector_sidebar.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
-[browser_inspector_extension_sidebar.js]
\ No newline at end of file
+[browser_inspector_extension_sidebar.js]
--- a/devtools/client/inspector/fonts/test/browser.ini
+++ b/devtools/client/inspector/fonts/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
   test_iframe.html
   ostrich-black.ttf
   ostrich-regular.ttf
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_fontinspector.js]
 [browser_fontinspector_copy-URL.js]
 skip-if = !e10s # too slow on !e10s, logging fully serialized actors (Bug 1446595)
 subsuite = clipboard
 [browser_fontinspector_edit-previews.js]
--- a/devtools/client/inspector/grids/test/browser.ini
+++ b/devtools/client/inspector/grids/test/browser.ini
@@ -4,16 +4,17 @@ subsuite = devtools
 support-files =
   doc_iframe_reloaded.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_grids_accordion-state.js]
 [browser_grids_color-in-rules-grid-toggle.js]
 [browser_grids_display-setting-extend-grid-lines.js]
 [browser_grids_display-setting-show-grid-line-numbers.js]
 [browser_grids_display-setting-show-grid-areas.js]
--- a/devtools/client/inspector/grids/test/browser_grids_number-of-css-grids-telemetry.js
+++ b/devtools/client/inspector/grids/test/browser_grids_number-of-css-grids-telemetry.js
@@ -17,29 +17,33 @@ const TEST_URI2 = `
     }
   </style>
   <div id="grid">
     <div id="cell1">cell1</div>
     <div id="cell2">cell2</div>
   </div>
 `;
 
-const CSS_GRID_COUNT_HISTOGRAM_ID = "DEVTOOLS_NUMBER_OF_CSS_GRIDS_IN_A_PAGE";
-
 add_task(async function() {
   await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI1));
 
+  startTelemetry();
+
   let { inspector } = await openLayoutView();
   let { store } = inspector;
 
   info("Navigate to TEST_URI2");
 
   let onGridListUpdate = waitUntilState(store, state => state.grids.length == 1);
   await navigateTo(inspector,
     "data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI2));
   await onGridListUpdate;
 
-  let histogram = Services.telemetry.getHistogramById(CSS_GRID_COUNT_HISTOGRAM_ID);
-  let snapshot = histogram.snapshot();
+  checkResults();
+});
 
-  is(snapshot.counts[1], 1, "Got a count of 1 for 1 CSS Grid element seen.");
-  is(snapshot.sum, 1, "Got the correct sum.");
-});
+function checkResults() {
+  // Check for:
+  //   - 1 CSS Grid Element
+  checkTelemetry("DEVTOOLS_NUMBER_OF_CSS_GRIDS_IN_A_PAGE", "",
+    [0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+     0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], "array");
+}
--- a/devtools/client/inspector/grids/test/head.js
+++ b/devtools/client/inspector/grids/test/head.js
@@ -1,13 +1,14 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 /* import-globals-from ../../../shared/test/shared-head.js */
+/* import-globals-from ../../../shared/test/telemetry-test-helpers.js */
 /* import-globals-from ../../test/head.js */
 "use strict";
 
 // Import the inspector's head.js first (which itself imports shared-head.js).
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
   this);
 
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -2140,45 +2140,45 @@ Inspector.prototype = {
   /**
    * Copy a unique selector of the selected Node to the clipboard.
    */
   copyUniqueSelector: function() {
     if (!this.selection.isNode()) {
       return;
     }
 
-    this.telemetry.toolOpened("copyuniquecssselector");
+    this.telemetry.logScalar("devtools.copy.unique.css.selector.opened", 1);
     this.selection.nodeFront.getUniqueSelector().then(selector => {
       clipboardHelper.copyString(selector);
     }).catch(console.error);
   },
 
   /**
    * Copy the full CSS Path of the selected Node to the clipboard.
    */
   copyCssPath: function() {
     if (!this.selection.isNode()) {
       return;
     }
 
-    this.telemetry.toolOpened("copyfullcssselector");
+    this.telemetry.logScalar("devtools.copy.full.css.selector.opened", 1);
     this.selection.nodeFront.getCssPath().then(path => {
       clipboardHelper.copyString(path);
     }).catch(console.error);
   },
 
   /**
    * Copy the XPath of the selected Node to the clipboard.
    */
   copyXPath: function() {
     if (!this.selection.isNode()) {
       return;
     }
 
-    this.telemetry.toolOpened("copyxpath");
+    this.telemetry.logScalar("devtools.copy.xpath.opened", 1);
     this.selection.nodeFront.getXPath().then(path => {
       clipboardHelper.copyString(path);
     }).catch(console.error);
   },
 
   /**
    * Initiate gcli screenshot command on selected node.
    */
--- a/devtools/client/inspector/markup/test/browser.ini
+++ b/devtools/client/inspector/markup/test/browser.ini
@@ -67,16 +67,17 @@ support-files =
   lib_react_dom_16.2.0_min.js
   lib_react_with_addons_15.3.1_min.js
   lib_react_with_addons_15.4.1.js
   react_external_listeners.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_markup_accessibility_focus_blur.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_markup_accessibility_navigation.js]
 skip-if = os == "mac" # Full keyboard navigation on OSX only works if Full Keyboard Access setting is set to All Control in System Keyboard Preferences
 [browser_markup_accessibility_new_selection.js]
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -39,16 +39,17 @@ support-files =
   doc_urls_clickable.html
   doc_variables_1.html
   doc_variables_2.html
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_rules_add-property-and-reselect.js]
 [browser_rules_add-property-cancel_01.js]
 [browser_rules_add-property-cancel_02.js]
 [browser_rules_add-property-cancel_03.js]
 [browser_rules_add-property-commented.js]
--- a/devtools/client/inspector/shared/test/browser.ini
+++ b/devtools/client/inspector/shared/test/browser.ini
@@ -12,16 +12,17 @@ support-files =
   doc_content_stylesheet_script.css
   doc_content_stylesheet_xul.css
   doc_frame_script.js
   head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_styleinspector_context-menu-copy-color_01.js]
 [browser_styleinspector_context-menu-copy-color_02.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_styleinspector_context-menu-copy-urls.js]
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -40,16 +40,17 @@ support-files =
   doc_inspector_select-last-selected-01.html
   doc_inspector_select-last-selected-02.html
   doc_inspector_svg.svg
   head.js
   img_browser_inspector_highlighter-eyedropper-image.png
   shared-head.js
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_inspector_addNode_01.js]
 [browser_inspector_addNode_02.js]
 [browser_inspector_addNode_03.js]
 [browser_inspector_addSidebarTab.js]
 [browser_inspector_breadcrumbs.js]
@@ -141,19 +142,16 @@ skip-if = (e10s && debug) # Bug 1250058 
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu-01-sensitivity.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
-[browser_inspector_menu-02-copy-items.js]
-subsuite = clipboard
-skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-03-paste-items.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-03-paste-items-svg.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_inspector_menu-04-use-in-console.js]
 [browser_inspector_menu-05-attribute-items.js]
deleted file mode 100644
--- a/devtools/client/inspector/test/browser_inspector_menu-02-copy-items.js
+++ /dev/null
@@ -1,98 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-// Test that the various copy items in the context menu works correctly.
-
-const TEST_URL = URL_ROOT + "doc_inspector_menu.html";
-const SELECTOR_UNIQUE = "devtools.copy.unique.css.selector.opened";
-const SELECTOR_FULL = "devtools.copy.full.css.selector.opened";
-const XPATH = "devtools.copy.xpath.opened";
-
-const COPY_ITEMS_TEST_DATA = [
-  {
-    desc: "copy inner html",
-    id: "node-menu-copyinner",
-    selector: "[data-id=\"copy\"]",
-    text: "Paragraph for testing copy",
-  },
-  {
-    desc: "copy outer html",
-    id: "node-menu-copyouter",
-    selector: "[data-id=\"copy\"]",
-    text: "<p data-id=\"copy\">Paragraph for testing copy</p>",
-  },
-  {
-    desc: "copy unique selector",
-    id: "node-menu-copyuniqueselector",
-    selector: "[data-id=\"copy\"]",
-    text: "body > div:nth-child(1) > p:nth-child(2)",
-  },
-  {
-    desc: "copy CSS path",
-    id: "node-menu-copycsspath",
-    selector: "[data-id=\"copy\"]",
-    text: "html body div p",
-  },
-  {
-    desc: "copy XPath",
-    id: "node-menu-copyxpath",
-    selector: "[data-id=\"copy\"]",
-    text: "/html/body/div/p[1]",
-  },
-  {
-    desc: "copy image data uri",
-    id: "node-menu-copyimagedatauri",
-    selector: "#copyimage",
-    text: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
-      "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
-  },
-];
-
-add_task(async function() {
-  let Telemetry = loadTelemetryAndRecordLogs();
-  let { inspector } = await openInspectorForURL(TEST_URL);
-
-  for (let {desc, id, selector, text} of COPY_ITEMS_TEST_DATA) {
-    info("Testing " + desc);
-    await selectNode(selector, inspector);
-
-    let allMenuItems = openContextMenuAndGetAllItems(inspector);
-    let item = allMenuItems.find(i => i.id === id);
-    ok(item, "The popup has a " + desc + " menu item.");
-
-    await waitForClipboardPromise(() => item.click(), text);
-  }
-
-  checkTelemetryResults(Telemetry);
-  stopRecordingTelemetryLogs(Telemetry);
-});
-
-function checkTelemetryResults(Telemetry) {
-  let data = Telemetry.prototype.telemetryInfo;
-  let results = new Map();
-
-  for (let key in data) {
-    if (key.toLowerCase() === key) {
-      let pings = data[key].length;
-
-      results.set(key, pings);
-    }
-  }
-
-  is(results.size, 3, "The correct number of scalars were logged");
-
-  let pings = checkPings(SELECTOR_UNIQUE, results);
-  is(pings, 1, `${SELECTOR_UNIQUE} has just 1 ping`);
-
-  pings = checkPings(SELECTOR_FULL, results);
-  is(pings, 1, `${SELECTOR_FULL} has just 1 ping`);
-
-  pings = checkPings(XPATH, results);
-  is(pings, 1, `${XPATH} has just 1 ping`);
-}
-
-function checkPings(scalarId, results) {
-  return results.get(scalarId);
-}
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
   simple_json.json
   simple_json.json^headers^
   valid_json.json
   valid_json.json^headers^
   !/devtools/client/commandline/test/head.js
   !/devtools/client/framework/test/head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_json_refresh.js]
 [browser_jsonview_bug_1380828.js]
 [browser_jsonview_chunked_json.js]
 support-files =
   chunked_json.sjs
 [browser_jsonview_content_type.js]
 [browser_jsonview_copy_headers.js]
--- a/devtools/client/memory/test/browser/browser.ini
+++ b/devtools/client/memory/test/browser/browser.ini
@@ -3,16 +3,17 @@ tags = devtools devtools-memory
 subsuite = devtools
 support-files =
   head.js
   doc_big_tree.html
   doc_empty.html
   doc_steady_allocation.html
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_memory_allocationStackDisplay_01.js]
 skip-if = debug # bug 1219554
 [browser_memory_displays_01.js]
 [browser_memory_clear_snapshots.js]
 [browser_memory_diff_01.js]
 [browser_memory_dominator_trees_01.js]
 skip-if = ccov # bug 1347244
--- a/devtools/client/netmonitor/src/har/test/browser.ini
+++ b/devtools/client/netmonitor/src/har/test/browser.ini
@@ -6,14 +6,15 @@ support-files =
   head.js
   html_har_import-test-page.html
   html_har_post-data-test-page.html
   sjs_cache-test-server.sjs
   sjs_cookies-test-server.sjs
   !/devtools/client/netmonitor/test/head.js
   !/devtools/client/netmonitor/test/html_simple-test-page.html
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_net_har_copy_all_as_har.js]
 [browser_net_har_import.js]
 [browser_net_har_post_data.js]
 [browser_net_har_throttle_upload.js]
 [browser_net_har_post_data_on_get.js]
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -55,16 +55,17 @@ support-files =
   test-image.png
   service-workers/status-codes.html
   service-workers/status-codes-service-worker.js
   xhr_bundle.js
   xhr_bundle.js.map
   xhr_original.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_net_accessibility-01.js]
 [browser_net_accessibility-02.js]
 [browser_net_api-calls.js]
 [browser_net_background_update.js]
 [browser_net_autoscroll.js]
 [browser_net_cached-status.js]
 [browser_net_cause.js]
--- a/devtools/client/performance/test/browser.ini
+++ b/devtools/client/performance/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
   doc_allocs.html
   doc_innerHTML.html
   doc_markers.html
   doc_simple-test.html
   doc_worker.html
   js_simpleWorker.js
   head.js
   !/devtools/client/shared/test/frame-script-utils.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_aaa-run-first-leaktest.js]
 [browser_perf-button-states.js]
 [browser_perf-calltree-js-categories.js]
 [browser_perf-calltree-js-columns.js]
 [browser_perf-calltree-js-events.js]
 [browser_perf-calltree-memory-columns.js]
 [browser_perf-console-record-01.js]
--- a/devtools/client/performance/test/browser_perf-telemetry-01.js
+++ b/devtools/client/performance/test/browser_perf-telemetry-01.js
@@ -1,53 +1,52 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
 /**
  * Tests that the performance telemetry module records events at appropriate times.
  * Specificaly the state about a recording process.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { UI_ENABLE_MEMORY_PREF } = require("devtools/client/performance/test/helpers/prefs");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 
 add_task(async function() {
+  startTelemetry();
+
   let { panel } = await initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
-  let { PerformanceController } = panel.panelWin;
-
-  let telemetry = PerformanceController._telemetry;
-  let logs = telemetry.getLogs();
-  let DURATION = "DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS";
-  let COUNT = "DEVTOOLS_PERFTOOLS_RECORDING_COUNT";
-  let CONSOLE_COUNT = "DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT";
-  let FEATURES = "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED";
-
   Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, false);
 
   await startRecording(panel);
   await stopRecording(panel);
 
   Services.prefs.setBoolPref(UI_ENABLE_MEMORY_PREF, true);
 
   await startRecording(panel);
   await stopRecording(panel);
 
-  is(logs[DURATION].length, 2, `There are two entries for ${DURATION}.`);
-  ok(logs[DURATION].every(d => typeof d === "number"),
-     `Every ${DURATION} entry is a number.`);
-  is(logs[COUNT].length, 2, `There are two entries for ${COUNT}.`);
-  is(logs[CONSOLE_COUNT], void 0, `There are no entries for ${CONSOLE_COUNT}.`);
-  is(logs[FEATURES].length, 8,
-     `There are two recordings worth of entries for ${FEATURES}.`);
-  ok(logs[FEATURES].find(r => r[0] === "withMemory" && r[1] === true),
-     "One feature entry for memory enabled.");
-  ok(logs[FEATURES].find(r => r[0] === "withMemory" && r[1] === false),
-    "One feature entry for memory disabled.");
+  checkResults();
 
   await teardownToolboxAndRemoveTab(panel);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PERFTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS", "", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withMarkers", [0, 2, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withMemory", [1, 1, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withAllocations", [2, 0, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withTicks", [0, 2, 0], "array");
+}
--- a/devtools/client/performance/test/browser_perf-telemetry-02.js
+++ b/devtools/client/performance/test/browser_perf-telemetry-02.js
@@ -8,41 +8,42 @@
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
 
 add_task(async function() {
+  startTelemetry();
+
   let { panel } = await initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { EVENTS, PerformanceController } = panel.panelWin;
 
-  let telemetry = PerformanceController._telemetry;
-  let logs = telemetry.getLogs();
-  let EXPORTED = "DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG";
-  let IMPORTED = "DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG";
-
   await startRecording(panel);
   await stopRecording(panel);
 
   let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]);
   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
 
   let exported = once(PerformanceController, EVENTS.RECORDING_EXPORTED);
   await PerformanceController.exportRecording(PerformanceController.getCurrentRecording(),
     file);
   await exported;
 
-  ok(logs[EXPORTED], `A telemetry entry for ${EXPORTED} exists after exporting.`);
-
   let imported = once(PerformanceController, EVENTS.RECORDING_IMPORTED);
   await PerformanceController.importRecording(file);
   await imported;
 
-  ok(logs[IMPORTED], `A telemetry entry for ${IMPORTED} exists after importing.`);
-
+  checkResults();
   await teardownToolboxAndRemoveTab(panel);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PERFTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG", "", [0, 1, 0], "array");
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG", "", [0, 1, 0], "array");
+}
--- a/devtools/client/performance/test/browser_perf-telemetry-03.js
+++ b/devtools/client/performance/test/browser_perf-telemetry-03.js
@@ -8,49 +8,50 @@
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { startRecording, stopRecording } = require("devtools/client/performance/test/helpers/actions");
 const { once } = require("devtools/client/performance/test/helpers/event-utils");
 
 add_task(async function() {
+  startTelemetry();
+
   let { panel } = await initPerformanceInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let {
     EVENTS,
-    PerformanceController,
     DetailsView,
     JsCallTreeView,
     JsFlameGraphView
   } = panel.panelWin;
 
-  let telemetry = PerformanceController._telemetry;
-  let logs = telemetry.getLogs();
-  let VIEWS = "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS";
-
   await startRecording(panel);
   await stopRecording(panel);
 
   let calltreeRendered = once(JsCallTreeView, EVENTS.UI_JS_CALL_TREE_RENDERED);
   let flamegraphRendered = once(JsFlameGraphView, EVENTS.UI_JS_FLAMEGRAPH_RENDERED);
 
   // Go through some views to check later.
   await DetailsView.selectView("js-calltree");
   await calltreeRendered;
 
   await DetailsView.selectView("js-flamegraph");
   await flamegraphRendered;
 
   await teardownToolboxAndRemoveTab(panel);
 
-  // Check views after destruction to ensure `js-flamegraph` gets called
-  // with a time during destruction.
-  ok(logs[VIEWS].find(r => r[0] === "waterfall" && typeof r[1] === "number"),
-     `${VIEWS} for waterfall view and time.`);
-  ok(logs[VIEWS].find(r => r[0] === "js-calltree" && typeof r[1] === "number"),
-     `${VIEWS} for js-calltree view and time.`);
-  ok(logs[VIEWS].find(r => r[0] === "js-flamegraph" && typeof r[1] === "number"),
-     `${VIEWS} for js-flamegraph view and time.`);
+  checkResults();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PERFTOOLS_")
+  // here.
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS", "js-calltree", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS", "js-flamegraph", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_SELECTED_VIEW_MS", "waterfall", null, "hasentries");
+}
--- a/devtools/client/performance/test/browser_perf-telemetry-04.js
+++ b/devtools/client/performance/test/browser_perf-telemetry-04.js
@@ -6,45 +6,49 @@
  * Tests that the performance telemetry module records events at appropriate times.
  */
 
 const { SIMPLE_URL } = require("devtools/client/performance/test/helpers/urls");
 const { initPerformanceInTab, initConsoleInNewTab, teardownToolboxAndRemoveTab } = require("devtools/client/performance/test/helpers/panel-utils");
 const { waitForRecordingStartedEvents, waitForRecordingStoppedEvents } = require("devtools/client/performance/test/helpers/actions");
 
 add_task(async function() {
+  startTelemetry();
+
   let { target, console } = await initConsoleInNewTab({
     url: SIMPLE_URL,
     win: window
   });
 
   let { panel } = await initPerformanceInTab({ tab: target.tab });
-  let { PerformanceController } = panel.panelWin;
-
-  let telemetry = PerformanceController._telemetry;
-  let logs = telemetry.getLogs();
-  let DURATION = "DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS";
-  let CONSOLE_COUNT = "DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT";
-  let FEATURES = "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED";
 
   let started = waitForRecordingStartedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   await console.profile("rust");
   await started;
 
   let stopped = waitForRecordingStoppedEvents(panel, {
     // only emitted for manual recordings
     skipWaitingForBackendReady: true
   });
   await console.profileEnd("rust");
   await stopped;
 
-  is(logs[DURATION].length, 1, `There is one entry for ${DURATION}.`);
-  ok(logs[DURATION].every(d => typeof d === "number"),
-     `Every ${DURATION} entry is a number.`);
-  is(logs[CONSOLE_COUNT].length, 1, `There is one entry for ${CONSOLE_COUNT}.`);
-  is(logs[FEATURES].length, 4,
-     `There is one recording worth of entries for ${FEATURES}.`);
-
+  checkResults();
   await teardownToolboxAndRemoveTab(panel);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PERFTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_PERFTOOLS_CONSOLE_RECORDING_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_PERFTOOLS_RECORDING_DURATION_MS", "", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withMarkers", [0, 1, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withMemory", [1, 0, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withAllocations", [1, 0, 0], "array");
+  checkTelemetry(
+    "DEVTOOLS_PERFTOOLS_RECORDING_FEATURES_USED", "withTicks", [0, 1, 0], "array");
+}
--- a/devtools/client/performance/test/head.js
+++ b/devtools/client/performance/test/head.js
@@ -1,14 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* import-globals-from ../../shared/test/telemetry-test-helpers.js */
+
 "use strict";
 
 const { require, loader } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/telemetry-test-helpers.js", this);
+
 /* exported loader, either, click, dblclick, mousedown, rightMousedown, key */
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 // Performance tests are much heavier because of their reliance on the
 // profiler module, memory measurements, frequent canvas operations etc. Many of
 // of them take longer than 30 seconds to finish on try server VMs, even though
 // they superficially do very little.
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -9,16 +9,17 @@ support-files =
   doc_page_state.html
   geolocation.html
   head.js
   touch.html
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/shared/test/shared-head.js
   !/devtools/client/shared/test/shared-redux-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_cmd_click.js]
 [browser_device_change.js]
 [browser_device_custom_remove.js]
 [browser_device_custom.js]
 [browser_device_modal_error.js]
--- a/devtools/client/responsive.html/test/browser/browser_telemetry_activate_rdm.js
+++ b/devtools/client/responsive.html/test/browser/browser_telemetry_activate_rdm.js
@@ -1,14 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
-const URL =
-  "data:text/html;charset=utf8,browser_telemetry_activate_rdm.js";
+const URL = "data:text/html;charset=utf8,browser_telemetry_activate_rdm.js";
 const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
 const DATA = [
   {
     timestamp: null,
     category: "devtools.main",
     method: "activate",
     object: "responsive_design",
     value: null,
--- a/devtools/client/shadereditor/test/browser.ini
+++ b/devtools/client/shadereditor/test/browser.ini
@@ -5,16 +5,17 @@ support-files =
   doc_blended-geometry.html
   doc_multiple-contexts.html
   doc_overlapping-geometry.html
   doc_shader-order.html
   doc_simple-canvas.html
   head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_se_aaa_run_first_leaktest.js]
 [browser_se_bfcache.js]
 skip-if = true # Bug 942473, caused by Bug 940541
 [browser_se_editors-contents.js]
 [browser_se_editors-error-gutter.js]
 [browser_se_editors-error-tooltip.js]
 [browser_se_editors-lazy-init.js]
--- a/devtools/client/shared/components/test/browser/browser.ini
+++ b/devtools/client/shared/components/test/browser/browser.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_notification_box_basic.js]
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -145,25 +145,16 @@ class Telemetry {
         histogram: "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT",
       },
       pickereyedropper: {
         histogram: "DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT",
       },
       toolbareyedropper: {
         scalar: "devtools.toolbar.eyedropper.opened",
       },
-      copyuniquecssselector: {
-        scalar: "devtools.copy.unique.css.selector.opened",
-      },
-      copyfullcssselector: {
-        scalar: "devtools.copy.full.css.selector.opened",
-      },
-      copyxpath: {
-        scalar: "devtools.copy.xpath.opened",
-      },
       developertoolbar: {
         histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_COUNT",
         timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS"
       },
       aboutdebugging: {
         histogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_COUNT",
         timerHistogram: "DEVTOOLS_ABOUTDEBUGGING_TIME_ACTIVE_SECONDS"
       },
--- a/devtools/client/shared/test/browser.ini
+++ b/devtools/client/shared/test/browser.ini
@@ -30,16 +30,17 @@ support-files =
   frame-script-utils.js
   head.js
   helper_color_data.js
   helper_html_tooltip.js
   helper_inplace_editor.js
   leakhunt.js
   shared-head.js
   shared-redux-head.js
+  telemetry-test-helpers.js
   test-actor-registry.js
   test-actor.js
   !/devtools/client/responsive.html/test/browser/devices.json
 
 [browser_css_angle.js]
 [browser_css_color.js]
 [browser_cubic-bezier-01.js]
 [browser_cubic-bezier-02.js]
@@ -169,20 +170,20 @@ skip-if = e10s # Test intermittently fai
 [browser_require_raw.js]
 [browser_spectrum.js]
 [browser_theme.js]
 [browser_tableWidget_basic.js]
 [browser_tableWidget_keyboard_interaction.js]
 [browser_tableWidget_mouse_interaction.js]
 [browser_telemetry_button_eyedropper.js]
 [browser_telemetry_button_paintflashing.js]
-skip-if = e10s # Bug 937167 - e10s paintflashing
 [browser_telemetry_button_responsive.js]
 skip-if = !e10s || os == "win" # RDM only works for remote tabs, Win: bug 1404197
 [browser_telemetry_button_scratchpad.js]
+[browser_telemetry_misc.js]
 [browser_telemetry_sidebar.js]
 [browser_telemetry_toolbox.js]
 [browser_telemetry_toolboxtabs_canvasdebugger.js]
 [browser_telemetry_toolboxtabs_inspector.js]
 [browser_telemetry_toolboxtabs_jsdebugger.js]
 [browser_telemetry_toolboxtabs_jsprofiler.js]
 [browser_telemetry_toolboxtabs_netmonitor.js]
 [browser_telemetry_toolboxtabs_options.js]
--- a/devtools/client/shared/test/browser_telemetry_button_eyedropper.js
+++ b/devtools/client/shared/test/browser_telemetry_button_eyedropper.js
@@ -1,54 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
 const TEST_URI = "data:text/html;charset=utf-8," +
   "<p>browser_telemetry_button_eyedropper.js</p><div>test</div>";
-const EYEDROPPER_OPENED = "devtools.toolbar.eyedropper.opened";
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
-  info("inspector opened");
 
   info("testing the eyedropper button");
-  await testButton(toolbox, Telemetry);
+  await testButton(toolbox);
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
 async function testButton(toolbox, Telemetry) {
   info("Calling the eyedropper button's callback");
   // We call the button callback directly because we don't need to test the UI here, we're
   // only concerned about testing the telemetry probe.
   await toolbox.getPanel("inspector").showEyeDropper();
 
-  checkTelemetryResults(Telemetry);
+  checkResults();
 }
 
-function checkTelemetryResults(Telemetry) {
-  let data = Telemetry.prototype.telemetryInfo;
-  let results = new Map();
-
-  for (let key in data) {
-    if (key.toLowerCase() === key) {
-      let pings = data[key].length;
-
-      results.set(key, pings);
-    }
-  }
-
-  is(results.size, 1, "The correct number of scalars were logged");
-
-  let pings = checkPings(EYEDROPPER_OPENED, results);
-  is(pings, 1, `${EYEDROPPER_OPENED} has just 1 ping`);
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("devtools.")
+  // here.
+  checkTelemetry("devtools.toolbar.eyedropper.opened", "", 1, "scalar");
 }
-
-function checkPings(scalarId, results) {
-  return results.get(scalarId);
-}
--- a/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
+++ b/devtools/client/shared/test/browser_telemetry_button_paintflashing.js
@@ -8,88 +8,63 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_button_paintflashing.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
+
   await pushPref("devtools.command-button-paintflashing.enabled", true);
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
 
   info("testing the paintflashing button");
-  await testButton(toolbox, Telemetry);
+  await testButton(toolbox);
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
-async function testButton(toolbox, Telemetry) {
+async function testButton(toolbox) {
   info("Testing command-button-paintflashing");
 
   let button = toolbox.doc.querySelector("#command-button-paintflashing");
   ok(button, "Captain, we have the button");
 
   await delayedClicks(toolbox, button, 4);
-  checkResults("_PAINTFLASHING_", Telemetry);
+  checkResults();
 }
 
 async function delayedClicks(toolbox, node, clicks) {
   for (let i = 0; i < clicks; i++) {
     await new Promise(resolve => {
       // See TOOL_DELAY for why we need setTimeout here
       setTimeout(() => resolve(), TOOL_DELAY);
     });
 
     let { CommandState } = require("devtools/shared/gcli/command-state");
     let clicked = new Promise(resolve => {
-      CommandState.on("changed", function changed(type, { command }) {
+      CommandState.on("changed", function changed({command}) {
         if (command === "paintflashing") {
           CommandState.off("changed", changed);
           resolve();
         }
       });
     });
 
     info("Clicking button " + node.id);
     node.click();
 
     await clicked;
   }
 }
 
-function checkResults(histIdFocus, Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Object.entries(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
-        !histId.includes(histIdFocus)) {
-      // Inspector stats are tested in
-      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
-      // because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_PAINTFLASHING_")
+  // here.
+  checkTelemetry("DEVTOOLS_PAINTFLASHING_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS", "", null, "hasentries");
 }
--- a/devtools/client/shared/test/browser_telemetry_button_responsive.js
+++ b/devtools/client/shared/test/browser_telemetry_button_responsive.js
@@ -29,39 +29,38 @@ registerCleanupFunction(() => {
   asyncStorage.removeItem("devtools.devices.url_cache");
   asyncStorage.removeItem("devtools.devices.local");
 });
 
 loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
 
   info("testing the responsivedesign button");
-  await testButton(toolbox, Telemetry);
+  await testButton(toolbox);
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
-async function testButton(toolbox, Telemetry) {
+async function testButton(toolbox) {
   info("Testing command-button-responsive");
 
   let button = toolbox.doc.querySelector("#command-button-responsive");
   ok(button, "Captain, we have the button");
 
   await delayedClicks(button, 4);
 
-  checkResults("_RESPONSIVE_", Telemetry);
+  checkResults();
 }
 
 function waitForToggle() {
   return new Promise(resolve => {
     let handler = () => {
       ResponsiveUIManager.off("on", handler);
       ResponsiveUIManager.off("off", handler);
       resolve();
@@ -77,39 +76,14 @@ var delayedClicks = async function(node,
     let toggled = waitForToggle();
     node.click();
     await toggled;
     // See TOOL_DELAY for why we need setTimeout here
     await DevToolsUtils.waitForTime(TOOL_DELAY);
   }
 };
 
-function checkResults(histIdFocus, Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Object.entries(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
-        !histId.includes(histIdFocus)) {
-      // Inspector stats are tested in
-      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
-      // because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_RESPONSIVE_")
+  // here.
+  checkTelemetry("DEVTOOLS_RESPONSIVE_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_RESPONSIVE_TIME_ACTIVE_SECONDS", "", null, "hasentries");
 }
--- a/devtools/client/shared/test/browser_telemetry_button_scratchpad.js
+++ b/devtools/client/shared/test/browser_telemetry_button_scratchpad.js
@@ -8,33 +8,32 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_button_scratchpad.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await pushPref("devtools.command-button-scratchpad.enabled", true);
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
 
   let onAllWindowsOpened = trackScratchpadWindows();
 
   info("testing the scratchpad button");
-  await testButton(toolbox, Telemetry);
+  await testButton(toolbox);
   await onAllWindowsOpened;
 
-  checkResults("_SCRATCHPAD_", Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
 function trackScratchpadWindows() {
   info("register the window observer to track when scratchpad windows open");
 
   let numScratchpads = 0;
@@ -63,17 +62,17 @@ function trackScratchpadWindows() {
             });
           }
         }, {once: true});
       }
     });
   });
 }
 
-async function testButton(toolbox, Telemetry) {
+async function testButton(toolbox) {
   info("Testing command-button-scratchpad");
   let button = toolbox.doc.querySelector("#command-button-scratchpad");
   ok(button, "Captain, we have the button");
 
   await delayedClicks(button, 4);
 }
 
 function delayedClicks(node, clicks) {
@@ -90,39 +89,13 @@ function delayedClicks(node, clicks) {
         resolve(node);
       } else {
         setTimeout(delayedClick, TOOL_DELAY);
       }
     }, TOOL_DELAY);
   });
 }
 
-function checkResults(histIdFocus, Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Object.entries(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
-        !histId.includes(histIdFocus)) {
-      // Inspector stats are tested in
-      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
-      // because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_SCRATCHPAD_")
+  // here.
+  checkTelemetry("DEVTOOLS_SCRATCHPAD_WINDOW_OPENED_COUNT", "", [4, 0, 0], "array");
 }
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/browser_telemetry_misc.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_misc.js</p>";
+const TOOL_DELAY = 0;
+
+add_task(async function() {
+  await addTab(TEST_URI);
+
+  startTelemetry();
+
+  await openAndCloseToolbox(1, TOOL_DELAY, "inspector");
+  checkResults();
+
+  gBrowser.removeCurrentTab();
+});
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_TOOLBOX_OPENED_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_RULEVIEW_OPENED_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_INSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_RULEVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry(
+    "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_TOOLBOX_HOST", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_sidebar.js
+++ b/devtools/client/shared/test/browser_telemetry_sidebar.js
@@ -7,39 +7,38 @@
 const TEST_URI = "data:text/html;charset=utf-8,<p>browser_telemetry_sidebar.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   let toolbox = await gDevTools.showToolbox(target, "inspector");
   info("inspector opened");
 
   await testSidebar(toolbox);
-  checkResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   await gDevTools.closeToolbox(target);
   gBrowser.removeCurrentTab();
 });
 
 function testSidebar(toolbox) {
   info("Testing sidebar");
 
   let inspector = toolbox.getCurrentPanel();
   let sidebarTools = ["ruleview", "computedview", "layoutview", "fontinspector",
                       "animationinspector"];
 
   // Concatenate the array with itself so that we can open each tool twice.
-  sidebarTools.push.apply(sidebarTools, sidebarTools);
+  sidebarTools = [...sidebarTools, ...sidebarTools];
 
   return new Promise(resolve => {
     // See TOOL_DELAY for why we need setTimeout here
     setTimeout(function selectSidebarTab() {
       let tool = sidebarTools.pop();
       if (tool) {
         inspector.sidebar.select(tool);
         setTimeout(function() {
@@ -47,39 +46,21 @@ function testSidebar(toolbox) {
         }, TOOL_DELAY);
       } else {
         resolve();
       }
     }, TOOL_DELAY);
   });
 }
 
-function checkResults(Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Object.entries(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_")) {
-      // Inspector stats are tested in browser_telemetry_toolboxtabs.js so we
-      // skip them here because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId === "DEVTOOLS_TOOLBOX_OPENED_COUNT") {
-      is(value.length, 1, histId + " has only one entry");
-    } else if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_")
+  // here.
+  checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", [1, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_RULEVIEW_OPENED_COUNT", "", [3, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_COMPUTEDVIEW_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_LAYOUTVIEW_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_FONTINSPECTOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_RULEVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
 }
--- a/devtools/client/shared/test/browser_telemetry_toolbox.js
+++ b/devtools/client/shared/test/browser_telemetry_toolbox.js
@@ -7,16 +7,23 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolbox.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(3, TOOL_DELAY, "inspector");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_TOOLBOX_")
+  // here.
+  checkTelemetry("DEVTOOLS_TOOLBOX_OPENED_COUNT", "", [3, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+  checkTelemetry("DEVTOOLS_TOOLBOX_HOST", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_canvasdebugger.js
@@ -11,19 +11,25 @@ const TEST_URI = "data:text/html;charset
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   info("Activate the canvasdebugger");
   let originalPref = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled");
   Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", true);
 
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "canvasdebugger");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 
   info("De-activate the canvasdebugger");
   Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", originalPref);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_CANVASDEBUGGER")
+  // here.
+  checkTelemetry("DEVTOOLS_CANVASDEBUGGER_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_CANVASDEBUGGER_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_inspector.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_inspector.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_inspector.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "inspector");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_INSPECTOR_")
+  // here.
+  checkTelemetry("DEVTOOLS_INSPECTOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_INSPECTOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_jsdebugger.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_jsdebugger.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "jsdebugger");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_JSDEBUGGER_")
+  // here.
+  checkTelemetry("DEVTOOLS_JSDEBUGGER_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_JSDEBUGGER_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_jsprofiler.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_jsprofiler.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "performance");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_JSPROFILER")
+  // here.
+  checkTelemetry("DEVTOOLS_JSPROFILER_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_netmonitor.js
@@ -7,17 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_netmonitor.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "netmonitor");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
 
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_NETMONITOR_")
+  // here.
+  checkTelemetry("DEVTOOLS_NETMONITOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_options.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_options.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "options");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_OPTIONS_")
+  // here.
+  checkTelemetry("DEVTOOLS_OPTIONS_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_OPTIONS_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_shadereditor.js
@@ -12,19 +12,25 @@ const TOOL_DELAY = 200;
 const TOOL_PREF = "devtools.shadereditor.enabled";
 
 add_task(async function() {
   info("Active the sharer editor");
   let originalPref = Services.prefs.getBoolPref(TOOL_PREF);
   Services.prefs.setBoolPref(TOOL_PREF, true);
 
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "shadereditor");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 
   info("De-activate the sharer editor");
   Services.prefs.setBoolPref(TOOL_PREF, originalPref);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_SHADEREDITOR_")
+  // here.
+  checkTelemetry("DEVTOOLS_SHADEREDITOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_storage.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_storage.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 1000;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "storage");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_STORAGE_")
+  // here.
+  checkTelemetry("DEVTOOLS_STORAGE_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_STORAGE_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_styleeditor.js
@@ -7,17 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_styleeditor.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "styleeditor");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
 
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_STYLEEDITOR_")
+  // here.
+  checkTelemetry("DEVTOOLS_STYLEEDITOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_STYLEEDITOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_webaudioeditor.js
@@ -11,19 +11,25 @@ const TEST_URI = "data:text/html;charset
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   info("Activating the webaudioeditor");
   let originalPref = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
   Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", true);
 
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "webaudioeditor");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 
   info("De-activating the webaudioeditor");
   Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", originalPref);
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_WEBAUDIOEDITOR")
+  // here.
+  checkTelemetry("DEVTOOLS_WEBAUDIOEDITOR_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_WEBAUDIOEDITOR_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js
+++ b/devtools/client/shared/test/browser_telemetry_toolboxtabs_webconsole.js
@@ -7,16 +7,22 @@ const TEST_URI = "data:text/html;charset
   "<p>browser_telemetry_toolboxtabs_styleeditor_webconsole.js</p>";
 
 // Because we need to gather stats for the period of time that a tool has been
 // opened we make use of setTimeout() to create tool active times.
 const TOOL_DELAY = 200;
 
 add_task(async function() {
   await addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
+  startTelemetry();
 
   await openAndCloseToolbox(2, TOOL_DELAY, "webconsole");
-  checkTelemetryResults(Telemetry);
+  checkResults();
 
-  stopRecordingTelemetryLogs(Telemetry);
   gBrowser.removeCurrentTab();
 });
+
+function checkResults() {
+  // For help generating these tests use generateTelemetryTests("DEVTOOLS_WEBCONSOLE_")
+  // here.
+  checkTelemetry("DEVTOOLS_WEBCONSOLE_OPENED_COUNT", "", [2, 0, 0], "array");
+  checkTelemetry("DEVTOOLS_WEBCONSOLE_TIME_ACTIVE_SECONDS", "", null, "hasentries");
+}
--- a/devtools/client/shared/test/head.js
+++ b/devtools/client/shared/test/head.js
@@ -1,13 +1,14 @@
 /* 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/. */
 /* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
 /* import-globals-from shared-head.js */
+/* import-globals-from telemetry-test-helpers.js */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/shared/test/shared-head.js", this);
 
 const {DOMHelpers} = ChromeUtils.import("resource://devtools/client/shared/DOMHelpers.jsm", {});
 const {Hosts} = require("devtools/client/framework/toolbox-hosts");
@@ -114,46 +115,16 @@ async function(type = "bottom", src = CH
     iframe.setAttribute("src", src);
     domHelper.onceDOMReady(resolve);
   });
 
   return [host, iframe.contentWindow, iframe.contentDocument];
 };
 
 /**
- * Check the correctness of the data recorded in Telemetry after
- * loadTelemetryAndRecordLogs was called.
- */
-function checkTelemetryResults(Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let histId in result) {
-    let value = result[histId];
-
-    if (histId.endsWith("OPENED_COUNT")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element === true;
-      });
-
-      ok(okay, "All " + histId + " entries are === true");
-    } else if (histId.endsWith("TIME_ACTIVE_SECONDS")) {
-      ok(value.length > 1, histId + " has more than one entry");
-
-      let okay = value.every(function(element) {
-        return element > 0;
-      });
-
-      ok(okay, "All " + histId + " entries have time > 0");
-    }
-  }
-}
-
-/**
  * Open and close the toolbox in the current browser tab, several times, waiting
  * some amount of time in between.
  * @param {Number} nbOfTimes
  * @param {Number} usageTime in milliseconds
  * @param {String} toolId
  */
 async function openAndCloseToolbox(nbOfTimes, usageTime, toolId) {
   for (let i = 0; i < nbOfTimes; i++) {
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -32,16 +32,19 @@ const KeyShortcuts = require("devtools/c
 
 const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 const CHROME_URL_ROOT = TEST_DIR + "/";
 const URL_ROOT = CHROME_URL_ROOT.replace("chrome://mochitests/content/",
                                          "http://example.com/");
 const URL_ROOT_SSL = CHROME_URL_ROOT.replace("chrome://mochitests/content/",
                                              "https://example.com/");
 
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/shared/test/telemetry-test-helpers.js", this);
+
 // Force devtools to be initialized so menu items and keyboard shortcuts get installed
 require("devtools/client/framework/devtools-browser");
 
 // All test are asynchronous
 waitForExplicitFinish();
 
 var EXPECTED_DTU_ASSERT_FAILURE_COUNT = 0;
 
@@ -577,67 +580,16 @@ function lookupPath(obj, path) {
 }
 
 var closeToolbox = async function() {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   await gDevTools.closeToolbox(target);
 };
 
 /**
- * Load the Telemetry utils, then stub Telemetry.prototype.log and
- * Telemetry.prototype.logKeyed in order to record everything that's logged in
- * it.
- * Store all recordings in Telemetry.telemetryInfo.
- * @return {Telemetry}
- */
-function loadTelemetryAndRecordLogs() {
-  info("Mock the Telemetry log function to record logged information");
-
-  let Telemetry = require("devtools/client/shared/telemetry");
-  Telemetry.prototype.telemetryInfo = {};
-  Telemetry.prototype._oldlog = Telemetry.prototype.log;
-  Telemetry.prototype.log = function(histogramId, value) {
-    if (!this.telemetryInfo) {
-      // Telemetry instance still in use after stopRecordingTelemetryLogs
-      return;
-    }
-    if (histogramId) {
-      if (!this.telemetryInfo[histogramId]) {
-        this.telemetryInfo[histogramId] = [];
-      }
-      this.telemetryInfo[histogramId].push(value);
-    }
-  };
-  Telemetry.prototype._oldlogScalar = Telemetry.prototype.logScalar;
-  Telemetry.prototype.logScalar = Telemetry.prototype.log;
-  Telemetry.prototype._oldlogKeyed = Telemetry.prototype.logKeyed;
-  Telemetry.prototype.logKeyed = function(histogramId, key, value) {
-    this.log(`${histogramId}|${key}`, value);
-  };
-
-  return Telemetry;
-}
-
-/**
- * Stop recording the Telemetry logs and put back the utils as it was before.
- * @param {Telemetry} Required Telemetry
- *        Telemetry object that needs to be stopped.
- */
-function stopRecordingTelemetryLogs(Telemetry) {
-  info("Stopping Telemetry");
-  Telemetry.prototype.log = Telemetry.prototype._oldlog;
-  Telemetry.prototype.logScalar = Telemetry.prototype._oldlogScalar;
-  Telemetry.prototype.logKeyed = Telemetry.prototype._oldlogKeyed;
-  delete Telemetry.prototype._oldlog;
-  delete Telemetry.prototype._oldlogScalar;
-  delete Telemetry.prototype._oldlogKeyed;
-  delete Telemetry.prototype.telemetryInfo;
-}
-
-/**
  * Clean the logical clipboard content. This method only clears the OS clipboard on
  * Windows (see Bug 666254).
  */
 function emptyClipboard() {
   let clipboard = Services.clipboard;
   clipboard.emptyClipboard(clipboard.kGlobalClipboard);
 }
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/test/telemetry-test-helpers.js
@@ -0,0 +1,294 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* global is ok registerCleanupFunction Services */
+
+"use strict";
+
+// We try to avoid polluting the global scope as far as possible by defining
+// constants in the methods that use them because this script is not sandboxed
+// meaning that it is loaded via Services.scriptloader.loadSubScript()
+
+class TelemetryHelpers {
+  constructor() {
+    this.oldCanRecord = Services.telemetry.canRecordExtended;
+    this.generateTelemetryTests = this.generateTelemetryTests.bind(this);
+    registerCleanupFunction(this.stopTelemetry.bind(this));
+  }
+
+  /**
+   * Allow collection of extended telemetry data.
+   */
+  startTelemetry() {
+    Services.telemetry.canRecordExtended = true;
+  }
+
+  /**
+   * Clear all telemetry types.
+   */
+  stopTelemetry() {
+    this.clearToolsOpenedPref();
+    Services.telemetry.canRecordExtended = this.oldCanRecord;
+
+    // Clear histograms, scalars and Telemetry Events.
+    this.clearHistograms(Services.telemetry.snapshotHistograms);
+    this.clearHistograms(Services.telemetry.snapshotKeyedHistograms);
+    Services.telemetry.clearScalars();
+    Services.telemetry.clearEvents();
+  }
+
+  /**
+   * Clears both OPTIN and OPTOUT versions of Telemetry Histograms.
+   *
+   * @param {Function} snapshotFunc
+   *        The function used to take the snapshot. This can be one of the
+   *        following:
+   *          - Services.telemetry.snapshotHistograms
+   *          - Services.telemetry.snapshotKeyedHistograms
+   *
+   *        `snapshotFunc(OPTIN, true, true)` should clear the histograms but this
+   *        only deletes seemingly random histograms, hence this method.
+   */
+  clearHistograms(snapshotFunc) {
+    // Although most of our Telemetry probes are OPTOUT, OPTIN includes all OPTIN
+    // *and* OPTOUT data.
+    const OPTIN = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
+    const OPTOUT = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT;
+    const tel = Services.telemetry;
+
+    for (let optInOut of [OPTIN, OPTOUT]) {
+      const snapshot = snapshotFunc(optInOut, true, false).parent;
+      const histKeys = Object.keys(snapshot);
+
+      for (let getHistogram of [tel.getHistogramById, tel.getKeyedHistogramById]) {
+        for (let key of histKeys) {
+          try {
+            getHistogram(key).clear();
+          } catch (e) {
+            // Some histograms may have already been cleaned up by the system so we
+            // swallow the "histogram does not exist" error silently here.
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Clears the pref that is used to log telemetry data once per browser version.
+   */
+  clearToolsOpenedPref() {
+    const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version";
+
+    Services.prefs.clearUserPref(TOOLS_OPENED_PREF);
+  }
+
+  /**
+   * Check the value of a given telemetry histogram.
+   *
+   * @param  {String} histId
+   *         Histogram id
+   * @param  {String} key
+   *         Keyed histogram key
+   * @param  {Array|Number} expected
+   *         Expected value
+   * @param  {String} checkType
+   *         "array" (default) - Check that an array matches the histogram data.
+   *         "hasentries"  - For non-enumerated linear and exponential
+   *                             histograms. This checks for at least one entry.
+   *         "scalar" - Telemetry type is a scalar.
+   *         "keyedscalar" - Telemetry type is a keyed scalar.
+   */
+  checkTelemetry(histId, key, expected, checkType) {
+    // Although most of our Telemetry probes are OPTOUT, OPTIN includes all OPTIN
+    // *and* OPTOUT data.
+    const OPTIN = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
+
+    let actual;
+    let msg;
+
+    if (checkType === "array" || checkType === "hasentries") {
+      if (key) {
+        const keyedHistogram =
+          Services.telemetry.getKeyedHistogramById(histId).snapshot();
+        const result = keyedHistogram[key];
+
+        if (result) {
+          actual = result.counts;
+        } else {
+          ok(false, `${histId}[${key}] exists`);
+          return;
+        }
+      } else {
+        actual = Services.telemetry.getHistogramById(histId).snapshot().counts;
+      }
+    }
+
+    switch (checkType) {
+      case "array":
+        msg = key ? `${histId}["${key}"] correct.` : `${histId} correct.`;
+        is(JSON.stringify(actual), JSON.stringify(expected), msg);
+        break;
+      case "hasentries":
+        let hasEntry = actual.some(num => num > 0);
+        if (key) {
+          ok(hasEntry, `${histId}["${key}"] has at least one entry.`);
+        } else {
+          ok(hasEntry, `${histId} has at least one entry.`);
+        }
+        break;
+      case "scalar":
+        const scalars =
+          Services.telemetry.snapshotScalars(OPTIN, false).parent;
+
+        is(scalars[histId], expected, `${histId} correct`);
+        break;
+      case "keyedscalar":
+        const keyedScalars =
+          Services.telemetry.snapshotKeyedScalars(OPTIN, false).parent;
+        const value = keyedScalars[histId][key];
+
+        msg = key ? `${histId}["${key}"] correct.` : `${histId} correct.`;
+        is(value, expected, msg);
+        break;
+    }
+  }
+
+  /**
+   * Generate telemetry tests. You should call generateTelemetryTests("DEVTOOLS_")
+   * from your result checking code in telemetry tests. It logs checkTelemetry
+   * calls for all changed telemetry values.
+   *
+   * @param  {String} prefix
+   *         Optionally limits results to histogram ids starting with prefix.
+   */
+  generateTelemetryTests(prefix = "") {
+    // Although most of our Telemetry probes are OPTOUT, OPTIN includes all OPTIN
+    // *and* OPTOUT data.
+    const OPTIN = Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN;
+
+    // Get all histograms and scalars
+    const histograms =
+      Services.telemetry.snapshotHistograms(OPTIN, true, false).parent;
+    const keyedHistograms =
+      Services.telemetry.snapshotKeyedHistograms(OPTIN, true, false).parent;
+    const scalars =
+      Services.telemetry.snapshotScalars(OPTIN, false).parent;
+    const keyedScalars =
+      Services.telemetry.snapshotKeyedScalars(OPTIN, false).parent;
+    const allHistograms = Object.assign({},
+                                        histograms,
+                                        keyedHistograms,
+                                        scalars,
+                                        keyedScalars);
+    // Get all keys
+    const histIds = Object.keys(allHistograms)
+                          .filter(histId => histId.startsWith(prefix));
+
+    dump("=".repeat(80) + "\n");
+    for (let histId of histIds) {
+      let snapshot = allHistograms[histId];
+
+      if (histId === histId.toLowerCase()) {
+        if (typeof snapshot === "object") {
+          // Keyed Scalar
+          const keys = Object.keys(snapshot);
+
+          for (let key of keys) {
+            const value = snapshot[key];
+
+            dump(`checkTelemetry("${histId}", "${key}", ${value}, "keyedscalar");\n`);
+          }
+        } else {
+          // Scalar
+          dump(`checkTelemetry("${histId}", "", ${snapshot}, "scalar");\n`);
+        }
+      } else if (typeof snapshot.histogram_type !== "undefined" &&
+                typeof snapshot.counts !== "undefined") {
+        // Histogram
+        const actual = snapshot.counts;
+
+        this.displayDataFromHistogramSnapshot(snapshot, "", histId, actual);
+      } else {
+        // Keyed Histogram
+        const keys = Object.keys(snapshot);
+
+        for (let key of keys) {
+          const value = snapshot[key];
+          const actual = value.counts;
+
+          this.displayDataFromHistogramSnapshot(value, key, histId, actual);
+        }
+      }
+    }
+    dump("=".repeat(80) + "\n");
+  }
+
+  /**
+   * Generates the inner contents of a test's checkTelemetry() method.
+   *
+   * @param {HistogramSnapshot} snapshot
+   *        A snapshot of a telemetry chart obtained via snapshotHistograms or
+   *        similar.
+   * @param {String} key
+   *        Only used for keyed histograms. This is the key we are interested in
+   *        checking.
+   * @param {String} histId
+   *        The histogram ID.
+   * @param {Array|String|Boolean} actual
+   *        The value of the histogram data.
+   */
+  displayDataFromHistogramSnapshot(snapshot, key, histId, actual) {
+    key = key ? `"${key}"` : `""`;
+
+    switch (snapshot.histogram_type) {
+      case Services.telemetry.HISTOGRAM_EXPONENTIAL:
+      case Services.telemetry.HISTOGRAM_LINEAR:
+        let total = 0;
+        for (let val of actual) {
+          total += val;
+        }
+
+        if (histId.endsWith("_ENUMERATED")) {
+          if (total > 0) {
+            actual = actual.toSource();
+            dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
+          }
+          return;
+        }
+
+        dump(`checkTelemetry("${histId}", ${key}, null, "hasentries");\n`);
+        break;
+      case Services.telemetry.HISTOGRAM_BOOLEAN:
+        actual = actual.toSource();
+
+        if (actual !== "[0, 0, 0]") {
+          dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
+        }
+        break;
+      case Services.telemetry.HISTOGRAM_FLAG:
+        actual = actual.toSource();
+
+        if (actual !== "[1, 0, 0]") {
+          dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
+        }
+        break;
+      case Services.telemetry.HISTOGRAM_COUNT:
+        actual = actual.toSource();
+
+        dump(`checkTelemetry("${histId}", ${key}, ${actual}, "array");\n`);
+        break;
+    }
+  }
+}
+
+// "exports"... because this is a helper and not imported via require we need to
+// expose the three main methods that should be used by tests. The reason this
+// is not imported via require is because it needs access to test methods
+// (is, ok etc).
+
+/* eslint-disable no-unused-vars */
+const telemetryHelpers = new TelemetryHelpers();
+const generateTelemetryTests = telemetryHelpers.generateTelemetryTests;
+const checkTelemetry = telemetryHelpers.checkTelemetry;
+const startTelemetry = telemetryHelpers.startTelemetry;
+/* eslint-enable no-unused-vars */
--- a/devtools/client/sourceeditor/test/browser.ini
+++ b/devtools/client/sourceeditor/test/browser.ini
@@ -20,16 +20,17 @@ support-files =
   css_statemachine_tests.json
   css_autocompletion_tests.json
   head.js
   head.xul
   helper_codemirror_runner.js
   cm_mode_ruby.js
   cm_script_injection_test.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_editor_autocomplete_basic.js]
 [browser_editor_autocomplete_events.js]
 [browser_editor_autocomplete_js.js]
 [browser_editor_basic.js]
 [browser_editor_cursor.js]
 [browser_editor_find_again.js]
 [browser_editor_goto_line.js]
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -20,16 +20,17 @@ support-files =
   storage-secured-iframe.html
   storage-secured-iframe-usercontextid.html
   storage-sessionstorage.html
   storage-unsecured-iframe.html
   storage-unsecured-iframe-usercontextid.html
   storage-updates.html
   head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_storage_basic.js]
 [browser_storage_basic_usercontextid_1.js]
 [browser_storage_basic_usercontextid_2.js]
 tags = usercontextid
 [browser_storage_basic_with_fragment.js]
 [browser_storage_cache_delete.js]
 [browser_storage_cache_error.js]
--- a/devtools/client/styleeditor/test/browser.ini
+++ b/devtools/client/styleeditor/test/browser.ini
@@ -62,16 +62,17 @@ support-files =
   sync.html
   utf-16.css
   !/devtools/client/commandline/test/helpers.js
   !/devtools/client/inspector/shared/test/head.js
   !/devtools/client/inspector/test/head.js
   !/devtools/client/inspector/test/shared-head.js
   !/devtools/client/responsive.html/test/browser/devices.json
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor-registry.js
   !/devtools/client/shared/test/test-actor.js
 
 [browser_styleeditor_add_stylesheet.js]
 [browser_styleeditor_autocomplete.js]
 [browser_styleeditor_autocomplete-disabled.js]
 [browser_styleeditor_bom.js]
 [browser_styleeditor_bug_740541_iframes.js]
--- a/devtools/client/webaudioeditor/test/browser.ini
+++ b/devtools/client/webaudioeditor/test/browser.ini
@@ -15,16 +15,17 @@ support-files =
   doc_bug_1112378.html
   doc_bug_1125817.html
   doc_bug_1130901.html
   doc_bug_1141261.html
   440hz_sine.ogg
   head.js
   !/devtools/client/shared/test/frame-script-utils.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
 
 [browser_audionode-actor-get-param-flags.js]
 [browser_audionode-actor-get-params-01.js]
 [browser_audionode-actor-get-params-02.js]
 [browser_audionode-actor-get-set-param.js]
 [browser_audionode-actor-type.js]
 [browser_audionode-actor-source.js]
 [browser_audionode-actor-bypass.js]
--- a/devtools/client/webconsole/test/fixtures/stub-generators/browser.ini
+++ b/devtools/client/webconsole/test/fixtures/stub-generators/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   ../stubs/*
   test-console-api.html
   test-css-message.html
   test-network-event.html
 
 [browser_webconsole_check_stubs_console_api.js]
 [browser_webconsole_check_stubs_css_message.js]
 [browser_webconsole_check_stubs_evaluation_result.js]
--- a/devtools/client/webconsole/test/mochitest/browser.ini
+++ b/devtools/client/webconsole/test/mochitest/browser.ini
@@ -151,16 +151,17 @@ support-files =
   test-trackingprotection-securityerrors.html
   test-webconsole-error-observer.html
   test-websocket.html
   test-websocket.js
   testscript.js
   !/devtools/client/netmonitor/test/sjs_cors-test-server.sjs
   !/image/test/mochitest/blue.png
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_console.js]
 [browser_console_clear_cache.js]
 [browser_console_clear_method.js]
 skip-if = true # Bug 1437843
 [browser_console_consolejsm_output.js]
--- a/devtools/server/tests/browser/browser.ini
+++ b/devtools/server/tests/browser/browser.ini
@@ -21,16 +21,17 @@ support-files =
   storage-unsecured-iframe.html
   storage-updates.html
   storage-secured-iframe.html
   stylesheets-nested-iframes.html
   timeline-iframe-child.html
   timeline-iframe-parent.html
   storage-helpers.js
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   !/devtools/server/tests/mochitest/hello-actor.js
 
 [browser_accessibility_node.js]
 [browser_accessibility_node_events.js]
 [browser_accessibility_simple.js]
 [browser_accessibility_walker.js]
 [browser_actor_error.js]
 [browser_animation_emitMutations.js]
--- a/devtools/shared/tests/browser/browser.ini
+++ b/devtools/shared/tests/browser/browser.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   !/devtools/client/shared/test/shared-head.js
+  !/devtools/client/shared/test/telemetry-test-helpers.js
   ../../../server/tests/browser/head.js
 
 [browser_async_storage.js]
 [browser_l10n_localizeMarkup.js]