☠☠ backed out by 2a621e79e6b9 ☠ ☠ | |
author | J. Ryan Stinnett <jryans@gmail.com> |
Wed, 06 Aug 2014 20:37:00 -0400 | |
changeset 198577 | 5c1ba06b972b6a8b903362ad0ebb9b7112665e86 |
parent 198576 | 07bf0e09b1b0d47e310538f277113dfe9e15fc93 |
child 198578 | 2ead24c96a8e9e278fca79543c91aac03b031800 |
push id | 27277 |
push user | ryanvm@gmail.com |
push date | Fri, 08 Aug 2014 20:25:17 +0000 |
treeherder | mozilla-central@1d6500527f66 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | mratcliffe, paul |
bugs | 916804 |
milestone | 34.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/browser/devtools/shared/telemetry.js +++ b/browser/devtools/shared/telemetry.js @@ -31,17 +31,17 @@ * 6. When your tool is closed call: * this._telemetry.toolClosed("mytoolname"); * * Note: * You can view telemetry stats for your local Firefox instance via * about:telemetry. * * You can view telemetry stats for large groups of Firefox users at - * metrics.mozilla.com. + * telemetry.mozilla.org. */ const TOOLS_OPENED_PREF = "devtools.telemetry.tools.opened.version"; this.Telemetry = function() { // Bind pretty much all functions so that callers do not need to. this.toolOpened = this.toolOpened.bind(this); this.toolClosed = this.toolClosed.bind(this); @@ -165,16 +165,21 @@ Telemetry.prototype = { userHistogram: "DEVTOOLS_RESPONSIVE_OPENED_PER_USER_FLAG", timerHistogram: "DEVTOOLS_RESPONSIVE_TIME_ACTIVE_SECONDS" }, developertoolbar: { histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_BOOLEAN", userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG", timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS" }, + webide: { + histogram: "DEVTOOLS_WEBIDE_OPENED_BOOLEAN", + userHistogram: "DEVTOOLS_WEBIDE_OPENED_PER_USER_FLAG", + timerHistogram: "DEVTOOLS_WEBIDE_TIME_ACTIVE_SECONDS" + }, custom: { histogram: "DEVTOOLS_CUSTOM_OPENED_BOOLEAN", userHistogram: "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG", timerHistogram: "DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS" } }, /** @@ -189,33 +194,52 @@ Telemetry.prototype = { if (charts.histogram) { this.log(charts.histogram, true); } if (charts.userHistogram) { this.logOncePerBrowserVersion(charts.userHistogram, true); } if (charts.timerHistogram) { - this._timers.set(charts.timerHistogram, new Date()); + this.startTimer(charts.timerHistogram); } }, toolClosed: function(id) { let charts = this._histograms[id]; if (!charts || !charts.timerHistogram) { return; } - let startTime = this._timers.get(charts.timerHistogram); + this.stopTimer(charts.timerHistogram); + }, + /** + * Record the start time for a timing-based histogram entry. + * + * @param String histogramId + * Histogram in which the data is to be stored. + */ + startTimer: function(histogramId) { + this._timers.set(histogramId, new Date()); + }, + + /** + * Stop the timer and log elasped time for a timing-based histogram entry. + * + * @param String histogramId + * Histogram in which the data is to be stored. + */ + stopTimer: function(histogramId) { + let startTime = this._timers.get(histogramId); if (startTime) { let time = (new Date() - startTime) / 1000; - this.log(charts.timerHistogram, time); - this._timers.delete(charts.timerHistogram); + this.log(histogramId, time); + this._timers.delete(histogramId); } }, /** * Log a value to a histogram. * * @param {String} histogramId * Histogram in which the data is to be stored. @@ -253,20 +277,17 @@ Telemetry.prototype = { latestObj[perUserHistogram] = currentVersion; latest = JSON.stringify(latestObj); Services.prefs.setCharPref(TOOLS_OPENED_PREF, latest); this.log(perUserHistogram, value); } }, destroy: function() { - for (let [histogram, time] of this._timers) { - time = (new Date() - time) / 1000; - - this.log(histogram, time); - this._timers.delete(histogram); + for (let histogramId of this._timers.keys()) { + this.stopTimer(histogramId); } } }; XPCOMUtils.defineLazyGetter(this, "appInfo", function() { return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo); });
--- a/browser/devtools/webide/content/runtimedetails.js +++ b/browser/devtools/webide/content/runtimedetails.js @@ -84,17 +84,17 @@ function CheckLockState() { if (AppManager.connection && AppManager.connection.status == Connection.Status.CONNECTED && AppManager.deviceFront) { // ADB check if (AppManager.selectedRuntime instanceof USBRuntime) { let device = Devices.getByName(AppManager.selectedRuntime.id); - if (device.summonRoot) { + if (device && device.summonRoot) { device.isRoot().then(isRoot => { if (isRoot) { adbCheckResult.textContent = sYes; flipCertPerfButton.removeAttribute("disabled"); } else { adbCheckResult.textContent = sNo; adbRootAction.removeAttribute("hidden"); }
--- a/browser/devtools/webide/content/webide.js +++ b/browser/devtools/webide/content/webide.js @@ -16,16 +16,17 @@ const {Services} = Cu.import("resource:/ const {AppProjects} = require("devtools/app-manager/app-projects"); const {Connection} = require("devtools/client/connection-manager"); const {AppManager} = require("devtools/webide/app-manager"); const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); const ProjectEditor = require("projecteditor/projecteditor"); const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm"); const {GetAvailableAddons} = require("devtools/webide/addons"); const {GetTemplatesJSON, GetAddonsJSON} = require("devtools/webide/remote-resources"); +const Telemetry = require("devtools/shared/telemetry"); const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties"); const HTML = "http://www.w3.org/1999/xhtml"; const HELP_URL = "https://developer.mozilla.org/docs/Tools/WebIDE/Troubleshooting"; // download template index early GetTemplatesJSON(true); @@ -42,16 +43,19 @@ window.addEventListener("load", function window.addEventListener("unload", function onUnload() { window.removeEventListener("unload", onUnload); UI.uninit(); }); let UI = { init: function() { + this._telemetry = new Telemetry(); + this._telemetry.toolOpened("webide"); + AppManager.init(); this.onMessage = this.onMessage.bind(this); window.addEventListener("message", this.onMessage); this.appManagerUpdate = this.appManagerUpdate.bind(this); AppManager.on("app-manager-update", this.appManagerUpdate); @@ -93,16 +97,18 @@ let UI = { } }, uninit: function() { window.removeEventListener("focus", this.onfocus, true); AppManager.off("app-manager-update", this.appManagerUpdate); AppManager.uninit(); window.removeEventListener("message", this.onMessage); + this.updateConnectionTelemetry(); + this._telemetry.toolClosed("webide"); }, onfocus: function() { // Because we can't track the activity in the folder project, // we need to validate the project regularly. Let's assume that // if a modification happened, it happened when the window was // not focused. if (AppManager.selectedProject && @@ -116,16 +122,17 @@ let UI = { // Got a message from app-manager.js switch (what) { case "runtimelist": this.updateRuntimeList(); break; case "connection": this.updateRuntimeButton(); this.updateCommands(); + this.updateConnectionTelemetry(); break; case "project": this.updateTitle(); this.closeToolbox(); this.updateCommands(); this.updateProjectButton(); this.openProject(); break; @@ -213,22 +220,23 @@ let UI = { }, 30000); }, cancelBusyTimeout: function() { clearTimeout(this._busyTimeout); }, busyWithProgressUntil: function(promise, operationDescription) { - this.busyUntil(promise, operationDescription); + let busy = this.busyUntil(promise, operationDescription); let win = document.querySelector("window"); let progress = document.querySelector("#action-busy-determined"); progress.mode = "undetermined"; win.classList.add("busy-determined"); win.classList.remove("busy-undetermined"); + return busy; }, busyUntil: function(promise, operationDescription) { // Freeze the UI until the promise is resolved. A 30s timeout // will unfreeze the UI, just in case the promise never gets // resolved. this._busyPromise = promise; this._busyOperationDescription = operationDescription; @@ -328,29 +336,71 @@ let UI = { }, true); } } }, connectToRuntime: function(runtime) { let name = runtime.getName(); let promise = AppManager.connectToRuntime(runtime); + promise.then(() => this.initConnectionTelemetry()); return this.busyUntil(promise, "connecting to runtime"); }, updateRuntimeButton: function() { let labelNode = document.querySelector("#runtime-panel-button > .panel-button-label"); if (!AppManager.selectedRuntime) { labelNode.setAttribute("value", Strings.GetStringFromName("runtimeButton_label")); } else { let name = AppManager.selectedRuntime.getName(); labelNode.setAttribute("value", name); } }, + _actionsToLog: new Set(), + + /** + * For each new connection, track whether play and debug were ever used. Only + * one value is collected for each button, even if they are used multiple + * times during a connection. + */ + initConnectionTelemetry: function() { + this._actionsToLog.add("play"); + this._actionsToLog.add("debug"); + }, + + /** + * Action occurred. Log that it happened, and remove it from the loggable + * set. + */ + onAction: function(action) { + if (!this._actionsToLog.has(action)) { + return; + } + this.logActionState(action, true); + this._actionsToLog.delete(action); + }, + + /** + * Connection status changed or we are shutting down. Record any loggable + * actions as having not occurred. + */ + updateConnectionTelemetry: function() { + for (let action of this._actionsToLog.values()) { + this.logActionState(action, false); + } + this._actionsToLog.clear(); + }, + + logActionState: function(action, state) { + let histogramId = "DEVTOOLS_WEBIDE_CONNECTION_" + + action.toUpperCase() + "_USED"; + this._telemetry.log(histogramId, state); + }, + /********** PROJECTS **********/ // Panel & button updateProjectButton: function() { let buttonNode = document.querySelector("#project-panel-button"); let labelNode = buttonNode.querySelector(".panel-button-label"); let imageNode = buttonNode.querySelector(".panel-button-image"); @@ -655,18 +705,17 @@ let UI = { // properly anymore. this.toolboxIframe.remove(); this.toolboxIframe = null; let splitter = document.querySelector(".devtools-horizontal-splitter"); splitter.setAttribute("hidden", "true"); document.querySelector("#action-button-debug").removeAttribute("active"); }, -} - +}; let Cmds = { quit: function() { window.close(); }, /** * testOptions: { chrome mochitest support @@ -904,32 +953,43 @@ let Cmds = { UI.selectDeckPanel("runtimedetails"); }, showMonitor: function() { UI.selectDeckPanel("monitor"); }, play: function() { + let busy; switch(AppManager.selectedProject.type) { case "packaged": - return UI.busyWithProgressUntil(AppManager.installAndRunProject(), "installing and running app"); + busy = UI.busyWithProgressUntil(AppManager.installAndRunProject(), + "installing and running app"); + break; case "hosted": - return UI.busyUntil(AppManager.installAndRunProject(), "installing and running app"); + busy = UI.busyUntil(AppManager.installAndRunProject(), + "installing and running app"); + break; case "runtimeApp": - return UI.busyUntil(AppManager.runRuntimeApp(), "running app"); + busy = UI.busyUntil(AppManager.runRuntimeApp(), "running app"); + break; } - return promise.reject(); + if (!busy) { + return promise.reject(); + } + UI.onAction("play"); + return busy; }, stop: function() { return UI.busyUntil(AppManager.stopRunningApp(), "stopping app"); }, toggleToolbox: function() { + UI.onAction("debug"); if (UI.toolboxIframe) { UI.closeToolbox(); return promise.resolve(); } else { UI.toolboxPromise = AppManager.getTarget().then((target) => { return UI.showToolbox(target); }, console.error); UI.busyUntil(UI.toolboxPromise, "opening toolbox"); @@ -956,9 +1016,9 @@ let Cmds = { showAddons: function() { UI.selectDeckPanel("addons"); }, showPrefs: function() { UI.selectDeckPanel("prefs"); }, -} +};
--- a/browser/devtools/webide/modules/app-manager.js +++ b/browser/devtools/webide/modules/app-manager.js @@ -20,16 +20,17 @@ const {ConnectionManager, Connection} = const AppActorFront = require("devtools/app-actor-front"); const {getDeviceFront} = require("devtools/server/actors/device"); const {getPreferenceFront} = require("devtools/server/actors/preference"); const {setTimeout} = require("sdk/timers"); const {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); const {USBRuntime, WiFiRuntime, SimulatorRuntime, gLocalRuntime, gRemoteRuntime} = require("devtools/webide/runtimes"); const discovery = require("devtools/toolkit/discovery/discovery"); +const Telemetry = require("devtools/shared/telemetry"); const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties"); const WIFI_SCANNING_PREF = "devtools.remote.wifi.scan"; exports.AppManager = AppManager = { // FIXME: will break when devtools/app-manager will be removed: @@ -61,16 +62,18 @@ exports.AppManager = AppManager = { this.trackWiFiRuntimes(); this.trackSimulatorRuntimes(); this.onInstallProgress = this.onInstallProgress.bind(this); AppActorFront.on("install-progress", this.onInstallProgress); this.observe = this.observe.bind(this); Services.prefs.addObserver(WIFI_SCANNING_PREF, this, false); + + this._telemetry = new Telemetry(); }, uninit: function() { AppActorFront.off("install-progress", this.onInstallProgress); this._unlistenToApps(); this.selectedProject = null; this.selectedRuntime = null; this.untrackUSBRuntimes(); @@ -340,16 +343,35 @@ exports.AppManager = AppManager = { () => {}, () => {deferred.reject()}); } catch(e) { console.error(e); deferred.reject(); } }, deferred.reject); + // Record connection result in telemetry + let logResult = result => { + this._telemetry.log("DEVTOOLS_WEBIDE_CONNECTION_RESULT", result); + if (runtime.type) { + this._telemetry.log("DEVTOOLS_WEBIDE_" + runtime.type + + "_CONNECTION_RESULT", result); + } + }; + deferred.promise.then(() => logResult(true), () => logResult(false)); + + // If successful, record connection time in telemetry + deferred.promise.then(() => { + const timerId = "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS"; + this._telemetry.startTimer(timerId); + this.connection.once(Connection.Events.STATUS_CHANGED, () => { + this._telemetry.stopTimer(timerId); + }); + }); + return deferred.promise; }, isMainProcessDebuggable: function() { return this._listTabsResponse && this._listTabsResponse.consoleActor; },
--- a/browser/devtools/webide/modules/runtimes.js +++ b/browser/devtools/webide/modules/runtimes.js @@ -8,21 +8,31 @@ const {Services} = Cu.import("resource:/ const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm"); const {ConnectionManager, Connection} = require("devtools/client/connection-manager"); const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm"); const discovery = require("devtools/toolkit/discovery/discovery"); const promise = require("promise"); const Strings = Services.strings.createBundle("chrome://browser/locale/devtools/webide.properties"); +// These type strings are used for logging events to Telemetry +let RuntimeTypes = { + usb: "USB", + wifi: "WIFI", + simulator: "SIMULATOR", + remote: "REMOTE", + local: "LOCAL" +}; + function USBRuntime(id) { this.id = id; } USBRuntime.prototype = { + type: RuntimeTypes.usb, connect: function(connection) { let device = Devices.getByName(this.id); if (!device) { return promise.reject("Can't find device: " + this.getName()); } return device.connect().then((port) => { connection.host = "localhost"; connection.port = port; @@ -54,16 +64,17 @@ USBRuntime.prototype = { }, } function WiFiRuntime(deviceName) { this.deviceName = deviceName; } WiFiRuntime.prototype = { + type: RuntimeTypes.wifi, connect: function(connection) { let service = discovery.getRemoteService("devtools", this.deviceName); if (!service) { return promise.reject("Can't find device: " + this.getName()); } connection.host = service.host; connection.port = service.port; connection.connect(); @@ -77,16 +88,17 @@ WiFiRuntime.prototype = { }, } function SimulatorRuntime(version) { this.version = version; } SimulatorRuntime.prototype = { + type: RuntimeTypes.simulator, connect: function(connection) { let port = ConnectionManager.getFreeTCPPort(); let simulator = Simulator.getByVersion(this.version); if (!simulator || !simulator.launch) { return promise.reject("Can't find simulator: " + this.getName()); } return simulator.launch({port: port}).then(() => { connection.port = port; @@ -99,32 +111,34 @@ SimulatorRuntime.prototype = { return this.version; }, getName: function() { return Simulator.getByVersion(this.version).appinfo.label; }, } let gLocalRuntime = { + type: RuntimeTypes.local, connect: function(connection) { if (!DebuggerServer.initialized) { DebuggerServer.init(); DebuggerServer.addBrowserActors(); } connection.port = null; connection.host = null; // Force Pipe transport connection.connect(); return promise.resolve(); }, getName: function() { return Strings.GetStringFromName("local_runtime"); }, } let gRemoteRuntime = { + type: RuntimeTypes.remote, connect: function(connection) { let win = Services.wm.getMostRecentWindow("devtools:webide"); if (!win) { return promise.reject(); } let ret = {value: connection.host + ":" + connection.port}; Services.prompt.prompt(win, Strings.GetStringFromName("remote_runtime_promptTitle"),
--- a/browser/devtools/webide/test/chrome.ini +++ b/browser/devtools/webide/test/chrome.ini @@ -26,8 +26,9 @@ support-files = [test_basic.html] [test_newapp.html] [test_import.html] [test_runtime.html] [test_manifestUpdate.html] [test_addons.html] [test_deviceinfo.html] +[test_telemetry.html]
--- a/browser/devtools/webide/test/head.js +++ b/browser/devtools/webide/test/head.js @@ -76,31 +76,40 @@ function closeWebIDE(win) { return deferred.promise; } function removeAllProjects() { return Task.spawn(function* () { yield AppProjects.load(); let projects = AppProjects.store.object.projects; - for (let i = 0; i < projects.length; i++) { - yield AppProjects.remove(projects[i].location); + // AppProjects.remove mutates the projects array in-place + while (projects.length > 0) { + yield AppProjects.remove(projects[0].location); } }); } function nextTick() { let deferred = promise.defer(); SimpleTest.executeSoon(() => { deferred.resolve(); }); return deferred.promise; } +function waitForTime(time) { + let deferred = promise.defer(); + setTimeout(() => { + deferred.resolve(); + }, time); + return deferred.promise; +} + function documentIsLoaded(doc) { let deferred = promise.defer(); if (doc.readyState == "complete") { deferred.resolve(); } else { doc.addEventListener("readystatechange", function onChange() { if (doc.readyState == "complete") { doc.removeEventListener("readystatechange", onChange);
new file mode 100644 --- /dev/null +++ b/browser/devtools/webide/test/test_telemetry.html @@ -0,0 +1,242 @@ +<!DOCTYPE html> + +<html> + + <head> + <meta charset="utf8"> + <title></title> + + <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script> + <script type="application/javascript" src="chrome://mochikit/content/chrome-harness.js"></script> + <script type="application/javascript;version=1.8" src="head.js"></script> + <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"> + </head> + + <body> + + <script type="application/javascript;version=1.8"> + const Telemetry = require("devtools/shared/telemetry"); + const { USBRuntime, WiFiRuntime, SimulatorRuntime, gRemoteRuntime, + gLocalRuntime } = require("devtools/webide/runtimes"); + + // 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; + + function patchTelemetry() { + Telemetry.prototype.telemetryInfo = {}; + Telemetry.prototype._oldlog = Telemetry.prototype.log; + Telemetry.prototype.log = function(histogramId, value) { + if (histogramId) { + if (!this.telemetryInfo[histogramId]) { + this.telemetryInfo[histogramId] = []; + } + this.telemetryInfo[histogramId].push(value); + } + } + } + + function resetTelemetry() { + Telemetry.prototype.log = Telemetry.prototype._oldlog; + delete Telemetry.prototype._oldlog; + delete Telemetry.prototype.telemetryInfo; + } + + function cycleWebIDE() { + return Task.spawn(function*() { + let win = yield openWebIDE(); + // Wait a bit, so we're open for a non-zero time + yield waitForTime(TOOL_DELAY); + yield closeWebIDE(win); + }); + } + + function addFakeRuntimes(win) { + // We use the real runtimes here (and switch out some functionality) + // so we can ensure that logging happens as it would in real use. + + let usb = new USBRuntime("fakeUSB"); + // Use local pipe instead + usb.connect = function(connection) { + ok(connection, win.AppManager.connection, "connection is valid"); + connection.host = null; // force connectPipe + connection.connect(); + return promise.resolve(); + }; + win.AppManager.runtimeList.usb.push(usb); + + let wifi = new WiFiRuntime("fakeWiFi"); + // Use local pipe instead + wifi.connect = function(connection) { + ok(connection, win.AppManager.connection, "connection is valid"); + connection.host = null; // force connectPipe + connection.connect(); + return promise.resolve(); + }; + win.AppManager.runtimeList.wifi.push(wifi); + + let sim = new SimulatorRuntime("fakeSimulator"); + // Use local pipe instead + sim.connect = function(connection) { + ok(connection, win.AppManager.connection, "connection is valid"); + connection.host = null; // force connectPipe + connection.connect(); + return promise.resolve(); + }; + sim.getName = function() { + return this.version; + }; + win.AppManager.runtimeList.simulator.push(sim); + + let remote = gRemoteRuntime; + // Use local pipe instead + remote.connect = function(connection) { + ok(connection, win.AppManager.connection, "connection is valid"); + connection.host = null; // force connectPipe + connection.connect(); + return promise.resolve(); + }; + let local = gLocalRuntime; + win.AppManager.runtimeList.custom = [gRemoteRuntime, gLocalRuntime]; + + win.AppManager.update("runtimelist"); + } + + function addTestApp(win) { + return Task.spawn(function*() { + let packagedAppLocation = getTestFilePath("app"); + yield win.Cmds.importPackagedApp(packagedAppLocation); + }); + } + + function startConnection(win, type, index) { + let panelNode = win.document.querySelector("#runtime-panel"); + let items = panelNode.querySelectorAll(".runtime-panel-item-" + type); + if (index === undefined) { + is(items.length, 1, "Found one runtime button"); + } + + let deferred = promise.defer(); + win.AppManager.connection.once( + win.Connection.Events.CONNECTED, + () => deferred.resolve()); + + items[index || 0].click(); + + return deferred.promise; + } + + function waitUntilConnected(win) { + return Task.spawn(function*() { + ok(win.document.querySelector("window").className, "busy", "UI is busy"); + yield win.UI._busyPromise; + is(Object.keys(DebuggerServer._connections).length, 1, "Connected"); + }); + } + + function connectToRuntime(win, type, index) { + return Task.spawn(function*() { + yield startConnection(win, type, index); + yield waitUntilConnected(win); + }); + } + + function checkResults() { + let result = Telemetry.prototype.telemetryInfo; + for (let [histId, value] of Iterator(result)) { + if (histId.endsWith("OPENED_PER_USER_FLAG")) { + ok(value.length === 1 && !!value[0], + "Per user value " + histId + " has a single value of true"); + } else if (histId.endsWith("OPENED_BOOLEAN")) { + ok(value.length > 1, histId + " has more than one entry"); + + let okay = value.every(function(element) { + return !!element; + }); + + 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"); + } else if (histId === "DEVTOOLS_WEBIDE_CONNECTION_RESULT") { + ok(value.length === 5, histId + " has 5 connection results"); + + let okay = value.every(function(element) { + return !!element; + }); + + ok(okay, "All " + histId + " connections succeeded"); + } else if (histId.endsWith("CONNECTION_RESULT")) { + ok(value.length === 1 && !!value[0], + histId + " has 1 successful connection"); + } else if (histId === "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS") { + ok(value.length === 5, histId + " has 5 connection results"); + + let okay = value.every(function(element) { + return element > 0; + }); + + ok(okay, "All " + histId + " connections have time > 0"); + } else if (histId.endsWith("USED")) { + info(value.length); + ok(value.length === 5, histId + " has 5 connection actions"); + + let okay = value.every(function(element) { + return !element; + }); + + ok(okay, "All " + histId + " actions were skipped"); + } else { + ok(false, "Unexpected " + histId + " was logged"); + } + } + } + + window.onload = function() { + SimpleTest.waitForExplicitFinish(); + Task.spawn(function* () { + Cu.import("resource://gre/modules/devtools/dbg-server.jsm"); + DebuggerServer.init(function () { return true; }); + DebuggerServer.addBrowserActors(); + + patchTelemetry(); + + // Cycle once, so we can test for multiple opens + yield cycleWebIDE(); + + let win = yield openWebIDE(); + // Wait a bit, so we're open for a non-zero time + yield waitForTime(TOOL_DELAY); + addFakeRuntimes(win); + yield addTestApp(win); + + // Each one should log a connection result and non-zero connection + // time + yield connectToRuntime(win, "usb"); + yield waitForTime(TOOL_DELAY); + yield connectToRuntime(win, "wifi"); + yield waitForTime(TOOL_DELAY); + yield connectToRuntime(win, "simulator"); + yield waitForTime(TOOL_DELAY); + yield connectToRuntime(win, "custom", 0 /* remote */); + yield waitForTime(TOOL_DELAY); + yield connectToRuntime(win, "custom", 1 /* local */); + yield waitForTime(TOOL_DELAY); + yield closeWebIDE(win); + + checkResults(); + resetTelemetry(); + + DebuggerServer.destroy(); + + SimpleTest.finish(); + }); + } + </script> + </body> +</html>
--- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -5866,16 +5866,21 @@ "kind": "boolean", "description": "How many times has the devtool's Responsive View been opened via the toolbox button?" }, "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_BOOLEAN": { "expires_in_version": "never", "kind": "boolean", "description": "How many times has the devtool's Developer Toolbar been opened via the toolbox button?" }, + "DEVTOOLS_WEBIDE_OPENED_BOOLEAN": { + "expires_in_version": "never", + "kind": "boolean", + "description": "How many times has the DevTools WebIDE been opened?" + }, "DEVTOOLS_CUSTOM_OPENED_BOOLEAN": { "expires_in_version": "never", "kind": "boolean", "description": "How many times has a custom developer tool been opened via the toolbox button?" }, "DEVTOOLS_TOOLBOX_OPENED_PER_USER_FLAG": { "expires_in_version": "never", "kind": "flag", @@ -5981,16 +5986,21 @@ "kind": "flag", "description": "How many users have opened the devtool's Responsive View been opened via the toolbox button?" }, "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG": { "expires_in_version": "never", "kind": "flag", "description": "How many users have opened the devtool's Developer Toolbar been opened via the toolbox button?" }, + "DEVTOOLS_WEBIDE_OPENED_PER_USER_FLAG": { + "expires_in_version": "never", + "kind": "flag", + "description": "How many users have opened the DevTools WebIDE?" + }, "DEVTOOLS_CUSTOM_OPENED_PER_USER_FLAG": { "expires_in_version": "never", "kind": "flag", "description": "How many users have opened a custom developer tool via the toolbox button?" }, "DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS": { "expires_in_version": "never", "kind": "exponential", @@ -6140,23 +6150,77 @@ }, "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS": { "expires_in_version": "never", "kind": "exponential", "high": "10000000", "n_buckets": 100, "description": "How long has the developer toolbar been active (seconds)" }, + "DEVTOOLS_WEBIDE_TIME_ACTIVE_SECONDS": { + "expires_in_version": "never", + "kind": "exponential", + "high": "10000000", + "n_buckets": 100, + "description": "How long has WebIDE been active (seconds)" + }, "DEVTOOLS_CUSTOM_TIME_ACTIVE_SECONDS": { "expires_in_version": "never", "kind": "exponential", "high": "10000000", "n_buckets": 100, "description": "How long has a custom developer tool been active (seconds)" }, + "DEVTOOLS_WEBIDE_CONNECTION_RESULT": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Did WebIDE runtime connection succeed?" + }, + "DEVTOOLS_WEBIDE_USB_CONNECTION_RESULT": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Did WebIDE USB runtime connection succeed?" + }, + "DEVTOOLS_WEBIDE_WIFI_CONNECTION_RESULT": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Did WebIDE WiFi runtime connection succeed?" + }, + "DEVTOOLS_WEBIDE_SIMULATOR_CONNECTION_RESULT": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Did WebIDE simulator runtime connection succeed?" + }, + "DEVTOOLS_WEBIDE_REMOTE_CONNECTION_RESULT": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Did WebIDE remote runtime connection succeed?" + }, + "DEVTOOLS_WEBIDE_LOCAL_CONNECTION_RESULT": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Did WebIDE local runtime connection succeed?" + }, + "DEVTOOLS_WEBIDE_CONNECTION_TIME_SECONDS": { + "expires_in_version": "never", + "kind": "exponential", + "high": "10000000", + "n_buckets": 100, + "description": "How long was WebIDE connected to a runtime (seconds)?" + }, + "DEVTOOLS_WEBIDE_CONNECTION_PLAY_USED": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Was WebIDE's play button used during this runtime connection?" + }, + "DEVTOOLS_WEBIDE_CONNECTION_DEBUG_USED": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Was WebIDE's debug button used during this runtime connection?" + }, "BROWSER_IS_USER_DEFAULT": { "expires_in_version": "never", "kind": "boolean", "description": "The result of the startup default desktop browser check." }, "MIXED_CONTENT_PAGE_LOAD": { "expires_in_version": "never", "kind": "enumerated",