Bug 1468550 - Track webextension activity - r?kmag draft
authorTarek Ziadé <tarek@mozilla.com>
Fri, 27 Jul 2018 19:26:12 +0200
changeset 823591 7b6cf3c1b9627ddccae10dddcb64e04984671ac7
parent 823410 1b89cf85e398774d69f39fd0a8615eaefd306436
child 823793 75c6f9a3c2b91ff002e5dbc26b95ad1b4d3cf91d
child 824615 459552239f3a1bdeaaa075e1b67f244edfaca793
child 824621 6f5e7bd51ec9f66899ecbd4e3e95ae15438665e0
push id117742
push usertziade@mozilla.com
push dateFri, 27 Jul 2018 17:27:34 +0000
reviewerskmag
bugs1468550
milestone63.0a1
Bug 1468550 - Track webextension activity - r?kmag Adds a performance counter in ParentAPIManager to track the number and duration of API calls by webextensions. MozReview-Commit-ID: PTpaSCkE6A
modules/libpref/init/all.js
toolkit/components/extensions/ExtensionParent.jsm
toolkit/components/extensions/test/xpcshell/test_ext_performance_counters.js
toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4964,16 +4964,19 @@ pref("extensions.webextensions.protocol.
 // Enable tab hiding API by default.
 pref("extensions.webextensions.tabhide.enabled", true);
 
 pref("extensions.webextensions.background-delayed-startup", false);
 
 // Whether or not the installed extensions should be migrated to the storage.local IndexedDB backend.
 pref("extensions.webextensions.ExtensionStorageIDB.enabled", false);
 
+// if enabled, store execution times for API calls
+pref("extensions.webextensions.enablePerformanceCounters", false);
+
 // Report Site Issue button
 pref("extensions.webcompat-reporter.newIssueEndpoint", "https://webcompat.com/issues/new");
 #if defined(MOZ_DEV_EDITION) || defined(NIGHTLY_BUILD)
 pref("extensions.webcompat-reporter.enabled", true);
 #else
 pref("extensions.webcompat-reporter.enabled", false);
 #endif
 
--- a/toolkit/components/extensions/ExtensionParent.jsm
+++ b/toolkit/components/extensions/ExtensionParent.jsm
@@ -55,16 +55,17 @@ var {
   promiseEvent,
   promiseObserved,
 } = ExtensionUtils;
 
 const BASE_SCHEMA = "chrome://extensions/content/schemas/manifest.json";
 const CATEGORY_EXTENSION_MODULES = "webextension-modules";
 const CATEGORY_EXTENSION_SCHEMAS = "webextension-schemas";
 const CATEGORY_EXTENSION_SCRIPTS = "webextension-scripts";
+const TIMING_ENABLED_PREF = "extensions.webextensions.enablePerformanceCounters";
 
 let schemaURLs = new Set();
 
 schemaURLs.add("chrome://extensions/content/schemas/experiments.json");
 
 let GlobalManager;
 let ParentAPIManager;
 let ProxyMessenger;
@@ -744,26 +745,30 @@ class DevToolsExtensionPageContextParent
 
     this._devToolsToolbox = null;
 
     super.shutdown();
   }
 }
 
 ParentAPIManager = {
+  // stores dispatches counts per web extension and API
+  performanceCounters: new DefaultMap(() => new DefaultMap(() => ({duration: 0, calls: 0}))),
+
   proxyContexts: new Map(),
 
   init() {
     Services.obs.addObserver(this, "message-manager-close");
 
     Services.mm.addMessageListener("API:CreateProxyContext", this);
     Services.mm.addMessageListener("API:CloseProxyContext", this, true);
     Services.mm.addMessageListener("API:Call", this);
     Services.mm.addMessageListener("API:AddListener", this);
     Services.mm.addMessageListener("API:RemoveListener", this);
+    XPCOMUtils.defineLazyPreferenceGetter(this, "_timingEnabled", TIMING_ENABLED_PREF, false);
   },
 
   attachMessageManager(extension, processMessageManager) {
     extension.parentMessageManager = processMessageManager;
   },
 
   async observe(subject, topic, data) {
     if (topic === "message-manager-close") {
@@ -871,16 +876,36 @@ ParentAPIManager = {
   closeProxyContext(childId) {
     let context = this.proxyContexts.get(childId);
     if (context) {
       context.unload();
       this.proxyContexts.delete(childId);
     }
   },
 
+  storeExecutionTime(webExtensionId, apiPath, duration) {
+    let apiCounter = this.performanceCounters.get(webExtensionId).get(apiPath);
+    apiCounter.duration += duration;
+    apiCounter.calls += 1;
+  },
+
+  async withTiming(data, callable) {
+    if (!this._timingEnabled) {
+      return callable();
+    }
+    let webExtId = data.childId.split(".")[0];
+    let start = Cu.now();
+    try {
+      return callable();
+    } finally {
+      let end = Cu.now();
+      this.storeExecutionTime(webExtId, data.path, end - start);
+    }
+  },
+
   async call(data, target) {
     let context = this.getContextById(data.childId);
     if (context.parentMessageManager !== target.messageManager) {
       throw new Error("Got message on unexpected message manager");
     }
 
     let reply = result => {
       if (!context.parentMessageManager) {
@@ -896,18 +921,21 @@ ParentAPIManager = {
           callId: data.callId,
         }, result));
     };
 
     try {
       let args = data.args;
       let pendingBrowser = context.pendingEventBrowser;
       let fun = await context.apiCan.asyncFindAPIPath(data.path);
-      let result = context.withPendingBrowser(pendingBrowser,
-                                              () => fun(...args));
+      let result = this.withTiming(data, () => {
+        return context.withPendingBrowser(pendingBrowser,
+                                          () => fun(...args));
+      });
+
       if (data.callId) {
         result = result || Promise.resolve();
 
         result.then(result => {
           result = result instanceof SpreadArgs ? [...result] : [result];
 
           let holder = new StructuredCloneHolder(result);
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_performance_counters.js
@@ -0,0 +1,43 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/ExtensionParent.jsm");
+const ENABLE_COUNTER_PREF = "extensions.webextensions.enablePerformanceCounters";
+
+let {
+  ParentAPIManager,
+} = ExtensionParent;
+
+
+async function test_counter() {
+  async function background() {
+    // creating a bookmark
+    let folder = await browser.bookmarks.create({title: "Folder"});
+    await browser.bookmarks.create({title: "Bookmark", url: "http://example.com",
+                                    parentId: folder.id});
+    browser.test.sendMessage("done");
+  }
+
+  let extensionData = {
+    background,
+    manifest: {
+      permissions: ["bookmarks"],
+    },
+  };
+
+  let extension = ExtensionTestUtils.loadExtension(extensionData);
+  await extension.startup();
+  await extension.awaitMessage("done");
+  await extension.unload();
+
+  // check that the bookmarks.create API was tracked
+  let counters = ParentAPIManager.performanceCounters;
+  let counter = counters.get(extension.id).get("bookmarks.create");
+  ok(counter.calls > 0);
+  ok(counter.duration > 0);
+}
+
+add_task(function test_performance_counter() {
+  return runWithPrefs([[ENABLE_COUNTER_PREF, true]], test_counter);
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -129,8 +129,10 @@ skip-if = true # Too frequent intermitte
 subprocess = true
 skip-if = os == "android"
 [test_ext_permissions.js]
 skip-if = os == "android" # Bug 1350559
 [test_proxy_listener.js]
 [test_proxy_scripts.js]
 [test_proxy_scripts_results.js]
 [test_ext_brokenlinks.js]
+[test_ext_performance_counters.js]
+skip-if = os == "android"