Bug 1159043 - Record WebIDE runtime info in Telemetry. r=ochameau a=sylvestre
authorJ. Ryan Stinnett <jryans@gmail.com>
Thu, 07 May 2015 17:10:49 -0500
changeset 275041 3527e2751686fa3a546beac34695a1221bcd9efb
parent 275040 1b69a66f875651d6f44d694dbf40e2c076c404d8
child 275042 8f9997e401f6372d6b55da66b2aac6d8524b6c23
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersochameau, sylvestre
bugs1159043
milestone40.0a2
Bug 1159043 - Record WebIDE runtime info in Telemetry. r=ochameau a=sylvestre
browser/devtools/shared/telemetry.js
browser/devtools/webide/modules/app-manager.js
browser/devtools/webide/test/sidebars/test_telemetry.html
browser/devtools/webide/test/test_telemetry.html
toolkit/components/telemetry/Histograms.json
--- a/browser/devtools/shared/telemetry.js
+++ b/browser/devtools/shared/telemetry.js
@@ -276,16 +276,38 @@ Telemetry.prototype = {
       } catch(e) {
         dump("Warning: An attempt was made to write to the " + histogramId +
              " histogram, which is not defined in Histograms.json\n");
       }
     }
   },
 
   /**
+   * Log a value to a keyed histogram.
+   *
+   * @param  {String} histogramId
+   *         Histogram in which the data is to be stored.
+   * @param  {String} key
+   *         The key within the single histogram.
+   * @param  value
+   *         Value to store.
+   */
+  logKeyed: function(histogramId, key, value) {
+    if (histogramId) {
+      try {
+        let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
+        histogram.add(key, value);
+      } catch(e) {
+        dump("Warning: An attempt was made to write to the " + histogramId +
+             " histogram, which is not defined in Histograms.json\n");
+      }
+    }
+  },
+
+  /**
    * Log info about usage once per browser version. This allows us to discover
    * how many individual users are using our tools for each browser version.
    *
    * @param  {String} perUserHistogram
    *         Histogram in which the data is to be stored.
    */
   logOncePerBrowserVersion: function(perUserHistogram, value) {
     let currentVersion = appInfo.version;
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -124,16 +124,18 @@ let AppManager = exports.AppManager = {
    *     specific tab or app) are now available, so we can test for features
    *     like preferences and settings.
    *   runtime-details:
    *     The selected runtime's details have changed, such as its user-visible
    *     name.
    *   runtime-list:
    *     The list of available runtimes has changed, or any of the user-visible
    *     details (like names) for the non-selected runtimes has changed.
+   *   runtime-telemetry:
+   *     Detailed runtime telemetry has been recorded.  Used by tests.
    *   runtime-targets:
    *     The list of remote runtime targets available from the currently
    *     connected runtime (such as tabs or apps) has changed, or any of the
    *     user-visible details (like names) for the non-selected runtime targets
    *     has changed.  This event includes |type| in the details, to distinguish
    *     "apps" and "tabs".
    */
   update: function(what, details) {
@@ -178,25 +180,27 @@ let AppManager = exports.AppManager = {
           front.on("install-progress", this.onInstallProgress);
           front.watchApps(() => this.checkIfProjectIsRunning())
           .then(() => {
             // This can't be done earlier as many operations
             // in the apps actor require watchApps to be called
             // first.
             this._appsFront = front;
             this._listTabsResponse = response;
+            this._recordRuntimeInfo();
             this.update("runtime-global-actors");
           })
           .then(() => {
             this.checkIfProjectIsRunning();
             this.update("runtime-targets", { type: "apps" });
             front.fetchIcons().then(() => this.update("runtime-apps-icons"));
           });
         } else {
           this._listTabsResponse = response;
+          this._recordRuntimeInfo();
           this.update("runtime-global-actors");
         }
       });
     }
 
     this.update("connection");
   },
 
