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
--- 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");
+ });
});