From 9ea9589875303aa73743ef832133b3b0363e6bfe Mon Sep 17 00:00:00 2001 From: Ethan Tseng Date: Thu, 29 Aug 2013 14:15:08 +0800 Subject: [PATCH] Bug 855951 - Collect and save TX/RX traffic amounts of TCP connections per-App, r=gene, mcmanus --- dom/network/interfaces/nsIDOMTCPSocket.idl | 13 +-- dom/network/interfaces/nsITCPSocketParent.idl | 5 +- dom/network/src/TCPSocket.js | 79 +++++++++++++++++++ dom/network/src/TCPSocketParent.cpp | 14 +++- .../src/TCPSocketParentIntermediary.js | 7 +- dom/network/src/moz.build | 5 +- dom/system/gonk/nsINetworkManager.idl | 3 +- 7 files changed, 115 insertions(+), 11 deletions(-) diff --git a/dom/network/interfaces/nsIDOMTCPSocket.idl b/dom/network/interfaces/nsIDOMTCPSocket.idl index f12afa40934b..d862cf11b97b 100644 --- a/dom/network/interfaces/nsIDOMTCPSocket.idl +++ b/dom/network/interfaces/nsIDOMTCPSocket.idl @@ -27,7 +27,7 @@ interface nsISocketTransport; // Once bug 723206 will be fixed, this method could be replaced by // arguments when instantiating a TCPSocket object. For example it will // be possible to do (similarly to the WebSocket API): -// var s = new MozTCPSocket(host, port); +// var s = new MozTCPSocket(host, port); // Bug 797561 - Expose a server tcp socket API to web applications @@ -215,7 +215,7 @@ interface nsIDOMTCPSocket : nsISupports * Needed to account for multiple possible types that can be provided to * the socket callbacks as arguments. */ -[scriptable, uuid(0baa1be1-6a88-4f85-a6c8-29e95f35c122)] +[scriptable, uuid(234c664c-3d6c-4859-b45c-4e9a98cb5bdc)] interface nsITCPSocketInternal : nsISupports { // Trigger the callback for |type| and provide a DOMError() object with the given data void callListenerError(in DOMString type, in DOMString name); @@ -245,17 +245,20 @@ interface nsITCPSocketInternal : nsISupports { // Create a DOM socket on the child side // This is called when the socket is accepted on the parent side. - // + // // @param socketChild // The socket child object for the IPC implementation. // @param binaryType - // "arraybuffer" to use ArrayBuffer instances + // "arraybuffer" to use ArrayBuffer instances // in the ondata callback and as the argument to send. // @param window // An object to create ArrayBuffer for this window. See Bug 831107. nsIDOMTCPSocket createAcceptedChild(in nsITCPSocketChild socketChild, - in DOMString binaryType, + in DOMString binaryType, in nsIDOMWindow window); + + // Set App ID. + void setAppId(in unsigned long appId); }; /** diff --git a/dom/network/interfaces/nsITCPSocketParent.idl b/dom/network/interfaces/nsITCPSocketParent.idl index ed562ce2ee39..1433eaa68d6e 100644 --- a/dom/network/interfaces/nsITCPSocketParent.idl +++ b/dom/network/interfaces/nsITCPSocketParent.idl @@ -38,12 +38,13 @@ interface nsITCPSocketParent : nsISupports // Intermediate class to handle sending multiple possible data types // and kicking off the chrome process socket object's connection. -[scriptable, uuid(38bec1ed-b863-40dd-ba69-7bd92e568ee3)] +[scriptable, uuid(be67b1b8-03b0-4171-a791-d004458021b6)] interface nsITCPSocketIntermediary : nsISupports { // Open the connection to the server with the given parameters nsIDOMTCPSocket open(in nsITCPSocketParent parent, in DOMString host, in unsigned short port, - in boolean useSSL, in DOMString binaryType); + in boolean useSSL, in DOMString binaryType, + in unsigned long appId); // Listen on a port nsIDOMTCPServerSocket listen(in nsITCPServerSocketParent parent, diff --git a/dom/network/src/TCPSocket.js b/dom/network/src/TCPSocket.js index fbb7288fd58e..94b2a86fbd94 100644 --- a/dom/network/src/TCPSocket.js +++ b/dom/network/src/TCPSocket.js @@ -37,6 +37,7 @@ const kCLOSED = 'closed'; const kRESUME_ERROR = 'Calling resume() on a connection that was not suspended.'; const BUFFER_SIZE = 65536; +const NETWORK_STATS_THRESHOLD = 65536; // XXX we have no TCPError implementation right now because it's really hard to // do on b2g18. On mozilla-central we want a proper TCPError that ideally @@ -161,6 +162,14 @@ TCPSocket.prototype = { _waitingForStartTLS: false, _pendingDataAfterStartTLS: [], +#ifdef MOZ_WIDGET_GONK + // Network statistics (Gonk-specific feature) + _txBytes: 0, + _rxBytes: 0, + _appId: Ci.nsIScriptSecurityManager.NO_APP_ID, + _connectionType: Ci.nsINetworkInterface.NETWORK_TYPE_UNKNOWN, +#endif + // Public accessors. get readyState() { return this._readyState; @@ -315,6 +324,38 @@ TCPSocket.prototype = { BUFFER_SIZE, /* close source*/ false, /* close sink */ false); }, +#ifdef MOZ_WIDGET_GONK + // Helper method for collecting network statistics. + // Note this method is Gonk-specific. + _saveNetworkStats: function ts_saveNetworkStats(enforce) { + if (this._txBytes <= 0 && this._rxBytes <= 0) { + // There is no traffic at all. No need to save statistics. + return; + } + + // If "enforce" is false, the traffic amount is saved to NetworkStatsServiceProxy + // only when the total amount exceeds the predefined threshold value. + // The purpose is to avoid too much overhead for collecting statistics. + let totalBytes = this._txBytes + this._rxBytes; + if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) { + return; + } + + let nssProxy = Cc["@mozilla.org/networkstatsServiceProxy;1"] + .getService(Ci.nsINetworkStatsServiceProxy); + if (!nssProxy) { + LOG("Error: Ci.nsINetworkStatsServiceProxy service is not available."); + return; + } + nssProxy.saveAppStats(this._appId, this._connectionType, Date.now(), + this._rxBytes, this._txBytes); + + // Reset the counters once the statistics is saved to NetworkStatsServiceProxy. + this._txBytes = this._rxBytes = 0; + }, + // End of helper method for network statistics. +#endif + callListener: function ts_callListener(type, data) { if (!this["on" + type]) return; @@ -371,6 +412,14 @@ TCPSocket.prototype = { return that; }, + setAppId: function ts_setAppId(appId) { +#ifdef MOZ_WIDGET_GONK + this._appId = appId; +#else + // Do nothing because _appId only exists on Gonk-specific platform. +#endif + }, + /* end nsITCPSocketInternal methods */ initWindowless: function ts_initWindowless() { @@ -479,6 +528,17 @@ TCPSocket.prototype = { let transport = that._transport = this._createTransport(host, port, that._ssl); transport.setEventSink(that, Services.tm.currentThread); that._initStream(that._binaryType); + +#ifdef MOZ_WIDGET_GONK + // Set _connectionType, which is only required for network statistics. + // Note that nsINetworkManager, as well as nsINetworkStatsServiceProxy, is + // Gonk-specific. + let networkManager = Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager); + if (networkManager && networkManager.active) { + that._connectionType = networkManager.active.type; + } +#endif + return that; }, @@ -589,6 +649,13 @@ TCPSocket.prototype = { } this._ensureCopying(); + +#ifdef MOZ_WIDGET_GONK + // Collect transmitted amount for network statistics. + this._txBytes += length; + this._saveNetworkStats(false); +#endif + return bufferNotFull; }, @@ -621,6 +688,12 @@ TCPSocket.prototype = { }, _maybeReportErrorAndCloseIfOpen: function(status) { +#ifdef MOZ_WIDGET_GONK + // Save network statistics once the connection is closed. + // For now this function is Gonk-specific. + this._saveNetworkStats(true); +#endif + // If we're closed, we've already reported the error or just don't need to // report the error. if (this._readyState === kCLOSED) @@ -813,6 +886,12 @@ TCPSocket.prototype = { } else { this.callListener("data", this._inputStreamScriptable.read(count)); } + +#ifdef MOZ_WIDGET_GONK + // Collect received amount for network statistics. + this._rxBytes += count; + this._saveNetworkStats(false); +#endif }, classID: Components.ID("{cda91b22-6472-11e1-aa11-834fec09cd0a}"), diff --git a/dom/network/src/TCPSocketParent.cpp b/dom/network/src/TCPSocketParent.cpp index 3ac96ea38dde..31732ce76ef0 100644 --- a/dom/network/src/TCPSocketParent.cpp +++ b/dom/network/src/TCPSocketParent.cpp @@ -12,6 +12,8 @@ #include "mozilla/AppProcessChecker.h" #include "mozilla/net/NeckoCommon.h" #include "mozilla/net/PNeckoParent.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/TabParent.h" namespace IPC { @@ -91,6 +93,15 @@ TCPSocketParent::RecvOpen(const nsString& aHost, const uint16_t& aPort, const bo return true; } + // Obtain App ID + uint32_t appId = nsIScriptSecurityManager::NO_APP_ID; + const PContentParent *content = Manager()->Manager(); + const InfallibleTArray& browsers = content->ManagedPBrowserParent(); + if (browsers.Length() > 0) { + TabParent *tab = static_cast(browsers[0]); + appId = tab->OwnAppId(); + } + nsresult rv; mIntermediary = do_CreateInstance("@mozilla.org/tcp-socket-intermediary;1", &rv); if (NS_FAILED(rv)) { @@ -98,7 +109,8 @@ TCPSocketParent::RecvOpen(const nsString& aHost, const uint16_t& aPort, const bo return true; } - rv = mIntermediary->Open(this, aHost, aPort, aUseSSL, aBinaryType, getter_AddRefs(mSocket)); + rv = mIntermediary->Open(this, aHost, aPort, aUseSSL, aBinaryType, appId, + getter_AddRefs(mSocket)); if (NS_FAILED(rv) || !mSocket) { FireInteralError(this, __LINE__); return true; diff --git a/dom/network/src/TCPSocketParentIntermediary.js b/dom/network/src/TCPSocketParentIntermediary.js index 00e2c4c0736d..11ff0658d177 100644 --- a/dom/network/src/TCPSocketParentIntermediary.js +++ b/dom/network/src/TCPSocketParentIntermediary.js @@ -30,12 +30,17 @@ TCPSocketParentIntermediary.prototype = { ); }, - open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType) { + open: function(aParentSide, aHost, aPort, aUseSSL, aBinaryType, aAppId) { let baseSocket = Cc["@mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket); let socket = baseSocket.open(aHost, aPort, {useSecureTransport: aUseSSL, binaryType: aBinaryType}); if (!socket) return null; + let socketInternal = socket.QueryInterface(Ci.nsITCPSocketInternal); + if (socketInternal) { + socketInternal.setAppId(aAppId); + } + // Handlers are set to the JS-implemented socket object on the parent side. this._setCallbacks(aParentSide, socket); return socket; diff --git a/dom/network/src/moz.build b/dom/network/src/moz.build index e8b7b9483f0e..35f6555de43e 100644 --- a/dom/network/src/moz.build +++ b/dom/network/src/moz.build @@ -32,11 +32,14 @@ if CONFIG['MOZ_B2G_RIL']: EXTRA_COMPONENTS += [ 'TCPServerSocket.js', - 'TCPSocket.js', 'TCPSocket.manifest', 'TCPSocketParentIntermediary.js', ] +EXTRA_PP_COMPONENTS += [ + 'TCPSocket.js', +] + if CONFIG['MOZ_B2G_RIL']: EXTRA_COMPONENTS += [ 'NetworkStatsManager.js', diff --git a/dom/system/gonk/nsINetworkManager.idl b/dom/system/gonk/nsINetworkManager.idl index 8448c8c0dc63..a9ff4dc82044 100644 --- a/dom/system/gonk/nsINetworkManager.idl +++ b/dom/system/gonk/nsINetworkManager.idl @@ -7,7 +7,7 @@ /** * Information about networks that is exposed to network manager API consumers. */ -[scriptable, uuid(04fe5049-1ea8-4b4f-8c27-d23cd24611bb)] +[scriptable, uuid(f4cf9d88-f962-4d29-9baa-fb295dad387b)] interface nsINetworkInterface : nsISupports { const long NETWORK_STATE_UNKNOWN = -1; @@ -24,6 +24,7 @@ interface nsINetworkInterface : nsISupports */ readonly attribute long state; + const long NETWORK_TYPE_UNKNOWN = -1; const long NETWORK_TYPE_WIFI = 0; const long NETWORK_TYPE_MOBILE = 1; const long NETWORK_TYPE_MOBILE_MMS = 2;