@@ -493,16 +497,43 @@ let AppManager = exports.AppManager = {
       // Empty rejection handler to silence uncaught rejection warnings
       // |connectToRuntime| caller should listen for rejections.
       // Bug 1121100 may find a better way to silence these.
     });
 
     return deferred.promise;
   },
 
+  _recordRuntimeInfo: Task.async(function*() {
+    if (!this.connected) {
+      return;
+    }
+    let runtime = this.selectedRuntime;
+    this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE",
+                             runtime.type || "UNKNOWN", true);
+    this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID",
+                             runtime.id || "unknown", true);
+    if (!this.deviceFront) {
+      this.update("runtime-telemetry");
+      return;
+    }
+    let d = yield this.deviceFront.getDescription();
+    this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PROCESSOR",
+                             d.processor, true);
+    this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_OS",
+                             d.os, true);
+    this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PLATFORM_VERSION",
+                             d.platformversion, true);
+    this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_APP_TYPE",
+                             d.apptype, true);
+    this._telemetry.logKeyed("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_VERSION",
+                             d.version, true);
+    this.update("runtime-telemetry");
+  }),
+
   isMainProcessDebuggable: function() {
     // Fx <39 exposes chrome tab actors on RootActor
     // Fx >=39 exposes a dedicated actor via getProcess request
     return this.connection.client &&
            this.connection.client.mainRoot &&
            this.connection.client.mainRoot.traits.allowChromeProcess ||
            (this._listTabsResponse &&
             this._listTabsResponse.consoleActor);
--- a/browser/devtools/webide/test/sidebars/test_telemetry.html
+++ b/browser/devtools/webide/test/sidebars/test_telemetry.html
@@ -30,21 +30,28 @@
         Telemetry.prototype.log = function(histogramId, value) {
           if (histogramId) {
             if (!this.telemetryInfo[histogramId]) {
               this.telemetryInfo[histogramId] = [];
             }
             this.telemetryInfo[histogramId].push(value);
           }
         }
+        Telemetry.prototype._oldlogKeyed = Telemetry.prototype.logKeyed;
+        Telemetry.prototype.logKeyed = function(histogramId, key, value) {
+          // This simple reduction is enough to test WebIDE's usage
+          this.log(`${histogramId}|${key}`, value);
+        }
       }
 
       function resetTelemetry() {
         Telemetry.prototype.log = Telemetry.prototype._oldlog;
+        Telemetry.prototype.logKeyed = Telemetry.prototype._oldlogKeyed;
         delete Telemetry.prototype._oldlog;
+        delete Telemetry.prototype._oldlogKeyed;
         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);
@@ -71,17 +78,17 @@
         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");
+        let sim = new _SimulatorRuntime({ id: "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();
         };
         Object.defineProperty(sim, "name", {
@@ -134,16 +141,20 @@
         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");
+          // Logging runtime info needs to use the device actor
+          yield waitForUpdate(win, "runtime-global-actors");
+          // Ensure detailed telemetry is recorded
+          yield waitForUpdate(win, "runtime-telemetry");
         });
       }
 
       function connectToRuntime(win, docRuntime, type, index) {
         return Task.spawn(function*() {
           startConnection(win, docRuntime, type, index);
           yield waitUntilConnected(win);
         });
@@ -193,29 +204,71 @@
           } else if (histId.endsWith("USED")) {
             ok(value.length === 6, histId + " has 6 connection actions");
 
             let okay = value.every(function(element) {
               return !element;
             });
 
             ok(okay, "All " + histId + " actions were skipped");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|USB") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|WIFI") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|SIMULATOR") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|REMOTE") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|LOCAL") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|OTHER") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeUSB") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeWiFi") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeSimulator") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|unknown") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|local") {
