/* 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
*/
// 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.recordEvent = this.recordEvent.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)) { returnfalse;
}
/** * 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: () => {},
}
);
}
/** * 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) { thrownew 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 (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. thrownew 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);
/** * 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; if (value !== null) {
extra.value = value;
}
// Using the Glean API directly insteade of doing string manipulations // would be better. See bug 1921793. const eventName = `${method}_${object}`.replace(/(_[a-z])/g, c =>
c[1].toUpperCase()
);
Glean.devtoolsMain[eventName]?.record(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);
/** * 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;
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) { returnnull;
}
let useTimedEvent = null;
let timerHist = null;
let countHist = null;
let gleanCounter = null;
/** * 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;
¤ Dauer der Verarbeitung: 0.17 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.