Bug 1466880 - Track toolbox session id in event telemetry probes r?yulia draft
authorMichael Ratcliffe <mratcliffe@mozilla.com>
Fri, 29 Jun 2018 16:36:24 +0100
changeset 812693 20fb91bfeaba61fa6f7dabdae2e4c00e08ca4c68
parent 812548 a009b5249a4b78a889fdc5ffcf55ad51715cc686
child 812731 35f2a64e55805d957fb951a16123165a42ce972d
push id114649
push usermratcliffe@mozilla.com
push dateFri, 29 Jun 2018 21:35:03 +0000
reviewersyulia
bugs1466880
milestone63.0a1
Bug 1466880 - Track toolbox session id in event telemetry probes r?yulia MozReview-Commit-ID: SC6Vm4Qn7n
devtools/client/framework/toolbox-host-manager.js
devtools/client/framework/toolbox.js
devtools/client/inspector/markup/markup.js
devtools/client/inspector/rules/views/rule-editor.js
devtools/client/inspector/rules/views/text-property-editor.js
devtools/client/inspector/toolsidebar.js
devtools/client/responsive.html/manager.js
devtools/client/responsive.html/test/browser/browser_telemetry_activate_rdm.js
devtools/client/shared/telemetry.js
devtools/client/webconsole/components/JSTerm.js
devtools/docs/frontend/telemetry.md
toolkit/components/telemetry/Events.yaml
--- a/devtools/client/framework/toolbox-host-manager.js
+++ b/devtools/client/framework/toolbox-host-manager.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const {LocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const Telemetry = require("devtools/client/shared/telemetry");
 
 loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
 loader.lazyRequireGetter(this, "Hosts", "devtools/client/framework/toolbox-hosts", true);
 
 /**
  * Implement a wrapper on the chrome side to setup a Toolbox within Firefox UI.
  *
  * This component handles iframe creation within Firefox, in which we are loading
@@ -47,37 +48,44 @@ function ToolboxHostManager(target, host
     if (!Hosts[hostType]) {
       // If the preference value is unexpected, restore to the default value.
       Services.prefs.clearUserPref(LAST_HOST);
       hostType = Services.prefs.getCharPref(LAST_HOST);
     }
   }
   this.host = this.createHost(hostType, hostOptions);
   this.hostType = hostType;
+  this.telemetry = new Telemetry();
 }
 
 ToolboxHostManager.prototype = {
   async create(toolId) {
     await this.host.create();
 
     this.host.frame.setAttribute("aria-label", L10N.getStr("toolbox.label"));
     this.host.frame.ownerDocument.defaultView.addEventListener("message", this);
     // We have to listen on capture as no event fires on bubble
     this.host.frame.addEventListener("unload", this, true);
 
+    const msSinceProcessStart = parseInt(this.telemetry.msSinceProcessStart(), 10);
     const toolbox = new Toolbox(this.target, toolId, this.host.type,
-                              this.host.frame.contentWindow, this.frameId);
+                                this.host.frame.contentWindow, this.frameId,
+                                msSinceProcessStart);
 
     // Prevent reloading the toolbox when loading the tools in a tab
     // (e.g. from about:debugging)
     const location = this.host.frame.contentWindow.location;
     if (!location.href.startsWith("about:devtools-toolbox")) {
       this.host.frame.setAttribute("src", "about:devtools-toolbox");
     }
 
+    // We set an attribute on the toolbox iframe so that apps do not need
+    // access to the toolbox internals in order to get the session ID.
+    this.host.frame.setAttribute("session_id", msSinceProcessStart);
+
     return toolbox;
   },
 
   handleEvent(event) {
     switch (event.type) {
       case "message":
         this.onMessage(event);
         break;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -95,23 +95,32 @@ loader.lazyGetter(this, "registerHarOver
  *        Tool to select initially
  * @param {Toolbox.HostType} hostType
  *        Type of host that will host the toolbox (e.g. sidebar, window)
  * @param {DOMWindow} contentWindow
  *        The window object of the toolbox document
  * @param {string} frameId
  *        A unique identifier to differentiate toolbox documents from the
  *        chrome codebase when passing DOM messages
+ * @param {Number} msSinceProcessStart
+ *        the number of milliseconds since process start using monotonic
+ *        timestamps (unaffected by system clock changes).
  */
-function Toolbox(target, selectedTool, hostType, contentWindow, frameId) {
+function Toolbox(target, selectedTool, hostType, contentWindow, frameId,
+                 msSinceProcessStart) {
   this._target = target;
   this._win = contentWindow;
   this.frameId = frameId;
   this.telemetry = new Telemetry();
 
+  // The session ID is used to determine which telemetry events belong to which
+  // toolbox session. Because we use Amplitude to analyse the telemetry data we
+  // must use the time since the system wide epoch as the session ID.
+  this.sessionId = msSinceProcessStart;
+
   // Map of the available DevTools WebExtensions:
   //   Map<extensionUUID, extensionName>
   this._webExtensions = new Map();
 
   this._toolPanels = new Map();
   // Map of tool startup components for given tool id.
   this._toolStartups = new Map();
   this._inspectorExtensionSidebars = new Map();
@@ -719,18 +728,20 @@ Toolbox.prototype = {
 
     this.telemetry.getHistogramById(HOST_HISTOGRAM).add(this._getTelemetryHostId());
 
     // Log current theme. The question we want to answer is:
     // "What proportion of users use which themes?"
     const currentTheme = Services.prefs.getCharPref("devtools.theme");
     this.telemetry.keyedScalarAdd(CURRENT_THEME_SCALAR, currentTheme, 1);
 
-    this.telemetry.preparePendingEvent("devtools.main", "open", "tools", null,
-      ["entrypoint", "first_panel", "host", "shortcut", "splitconsole", "width"]);
+    this.telemetry.preparePendingEvent("devtools.main", "open", "tools", null, [
+      "entrypoint", "first_panel", "host", "shortcut",
+      "splitconsole", "width", "session_id"
+    ]);
     this.telemetry.addEventProperty(
       "devtools.main", "open", "tools", null, "host", this._getTelemetryHostString()
     );
   },
 
   /**
    * Create a simple object to store the state of a toolbox button. The checked state of
    * a button can be updated arbitrarily outside of the scope of the toolbar and its
@@ -1888,44 +1899,55 @@ Toolbox.prototype = {
     const prevPanelName = this.getTelemetryPanelNameOrOther(this.currentToolId);
     const cold = !this.getPanel(id);
 
     this.telemetry.addEventProperties("devtools.main", "enter", panelName, null, {
       "host": this._hostType,
       "width": width,
       "start_state": reason,
       "panel_name": panelName,
-      "cold": cold
+      "cold": cold,
+      // "session_id" is included at the end of this method.
     });
 
     // On first load this.currentToolId === undefined so we need to skip sending
     // a devtools.main.exit telemetry event.
     if (this.currentToolId) {
       this.telemetry.recordEvent("devtools.main", "exit", prevPanelName, null, {
         "host": this._hostType,
         "width": width,
         "panel_name": prevPanelName,
         "next_panel": panelName,
-        "reason": reason
+        "reason": reason,
+        "session_id": this.sessionId
       });
     }
 
-    const pending = ["host", "width", "start_state", "panel_name", "cold"];
+    const pending = ["host", "width", "start_state", "panel_name", "cold", "session_id"];
     if (id === "webconsole") {
       pending.push("message_count");
 
       // Cold webconsole event message_count is handled in
       // devtools/client/webconsole/webconsole-output-wrapper.js
       if (!cold) {
         this.telemetry.addEventProperty(
           "devtools.main", "enter", "webconsole", null, "message_count", 0);
       }
     }
     this.telemetry.preparePendingEvent(
       "devtools.main", "enter", panelName, null, pending);
+    this.telemetry.addEventProperty(
+      "devtools.main", "open", "tools", null, "session_id", this.sessionId
+    );
+    // We send the "enter" session ID here to ensure it is always sent *after*
+    // the "open" session ID.
+    this.telemetry.addEventProperty(
+      "devtools.main", "enter", panelName, null, "session_id", this.sessionId
+    );
+
     this.telemetry.toolOpened(id);
   },
 
   /**
    * Focus a tool's panel by id
    * @param  {string} id
    *         The id of tool to focus
    */
@@ -1986,17 +2008,18 @@ Toolbox.prototype = {
     if (iframe) {
       this.setIframeVisible(iframe, true);
     }
 
     return this.loadTool("webconsole").then(() => {
       this.component.setIsSplitConsoleActive(true);
       this.telemetry.recordEvent("devtools.main", "activate", "split_console", null, {
         "host": this._getTelemetryHostString(),
-        "width": Math.ceil(this.win.outerWidth / 50) * 50
+        "width": Math.ceil(this.win.outerWidth / 50) * 50,
+        "session_id": this.sessionId
       });
       this.emit("split-console");
       this.focusConsoleInput();
     });
   },
 
   /**
    * Closes the split console.
@@ -2007,17 +2030,18 @@ Toolbox.prototype = {
   closeSplitConsole: function() {
     this._splitConsole = false;
     Services.prefs.setBoolPref(SPLITCONSOLE_ENABLED_PREF, false);
     this._refreshConsoleDisplay();
     this.component.setIsSplitConsoleActive(false);
 
     this.telemetry.recordEvent("devtools.main", "deactivate", "split_console", null, {
       "host": this._getTelemetryHostString(),
-      "width": Math.ceil(this.win.outerWidth / 50) * 50
+      "width": Math.ceil(this.win.outerWidth / 50) * 50,
+      "session_id": this.sessionId
     });
 
     this.emit("split-console");
 
     if (this._lastFocusedElement) {
       this._lastFocusedElement.focus();
     }
     return promise.resolve();
@@ -2932,25 +2956,27 @@ Toolbox.prototype = {
     // We need to grab a reference to win before this._host is destroyed.
     const win = this.win;
     const host = this._getTelemetryHostString();
     const width = Math.ceil(win.outerWidth / 50) * 50;
     const prevPanelName = this.getTelemetryPanelNameOrOther(this.currentToolId);
 
     this.telemetry.toolClosed("toolbox");
     this.telemetry.recordEvent("devtools.main", "close", "tools", null, {
-      host: host,
-      width: width
+      "host": host,
+      "width": width,
+      "session_id": this.sessionId
     });
     this.telemetry.recordEvent("devtools.main", "exit", prevPanelName, null, {
       "host": host,
       "width": width,
       "panel_name": this.getTelemetryPanelNameOrOther(this.currentToolId),
       "next_panel": "none",
-      "reason": "toolbox_close"
+      "reason": "toolbox_close",
+      "session_id": this.sessionId
     });
 
     // Finish all outstanding tasks (which means finish destroying panels and
     // then destroying the host, successfully or not) before destroying the
     // target.
     deferred.resolve(settleAll(outstanding)
         .catch(console.error)
         .then(() => {
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -1537,17 +1537,18 @@ MarkupView.prototype = {
 
         if (commit) {
           this.updateNodeOuterHTML(node, value, oldValue);
         }
 
         const end = this.telemetry.msSystemNow();
         this.telemetry.recordEvent("devtools.main", "edit_html", "inspector", null, {
           "made_changes": commit,
-          "time_open": end - start
+          "time_open": end - start,
+          "session_id": this.toolbox.sessionId
         });
       });
 
       this.emit("begin-editing");
     });
   },
 
   /**
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.js
@@ -599,17 +599,19 @@ RuleEditor.prototype = {
     // these entries
     this.multipleAddedProperties =
       parseNamedDeclarations(this.rule.cssProperties.isKnown, value, true);
 
     // Blur the editor field now and deal with adding declarations later when
     // the field gets destroyed (see _newPropertyDestroy)
     this.editor.input.blur();
 
-    this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview");
+    this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview", null, {
+      "session_id": this.toolbox.sessionId
+    });
   },
 
   /**
    * Called when the new property editor is destroyed.
    * This is where the properties (type TextProperty) are actually being
    * added, since we want to wait until after the inplace editor `destroy`
    * event has been fired to keep consistent UI state.
    */
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -79,19 +79,19 @@ function TextPropertyEditor(ruleEditor, 
   this.popup = this.ruleView.popup;
   this.prop = property;
   this.prop.editor = this;
   this.browserWindow = this.doc.defaultView.top;
   this._populatedComputed = false;
   this._hasPendingClick = false;
   this._clickedElementOptions = null;
 
-  const toolbox = this.ruleView.inspector.toolbox;
-  this.telemetry = toolbox.telemetry;
-  this.cssProperties = getCssProperties(toolbox);
+  this.toolbox = this.ruleView.inspector.toolbox;
+  this.telemetry = this.toolbox.telemetry;
+  this.cssProperties = getCssProperties(this.toolbox);
 
   this.getGridlineNames = this.getGridlineNames.bind(this);
   this.update = this.update.bind(this);
   this.updatePropertyState = this.updatePropertyState.bind(this);
   this._onEnableClicked = this._onEnableClicked.bind(this);
   this._onExpandClicked = this._onExpandClicked.bind(this);
   this._onNameDone = this._onNameDone.bind(this);
   this._onStartEditing = this._onStartEditing.bind(this);
@@ -736,17 +736,19 @@ TextPropertyEditor.prototype = {
     const checked = this.enable.hasAttribute("checked");
     if (checked) {
       this.enable.removeAttribute("checked");
     } else {
       this.enable.setAttribute("checked", "");
     }
     this.prop.setEnabled(!checked);
     event.stopPropagation();
-    this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview");
+    this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview", null, {
+      "session_id": this.toolbox.sessionId
+    });
   },
 
   /**
    * Handles clicks on the computed property expander. If the computed list is
    * open due to user expanding or style filtering, collapse the computed list
    * and close the expander. Otherwise, add user-open attribute which is used to
    * expand the computed list and tracks whether or not the computed list is
    * expanded by manually by the user.
@@ -807,17 +809,19 @@ TextPropertyEditor.prototype = {
    */
   _onNameDone: function(value, commit, direction) {
     const isNameUnchanged = (!commit && !this.ruleEditor.isEditing) ||
                           this.committed.name === value;
     if (this.prop.value && isNameUnchanged) {
       return;
     }
 
-    this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview");
+    this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview", null, {
+      "session_id": this.toolbox.sessionId
+    });
 
     // Remove a property if the name is empty
     if (!value.trim()) {
       this.remove(direction);
       return;
     }
 
     // Remove a property if the property value is empty and the property
@@ -900,17 +904,19 @@ TextPropertyEditor.prototype = {
     // its original value and enabled or disabled state
     if (value.trim() && isValueUnchanged) {
       this.ruleEditor.rule.previewPropertyValue(this.prop, val.value,
                                                 val.priority);
       this.rule.setPropertyEnabled(this.prop, this.prop.enabled);
       return;
     }
 
-    this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview");
+    this.telemetry.recordEvent("devtools.main", "edit_rule", "ruleview", null, {
+      "session_id": this.toolbox.sessionId
+    });
 
     // Since the value was changed, check if the original propertywas a flex or grid
     // display declaration and hide their respective highlighters.
     if (this.isDisplayFlex()) {
       this.ruleView.highlighters.hideFlexboxHighlighter();
     }
 
     if (this.isDisplayGrid()) {
--- a/devtools/client/inspector/toolsidebar.js
+++ b/devtools/client/inspector/toolsidebar.js
@@ -332,18 +332,19 @@ ToolSidebar.prototype = {
       return;
     }
 
     if (previousToolId) {
       this._telemetry.toolClosed(previousToolId);
 
       this._telemetry.recordEvent("devtools.main", "sidepanel_changed", "inspector", null,
         {
-          oldpanel: previousToolId,
-          newpanel: currentToolId
+          "oldpanel": previousToolId,
+          "newpanel": currentToolId,
+          "session_id": this._toolPanel._toolbox.sessionId
         }
       );
     }
     this._telemetry.toolOpened(currentToolId);
   },
 
   /**
    * Show the sidebar.
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -97,17 +97,18 @@ const ResponsiveUIManager = exports.Resp
       const hasToolbox = !!toolbox;
       const tel = this._telemetry;
       if (hasToolbox) {
         tel.scalarAdd("devtools.responsive.toolbox_opened_first", 1);
       }
 
       tel.recordEvent("devtools.main", "activate", "responsive_design", null, {
         "host": hostType,
-        "width": Math.ceil(window.outerWidth / 50) * 50
+        "width": Math.ceil(window.outerWidth / 50) * 50,
+        "session_id": toolbox ? toolbox.sessionId : -1
       });
 
       // Track opens keyed by the UI entry point used.
       let { trigger } = options;
       if (!trigger) {
         trigger = "unknown";
       }
       tel.keyedScalarAdd("devtools.responsive.open_trigger", trigger, 1);
@@ -136,28 +137,38 @@ const ResponsiveUIManager = exports.Resp
    *          - `menu`:     Web Developer menu item
    *          - `shortcut`: Keyboard shortcut
    *        - `reason`: String detailing the specific cause for closing
    * @return Promise
    *         Resolved (with no value) when closing is complete.
    */
   async closeIfNeeded(window, tab, options = {}) {
     if (this.isActiveForTab(tab)) {
+      const isKnownTab = TargetFactory.isKnownTab(tab);
+      const target = TargetFactory.forTab(tab);
+      const toolbox = gDevTools.getToolbox(target);
+
+      if (!toolbox && !isKnownTab) {
+        // Destroy the tabTarget to avoid a memory leak.
+        target.destroy();
+      }
+
       const ui = this.activeTabs.get(tab);
       const destroyed = await ui.destroy(options);
       if (!destroyed) {
         // Already in the process of destroying, abort.
         return;
       }
 
-      const hostType = Services.prefs.getStringPref("devtools.toolbox.host");
+      const hostType = toolbox ? toolbox.hostType : "none";
       const t = this._telemetry;
       t.recordEvent("devtools.main", "deactivate", "responsive_design", null, {
         "host": hostType,
-        "width": Math.ceil(window.outerWidth / 50) * 50
+        "width": Math.ceil(window.outerWidth / 50) * 50,
+        "session_id": toolbox ? toolbox.sessionId : -1
       });
 
       this.activeTabs.delete(tab);
 
       if (!this.isActiveForWindow(window)) {
         this.removeMenuCheckListenerFor(window);
       }
       this.emit("off", { tab });
--- a/devtools/client/responsive.html/test/browser/browser_telemetry_activate_rdm.js
+++ b/devtools/client/responsive.html/test/browser/browser_telemetry_activate_rdm.js
@@ -17,17 +17,17 @@ const DATA = [
     }
   }, {
     timestamp: null,
     category: "devtools.main",
     method: "deactivate",
     object: "responsive_design",
     value: null,
     extra: {
-      host: "bottom",
+      host: "none",
       width: "1300"
     }
   }, {
     timestamp: null,
     category: "devtools.main",
     method: "activate",
     object: "responsive_design",
     value: null,
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -40,16 +40,24 @@ class Telemetry {
    * 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
    *        Optional parameter. If specified, the timer is associated with this
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -473,16 +473,17 @@ class JSTerm extends Component {
    *        - selectedNodeActor: tells the NodeActor ID of the current selection
    *        in the Inspector, if such a selection exists. This is used by
    *        helper functions that can evaluate on the current selection.
    * @return object
    *         A promise object that is resolved when the server response is
    *         received.
    */
   requestEvaluation(str, options = {}) {
+    const toolbox = gDevTools.getToolbox(this.hud.owner.target);
     const deferred = defer();
 
     function onResult(response) {
       if (!response.error) {
         deferred.resolve(response);
       } else {
         deferred.reject(response);
       }
@@ -497,18 +498,21 @@ class JSTerm extends Component {
       bindObjectActor: options.bindObjectActor,
       frameActor: frameActor,
       selectedNodeActor: options.selectedNodeActor,
       selectedObjectActor: options.selectedObjectActor,
     };
 
     this.webConsoleClient.evaluateJSAsync(str, onResult, evalOptions);
 
+    // Send telemetry event. If we are in the browser toolbox we send -1 as the
+    // toolbox session id.
     this._telemetry.recordEvent("devtools.main", "execute_js", "webconsole", null, {
-      lines: str.split(/\n/).length
+      "lines": str.split(/\n/).length,
+      "session_id": toolbox ? toolbox.sessionId : -1
     });
 
     return deferred.promise;
   }
 
   /**
    * Copy the object/variable by invoking the server
    * which invokes the `copy(variable)` command and makes it
--- a/devtools/docs/frontend/telemetry.md
+++ b/devtools/docs/frontend/telemetry.md
@@ -238,40 +238,42 @@ And use the instance to report e.g. tool
 ```js
 // Event telemetry is disabled by default so enable it for your category.
 this._telemetry.setEventRecordingEnabled("devtools.main", true);
 
 // If you already have all the properties for the event you can send the
 // telemetry event using:
 // this._telemetry.recordEvent(category, method, object, value, extra) e.g.
 this._telemetry.recordEvent("devtools.main", "open", "tools", null, {
-  entrypoint: "ContextMenu",
-  first_panel: "Inspector",
-  host: "bottom",
-  splitconsole: false,
-  width: 1024
+  "entrypoint": "ContextMenu",
+  "first_panel": "Inspector",
+  "host": "bottom",
+  "splitconsole": false,
+  "width": 1024,
+  "session_id": this.toolbox.sessionId
 });
 
 // If your "extra" properties are in different code paths you will need to
 // create a "pending event." These events contain a list of expected properties
 // that can be populated before or after creating the pending event.
 
 // Use the category, method, object, value combinations above to add a
 // property... we do this before creating the pending event simply to
 // demonstrate that properties can be sent before the pending event is created.
 this._telemetry.addEventProperty(
   "devtools.main", "open", "tools", null, "entrypoint", "ContextMenu");
 
 // In this example `"devtools.main", "open", "tools", null` make up the
 // signature of the event and needs to be sent with all properties.
 
 // Create the pending event using
-// this._telemetry.preparePendingEvent(category, method, object, value, expectedPropertyNames) e.g.
+// this._telemetry.preparePendingEvent(category, method, object, value,
+// expectedPropertyNames) e.g.
 this._telemetry.preparePendingEvent("devtools.main", "open", "tools", null,
-  ["entrypoint", "first_panel", "host", "splitconsole", "width"]
+  ["entrypoint", "first_panel", "host", "splitconsole", "width", "session_id"]
 );
 
 // Use the category, method, object, value combinations above to add each
 // property.
 this._telemetry.addEventProperty(
   "devtools.main", "open", "tools", null, "first_panel", "inspector");
 this._telemetry.addEventProperty(
   "devtools.main", "open", "tools", null, "host", "bottom");
--- a/toolkit/components/telemetry/Events.yaml
+++ b/toolkit/components/telemetry/Events.yaml
@@ -406,110 +406,121 @@ devtools.main:
     expiry_version: never
     extra_keys:
       entrypoint: How was the toolbox opened? CommandLine, ContextMenu, DeveloperToolbar, HamburgerMenu, KeyShortcut, SessionRestore or SystemMenu
       first_panel: The name of the first panel opened.
       host: "Toolbox host (positioning): bottom, side, window or other."
       splitconsole: Indicates whether the split console was open.
       width: Toolbox width rounded up to the nearest 50px.
       shortcut: The key combination pressed. Used only in the case that entrypoint === KeyShortcut.
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   close:
     objects: ["tools"]
     bug_numbers: [1453312]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User closes devtools toolbox.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       host: "Toolbox host (positioning): bottom, side, window or other."
       width: Toolbox width rounded up to the nearest 50px.
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   enter:
     objects: ["webconsole", "inspector", "jsdebugger", "styleeditor", "netmonitor", "storage", "other"]
     bug_numbers: [1441070]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User opens a tool in the devtools toolbox.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       host: "Toolbox host (positioning): bottom, side, window or other."
       width: Toolbox width rounded up to the nearest 50px.
       message_count: The number of cached console messages.
       start_state: debuggerStatement, breakpoint, exception, tab_switch, toolbox_show, initial_panel, toggle_settings_off, toggle_settings_on, key_shortcut, select_next_key, select_prev_key, tool_unloaded, inspect_dom, unknown etc.
       panel_name: The name of the panel opened, webconsole, inspector, jsdebugger, styleeditor, netmonitor, storage or other
       cold: Is this the first time the current panel has been opened in this toolbox?
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   exit:
     objects: ["webconsole", "inspector", "jsdebugger", "styleeditor", "netmonitor", "storage", "other"]
     bug_numbers: [1455270]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User closes a tool in the devtools toolbox.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       host: "Toolbox host (positioning): bottom, side, window or other."
       width: Toolbox width rounded up to the nearest 50px.
       next_panel: The name of the panel closed, webconsole, inspector, jsdebugger, styleeditor, netmonitor, storage or other.
       panel_name: The name of the panel opened, webconsole, inspector, jsdebugger, styleeditor, netmonitor, storage or other
       reason: debuggerStatement, breakpoint, exception, tab_switch, toolbox_show, initial_panel, toggle_settings_off, toggle_settings_on, key_shortcut, select_next_key, select_prev_key, tool_unloaded, inspect_dom, toolbox_closed, unknown etc.
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   activate:
     objects: ["responsive_design", "split_console"]
     bug_numbers: [1455273]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User activates the responsive_design or split_console in the devtools toolbox.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       host: "Toolbox host (positioning): bottom, side, window or other."
       width: Toolbox width rounded up to the nearest 50px.
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   deactivate:
     objects: ["responsive_design", "split_console"]
     bug_numbers: [1455275]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User deactivates the responsive_design or split_console in the devtools toolbox.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       host: "Toolbox host (positioning): bottom, side, window or other."
       width: Toolbox width rounded up to the nearest 50px.
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   edit_html:
     objects: ["inspector"]
     bug_numbers: [1463080]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User is editing HTML via the context menu item in the markup view.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       made_changes: Indicates whether changes were made.
       time_open: The amount of time in ms that the HTML editor was open.
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   edit_rule:
     objects: ["ruleview"]
     bug_numbers: [1463081]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User is editing a CSS rule by clicking on or next to a CSS property, enabling / disabling a rule or creating a new property.
     release_channel_collection: opt-out
     expiry_version: never
+    extra_keys:
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   sidepanel_changed:
     objects: ["inspector"]
     bug_numbers: [1463083]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User has switched sidepanel tabs.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       oldpanel: The panel the user is switching from
       newpanel: The panel the user is switching to
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.
   execute_js:
     objects: ["webconsole"]
     bug_numbers: [1463083]
     notification_emails: ["dev-developer-tools@lists.mozilla.org", "hkirschner@mozilla.com"]
     record_in_processes: ["main"]
     description: User has executed some JS in the Web Console.
     release_channel_collection: opt-out
     expiry_version: never
     extra_keys:
       lines: The number of lines contained in the command.
+      session_id: The start time of the session in milliseconds since epoch (Unix Timestamp) e.g. 1396381378123.