+            is(value.length, 2, histId + " has 2 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PROCESSOR")) {
+            let processor = histId.split("|")[1];
+            is(processor, Services.appinfo.XPCOMABI.split("-")[0], "Found runtime processor");
+            is(value.length, 6, histId + " has 6 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_OS")) {
+            let os = histId.split("|")[1];
+            is(os, Services.appinfo.OS, "Found runtime OS");
+            is(value.length, 6, histId + " has 6 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PLATFORM_VERSION")) {
+            let platformversion = histId.split("|")[1];
+            is(platformversion, Services.appinfo.platformVersion, "Found runtime platform version");
+            is(value.length, 6, histId + " has 6 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_APP_TYPE")) {
+            let apptype = histId.split("|")[1];
+            is(apptype, "firefox", "Found runtime app type");
+            is(value.length, 6, histId + " has 6 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_VERSION")) {
+            let version = histId.split("|")[1];
+            is(version, Services.appinfo.version, "Found runtime version");
+            is(value.length, 6, histId + " has 6 connection results");
           } else {
             ok(false, "Unexpected " + histId + " was logged");
           }
         }
       }
 
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         let win;
 
         SimpleTest.registerCleanupFunction(() => {
-          Task.spawn(function*() {
+          return Task.spawn(function*() {
             if (win) {
               yield closeWebIDE(win);
             }
             DebuggerServer.destroy();
             yield removeAllProjects();
             resetTelemetry();
           });
         });
@@ -240,28 +293,23 @@
           // 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, docRuntime, "usb");
-          yield waitForTime(TOOL_DELAY);
           yield connectToRuntime(win, docRuntime, "wifi");
-          yield waitForTime(TOOL_DELAY);
           yield connectToRuntime(win, docRuntime, "simulator");
-          yield waitForTime(TOOL_DELAY);
           yield connectToRuntime(win, docRuntime, "other", 0 /* remote */);
-          yield waitForTime(TOOL_DELAY);
           yield connectToRuntime(win, docRuntime, "other", 1 /* local */);
-          yield waitForTime(TOOL_DELAY);
           yield connectToRuntime(win, docRuntime, "other", 2 /* other */);
-          yield waitForTime(TOOL_DELAY);
           yield closeWebIDE(win);
+          win = null;
 
           checkResults();
 
           SimpleTest.finish();
         });
       }
     </script>
   </body>
--- a/browser/devtools/webide/test/test_telemetry.html
+++ b/browser/devtools/webide/test/test_telemetry.html
@@ -30,21 +30,28 @@
         Telemetry.prototype.log = function(histogramId, value) {
           if (histogramId) {
             if (!this.telemetryInfo[histogramId]) {
               this.telemetryInfo[histogramId] = [];
             }
             this.telemetryInfo[histogramId].push(value);
           }
         }
+        Telemetry.prototype._oldlogKeyed = Telemetry.prototype.logKeyed;
+        Telemetry.prototype.logKeyed = function(histogramId, key, value) {
+          // This simple reduction is enough to test WebIDE's usage
+          this.log(`${histogramId}|${key}`, value);
+        }
       }
 
       function resetTelemetry() {
         Telemetry.prototype.log = Telemetry.prototype._oldlog;
+        Telemetry.prototype.logKeyed = Telemetry.prototype._oldlogKeyed;
         delete Telemetry.prototype._oldlog;
+        delete Telemetry.prototype._oldlogKeyed;
         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);
@@ -71,17 +78,17 @@
         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");
