michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /** michael@0: * Telemetry. michael@0: * michael@0: * To add metrics for a tool: michael@0: * michael@0: * 1. Create boolean, flag and exponential entries in michael@0: * toolkit/components/telemetry/Histograms.json. Each type is optional but it michael@0: * is best if all three can be included. michael@0: * michael@0: * 2. Add your chart entries to browser/devtools/shared/telemetry.js michael@0: * (Telemetry.prototype._histograms): michael@0: * mytoolname: { michael@0: * histogram: "DEVTOOLS_MYTOOLNAME_OPENED_BOOLEAN", michael@0: * userHistogram: "DEVTOOLS_MYTOOLNAME_OPENED_PER_USER_FLAG", michael@0: * timerHistogram: "DEVTOOLS_MYTOOLNAME_TIME_ACTIVE_SECONDS" michael@0: * }, michael@0: * michael@0: * 3. Include this module at the top of your tool. Use: michael@0: * let Telemetry = require("devtools/shared/telemetry") michael@0: * michael@0: * 4. Create a telemetry instance in your tool's constructor: michael@0: * this._telemetry = new Telemetry(); michael@0: * michael@0: * 5. When your tool is opened call: michael@0: * this._telemetry.toolOpened("mytoolname"); michael@0: * michael@0: * 6. When your tool is closed call: michael@0: * this._telemetry.toolClosed("mytoolname"); michael@0: * michael@0: * Note: michael@0: * You can view telemetry stats for your local Firefox instance via michael@0: * about:telemetry. michael@0: * michael@0: * You can view telemetry stats for large groups of Firefox users at michael@0: * metrics.mozilla.com. michael@0: */ michael@0: michael@0: const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version"; michael@0: michael@0: this.Telemetry = function() { michael@0: // Bind pretty much all functions so that callers do not need to. michael@0: this.toolOpened = this.toolOpened.bind(this); michael@0: this.toolClosed = this.toolClosed.bind(this); michael@0: this.log = this.log.bind(this); michael@0: this.logOncePerBrowserVersion = this.logOncePerBrowserVersion.bind(this); michael@0: this.destroy = this.destroy.bind(this); michael@0: michael@0: this._timers = new Map(); michael@0: }; michael@0: michael@0: module.exports = Telemetry; michael@0: michael@0: let {Cc, Ci, Cu} = require("chrome"); michael@0: let {Services} = Cu.import("resource://gre/modules/Services.jsm", {}); michael@0: let {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); michael@0: michael@0: Telemetry.prototype = { michael@0: _histograms: { michael@0: toolbox: { michael@0: timerHistogram: "DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: options: { michael@0: histogram: "DEVTOOLS_OPTIONS_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_OPTIONS_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_OPTIONS_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: webconsole: { michael@0: histogram: "DEVTOOLS_WEBCONSOLE_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_WEBCONSOLE_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_WEBCONSOLE_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: browserconsole: { michael@0: histogram: "DEVTOOLS_BROWSERCONSOLE_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_BROWSERCONSOLE_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_BROWSERCONSOLE_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: inspector: { michael@0: histogram: "DEVTOOLS_INSPECTOR_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_INSPECTOR_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_INSPECTOR_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: ruleview: { michael@0: histogram: "DEVTOOLS_RULEVIEW_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_RULEVIEW_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_RULEVIEW_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: computedview: { michael@0: histogram: "DEVTOOLS_COMPUTEDVIEW_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_COMPUTEDVIEW_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_COMPUTEDVIEW_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: layoutview: { michael@0: histogram: "DEVTOOLS_LAYOUTVIEW_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_LAYOUTVIEW_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_LAYOUTVIEW_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: fontinspector: { michael@0: histogram: "DEVTOOLS_FONTINSPECTOR_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_FONTINSPECTOR_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_FONTINSPECTOR_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: jsdebugger: { michael@0: histogram: "DEVTOOLS_JSDEBUGGER_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_JSDEBUGGER_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_JSDEBUGGER_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: jsbrowserdebugger: { michael@0: histogram: "DEVTOOLS_JSBROWSERDEBUGGER_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_JSBROWSERDEBUGGER_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_JSBROWSERDEBUGGER_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: styleeditor: { michael@0: histogram: "DEVTOOLS_STYLEEDITOR_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_STYLEEDITOR_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_STYLEEDITOR_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: shadereditor: { michael@0: histogram: "DEVTOOLS_SHADEREDITOR_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_SHADEREDITOR_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_SHADEREDITOR_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: jsprofiler: { michael@0: histogram: "DEVTOOLS_JSPROFILER_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_JSPROFILER_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_JSPROFILER_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: netmonitor: { michael@0: histogram: "DEVTOOLS_NETMONITOR_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_NETMONITOR_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_NETMONITOR_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: tilt: { michael@0: histogram: "DEVTOOLS_TILT_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_TILT_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_TILT_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: paintflashing: { michael@0: histogram: "DEVTOOLS_PAINTFLASHING_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_PAINTFLASHING_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_PAINTFLASHING_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: scratchpad: { michael@0: histogram: "DEVTOOLS_SCRATCHPAD_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_SCRATCHPAD_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_SCRATCHPAD_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: responsive: { michael@0: histogram: "DEVTOOLS_RESPONSIVE_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_RESPONSIVE_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_RESPONSIVE_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: developertoolbar: { michael@0: histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS" michael@0: }, michael@0: custom: { michael@0: histogram: "DEVTOOLS_CUSTOM_OPENED_BOOLEAN", michael@0: userHistogram: "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG", michael@0: timerHistogram: "DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS" michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Add an entry to a histogram. michael@0: * michael@0: * @param {String} id michael@0: * Used to look up the relevant histogram ID and log true to that michael@0: * histogram. michael@0: */ michael@0: toolOpened: function(id) { michael@0: let charts = this._histograms[id] || this._histograms.custom; michael@0: michael@0: if (charts.histogram) { michael@0: this.log(charts.histogram, true); michael@0: } michael@0: if (charts.userHistogram) { michael@0: this.logOncePerBrowserVersion(charts.userHistogram, true); michael@0: } michael@0: if (charts.timerHistogram) { michael@0: this._timers.set(charts.timerHistogram, new Date()); michael@0: } michael@0: }, michael@0: michael@0: toolClosed: function(id) { michael@0: let charts = this._histograms[id]; michael@0: michael@0: if (!charts || !charts.timerHistogram) { michael@0: return; michael@0: } michael@0: michael@0: let startTime = this._timers.get(charts.timerHistogram); michael@0: michael@0: if (startTime) { michael@0: let time = (new Date() - startTime) / 1000; michael@0: this.log(charts.timerHistogram, time); michael@0: this._timers.delete(charts.timerHistogram); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Log a value to a histogram. michael@0: * michael@0: * @param {String} histogramId michael@0: * Histogram in which the data is to be stored. michael@0: * @param value michael@0: * Value to store. michael@0: */ michael@0: log: function(histogramId, value) { michael@0: if (histogramId) { michael@0: let histogram; michael@0: michael@0: try { michael@0: let histogram = Services.telemetry.getHistogramById(histogramId); michael@0: histogram.add(value); michael@0: } catch(e) { michael@0: dump("Warning: An attempt was made to write to the " + histogramId + michael@0: " histogram, which is not defined in Histograms.json\n"); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Log info about usage once per browser version. This allows us to discover michael@0: * how many individual users are using our tools for each browser version. michael@0: * michael@0: * @param {String} perUserHistogram michael@0: * Histogram in which the data is to be stored. michael@0: */ michael@0: logOncePerBrowserVersion: function(perUserHistogram, value) { michael@0: let currentVersion = appInfo.version; michael@0: let latest = Services.prefs.getCharPref(TOOLS_OPENED_PREF); michael@0: let latestObj = JSON.parse(latest); michael@0: michael@0: let lastVersionHistogramUpdated = latestObj[perUserHistogram]; michael@0: michael@0: if (typeof lastVersionHistogramUpdated == "undefined" || michael@0: lastVersionHistogramUpdated !== currentVersion) { michael@0: latestObj[perUserHistogram] = currentVersion; michael@0: latest = JSON.stringify(latestObj); michael@0: Services.prefs.setCharPref(TOOLS_OPENED_PREF, latest); michael@0: this.log(perUserHistogram, value); michael@0: } michael@0: }, michael@0: michael@0: destroy: function() { michael@0: for (let [histogram, time] of this._timers) { michael@0: time = (new Date() - time) / 1000; michael@0: michael@0: this.log(histogram, time); michael@0: this._timers.delete(histogram); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: XPCOMUtils.defineLazyGetter(this, "appInfo", function() { michael@0: return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); michael@0: });