summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/telemetry.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/telemetry.js')
-rw-r--r--devtools/client/shared/telemetry.js819
1 files changed, 819 insertions, 0 deletions
diff --git a/devtools/client/shared/telemetry.js b/devtools/client/shared/telemetry.js
new file mode 100644
index 0000000000..e88221908a
--- /dev/null
+++ b/devtools/client/shared/telemetry.js
@@ -0,0 +1,819 @@
+/* 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/. */
+
+/**
+ * This is the telemetry module to report metrics for tools.
+ *
+ * Comprehensive documentation is in docs/frontend/telemetry.md
+ */
+
+"use strict";
+
+const TelemetryStopwatch = require("TelemetryStopwatch");
+const {
+ getNthPathExcluding,
+} = require("resource://devtools/shared/platform/stack.js");
+const { TelemetryEnvironment } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryEnvironment.sys.mjs"
+);
+const WeakMapMap = require("resource://devtools/client/shared/WeakMapMap.js");
+
+const CATEGORY = "devtools.main";
+
+// Object to be shared among all instances.
+const PENDING_EVENT_PROPERTIES = new WeakMapMap();
+const PENDING_EVENTS = new WeakMapMap();
+
+/**
+ * Instantiate a new Telemetry helper class.
+ *
+ * @param {Object} options [optional]
+ * @param {Boolean} options.useSessionId [optional]
+ * If true, this instance will automatically generate a unique "sessionId"
+ * and use it to aggregate all records against this unique session.
+ * This helps aggregate all data coming from a single toolbox instance for ex.
+ */
+class Telemetry {
+ constructor({ useSessionId = false } = {}) {
+ // Note that native telemetry APIs expect a string
+ this.sessionId = String(
+ useSessionId ? parseInt(this.msSinceProcessStart(), 10) : -1
+ );
+
+ // Bind pretty much all functions so that callers do not need to.
+ this.msSystemNow = this.msSystemNow.bind(this);
+ this.getHistogramById = this.getHistogramById.bind(this);
+ this.getKeyedHistogramById = this.getKeyedHistogramById.bind(this);
+ this.scalarSet = this.scalarSet.bind(this);
+ this.scalarAdd = this.scalarAdd.bind(this);
+ this.keyedScalarAdd = this.keyedScalarAdd.bind(this);
+ this.keyedScalarSet = this.keyedScalarSet.bind(this);
+ this.recordEvent = this.recordEvent.bind(this);
+ this.setEventRecordingEnabled = this.setEventRecordingEnabled.bind(this);
+ this.preparePendingEvent = this.preparePendingEvent.bind(this);
+ this.addEventProperty = this.addEventProperty.bind(this);
+ this.addEventProperties = this.addEventProperties.bind(this);
+ this.toolOpened = this.toolOpened.bind(this);
+ this.toolClosed = this.toolClosed.bind(this);
+ }
+
+ get osNameAndVersion() {
+ const osInfo = TelemetryEnvironment.currentEnvironment.system.os;
+
+ if (!osInfo) {
+ return "Unknown OS";
+ }
+
+ let osVersion = `${osInfo.name} ${osInfo.version}`;
+
+ if (osInfo.windowsBuildNumber) {
+ osVersion += `.${osInfo.windowsBuildNumber}`;
+ }
+
+ return osVersion;
+ }
+
+ /**
+ * Time since the system wide epoch. This is not a monotonic timer but
+ * can be used across process boundaries.
+ */
+ msSystemNow() {
+ return Services.telemetry.msSystemNow();
+ }
+
+ /**
+ * The number of milliseconds since process start using monotonic
+ * timestamps (unaffected by system clock changes).
+ */
+ msSinceProcessStart() {
+ return Services.telemetry.msSinceProcessStart();
+ }
+
+ /**
+ * Starts a timer associated with a telemetry histogram. The timer can be
+ * directly associated with a histogram, or with a pair of a histogram and
+ * an object.
+ *
+ * @param {String} histogramId
+ * A string which must be a valid histogram name.
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ * @param {Object} [options.inSeconds=false]
+ * Record elapsed time for this histogram in seconds instead of
+ * milliseconds. Defaults to false.
+ * @returns {Boolean}
+ * True if the timer was successfully started, false otherwise. If a
+ * timer already exists, it can't be started again.
+ */
+ start(histogramId, obj, { inSeconds } = {}) {
+ if (TelemetryStopwatch.running(histogramId, obj)) {
+ return false;
+ }
+
+ return TelemetryStopwatch.start(histogramId, obj, { inSeconds });
+ }
+
+ /**
+ * Starts a timer associated with a keyed telemetry histogram. The timer can
+ * be directly associated with a histogram and its key. Similarly to
+ * TelemetryStopwatch.start the histogram and its key can be associated
+ * with an object. Each key may have multiple associated objects and each
+ * object can be associated with multiple keys.
+ *
+ * @param {String} histogramId
+ * A string which must be a valid histogram name.
+ * @param {String} key
+ * A string which must be a valid histgram key.
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ * @param {Object} [options.inSeconds=false]
+ * Record elapsed time for this histogram in seconds instead of
+ * milliseconds. Defaults to false.
+ *
+ * @returns {Boolean}
+ * True if the timer was successfully started, false otherwise. If a
+ * timer already exists, it can't be started again, and the existing
+ * one will be cleared in order to avoid measurements errors.
+ */
+ startKeyed(histogramId, key, obj, { inSeconds } = {}) {
+ return TelemetryStopwatch.startKeyed(histogramId, key, obj, { inSeconds });
+ }
+
+ /**
+ * Stops the timer associated with the given histogram (and object),
+ * calculates the time delta between start and finish, and adds the value
+ * to the histogram.
+ *
+ * @param {String} histogramId
+ * A string which must be a valid histogram name.
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ * @param {Boolean} canceledOkay
+ * Optional parameter which will suppress any warnings that normally
+ * fire when a stopwatch is finished after being canceled.
+ * Defaults to false.
+ *
+ * @returns {Boolean}
+ * True if the timer was succesfully stopped and the data was added
+ * to the histogram, False otherwise.
+ */
+ finish(histogramId, obj, canceledOkay) {
+ return TelemetryStopwatch.finish(histogramId, obj, canceledOkay);
+ }
+
+ /**
+ * Stops the timer associated with the given keyed histogram (and object),
+ * calculates the time delta between start and finish, and adds the value
+ * to the keyed histogram.
+ *
+ * @param {String} histogramId
+ * A string which must be a valid histogram name.
+ * @param {String} key
+ * A string which must be a valid histogram key.
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ * @param {Boolean} canceledOkay
+ * Optional parameter which will suppress any warnings that normally
+ * fire when a stopwatch is finished after being canceled.
+ * Defaults to false.
+ *
+ * @returns {Boolean}
+ * True if the timer was succesfully stopped and the data was added
+ * to the histogram, False otherwise.
+ */
+ finishKeyed(histogramId, key, obj, canceledOkay) {
+ return TelemetryStopwatch.finishKeyed(histogramId, key, obj, canceledOkay);
+ }
+
+ /**
+ * Log a value to a histogram.
+ *
+ * @param {String} histogramId
+ * Histogram in which the data is to be stored.
+ */
+ getHistogramById(histogramId) {
+ let histogram = null;
+
+ if (histogramId) {
+ try {
+ histogram = Services.telemetry.getHistogramById(histogramId);
+ } catch (e) {
+ dump(
+ `Warning: An attempt was made to write to the ${histogramId} ` +
+ `histogram, which is not defined in Histograms.json\n` +
+ `CALLER: ${getCaller()}`
+ );
+ }
+ }
+
+ return (
+ histogram || {
+ add: () => {},
+ }
+ );
+ }
+
+ /**
+ * Get a keyed histogram.
+ *
+ * @param {String} histogramId
+ * Histogram in which the data is to be stored.
+ */
+ getKeyedHistogramById(histogramId) {
+ let histogram = null;
+
+ if (histogramId) {
+ try {
+ histogram = Services.telemetry.getKeyedHistogramById(histogramId);
+ } catch (e) {
+ dump(
+ `Warning: An attempt was made to write to the ${histogramId} ` +
+ `histogram, which is not defined in Histograms.json\n` +
+ `CALLER: ${getCaller()}`
+ );
+ }
+ }
+ return (
+ histogram || {
+ add: () => {},
+ }
+ );
+ }
+
+ /**
+ * Log a value to a scalar.
+ *
+ * @param {String} scalarId
+ * Scalar in which the data is to be stored.
+ * @param value
+ * Value to store.
+ */
+ scalarSet(scalarId, value) {
+ if (!scalarId) {
+ return;
+ }
+
+ try {
+ if (isNaN(value) && typeof value !== "boolean") {
+ dump(
+ `Warning: An attempt was made to write a non-numeric and ` +
+ `non-boolean value ${value} to the ${scalarId} scalar. Only ` +
+ `numeric and boolean values are allowed.\n` +
+ `CALLER: ${getCaller()}`
+ );
+
+ return;
+ }
+ Services.telemetry.scalarSet(scalarId, value);
+ } catch (e) {
+ dump(
+ `Warning: An attempt was made to write to the ${scalarId} ` +
+ `scalar, which is not defined in Scalars.yaml\n` +
+ `CALLER: ${getCaller()}`
+ );
+ }
+ }
+
+ /**
+ * Log a value to a count scalar.
+ *
+ * @param {String} scalarId
+ * Scalar in which the data is to be stored.
+ * @param value
+ * Value to store.
+ */
+ scalarAdd(scalarId, value) {
+ if (!scalarId) {
+ return;
+ }
+
+ try {
+ if (isNaN(value)) {
+ dump(
+ `Warning: An attempt was made to write a non-numeric value ` +
+ `${value} to the ${scalarId} scalar. Only numeric values are ` +
+ `allowed.\n` +
+ `CALLER: ${getCaller()}`
+ );
+
+ return;
+ }
+ Services.telemetry.scalarAdd(scalarId, value);
+ } catch (e) {
+ dump(
+ `Warning: An attempt was made to write to the ${scalarId} ` +
+ `scalar, which is not defined in Scalars.yaml\n` +
+ `CALLER: ${getCaller()}`
+ );
+ }
+ }
+
+ /**
+ * Log a value to a keyed scalar.
+ *
+ * @param {String} scalarId
+ * Scalar in which the data is to be stored.
+ * @param {String} key
+ * The key within the scalar.
+ * @param value
+ * Value to store.
+ */
+ keyedScalarSet(scalarId, key, value) {
+ if (!scalarId) {
+ return;
+ }
+
+ try {
+ if (isNaN(value) && typeof value !== "boolean") {
+ dump(
+ `Warning: An attempt was made to write a non-numeric and ` +
+ `non-boolean value ${value} to the ${scalarId} scalar. Only ` +
+ `numeric and boolean values are allowed.\n` +
+ `CALLER: ${getCaller()}`
+ );
+
+ return;
+ }
+ Services.telemetry.keyedScalarSet(scalarId, key, value);
+ } catch (e) {
+ dump(
+ `Warning: An attempt was made to write to the ${scalarId} ` +
+ `scalar, which is not defined in Scalars.yaml\n` +
+ `CALLER: ${getCaller()}`
+ );
+ }
+ }
+
+ /**
+ * Log a value to a keyed count scalar.
+ *
+ * @param {String} scalarId
+ * Scalar in which the data is to be stored.
+ * @param {String} key
+ * The key within the scalar.
+ * @param value
+ * Value to store.
+ */
+ keyedScalarAdd(scalarId, key, value) {
+ if (!scalarId) {
+ return;
+ }
+
+ try {
+ if (isNaN(value)) {
+ dump(
+ `Warning: An attempt was made to write a non-numeric value ` +
+ `${value} to the ${scalarId} scalar. Only numeric values are ` +
+ `allowed.\n` +
+ `CALLER: ${getCaller()}`
+ );
+
+ return;
+ }
+ Services.telemetry.keyedScalarAdd(scalarId, key, value);
+ } catch (e) {
+ dump(
+ `Warning: An attempt was made to write to the ${scalarId} ` +
+ `scalar, which is not defined in Scalars.yaml\n` +
+ `CALLER: ${getCaller()}`
+ );
+ }
+ }
+
+ /**
+ * Event telemetry is disabled by default. Use this method to enable or
+ * disable it.
+ *
+ * @param {Boolean} enabled
+ * Enabled: true or false.
+ */
+ setEventRecordingEnabled(enabled) {
+ return Services.telemetry.setEventRecordingEnabled(CATEGORY, enabled);
+ }
+
+ /**
+ * Telemetry events often need to make use of a number of properties from
+ * completely different codepaths. To make this possible we create a
+ * "pending event" along with an array of property names that we need to wait
+ * for before sending the event.
+ *
+ * As each property is received via addEventProperty() we check if all
+ * properties have been received. Once they have all been received we send the
+ * telemetry event.
+ *
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ * @param {String} method
+ * The telemetry event method (describes the type of event that
+ * occurred e.g. "open")
+ * @param {String} object
+ * The telemetry event object name (the name of the object the event
+ * occurred on) e.g. "tools" or "setting"
+ * @param {String|null} value
+ * The telemetry event value (a user defined value, providing context
+ * for the event) e.g. "console"
+ * @param {Array} expected
+ * An array of the properties needed before sending the telemetry
+ * event e.g.
+ * [
+ * "host",
+ * "width"
+ * ]
+ */
+ preparePendingEvent(obj, method, object, value, expected = []) {
+ const sig = `${method},${object},${value}`;
+
+ if (expected.length === 0) {
+ throw new Error(
+ `preparePendingEvent() was called without any expected ` +
+ `properties.\n` +
+ `CALLER: ${getCaller()}`
+ );
+ }
+
+ const data = {
+ extra: {},
+ expected: new Set(expected),
+ };
+
+ PENDING_EVENTS.set(obj, sig, data);
+
+ const props = PENDING_EVENT_PROPERTIES.get(obj, sig);
+ if (props) {
+ for (const [name, val] of Object.entries(props)) {
+ this.addEventProperty(obj, method, object, value, name, val);
+ }
+ PENDING_EVENT_PROPERTIES.delete(obj, sig);
+ }
+ }
+
+ /**
+ * Adds an expected property for either a current or future pending event.
+ * This means that if preparePendingEvent() is called before or after sending
+ * the event properties they will automatically added to the event.
+ *
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ * @param {String} method
+ * The telemetry event method (describes the type of event that
+ * occurred e.g. "open")
+ * @param {String} object
+ * The telemetry event object name (the name of the object the event
+ * occurred on) e.g. "tools" or "setting"
+ * @param {String|null} value
+ * The telemetry event value (a user defined value, providing context
+ * for the event) e.g. "console"
+ * @param {String} pendingPropName
+ * The pending property name
+ * @param {String} pendingPropValue
+ * The pending property value
+ */
+ addEventProperty(
+ obj,
+ method,
+ object,
+ value,
+ pendingPropName,
+ pendingPropValue
+ ) {
+ const sig = `${method},${object},${value}`;
+ const events = PENDING_EVENTS.get(obj, sig);
+
+ // If the pending event has not been created add the property to the pending
+ // list.
+ if (!events) {
+ const props = PENDING_EVENT_PROPERTIES.get(obj, sig);
+
+ if (props) {
+ props[pendingPropName] = pendingPropValue;
+ } else {
+ PENDING_EVENT_PROPERTIES.set(obj, sig, {
+ [pendingPropName]: pendingPropValue,
+ });
+ }
+ return;
+ }
+
+ const { expected, extra } = events;
+
+ if (expected.has(pendingPropName)) {
+ extra[pendingPropName] = pendingPropValue;
+
+ if (expected.size === Object.keys(extra).length) {
+ this._sendPendingEvent(obj, method, object, value);
+ }
+ } else {
+ // The property was not expected, warn and bail.
+ throw new Error(
+ `An attempt was made to add the unexpected property ` +
+ `"${pendingPropName}" to a telemetry event with the ` +
+ `signature "${sig}"\n` +
+ `CALLER: ${getCaller()}`
+ );
+ }
+ }
+
+ /**
+ * Adds expected properties for either a current or future pending event.
+ * This means that if preparePendingEvent() is called before or after sending
+ * the event properties they will automatically added to the event.
+ *
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ * @param {String} method
+ * The telemetry event method (describes the type of event that
+ * occurred e.g. "open")
+ * @param {String} object
+ * The telemetry event object name (the name of the object the event
+ * occurred on) e.g. "tools" or "setting"
+ * @param {String|null} value
+ * The telemetry event value (a user defined value, providing context
+ * for the event) e.g. "console"
+ * @param {String} pendingObject
+ * An object containing key, value pairs that should be added to the
+ * event as properties.
+ */
+ addEventProperties(obj, method, object, value, pendingObject) {
+ for (const [key, val] of Object.entries(pendingObject)) {
+ this.addEventProperty(obj, method, object, value, key, val);
+ }
+ }
+
+ /**
+ * A private method that is not to be used externally. This method is used to
+ * prepare a pending telemetry event for sending and then send it via
+ * recordEvent().
+ *
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ * @param {String} method
+ * The telemetry event method (describes the type of event that
+ * occurred e.g. "open")
+ * @param {String} object
+ * The telemetry event object name (the name of the object the event
+ * occurred on) e.g. "tools" or "setting"
+ * @param {String|null} value
+ * The telemetry event value (a user defined value, providing context
+ * for the event) e.g. "console"
+ */
+ _sendPendingEvent(obj, method, object, value) {
+ const sig = `${method},${object},${value}`;
+ const { extra } = PENDING_EVENTS.get(obj, sig);
+
+ PENDING_EVENTS.delete(obj, sig);
+ PENDING_EVENT_PROPERTIES.delete(obj, sig);
+ this.recordEvent(method, object, value, extra);
+ }
+
+ /**
+ * Send a telemetry event.
+ *
+ * @param {String} method
+ * The telemetry event method (describes the type of event that
+ * occurred e.g. "open")
+ * @param {String} object
+ * The telemetry event object name (the name of the object the event
+ * occurred on) e.g. "tools" or "setting"
+ * @param {String|null} [value]
+ * Optional telemetry event value (a user defined value, providing
+ * context for the event) e.g. "console"
+ * @param {Object} [extra]
+ * Optional telemetry event extra object containing the properties that
+ * will be sent with the event e.g.
+ * {
+ * host: "bottom",
+ * width: "1024"
+ * }
+ */
+ recordEvent(method, object, value = null, extra = null) {
+ // Only string values are allowed so cast all values to strings.
+ if (extra) {
+ for (let [name, val] of Object.entries(extra)) {
+ val = val + "";
+
+ if (val.length > 80) {
+ const sig = `${method},${object},${value}`;
+
+ dump(
+ `Warning: The property "${name}" was added to a telemetry ` +
+ `event with the signature ${sig} but it's value "${val}" is ` +
+ `longer than the maximum allowed length of 80 characters.\n` +
+ `The property value has been trimmed to 80 characters before ` +
+ `sending.\nCALLER: ${getCaller()}`
+ );
+
+ val = val.substring(0, 80);
+ }
+
+ extra[name] = val;
+ }
+ }
+ // Automatically flag the record with the session ID
+ // if the current Telemetry instance relates to a toolbox
+ // so that data can be aggregated per toolbox instance.
+ // Note that we also aggregate data per about:debugging instance.
+ if (!extra) {
+ extra = {};
+ }
+ extra.session_id = this.sessionId;
+
+ Services.telemetry.recordEvent(CATEGORY, method, object, value, extra);
+ }
+
+ /**
+ * Sends telemetry pings to indicate that a tool has been opened.
+ *
+ * @param {String} id
+ * The ID of the tool opened.
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ *
+ * NOTE: This method is designed for tools that send multiple probes on open,
+ * one of those probes being a counter and the other a timer. If you
+ * only have one probe you should be using another method.
+ */
+ toolOpened(id, obj) {
+ const charts = getChartsFromToolId(id);
+
+ if (!charts) {
+ return;
+ }
+
+ if (charts.useTimedEvent) {
+ this.preparePendingEvent(obj, "tool_timer", id, null, [
+ "os",
+ "time_open",
+ ]);
+ this.addEventProperty(
+ obj,
+ "tool_timer",
+ id,
+ null,
+ "time_open",
+ this.msSystemNow()
+ );
+ }
+ if (charts.timerHist) {
+ this.start(charts.timerHist, obj, { inSeconds: true });
+ }
+ if (charts.countHist) {
+ this.getHistogramById(charts.countHist).add(true);
+ }
+ if (charts.countScalar) {
+ this.scalarAdd(charts.countScalar, 1);
+ }
+ }
+
+ /**
+ * Sends telemetry pings to indicate that a tool has been closed.
+ *
+ * @param {String} id
+ * The ID of the tool opened.
+ * @param {Object} obj
+ * The telemetry event or ping is associated with this object, meaning
+ * that multiple events or pings for the same histogram may be run
+ * concurrently, as long as they are associated with different objects.
+ *
+ * NOTE: This method is designed for tools that send multiple probes on open,
+ * one of those probes being a counter and the other a timer. If you
+ * only have one probe you should be using another method.
+ */
+ toolClosed(id, obj) {
+ const charts = getChartsFromToolId(id);
+
+ if (!charts) {
+ return;
+ }
+
+ if (charts.useTimedEvent) {
+ const sig = `tool_timer,${id},null`;
+ const event = PENDING_EVENTS.get(obj, sig);
+ const time = this.msSystemNow() - event.extra.time_open;
+
+ this.addEventProperties(obj, "tool_timer", id, null, {
+ time_open: time,
+ os: this.osNameAndVersion,
+ });
+ }
+
+ if (charts.timerHist) {
+ this.finish(charts.timerHist, obj, false);
+ }
+ }
+}
+
+/**
+ * Returns the telemetry charts for a specific tool.
+ *
+ * @param {String} id
+ * The ID of the tool that has been opened.
+ *
+ */
+// eslint-disable-next-line complexity
+function getChartsFromToolId(id) {
+ if (!id) {
+ return null;
+ }
+
+ const lowerCaseId = id.toLowerCase();
+
+ let useTimedEvent = null;
+ let timerHist = null;
+ let countHist = null;
+ let countScalar = null;
+
+ id = id.toUpperCase();
+
+ if (id === "PERFORMANCE") {
+ id = "JSPROFILER";
+ }
+
+ switch (id) {
+ case "ABOUTDEBUGGING":
+ case "BROWSERCONSOLE":
+ case "DOM":
+ case "INSPECTOR":
+ case "JSBROWSERDEBUGGER":
+ case "JSDEBUGGER":
+ case "JSPROFILER":
+ case "MEMORY":
+ case "NETMONITOR":
+ case "OPTIONS":
+ case "RESPONSIVE":
+ case "STORAGE":
+ case "STYLEEDITOR":
+ case "TOOLBOX":
+ case "WEBCONSOLE":
+ timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
+ countHist = `DEVTOOLS_${id}_OPENED_COUNT`;
+ break;
+ case "ACCESSIBILITY":
+ case "APPLICATION":
+ timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
+ countScalar = `devtools.${lowerCaseId}.opened_count`;
+ break;
+ case "ACCESSIBILITY_PICKER":
+ timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
+ countScalar = `devtools.accessibility.picker_used_count`;
+ break;
+ case "CHANGESVIEW":
+ useTimedEvent = true;
+ timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
+ countScalar = `devtools.${lowerCaseId}.opened_count`;
+ break;
+ case "ANIMATIONINSPECTOR":
+ case "COMPATIBILITYVIEW":
+ case "COMPUTEDVIEW":
+ case "FONTINSPECTOR":
+ case "LAYOUTVIEW":
+ case "RULEVIEW":
+ useTimedEvent = true;
+ timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
+ countHist = `DEVTOOLS_${id}_OPENED_COUNT`;
+ break;
+ case "FLEXBOX_HIGHLIGHTER":
+ case "GRID_HIGHLIGHTER":
+ timerHist = `DEVTOOLS_${id}_TIME_ACTIVE_SECONDS`;
+ break;
+ default:
+ timerHist = `DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS`;
+ countHist = `DEVTOOLS_CUSTOM_OPENED_COUNT`;
+ }
+
+ return {
+ useTimedEvent,
+ timerHist,
+ countHist,
+ countScalar,
+ };
+}
+
+/**
+ * Displays the first caller and calling line outside of this file in the
+ * event of an error. This is the line that made the call that produced the
+ * error.
+ */
+function getCaller() {
+ return getNthPathExcluding(0, "/telemetry.js");
+}
+
+module.exports = Telemetry;