Bug 1470073 - Make IOActivityMonitor timer optional - r?valentin draft
authorTarek Ziadé <tarek@mozilla.com>
Tue, 26 Jun 2018 10:43:16 +0200
changeset 810767 447dd1e37ef327649d83f02af430c3a15a6a05a4
parent 809377 27e90ec610a4c8f6b2a73d79cb1a4df38e822e6a
push id114093
push usertziade@mozilla.com
push dateTue, 26 Jun 2018 13:08:08 +0000
reviewersvalentin
bugs1470073
milestone62.0a1
Bug 1470073 - Make IOActivityMonitor timer optional - r?valentin - Introduced the io.activity.enabled pref, so IOActivityMonitor can run without a timer - Added IOActivityMonitor::NotifyActivities() to trigger notifications manually - Added ChromeUtils.requestIOActivity() so we can trigger it via JS MozReview-Commit-ID: 9JA2rLaM496
dom/base/ChromeUtils.cpp
dom/base/ChromeUtils.h
dom/chrome-webidl/ChromeUtils.webidl
modules/libpref/init/all.js
netwerk/base/IOActivityMonitor.cpp
netwerk/base/IOActivityMonitor.h
netwerk/base/nsPISocketTransportService.idl
netwerk/base/nsSocketTransportService2.cpp
netwerk/test/browser/browser_test_io_activity.js
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -8,21 +8,23 @@
 
 #include "jsfriendapi.h"
 #include "WrapperFactory.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/PerformanceUtils.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/IdleDeadline.h"
 #include "mozilla/dom/UnionTypes.h"
 #include "mozilla/dom/WindowBinding.h" // For IdleRequestCallback/Options