+        let sim = new _SimulatorRuntime({ id: "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();
         };
         Object.defineProperty(sim, "name", {
@@ -134,16 +141,20 @@
         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");
+          // Logging runtime info needs to use the device actor
+          yield waitForUpdate(win, "runtime-global-actors");
+          // Ensure detailed telemetry is recorded
+          yield waitForUpdate(win, "runtime-telemetry");
         });
       }
 
       function connectToRuntime(win, type, index) {
         return Task.spawn(function*() {
           startConnection(win, type, index);
           yield waitUntilConnected(win);
         });
@@ -193,29 +204,71 @@
           } else if (histId.endsWith("USED")) {
             ok(value.length === 6, histId + " has 6 connection actions");
 
             let okay = value.every(function(element) {
               return !element;
             });
 
             ok(okay, "All " + histId + " actions were skipped");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|USB") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|WIFI") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|SIMULATOR") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|REMOTE") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|LOCAL") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE|OTHER") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeUSB") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeWiFi") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|fakeSimulator") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|unknown") {
+            is(value.length, 1, histId + " has 1 connection results");
+          } else if (histId === "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID|local") {
+            is(value.length, 2, histId + " has 2 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PROCESSOR")) {
+            let processor = histId.split("|")[1];
+            is(processor, Services.appinfo.XPCOMABI.split("-")[0], "Found runtime processor");
+            is(value.length, 6, histId + " has 6 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_OS")) {
+            let os = histId.split("|")[1];
+            is(os, Services.appinfo.OS, "Found runtime OS");
+            is(value.length, 6, histId + " has 6 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PLATFORM_VERSION")) {
+            let platformversion = histId.split("|")[1];
+            is(platformversion, Services.appinfo.platformVersion, "Found runtime platform version");
+            is(value.length, 6, histId + " has 6 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_APP_TYPE")) {
+            let apptype = histId.split("|")[1];
+            is(apptype, "firefox", "Found runtime app type");
+            is(value.length, 6, histId + " has 6 connection results");
+          } else if (histId.startsWith("DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_VERSION")) {
+            let version = histId.split("|")[1];
+            is(version, Services.appinfo.version, "Found runtime version");
+            is(value.length, 6, histId + " has 6 connection results");
           } else {
             ok(false, "Unexpected " + histId + " was logged");
           }
         }
       }
 
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         let win;
 
         SimpleTest.registerCleanupFunction(() => {
-          Task.spawn(function*() {
+          return Task.spawn(function*() {
             if (win) {
               yield closeWebIDE(win);
             }
             DebuggerServer.destroy();
             yield removeAllProjects();
             resetTelemetry();
           });
         });
@@ -237,28 +290,23 @@
           // 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, "other", 0 /* remote */);
-          yield waitForTime(TOOL_DELAY);
           yield connectToRuntime(win, "other", 1 /* local */);
-          yield waitForTime(TOOL_DELAY);
           yield connectToRuntime(win, "other", 2 /* other */);
-          yield waitForTime(TOOL_DELAY);
           yield closeWebIDE(win);
+          win = null;
 
           checkResults();
 
           SimpleTest.finish();
         });
       }
     </script>
   </body>
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6566,16 +6566,58 @@
     "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?"
   },
+  "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_TYPE": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "keyed": true,
+    "description": "What runtime type did WebIDE connect to?"
+  },
+  "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_ID": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "keyed": true,
+    "description": "What runtime ID did WebIDE connect to?"
+  },
+  "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PROCESSOR": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "keyed": true,
+    "description": "What runtime processor did WebIDE connect to?"
+  },
+  "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_OS": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "keyed": true,
+    "description": "What runtime OS did WebIDE connect to?"
+  },
+  "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_PLATFORM_VERSION": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "keyed": true,
+    "description": "What runtime platform version did WebIDE connect to?"
+  },
+  "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_APP_TYPE": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "keyed": true,
+    "description": "What runtime app type did WebIDE connect to?"
+  },
+  "DEVTOOLS_WEBIDE_CONNECTED_RUNTIME_VERSION": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "keyed": true,
+    "description": "What runtime version did WebIDE connect to?"
+  },
   "DEVTOOLS_OS_ENUMERATED_PER_USER": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 13,
     "description": "OS of DevTools user (0:Windows XP, 1:Windows Vista, 2:Windows 7, 3:Windows 8, 4:Windows 8.1, 5:OSX, 6:Linux 7:reserved, 8:reserved, 9:reserved, 10:reserved, 11:reserved, 12:other)"
   },
   "DEVTOOLS_OS_IS_64_BITS_PER_USER": {
     "expires_in_version": "never",