Bug 1135095: add telemetry for window and tab sharing triggers in Loop. r=Standard8,vladan
authorMike de Boer <mdeboer@mozilla.com>
Tue, 24 Mar 2015 11:21:20 +0100
changeset 235160 5c87c00175317df304dfbd6f96170d58b6f6ee69
parent 235159 6ea6b9136d588edea4123e1a052b731f37b728d1
child 235161 d4a39def58114f2146efa7a363d079a1bdb6f28f
push id11933
push usermdeboer@mozilla.com
push dateTue, 24 Mar 2015 10:24:30 +0000
treeherderfx-team@5c87c0017531 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8, vladan
bugs1135095
milestone39.0a1
Bug 1135095: add telemetry for window and tab sharing triggers in Loop. r=Standard8,vladan
browser/components/loop/MozLoopAPI.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/content/shared/js/otSdkDriver.js
browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
browser/components/loop/test/shared/otSdkDriver_test.js
toolkit/components/telemetry/Histograms.json
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -634,16 +634,23 @@ function injectLoopAPI(targetWindow) {
 
     TWO_WAY_MEDIA_CONN_LENGTH: {
       enumerable: true,
       get: function() {
         return Cu.cloneInto(TWO_WAY_MEDIA_CONN_LENGTH, targetWindow);
       }
     },
 
+    SHARING_STATE_CHANGE: {
+      enumerable: true,
+      get: function() {
+        return Cu.cloneInto(SHARING_STATE_CHANGE, targetWindow);
+      }
+    },
+
     fxAEnabled: {
       enumerable: true,
       get: function() {
         return MozLoopService.fxAEnabled;
       },
     },
 
     logInToFxA: {
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -24,30 +24,44 @@ const LOOP_SESSION_TYPE = {
  */
 const TWO_WAY_MEDIA_CONN_LENGTH = {
   SHORTER_THAN_10S: "SHORTER_THAN_10S",
   BETWEEN_10S_AND_30S: "BETWEEN_10S_AND_30S",
   BETWEEN_30S_AND_5M: "BETWEEN_30S_AND_5M",
   MORE_THAN_5M: "MORE_THAN_5M",
 };
 
+/**
+ * Buckets that we segment sharing state change telemetry probes into.
+ *
+ * @type {{WINDOW_ENABLED: String, WINDOW_DISABLED: String,
+ *   BROWSER_ENABLED: String, BROWSER_DISABLED: String}}
+ */
+const SHARING_STATE_CHANGE = {
+  WINDOW_ENABLED: "WINDOW_ENABLED",
+  WINDOW_DISABLED: "WINDOW_DISABLED",
+  BROWSER_ENABLED: "BROWSER_ENABLED",
+  BROWSER_DISABLED: "BROWSER_DISABLED"
+};
+
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL = "loop.debug.loglevel";
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
 
 Cu.importGlobalProperties(["URL"]);
 
-this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE", "TWO_WAY_MEDIA_CONN_LENGTH"];
+this.EXPORTED_SYMBOLS = ["MozLoopService", "LOOP_SESSION_TYPE",
+  "TWO_WAY_MEDIA_CONN_LENGTH", "SHARING_STATE_CHANGE"];
 
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI",
   "resource:///modules/loop/MozLoopAPI.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "convertToRTCStatsReport",
   "resource://gre/modules/media/RTCStatsReport.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Chat", "resource:///modules/Chat.jsm");
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -160,16 +160,18 @@ loop.OTSdkDriver = (function() {
       }
 
       var config = _.extend(this._getCopyPublisherConfig(), options);
 
       this.screenshare = this.sdk.initPublisher(this.getScreenShareElementFunc(),
         config);
       this.screenshare.on("accessAllowed", this._onScreenShareGranted.bind(this));
       this.screenshare.on("accessDenied", this._onScreenShareDenied.bind(this));
+
+      this._noteSharingState(options.videoSource, true);
     },
 
     /**
      * Initiates switching the browser window that is being shared.
      *
      * @param {Integer} windowId  The windowId of the browser.
      */
     switchAcquiredWindow: function(windowId) {
@@ -191,16 +193,17 @@ loop.OTSdkDriver = (function() {
       if (!this.screenshare) {
         return false;
       }
 
       this.session.unpublish(this.screenshare);
       this.screenshare.off("accessAllowed accessDenied");
       this.screenshare.destroy();
       delete this.screenshare;
+      this._noteSharingState(this._windowId ? "browser" : "window", false);
       delete this._windowId;
       return true;
     },
 
     /**
      * Connects a session for the SDK, listening to the required events.
      *
      * sessionData items:
@@ -643,25 +646,25 @@ loop.OTSdkDriver = (function() {
      * Wrapper for adding a keyed value that also updates
      * connectionLengthNoted calls and sets the twoWayMediaStartTime to
      * this.CONNECTION_START_TIME_ALREADY_NOTED.
      *
      * @param {number} callLengthSeconds  the call length in seconds
      * @private
      */
     _noteConnectionLength: function(callLengthSeconds) {
+      var buckets = this.mozLoop.TWO_WAY_MEDIA_CONN_LENGTH;
 
-      var bucket = this.mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S;
-
+      var bucket = buckets.SHORTER_THAN_10S;
       if (callLengthSeconds >= 10 && callLengthSeconds <= 30) {
-        bucket = this.mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S;
+        bucket = buckets.BETWEEN_10S_AND_30S;
       } else if (callLengthSeconds > 30 && callLengthSeconds <= 300) {
-        bucket = this.mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M;
+        bucket = buckets.BETWEEN_30S_AND_5M;
       } else if (callLengthSeconds > 300) {
-        bucket = this.mozLoop.TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M;
+        bucket = buckets.MORE_THAN_5M;
       }
 
       this.mozLoop.telemetryAddKeyedValue("LOOP_TWO_WAY_MEDIA_CONN_LENGTH",
         bucket);
       this._setTwoWayMediaStartTime(this.CONNECTION_START_TIME_ALREADY_NOTED);
 
       this._connectionLengthNotedCalls++;
       if (this._debugTwoWayMediaTelemetry) {
@@ -700,14 +703,39 @@ loop.OTSdkDriver = (function() {
       var callLengthSeconds = (endTime - startTime) / 1000;
       this._noteConnectionLength(callLengthSeconds);
     },
 
     /**
      * If set to true, make it easy to test/verify 2-way media connection
      * telemetry code operation by viewing the logs.
      */
-    _debugTwoWayMediaTelemetry: false
+    _debugTwoWayMediaTelemetry: false,
+
+    /**
+     * Note the sharing state. If this.mozLoop is not defined, we're assumed to
+     * be running in the standalone client and return immediately.
+     *
+     * @param  {String}  type    Type of sharing that was flipped. May be 'window'
+     *                           or 'tab'.
+     * @param  {Boolean} enabled Flag that tells us if the feature was flipped on
+     *                           or off.
+     * @private
+     */
+    _noteSharingState: function(type, enabled) {
+      if (!this.mozLoop) {
+        return;
+      }
+
+      var bucket = this.mozLoop.SHARING_STATE_CHANGE[type.toUpperCase() + "_" +
+        (enabled ? "ENABLED" : "DISABLED")];
+      if (!bucket) {
+        console.error("No sharing state bucket found for '" + type + "'");
+        return;
+      }
+
+      this.mozLoop.telemetryAddKeyedValue("LOOP_SHARING_STATE_CHANGE", bucket);
+    }
   };
 
   return OTSdkDriver;
 
 })();
--- a/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
@@ -42,8 +42,34 @@ add_task(function* test_mozLoop_telemetr
   }
 
   let snapshot = histogram.snapshot();
   is(snapshot["SHORTER_THAN_10S"].sum, 1, "TWO_WAY_MEDIA_CONN_LENGTH.SHORTER_THAN_10S");
   is(snapshot["BETWEEN_10S_AND_30S"].sum, 2, "TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_10S_AND_30S");
   is(snapshot["BETWEEN_30S_AND_5M"].sum, 3, "TWO_WAY_MEDIA_CONN_LENGTH.BETWEEN_30S_AND_5M");
   is(snapshot["MORE_THAN_5M"].sum, 4, "TWO_WAY_MEDIA_CONN_LENGTH.MORE_THAN_5M");
 });
+
+add_task(function* test_mozLoop_telemetryAdd_sharing_buckets() {
+  let histogramId = "LOOP_SHARING_STATE_CHANGE";
+  let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
+  const SHARING_STATES = gMozLoopAPI.SHARING_STATE_CHANGE;
+
+  histogram.clear();
+  for (let value of [SHARING_STATES.WINDOW_ENABLED,
+                     SHARING_STATES.WINDOW_DISABLED,
+                     SHARING_STATES.WINDOW_DISABLED,
+                     SHARING_STATES.BROWSER_ENABLED,
+                     SHARING_STATES.BROWSER_ENABLED,
+                     SHARING_STATES.BROWSER_ENABLED,
+                     SHARING_STATES.BROWSER_DISABLED,
+                     SHARING_STATES.BROWSER_DISABLED,
+                     SHARING_STATES.BROWSER_DISABLED,
+                     SHARING_STATES.BROWSER_DISABLED]) {
+    gMozLoopAPI.telemetryAddKeyedValue(histogramId, value);
+  }
+
+  let snapshot = histogram.snapshot();
+  Assert.strictEqual(snapshot["WINDOW_ENABLED"].sum, 1, "SHARING_STATE_CHANGE.WINDOW_ENABLED");
+  Assert.strictEqual(snapshot["WINDOW_DISABLED"].sum, 2, "SHARING_STATE_CHANGE.WINDOW_DISABLED");
+  Assert.strictEqual(snapshot["BROWSER_ENABLED"].sum, 3, "SHARING_STATE_CHANGE.BROWSER_ENABLED");
+  Assert.strictEqual(snapshot["BROWSER_DISABLED"].sum, 4, "SHARING_STATE_CHANGE.BROWSER_DISABLED");
+});
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -66,16 +66,22 @@ describe("loop.OTSdkDriver", function ()
 
     mozLoop = {
       telemetryAddKeyedValue: sinon.stub(),
       TWO_WAY_MEDIA_CONN_LENGTH: {
         SHORTER_THAN_10S: "SHORTER_THAN_10S",
         BETWEEN_10S_AND_30S: "BETWEEN_10S_AND_30S",
         BETWEEN_30S_AND_5M: "BETWEEN_30S_AND_5M",
         MORE_THAN_5M: "MORE_THAN_5M"
+      },
+      SHARING_STATE_CHANGE: {
+        WINDOW_ENABLED: "WINDOW_ENABLED",
+        WINDOW_DISABLED: "WINDOW_DISABLED",
+        BROWSER_ENABLED: "BROWSER_ENABLED",
+        BROWSER_DISABLED: "BROWSER_DISABLED"
       }
     };
 
     driver = new loop.OTSdkDriver({
       dispatcher: dispatcher,
       sdk: sdk,
       mozLoop: mozLoop,
       isDesktop: true
@@ -184,16 +190,17 @@ describe("loop.OTSdkDriver", function ()
     });
   });
 
   describe("#startScreenShare", function() {
     var fakeElement;
 
     beforeEach(function() {
       sandbox.stub(dispatcher, "dispatch");
+      sandbox.stub(driver, "_noteSharingState");
 
       fakeElement = {
         className: "fakeVideo"
       };
 
       driver.getScreenShareElementFunc = function() {
         return fakeElement;
       };
@@ -209,16 +216,29 @@ describe("loop.OTSdkDriver", function ()
           scrollWithPage: true
         }
       };
       driver.startScreenShare(options);
 
       sinon.assert.calledOnce(sdk.initPublisher);
       sinon.assert.calledWithMatch(sdk.initPublisher, fakeElement, options);
     });
+
+    it("should log a telemetry action", function() {
+      var options = {
+        videoSource: "browser",
+        constraints: {
+          browserWindow: 42,
+          scrollWithPage: true
+        }
+      };
+      driver.startScreenShare(options);
+
+      sinon.assert.calledWithExactly(driver._noteSharingState, "browser", true);
+    });
   });
 
   describe("#switchAcquiredWindow", function() {
     beforeEach(function() {
       var options = {
         videoSource: "browser",
         constraints: {
           browserWindow: 42,
@@ -246,36 +266,80 @@ describe("loop.OTSdkDriver", function ()
       sinon.assert.notCalled(publisher._.switchAcquiredWindow);
     });
   });
 
   describe("#endScreenShare", function() {
     beforeEach(function() {
       driver.getScreenShareElementFunc = function() {};
 
+      sandbox.stub(dispatcher, "dispatch");
+      sandbox.stub(driver, "_noteSharingState");
+    });
+
+    it("should unpublish the share", function() {
       driver.startScreenShare({
         videoSource: "window"
       });
-
-      sandbox.stub(dispatcher, "dispatch");
+      driver.session = session;
 
-      driver.session = session;
-    });
-
-    it("should unpublish the share", function() {
       driver.endScreenShare(new sharedActions.EndScreenShare());
 
       sinon.assert.calledOnce(session.unpublish);
     });
 
+    it("should log a telemetry action", function() {
+      driver.startScreenShare({
+        videoSource: "window"
+      });
+      driver.session = session;
+
+      driver.endScreenShare(new sharedActions.EndScreenShare());
+
+      sinon.assert.calledWithExactly(driver._noteSharingState, "window", false);
+    });
+
     it("should destroy the share", function() {
+      driver.startScreenShare({
+        videoSource: "window"
+      });
+      driver.session = session;
+
       expect(driver.endScreenShare()).to.equal(true);
 
       sinon.assert.calledOnce(publisher.destroy);
     });
+
+    it("should unpublish the share too when type is 'browser'", function() {
+      driver.startScreenShare({
+        videoSource: "browser",
+        constraints: {
+          browserWindow: 42
+        }
+      });
+      driver.session = session;
+
+      driver.endScreenShare(new sharedActions.EndScreenShare());
+
+      sinon.assert.calledOnce(session.unpublish);
+    });
+
+    it("should log a telemetry action too when type is 'browser'", function() {
+      driver.startScreenShare({
+        videoSource: "browser",
+        constraints: {
+          browserWindow: 42
+        }
+      });
+      driver.session = session;
+
+      driver.endScreenShare(new sharedActions.EndScreenShare());
+
+      sinon.assert.calledWithExactly(driver._noteSharingState, "browser", false);
+    });
   });
 
   describe("#connectSession", function() {
     it("should initialise a new session", function() {
       driver.connectSession(sessionData);
 
       sinon.assert.calledOnce(sdk.initSession);
       sinon.assert.calledWithExactly(sdk.initSession, "3216549870");
@@ -426,16 +490,54 @@ describe("loop.OTSdkDriver", function ()
         driver._isDesktop = false;
 
         driver._noteConnectionLengthIfNeeded(startTimeMS, endTimeMS);
 
         sinon.assert.notCalled(mozLoop.telemetryAddKeyedValue);
       });
   });
 
+  describe("#_noteSharingState", function() {
+    it("should record enabled sharing states for window", function() {
+      driver._noteSharingState("window", true);
+
+      sinon.assert.calledOnce(mozLoop.telemetryAddKeyedValue);
+      sinon.assert.calledWithExactly(mozLoop.telemetryAddKeyedValue,
+        "LOOP_SHARING_STATE_CHANGE",
+        mozLoop.SHARING_STATE_CHANGE.WINDOW_ENABLED);
+    });
+
+    it("should record enabled sharing states for browser", function() {
+      driver._noteSharingState("browser", true);
+
+      sinon.assert.calledOnce(mozLoop.telemetryAddKeyedValue);
+      sinon.assert.calledWithExactly(mozLoop.telemetryAddKeyedValue,
+        "LOOP_SHARING_STATE_CHANGE",
+        mozLoop.SHARING_STATE_CHANGE.BROWSER_ENABLED);
+    });
+
+    it("should record disabled sharing states for window", function() {
+      driver._noteSharingState("window", false);
+
+      sinon.assert.calledOnce(mozLoop.telemetryAddKeyedValue);
+      sinon.assert.calledWithExactly(mozLoop.telemetryAddKeyedValue,
+        "LOOP_SHARING_STATE_CHANGE",
+        mozLoop.SHARING_STATE_CHANGE.WINDOW_DISABLED);
+    });
+
+    it("should record disabled sharing states for browser", function() {
+      driver._noteSharingState("browser", false);
+
+      sinon.assert.calledOnce(mozLoop.telemetryAddKeyedValue);
+      sinon.assert.calledWithExactly(mozLoop.telemetryAddKeyedValue,
+        "LOOP_SHARING_STATE_CHANGE",
+        mozLoop.SHARING_STATE_CHANGE.BROWSER_DISABLED);
+    });
+  });
+
   describe("#forceDisconnectAll", function() {
     it("should not disconnect anything when not connected", function() {
       driver.session = session;
       driver.forceDisconnectAll(function() {});
 
       sinon.assert.notCalled(session.forceDisconnect);
     });
 
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -7282,16 +7282,24 @@
   },
   "LOOP_TWO_WAY_MEDIA_CONN_LENGTH": {
     "expires_in_version": "43",
     "kind": "count",
     "keyed": true,
     "releaseChannelCollection": "opt-out",
     "description": "Connection length for bi-directionally connected media"
   },
+  "LOOP_SHARING_STATE_CHANGE": {
+    "alert_emails": ["firefox-dev@mozilla.org", "mdeboer@mozilla.com"],
+    "expires_in_version": "43",
+    "kind": "count",
+    "keyed": true,
+    "releaseChannelCollection": "opt-in",
+    "description": "Number of times the sharing feature has been enabled and disabled"
+  },
   "E10S_AUTOSTART": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether a session is set to autostart e10s windows"
   },
   "E10S_WINDOW": {
     "expires_in_version": "never",
     "kind": "boolean",