+#include "IOActivityMonitor.h"
 #include "nsThreadUtils.h"
 #include "mozJSComponentLoader.h"
 #include "GeckoProfiler.h"
 
 namespace mozilla {
 namespace dom {
 
 /* static */ void
@@ -762,10 +764,18 @@ ChromeUtils::CreateError(const GlobalObj
   if (aStack && !JS_WrapObject(cx, &retVal)) {
     return;
   }
 
   cleanup.release();
   aRetVal.set(retVal);
 }
 
+/* static */ void
+ChromeUtils::RequestIOActivity(GlobalObject&)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(Preferences::GetBool(IO_ACTIVITY_ENABLED_PREF, false));
+  mozilla::Unused << mozilla::net::IOActivityMonitor::NotifyActivities();
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -177,14 +177,16 @@ public:
   static void
   GetCallerLocation(const GlobalObject& global, nsIPrincipal* principal,
                     JS::MutableHandle<JSObject*> aRetval);
 
   static void
   CreateError(const GlobalObject& global, const nsAString& message,
               JS::Handle<JSObject*> stack,
               JS::MutableHandle<JSObject*> aRetVal, ErrorResult& aRv);
+
+  static void RequestIOActivity(GlobalObject& aGlobal);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_ChromeUtils__
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -343,16 +343,21 @@ partial namespace ChromeUtils {
    */
   [Throws]
   object createError(DOMString message, optional object? stack = null);
 
   /**
    * Request performance metrics to the current process & all ontent processes.
    */
   void requestPerformanceMetrics();
+
+  /**
+  * Request IOActivityMonitor to send a notification containing I/O activity
+  */
+  void requestIOActivity();
 };
 
 /**
  * Used by principals and the script security manager to represent origin
  * attributes. The first dictionary is designed to contain the full set of
  * OriginAttributes, the second is used for pattern-matching (i.e. does this
  * OriginAttributesDictionary match the non-empty attributes in this pattern).
  *
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5126,22 +5126,27 @@ pref("memory.dump_reports_on_oom", false
 
 // Number of stack frames to capture in createObjectURL for about:memory.
 pref("memory.blob_report.stack_frames", 0);
 
 // Disable idle observer fuzz, because only privileged content can access idle
 // observers (bug 780507).
 pref("dom.idle-observers-api.fuzz_time.disabled", true);
 
+// Activates the activity monitor
+pref("io.activity.enabled", false);
+
 // Minimum delay in milliseconds between I/O activity notifications (0 means
 // no notifications). I/O activity includes socket and disk files.
 //
 // The delay is the same for both read and write, though
 // they are handled separately. This pref is only read once at startup:
 // a restart is required to enable a new value.
+//
+// io.activity.enabled needs to be set to true
 pref("io.activity.intervalMilliseconds", 0);
 
 // If true, reuse the same global for (almost) everything loaded by the component
 // loader (JS components, JSMs, etc). This saves memory, but makes it possible
 // for the scripts to interfere with each other.  A restart is required for this
 // to take effect.
 pref("jsloader.shareGlobal", true);
 
--- a/netwerk/base/IOActivityMonitor.cpp
+++ b/netwerk/base/IOActivityMonitor.cpp
@@ -378,16 +378,33 @@ IOActivityMonitor::IOActivityMonitor()
 {
   RefPtr<IOActivityMonitor> mon(gInstance);
   MOZ_ASSERT(!mon, "multiple IOActivityMonitor instances!");
 }
 
 NS_IMETHODIMP
 IOActivityMonitor::Notify(nsITimer* aTimer)
 {
+  return NotifyActivities();
+}
+
+// static
+nsresult
+IOActivityMonitor::NotifyActivities()
+{
+  RefPtr<IOActivityMonitor> mon(gInstance);
+  if (!IsActive()) {
+    return NS_ERROR_FAILURE;
+  }
+  return mon->NotifyActivities_Internal();
+}
+
+nsresult
+IOActivityMonitor::NotifyActivities_Internal()
+{
   mozilla::MutexAutoLock lock(mLock);
   nsCOMPtr<nsIRunnable> ev = NotifyIOActivity::Create(mActivities, lock);
   nsresult rv = SystemGroup::EventTargetFor(TaskCategory::Performance)->Dispatch(ev.forget());
   if (NS_FAILED(rv)) {
     NS_WARNING("NS_DispatchToMainThread failed");
     return rv;
   }
   // Reset the counters, remove inactive activities
@@ -446,18 +463,25 @@ IOActivityMonitor::Init_Internal(int32_t
     sNetActivityMonitorLayerMethods.send       = nsNetMon_Send;
     sNetActivityMonitorLayerMethods.recvfrom   = nsNetMon_RecvFrom;
     sNetActivityMonitorLayerMethods.sendto     = nsNetMon_SendTo;
     sNetActivityMonitorLayerMethods.acceptread = nsNetMon_AcceptRead;
     sNetActivityMonitorLayerMethods.close      = nsNetMon_Close;
     sNetActivityMonitorLayerMethodsPtr = &sNetActivityMonitorLayerMethods;
   }
 
+  mInterval = aInterval;
+
+  // if the interval is 0, the timer is not fired
+  // and calls are done explicitely via NotifyActivities
+  if (mInterval == 0) {
+    return NS_OK;
+  }
+
   // create and fire the timer
-  mInterval = aInterval;
   mTimer = NS_NewTimer();
   if (!mTimer) {
     return NS_ERROR_FAILURE;
   }
   return mTimer->InitWithCallback(this, mInterval, nsITimer::TYPE_REPEATING_SLACK);
 }
 
 nsresult
@@ -469,17 +493,19 @@ IOActivityMonitor::Shutdown()
   }
   return mon->Shutdown_Internal();
 }
 
 nsresult
 IOActivityMonitor::Shutdown_Internal()
 {
   mozilla::MutexAutoLock lock(mLock);
-  mTimer->Cancel();
+  if (mTimer) {
+    mTimer->Cancel();
+  }
   mActivities.Clear();
   gInstance = nullptr;
   return NS_OK;
 }
 
 nsresult
 IOActivityMonitor::MonitorSocket(PRFileDesc *aFd)
 {
--- a/netwerk/base/IOActivityMonitor.h
+++ b/netwerk/base/IOActivityMonitor.h
@@ -17,16 +17,19 @@
 #include "nsITimer.h"
 #include "prinrval.h"
 #include "prio.h"
 #include "private/pprio.h"
 #include <stdint.h>
 
 namespace mozilla { namespace net {
 
+#define IO_ACTIVITY_ENABLED_PREF "io.activity.enabled"
+#define IO_ACTIVITY_INTERVAL_PREF "io.activity.intervalMilliseconds"
+
 //
 // IOActivity keeps track of the amount of data
 // sent and received for an FD / Location
 //
 struct IOActivity {
   // the resource location, can be:
   // - socket://ip:port
   // - file://absolute/path
@@ -93,24 +96,29 @@ public:
   static nsresult Write(const nsACString& location, uint32_t aAmount);
 
   static nsresult MonitorFile(PRFileDesc *aFd, const char* aPath);
   static nsresult MonitorSocket(PRFileDesc *aFd);
   static nsresult Read(PRFileDesc *fd, uint32_t aAmount);
   static nsresult Write(PRFileDesc *fd, uint32_t aAmount);
 
   static bool IsActive();
+
+  // collects activities and notifies observers
+  // this method can be called manually or via the timer callback
+  static nsresult NotifyActivities();
 private:
   virtual ~IOActivityMonitor() = default;
   nsresult Init_Internal(int32_t aInterval);
   nsresult Shutdown_Internal();
 
   IOActivity* GetActivity(const nsACString& location);
   nsresult Write_Internal(const nsACString& location, uint32_t aAmount);
   nsresult Read_Internal(const nsACString& location, uint32_t aAmount);
+  nsresult NotifyActivities_Internal();
 
   Activities mActivities;
 
   // timer used to send notifications
   uint32_t mInterval;
   nsCOMPtr<nsITimer> mTimer;
   // protects mActivities accesses
   Mutex mLock;
--- a/netwerk/base/nsPISocketTransportService.idl
+++ b/netwerk/base/nsPISocketTransportService.idl
@@ -44,18 +44,23 @@ interface nsPISocketTransportService : n
   /**
    * Controls the default retransmission count for keepalive probes.
    */
   readonly attribute long keepaliveProbeCount;
 };
 
 %{C++
 /*
- * I/O activity: we send out this topic no more than every
- * intervalMilliseconds (as set by the
- * "io.activity.intervalMilliseconds" preference: if 0 no notifications
- * are sent) if the I/O is currently active (i.e. we're sending/receiving
- * data to/from the socket or writing/reading to/from a file).
+ * I/O activity observer topic. Sends out information about the
+ * amount of data we're sending/receiving via sockets and disk files.
+ *
+ * Activated via the "io.activity.enabled" preference.
+ *
+ * when "io.activity.intervalMilliseconds" is > 0, automatically sends
+ * out this topic no more than every intervalMilliseconds.
+ *
+ * if "io.activity.intervalMilliseconds" is 0, notifications are triggered
+ * manually via IOActivityMonitor::NotifyActivities()
  */
 #define NS_IO_ACTIVITY "io-activity"
 
 
 %}
--- a/netwerk/base/nsSocketTransportService2.cpp
+++ b/netwerk/base/nsSocketTransportService2.cpp
@@ -44,17 +44,16 @@ static Atomic<PRThread*, Relaxed> gSocke
 
 #define SEND_BUFFER_PREF "network.tcp.sendbuffer"
 #define KEEPALIVE_ENABLED_PREF "network.tcp.keepalive.enabled"
 #define KEEPALIVE_IDLE_TIME_PREF "network.tcp.keepalive.idle_time"
 #define KEEPALIVE_RETRY_INTERVAL_PREF "network.tcp.keepalive.retry_interval"
 #define KEEPALIVE_PROBE_COUNT_PREF "network.tcp.keepalive.probe_count"
 #define SOCKET_LIMIT_TARGET 1000U
 #define SOCKET_LIMIT_MIN      50U
-#define IO_ACTIVITY_INTERVAL_PREF "io.activity.intervalMilliseconds"
 #define MAX_TIME_BETWEEN_TWO_POLLS "network.sts.max_time_for_events_between_two_polls"
 #define POLL_BUSY_WAIT_PERIOD "network.sts.poll_busy_wait_period"
 #define POLL_BUSY_WAIT_PERIOD_TIMEOUT "network.sts.poll_busy_wait_period_timeout"
 #define TELEMETRY_PREF "toolkit.telemetry.enabled"
 #define MAX_TIME_FOR_PR_CLOSE_DURING_SHUTDOWN "network.sts.max_time_for_pr_close_during_shutdown"
 #define POLLABLE_EVENT_TIMEOUT "network.sts.pollable_event_timeout"
 
 #define REPAIR_POLLABLE_EVENT_TIME 10
@@ -1441,22 +1440,20 @@ nsSocketTransportService::Observe(nsISup
     SOCKET_LOG(("nsSocketTransportService::Observe topic=%s", topic));
 
     if (!strcmp(topic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
         UpdatePrefs();
         return NS_OK;
     }
 
     if (!strcmp(topic, "profile-initial-state")) {
-        int32_t interval = Preferences::GetInt(IO_ACTIVITY_INTERVAL_PREF, 0);
-        if (interval <= 0) {
-            return NS_OK;
+        if (!Preferences::GetBool(IO_ACTIVITY_ENABLED_PREF, false)) {
+          return NS_OK;
         }
-
-        return net::IOActivityMonitor::Init(interval);
+        return net::IOActivityMonitor::Init(Preferences::GetInt(IO_ACTIVITY_INTERVAL_PREF, 0));
     }
 
     if (!strcmp(topic, "last-pb-context-exited")) {
       nsCOMPtr<nsIRunnable> ev = NewRunnableMethod(
         "net::nsSocketTransportService::ClosePrivateConnections",
         this,
         &nsSocketTransportService::ClosePrivateConnections);
       nsresult rv = Dispatch(ev, nsIEventTarget::DISPATCH_NORMAL);
--- a/netwerk/test/browser/browser_test_io_activity.js
+++ b/netwerk/test/browser/browser_test_io_activity.js
@@ -2,52 +2,82 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const TEST_URL = "http://example.com/browser/dom/tests/browser/dummy.html";
 
 
-add_task(async function test() {
-
-    SpecialPowers.setIntPref('io.activity.intervalMilliseconds', 50);
-    waitForExplicitFinish();
-
-    // grab events..
-    let gotSocket = false;
-    let gotFile = false;
-    let gotSqlite = false;
-    let gotEmptyData = false;
+var gotSocket = false;
+var gotFile = false;
+var gotSqlite = false;
+var gotEmptyData = false;
+var networkActivity = function(subject, topic, value) {
+    subject.QueryInterface(Ci.nsIMutableArray);
+    let enumerator = subject.enumerate();
+    while (enumerator.hasMoreElements()) {
+        let data = enumerator.getNext();
+        data = data.QueryInterface(Ci.nsIIOActivityData);
+        gotEmptyData = data.rx == 0 && data.tx == 0 && !gotEmptyData
+        gotSocket = data.location.startsWith("socket://127.0.0.1:") || gotSocket;
+        gotFile = data.location.endsWith(".js") || gotFile;
+        gotSqlite = data.location.endsWith("places.sqlite") || gotSqlite;
+    }
+};
 
-    let networkActivity = function(subject, topic, value) {
-        subject.QueryInterface(Ci.nsIMutableArray);
-        let enumerator = subject.enumerate();
-        while (enumerator.hasMoreElements()) {
-            let data = enumerator.getNext();
-            data = data.QueryInterface(Ci.nsIIOActivityData);
-            gotEmptyData = data.rx == 0 && data.tx == 0 && !gotEmptyData
-            gotSocket = data.location.startsWith("socket://127.0.0.1:") || gotSocket;
-            gotFile = data.location.endsWith(".js") || gotFile;
-            gotSqlite = data.location.endsWith("places.sqlite") || gotSqlite;
-        }
-    };
 
+function startObserver() {
+    gotSocket = gotFile = gotSqlite = gotEmptyData = false;
     Services.obs.addObserver(networkActivity, "io-activity");
-
     // why do I have to do this ??
     Services.obs.notifyObservers(null, "profile-initial-state", null);
+}
+
+// this test activates the timer and checks the results as they come in
+add_task(async function testWithTimer() {
+    await SpecialPowers.pushPrefEnv({
+      "set": [
+        ["io.activity.enabled", true],
+        ["io.activity.intervalMilliseconds", 50]
+      ]
+    });
+    waitForExplicitFinish();
+    startObserver();
 
     await BrowserTestUtils.withNewTab({ gBrowser, url: "http://example.com" },
       async function(browser) {
         // wait until we get the events back
         await BrowserTestUtils.waitForCondition(() => {
           return gotSocket && gotFile && gotSqlite && !gotEmptyData;
-        }, "wait for events to come in", 250, 5);
+        }, "wait for events to come in", 500);
 
         ok(gotSocket, "A socket was used");
         ok(gotFile, "A file was used");
         ok(gotSqlite, "A sqlite DB was used");
         ok(!gotEmptyData, "Every I/O event had data");
     });
+});
 
-    SpecialPowers.clearUserPref('io.activity.intervalMilliseconds');
+// this test manually triggers notifications via ChromeUtils.requestIOActivity()
+add_task(async function testWithManualCall() {
+    await SpecialPowers.pushPrefEnv({
+      "set": [
+        ["io.activity.enabled", true],
+      ]
+    });
+    waitForExplicitFinish();
+    startObserver();
+
+    await BrowserTestUtils.withNewTab({ gBrowser, url: "http://example.com" },
+      async function(browser) {
+        // wait until we get the events back
+        await BrowserTestUtils.waitForCondition(() => {
+          ChromeUtils.requestIOActivity();
+          return gotSocket && gotFile && gotSqlite && !gotEmptyData;
+        }, "wait for events to come in", 500);
+
+        ok(gotSocket, "A socket was used");
+        ok(gotFile, "A file was used");
+        ok(gotSqlite, "A sqlite DB was used");
+        ok(!gotEmptyData, "Every I/O event had data");
+    });
 });