1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/network/src/NetworkStatsService.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1155 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const DEBUG = false; 1.11 +function debug(s) { 1.12 + if (DEBUG) { 1.13 + dump("-*- NetworkStatsService: " + s + "\n"); 1.14 + } 1.15 +} 1.16 + 1.17 +const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components; 1.18 + 1.19 +this.EXPORTED_SYMBOLS = ["NetworkStatsService"]; 1.20 + 1.21 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.22 +Cu.import("resource://gre/modules/Services.jsm"); 1.23 +Cu.import("resource://gre/modules/NetworkStatsDB.jsm"); 1.24 + 1.25 +const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1"; 1.26 +const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}"); 1.27 + 1.28 +const TOPIC_BANDWIDTH_CONTROL = "netd-bandwidth-control" 1.29 + 1.30 +const TOPIC_INTERFACE_REGISTERED = "network-interface-registered"; 1.31 +const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered"; 1.32 +const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI; 1.33 +const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE; 1.34 + 1.35 +// Networks have different status that NetworkStats API needs to be aware of. 1.36 +// Network is present and ready, so NetworkManager provides the whole info. 1.37 +const NETWORK_STATUS_READY = 0; 1.38 +// Network is present but hasn't established a connection yet (e.g. SIM that has not 1.39 +// enabled 3G since boot). 1.40 +const NETWORK_STATUS_STANDBY = 1; 1.41 +// Network is not present, but stored in database by the previous connections. 1.42 +const NETWORK_STATUS_AWAY = 2; 1.43 + 1.44 +// The maximum traffic amount can be saved in the |cachedStats|. 1.45 +const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB 1.46 + 1.47 +const QUEUE_TYPE_UPDATE_STATS = 0; 1.48 +const QUEUE_TYPE_UPDATE_CACHE = 1; 1.49 +const QUEUE_TYPE_WRITE_CACHE = 2; 1.50 + 1.51 +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", 1.52 + "@mozilla.org/parentprocessmessagemanager;1", 1.53 + "nsIMessageListenerManager"); 1.54 + 1.55 +XPCOMUtils.defineLazyServiceGetter(this, "gRil", 1.56 + "@mozilla.org/ril;1", 1.57 + "nsIRadioInterfaceLayer"); 1.58 + 1.59 +XPCOMUtils.defineLazyServiceGetter(this, "networkService", 1.60 + "@mozilla.org/network/service;1", 1.61 + "nsINetworkService"); 1.62 + 1.63 +XPCOMUtils.defineLazyServiceGetter(this, "appsService", 1.64 + "@mozilla.org/AppsService;1", 1.65 + "nsIAppsService"); 1.66 + 1.67 +XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService", 1.68 + "@mozilla.org/settingsService;1", 1.69 + "nsISettingsService"); 1.70 + 1.71 +XPCOMUtils.defineLazyServiceGetter(this, "messenger", 1.72 + "@mozilla.org/system-message-internal;1", 1.73 + "nsISystemMessagesInternal"); 1.74 + 1.75 +this.NetworkStatsService = { 1.76 + init: function() { 1.77 + debug("Service started"); 1.78 + 1.79 + Services.obs.addObserver(this, "xpcom-shutdown", false); 1.80 + Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false); 1.81 + Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false); 1.82 + Services.obs.addObserver(this, TOPIC_BANDWIDTH_CONTROL, false); 1.83 + Services.obs.addObserver(this, "profile-after-change", false); 1.84 + 1.85 + this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); 1.86 + 1.87 + // Object to store network interfaces, each network interface is composed 1.88 + // by a network object (network type and network Id) and a interfaceName 1.89 + // that contains the name of the physical interface (wlan0, rmnet0, etc.). 1.90 + // The network type can be 0 for wifi or 1 for mobile. On the other hand, 1.91 + // the network id is '0' for wifi or the iccid for mobile (SIM). 1.92 + // Each networkInterface is placed in the _networks object by the index of 1.93 + // 'networkId + networkType'. 1.94 + // 1.95 + // _networks object allows to map available network interfaces at low level 1.96 + // (wlan0, rmnet0, etc.) to a network. It's not mandatory to have a 1.97 + // networkInterface per network but can't exist a networkInterface not 1.98 + // being mapped to a network. 1.99 + 1.100 + this._networks = Object.create(null); 1.101 + 1.102 + // There is no way to know a priori if wifi connection is available, 1.103 + // just when the wifi driver is loaded, but it is unloaded when 1.104 + // wifi is switched off. So wifi connection is hardcoded 1.105 + let netId = this.getNetworkId('0', NET_TYPE_WIFI); 1.106 + this._networks[netId] = { network: { id: '0', 1.107 + type: NET_TYPE_WIFI }, 1.108 + interfaceName: null, 1.109 + status: NETWORK_STATUS_STANDBY }; 1.110 + 1.111 + this.messages = ["NetworkStats:Get", 1.112 + "NetworkStats:Clear", 1.113 + "NetworkStats:ClearAll", 1.114 + "NetworkStats:SetAlarm", 1.115 + "NetworkStats:GetAlarms", 1.116 + "NetworkStats:RemoveAlarms", 1.117 + "NetworkStats:GetAvailableNetworks", 1.118 + "NetworkStats:GetAvailableServiceTypes", 1.119 + "NetworkStats:SampleRate", 1.120 + "NetworkStats:MaxStorageAge"]; 1.121 + 1.122 + this.messages.forEach(function(aMsgName) { 1.123 + ppmm.addMessageListener(aMsgName, this); 1.124 + }, this); 1.125 + 1.126 + this._db = new NetworkStatsDB(); 1.127 + 1.128 + // Stats for all interfaces are updated periodically 1.129 + this.timer.initWithCallback(this, this._db.sampleRate, 1.130 + Ci.nsITimer.TYPE_REPEATING_PRECISE); 1.131 + 1.132 + // Stats not from netd are firstly stored in the cached. 1.133 + this.cachedStats = Object.create(null); 1.134 + this.cachedStatsDate = new Date(); 1.135 + 1.136 + this.updateQueue = []; 1.137 + this.isQueueRunning = false; 1.138 + 1.139 + this._currentAlarms = {}; 1.140 + this.initAlarms(); 1.141 + }, 1.142 + 1.143 + receiveMessage: function(aMessage) { 1.144 + if (!aMessage.target.assertPermission("networkstats-manage")) { 1.145 + return; 1.146 + } 1.147 + 1.148 + debug("receiveMessage " + aMessage.name); 1.149 + 1.150 + let mm = aMessage.target; 1.151 + let msg = aMessage.json; 1.152 + 1.153 + switch (aMessage.name) { 1.154 + case "NetworkStats:Get": 1.155 + this.getSamples(mm, msg); 1.156 + break; 1.157 + case "NetworkStats:Clear": 1.158 + this.clearInterfaceStats(mm, msg); 1.159 + break; 1.160 + case "NetworkStats:ClearAll": 1.161 + this.clearDB(mm, msg); 1.162 + break; 1.163 + case "NetworkStats:SetAlarm": 1.164 + this.setAlarm(mm, msg); 1.165 + break; 1.166 + case "NetworkStats:GetAlarms": 1.167 + this.getAlarms(mm, msg); 1.168 + break; 1.169 + case "NetworkStats:RemoveAlarms": 1.170 + this.removeAlarms(mm, msg); 1.171 + break; 1.172 + case "NetworkStats:GetAvailableNetworks": 1.173 + this.getAvailableNetworks(mm, msg); 1.174 + break; 1.175 + case "NetworkStats:GetAvailableServiceTypes": 1.176 + this.getAvailableServiceTypes(mm, msg); 1.177 + break; 1.178 + case "NetworkStats:SampleRate": 1.179 + // This message is sync. 1.180 + return this._db.sampleRate; 1.181 + case "NetworkStats:MaxStorageAge": 1.182 + // This message is sync. 1.183 + return this._db.maxStorageSamples * this._db.sampleRate; 1.184 + } 1.185 + }, 1.186 + 1.187 + observe: function observe(aSubject, aTopic, aData) { 1.188 + switch (aTopic) { 1.189 + case TOPIC_INTERFACE_REGISTERED: 1.190 + case TOPIC_INTERFACE_UNREGISTERED: 1.191 + 1.192 + // If new interface is registered (notified from NetworkService), 1.193 + // the stats are updated for the new interface without waiting to 1.194 + // complete the updating period. 1.195 + 1.196 + let network = aSubject.QueryInterface(Ci.nsINetworkInterface); 1.197 + debug("Network " + network.name + " of type " + network.type + " status change"); 1.198 + 1.199 + let netId = this.convertNetworkInterface(network); 1.200 + if (!netId) { 1.201 + break; 1.202 + } 1.203 + 1.204 + this._updateCurrentAlarm(netId); 1.205 + 1.206 + debug("NetId: " + netId); 1.207 + this.updateStats(netId); 1.208 + break; 1.209 + 1.210 + case TOPIC_BANDWIDTH_CONTROL: 1.211 + debug("Bandwidth message from netd: " + JSON.stringify(aData)); 1.212 + 1.213 + let interfaceName = aData.substring(aData.lastIndexOf(" ") + 1); 1.214 + for (let networkId in this._networks) { 1.215 + if (interfaceName == this._networks[networkId].interfaceName) { 1.216 + let currentAlarm = this._currentAlarms[networkId]; 1.217 + if (Object.getOwnPropertyNames(currentAlarm).length !== 0) { 1.218 + this._fireAlarm(currentAlarm.alarm); 1.219 + } 1.220 + break; 1.221 + } 1.222 + } 1.223 + break; 1.224 + 1.225 + case "xpcom-shutdown": 1.226 + debug("Service shutdown"); 1.227 + 1.228 + this.messages.forEach(function(aMsgName) { 1.229 + ppmm.removeMessageListener(aMsgName, this); 1.230 + }, this); 1.231 + 1.232 + Services.obs.removeObserver(this, "xpcom-shutdown"); 1.233 + Services.obs.removeObserver(this, "profile-after-change"); 1.234 + Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED); 1.235 + Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED); 1.236 + Services.obs.removeObserver(this, TOPIC_BANDWIDTH_CONTROL); 1.237 + 1.238 + this.timer.cancel(); 1.239 + this.timer = null; 1.240 + 1.241 + // Update stats before shutdown 1.242 + this.updateAllStats(); 1.243 + break; 1.244 + } 1.245 + }, 1.246 + 1.247 + /* 1.248 + * nsITimerCallback 1.249 + * Timer triggers the update of all stats 1.250 + */ 1.251 + notify: function(aTimer) { 1.252 + this.updateAllStats(); 1.253 + }, 1.254 + 1.255 + /* 1.256 + * nsINetworkStatsService 1.257 + */ 1.258 + getRilNetworks: function() { 1.259 + let networks = {}; 1.260 + let numRadioInterfaces = gRil.numRadioInterfaces; 1.261 + for (let i = 0; i < numRadioInterfaces; i++) { 1.262 + let radioInterface = gRil.getRadioInterface(i); 1.263 + if (radioInterface.rilContext.iccInfo) { 1.264 + let netId = this.getNetworkId(radioInterface.rilContext.iccInfo.iccid, 1.265 + NET_TYPE_MOBILE); 1.266 + networks[netId] = { id : radioInterface.rilContext.iccInfo.iccid, 1.267 + type: NET_TYPE_MOBILE }; 1.268 + } 1.269 + } 1.270 + return networks; 1.271 + }, 1.272 + 1.273 + convertNetworkInterface: function(aNetwork) { 1.274 + if (aNetwork.type != NET_TYPE_MOBILE && 1.275 + aNetwork.type != NET_TYPE_WIFI) { 1.276 + return null; 1.277 + } 1.278 + 1.279 + let id = '0'; 1.280 + if (aNetwork.type == NET_TYPE_MOBILE) { 1.281 + if (!(aNetwork instanceof Ci.nsIRilNetworkInterface)) { 1.282 + debug("Error! Mobile network should be an nsIRilNetworkInterface!"); 1.283 + return null; 1.284 + } 1.285 + 1.286 + let rilNetwork = aNetwork.QueryInterface(Ci.nsIRilNetworkInterface); 1.287 + id = rilNetwork.iccId; 1.288 + } 1.289 + 1.290 + let netId = this.getNetworkId(id, aNetwork.type); 1.291 + 1.292 + if (!this._networks[netId]) { 1.293 + this._networks[netId] = Object.create(null); 1.294 + this._networks[netId].network = { id: id, 1.295 + type: aNetwork.type }; 1.296 + } 1.297 + 1.298 + this._networks[netId].status = NETWORK_STATUS_READY; 1.299 + this._networks[netId].interfaceName = aNetwork.name; 1.300 + return netId; 1.301 + }, 1.302 + 1.303 + getNetworkId: function getNetworkId(aIccId, aNetworkType) { 1.304 + return aIccId + '' + aNetworkType; 1.305 + }, 1.306 + 1.307 + /* Function to ensure that one network is valid. The network is valid if its status is 1.308 + * NETWORK_STATUS_READY, NETWORK_STATUS_STANDBY or NETWORK_STATUS_AWAY. 1.309 + * 1.310 + * The result is |netId| or null in case of a non-valid network 1.311 + * aCallback is signatured as |function(netId)|. 1.312 + */ 1.313 + validateNetwork: function validateNetwork(aNetwork, aCallback) { 1.314 + let netId = this.getNetworkId(aNetwork.id, aNetwork.type); 1.315 + 1.316 + if (this._networks[netId]) { 1.317 + aCallback(netId); 1.318 + return; 1.319 + } 1.320 + 1.321 + // Check if network is valid (RIL entry) but has not established a connection yet. 1.322 + // If so add to networks list with empty interfaceName. 1.323 + let rilNetworks = this.getRilNetworks(); 1.324 + if (rilNetworks[netId]) { 1.325 + this._networks[netId] = Object.create(null); 1.326 + this._networks[netId].network = rilNetworks[netId]; 1.327 + this._networks[netId].status = NETWORK_STATUS_STANDBY; 1.328 + this._currentAlarms[netId] = Object.create(null); 1.329 + aCallback(netId); 1.330 + return; 1.331 + } 1.332 + 1.333 + // Check if network is available in the DB. 1.334 + this._db.isNetworkAvailable(aNetwork, function(aError, aResult) { 1.335 + if (aResult) { 1.336 + this._networks[netId] = Object.create(null); 1.337 + this._networks[netId].network = aNetwork; 1.338 + this._networks[netId].status = NETWORK_STATUS_AWAY; 1.339 + this._currentAlarms[netId] = Object.create(null); 1.340 + aCallback(netId); 1.341 + return; 1.342 + } 1.343 + 1.344 + aCallback(null); 1.345 + }.bind(this)); 1.346 + }, 1.347 + 1.348 + getAvailableNetworks: function getAvailableNetworks(mm, msg) { 1.349 + let self = this; 1.350 + let rilNetworks = this.getRilNetworks(); 1.351 + this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) { 1.352 + 1.353 + // Also return the networks that are valid but have not 1.354 + // established connections yet. 1.355 + for (let netId in rilNetworks) { 1.356 + let found = false; 1.357 + for (let i = 0; i < aResult.length; i++) { 1.358 + if (netId == self.getNetworkId(aResult[i].id, aResult[i].type)) { 1.359 + found = true; 1.360 + break; 1.361 + } 1.362 + } 1.363 + if (!found) { 1.364 + aResult.push(rilNetworks[netId]); 1.365 + } 1.366 + } 1.367 + 1.368 + mm.sendAsyncMessage("NetworkStats:GetAvailableNetworks:Return", 1.369 + { id: msg.id, error: aError, result: aResult }); 1.370 + }); 1.371 + }, 1.372 + 1.373 + getAvailableServiceTypes: function getAvailableServiceTypes(mm, msg) { 1.374 + this._db.getAvailableServiceTypes(function onGetServiceTypes(aError, aResult) { 1.375 + mm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes:Return", 1.376 + { id: msg.id, error: aError, result: aResult }); 1.377 + }); 1.378 + }, 1.379 + 1.380 + initAlarms: function initAlarms() { 1.381 + debug("Init usage alarms"); 1.382 + let self = this; 1.383 + 1.384 + for (let netId in this._networks) { 1.385 + this._currentAlarms[netId] = Object.create(null); 1.386 + 1.387 + this._db.getFirstAlarm(netId, function getResult(error, result) { 1.388 + if (!error && result) { 1.389 + self._setAlarm(result, function onSet(error, success) { 1.390 + if (error == "InvalidStateError") { 1.391 + self._fireAlarm(result); 1.392 + } 1.393 + }); 1.394 + } 1.395 + }); 1.396 + } 1.397 + }, 1.398 + 1.399 + /* 1.400 + * Function called from manager to get stats from database. 1.401 + * In order to return updated stats, first is performed a call to 1.402 + * updateAllStats function, which will get last stats from netd 1.403 + * and update the database. 1.404 + * Then, depending on the request (stats per appId or total stats) 1.405 + * it retrieve them from database and return to the manager. 1.406 + */ 1.407 + getSamples: function getSamples(mm, msg) { 1.408 + let network = msg.network; 1.409 + let netId = this.getNetworkId(network.id, network.type); 1.410 + 1.411 + let appId = 0; 1.412 + let appManifestURL = msg.appManifestURL; 1.413 + if (appManifestURL) { 1.414 + appId = appsService.getAppLocalIdByManifestURL(appManifestURL); 1.415 + 1.416 + if (!appId) { 1.417 + mm.sendAsyncMessage("NetworkStats:Get:Return", 1.418 + { id: msg.id, 1.419 + error: "Invalid appManifestURL", result: null }); 1.420 + return; 1.421 + } 1.422 + } 1.423 + 1.424 + let serviceType = msg.serviceType || ""; 1.425 + 1.426 + let start = new Date(msg.start); 1.427 + let end = new Date(msg.end); 1.428 + 1.429 + let callback = (function (aError, aResult) { 1.430 + this._db.find(function onStatsFound(aError, aResult) { 1.431 + mm.sendAsyncMessage("NetworkStats:Get:Return", 1.432 + { id: msg.id, error: aError, result: aResult }); 1.433 + }, appId, serviceType, network, start, end, appManifestURL); 1.434 + }).bind(this); 1.435 + 1.436 + this.validateNetwork(network, function onValidateNetwork(aNetId) { 1.437 + if (!aNetId) { 1.438 + mm.sendAsyncMessage("NetworkStats:Get:Return", 1.439 + { id: msg.id, error: "Invalid connectionType", result: null }); 1.440 + return; 1.441 + } 1.442 + 1.443 + // If network is currently active we need to update the cached stats first before 1.444 + // retrieving stats from the DB. 1.445 + if (this._networks[aNetId].status == NETWORK_STATUS_READY) { 1.446 + debug("getstats for network " + network.id + " of type " + network.type); 1.447 + debug("appId: " + appId + " from appManifestURL: " + appManifestURL); 1.448 + debug("serviceType: " + serviceType); 1.449 + 1.450 + if (appId || serviceType) { 1.451 + this.updateCachedStats(callback); 1.452 + return; 1.453 + } 1.454 + 1.455 + this.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) { 1.456 + this.updateCachedStats(callback); 1.457 + }.bind(this)); 1.458 + return; 1.459 + } 1.460 + 1.461 + // Network not active, so no need to update 1.462 + this._db.find(function onStatsFound(aError, aResult) { 1.463 + mm.sendAsyncMessage("NetworkStats:Get:Return", 1.464 + { id: msg.id, error: aError, result: aResult }); 1.465 + }, appId, serviceType, network, start, end, appManifestURL); 1.466 + }.bind(this)); 1.467 + }, 1.468 + 1.469 + clearInterfaceStats: function clearInterfaceStats(mm, msg) { 1.470 + let self = this; 1.471 + let network = msg.network; 1.472 + 1.473 + debug("clear stats for network " + network.id + " of type " + network.type); 1.474 + 1.475 + this.validateNetwork(network, function onValidateNetwork(aNetId) { 1.476 + if (!aNetId) { 1.477 + mm.sendAsyncMessage("NetworkStats:Clear:Return", 1.478 + { id: msg.id, error: "Invalid connectionType", result: null }); 1.479 + return; 1.480 + } 1.481 + 1.482 + network = {network: network, networkId: aNetId}; 1.483 + self.updateStats(aNetId, function onUpdate(aResult, aMessage) { 1.484 + if (!aResult) { 1.485 + mm.sendAsyncMessage("NetworkStats:Clear:Return", 1.486 + { id: msg.id, error: aMessage, result: null }); 1.487 + return; 1.488 + } 1.489 + 1.490 + self._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) { 1.491 + self._updateCurrentAlarm(aNetId); 1.492 + mm.sendAsyncMessage("NetworkStats:Clear:Return", 1.493 + { id: msg.id, error: aError, result: aResult }); 1.494 + }); 1.495 + }); 1.496 + }); 1.497 + }, 1.498 + 1.499 + clearDB: function clearDB(mm, msg) { 1.500 + let self = this; 1.501 + this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) { 1.502 + if (aError) { 1.503 + mm.sendAsyncMessage("NetworkStats:ClearAll:Return", 1.504 + { id: msg.id, error: aError, result: aResult }); 1.505 + return; 1.506 + } 1.507 + 1.508 + let networks = aResult; 1.509 + networks.forEach(function(network, index) { 1.510 + networks[index] = {network: network, networkId: self.getNetworkId(network.id, network.type)}; 1.511 + }, self); 1.512 + 1.513 + self.updateAllStats(function onUpdate(aResult, aMessage){ 1.514 + if (!aResult) { 1.515 + mm.sendAsyncMessage("NetworkStats:ClearAll:Return", 1.516 + { id: msg.id, error: aMessage, result: null }); 1.517 + return; 1.518 + } 1.519 + 1.520 + self._db.clearStats(networks, function onDBCleared(aError, aResult) { 1.521 + networks.forEach(function(network, index) { 1.522 + self._updateCurrentAlarm(network.networkId); 1.523 + }, self); 1.524 + mm.sendAsyncMessage("NetworkStats:ClearAll:Return", 1.525 + { id: msg.id, error: aError, result: aResult }); 1.526 + }); 1.527 + }); 1.528 + }); 1.529 + }, 1.530 + 1.531 + updateAllStats: function updateAllStats(aCallback) { 1.532 + let elements = []; 1.533 + let lastElement = null; 1.534 + let callback = (function (success, message) { 1.535 + this.updateCachedStats(aCallback); 1.536 + }).bind(this); 1.537 + 1.538 + // For each connectionType create an object containning the type 1.539 + // and the 'queueIndex', the 'queueIndex' is an integer representing 1.540 + // the index of a connection type in the global queue array. So, if 1.541 + // the connection type is already in the queue it is not appended again, 1.542 + // else it is pushed in 'elements' array, which later will be pushed to 1.543 + // the queue array. 1.544 + for (let netId in this._networks) { 1.545 + if (this._networks[netId].status != NETWORK_STATUS_READY) { 1.546 + continue; 1.547 + } 1.548 + 1.549 + lastElement = { netId: netId, 1.550 + queueIndex: this.updateQueueIndex(netId) }; 1.551 + 1.552 + if (lastElement.queueIndex == -1) { 1.553 + elements.push({ netId: lastElement.netId, 1.554 + callbacks: [], 1.555 + queueType: QUEUE_TYPE_UPDATE_STATS }); 1.556 + } 1.557 + } 1.558 + 1.559 + if (!lastElement) { 1.560 + // No elements need to be updated, probably because status is different than 1.561 + // NETWORK_STATUS_READY. 1.562 + if (aCallback) { 1.563 + aCallback(true, "OK"); 1.564 + } 1.565 + return; 1.566 + } 1.567 + 1.568 + if (elements.length > 0) { 1.569 + // If length of elements is greater than 0, callback is set to 1.570 + // the last element. 1.571 + elements[elements.length - 1].callbacks.push(callback); 1.572 + this.updateQueue = this.updateQueue.concat(elements); 1.573 + } else { 1.574 + // Else, it means that all connection types are already in the queue to 1.575 + // be updated, so callback for this request is added to 1.576 + // the element in the main queue with the index of the last 'lastElement'. 1.577 + // But before is checked that element is still in the queue because it can 1.578 + // be processed while generating 'elements' array. 1.579 + let element = this.updateQueue[lastElement.queueIndex]; 1.580 + if (aCallback && 1.581 + (!element || element.netId != lastElement.netId)) { 1.582 + aCallback(); 1.583 + return; 1.584 + } 1.585 + 1.586 + this.updateQueue[lastElement.queueIndex].callbacks.push(callback); 1.587 + } 1.588 + 1.589 + // Call the function that process the elements of the queue. 1.590 + this.processQueue(); 1.591 + 1.592 + if (DEBUG) { 1.593 + this.logAllRecords(); 1.594 + } 1.595 + }, 1.596 + 1.597 + updateStats: function updateStats(aNetId, aCallback) { 1.598 + // Check if the connection is in the main queue, push a new element 1.599 + // if it is not being processed or add a callback if it is. 1.600 + let index = this.updateQueueIndex(aNetId); 1.601 + if (index == -1) { 1.602 + this.updateQueue.push({ netId: aNetId, 1.603 + callbacks: [aCallback], 1.604 + queueType: QUEUE_TYPE_UPDATE_STATS }); 1.605 + } else { 1.606 + this.updateQueue[index].callbacks.push(aCallback); 1.607 + return; 1.608 + } 1.609 + 1.610 + // Call the function that process the elements of the queue. 1.611 + this.processQueue(); 1.612 + }, 1.613 + 1.614 + /* 1.615 + * Find if a connection is in the main queue array and return its 1.616 + * index, if it is not in the array return -1. 1.617 + */ 1.618 + updateQueueIndex: function updateQueueIndex(aNetId) { 1.619 + return this.updateQueue.map(function(e) { return e.netId; }).indexOf(aNetId); 1.620 + }, 1.621 + 1.622 + /* 1.623 + * Function responsible of process all requests in the queue. 1.624 + */ 1.625 + processQueue: function processQueue(aResult, aMessage) { 1.626 + // If aResult is not undefined, the caller of the function is the result 1.627 + // of processing an element, so remove that element and call the callbacks 1.628 + // it has. 1.629 + if (aResult != undefined) { 1.630 + let item = this.updateQueue.shift(); 1.631 + for (let callback of item.callbacks) { 1.632 + if (callback) { 1.633 + callback(aResult, aMessage); 1.634 + } 1.635 + } 1.636 + } else { 1.637 + // The caller is a function that has pushed new elements to the queue, 1.638 + // if isQueueRunning is false it means there is no processing currently 1.639 + // being done, so start. 1.640 + if (this.isQueueRunning) { 1.641 + return; 1.642 + } else { 1.643 + this.isQueueRunning = true; 1.644 + } 1.645 + } 1.646 + 1.647 + // Check length to determine if queue is empty and stop processing. 1.648 + if (this.updateQueue.length < 1) { 1.649 + this.isQueueRunning = false; 1.650 + return; 1.651 + } 1.652 + 1.653 + // Call the update function for the next element. 1.654 + switch (this.updateQueue[0].queueType) { 1.655 + case QUEUE_TYPE_UPDATE_STATS: 1.656 + this.update(this.updateQueue[0].netId, this.processQueue.bind(this)); 1.657 + break; 1.658 + case QUEUE_TYPE_UPDATE_CACHE: 1.659 + this.updateCache(this.processQueue.bind(this)); 1.660 + break; 1.661 + case QUEUE_TYPE_WRITE_CACHE: 1.662 + this.writeCache(this.updateQueue[0].stats, this.processQueue.bind(this)); 1.663 + break; 1.664 + } 1.665 + }, 1.666 + 1.667 + update: function update(aNetId, aCallback) { 1.668 + // Check if connection type is valid. 1.669 + if (!this._networks[aNetId]) { 1.670 + if (aCallback) { 1.671 + aCallback(false, "Invalid network " + aNetId); 1.672 + } 1.673 + return; 1.674 + } 1.675 + 1.676 + let interfaceName = this._networks[aNetId].interfaceName; 1.677 + debug("Update stats for " + interfaceName); 1.678 + 1.679 + // Request stats to NetworkService, which will get stats from netd, passing 1.680 + // 'networkStatsAvailable' as a callback. 1.681 + if (interfaceName) { 1.682 + networkService.getNetworkInterfaceStats(interfaceName, 1.683 + this.networkStatsAvailable.bind(this, aCallback, aNetId)); 1.684 + return; 1.685 + } 1.686 + 1.687 + if (aCallback) { 1.688 + aCallback(true, "ok"); 1.689 + } 1.690 + }, 1.691 + 1.692 + /* 1.693 + * Callback of request stats. Store stats in database. 1.694 + */ 1.695 + networkStatsAvailable: function networkStatsAvailable(aCallback, aNetId, 1.696 + aResult, aRxBytes, 1.697 + aTxBytes, aDate) { 1.698 + if (!aResult) { 1.699 + if (aCallback) { 1.700 + aCallback(false, "Netd IPC error"); 1.701 + } 1.702 + return; 1.703 + } 1.704 + 1.705 + let stats = { appId: 0, 1.706 + serviceType: "", 1.707 + networkId: this._networks[aNetId].network.id, 1.708 + networkType: this._networks[aNetId].network.type, 1.709 + date: aDate, 1.710 + rxBytes: aTxBytes, 1.711 + txBytes: aRxBytes, 1.712 + isAccumulative: true }; 1.713 + 1.714 + debug("Update stats for: " + JSON.stringify(stats)); 1.715 + 1.716 + this._db.saveStats(stats, function onSavedStats(aError, aResult) { 1.717 + if (aCallback) { 1.718 + if (aError) { 1.719 + aCallback(false, aError); 1.720 + return; 1.721 + } 1.722 + 1.723 + aCallback(true, "OK"); 1.724 + } 1.725 + }); 1.726 + }, 1.727 + 1.728 + /* 1.729 + * Function responsible for receiving stats which are not from netd. 1.730 + */ 1.731 + saveStats: function saveStats(aAppId, aServiceType, aNetwork, aTimeStamp, 1.732 + aRxBytes, aTxBytes, aIsAccumulative, 1.733 + aCallback) { 1.734 + let netId = this.convertNetworkInterface(aNetwork); 1.735 + if (!netId) { 1.736 + if (aCallback) { 1.737 + aCallback(false, "Invalid network type"); 1.738 + } 1.739 + return; 1.740 + } 1.741 + 1.742 + // Check if |aConnectionType|, |aAppId| and |aServiceType| are valid. 1.743 + // There are two invalid cases for the combination of |aAppId| and 1.744 + // |aServiceType|: 1.745 + // a. Both |aAppId| is non-zero and |aServiceType| is non-empty. 1.746 + // b. Both |aAppId| is zero and |aServiceType| is empty. 1.747 + if (!this._networks[netId] || (aAppId && aServiceType) || 1.748 + (!aAppId && !aServiceType)) { 1.749 + debug("Invalid network interface, appId or serviceType"); 1.750 + return; 1.751 + } 1.752 + 1.753 + let stats = { appId: aAppId, 1.754 + serviceType: aServiceType, 1.755 + networkId: this._networks[netId].network.id, 1.756 + networkType: this._networks[netId].network.type, 1.757 + date: new Date(aTimeStamp), 1.758 + rxBytes: aRxBytes, 1.759 + txBytes: aTxBytes, 1.760 + isAccumulative: aIsAccumulative }; 1.761 + 1.762 + this.updateQueue.push({ stats: stats, 1.763 + callbacks: [aCallback], 1.764 + queueType: QUEUE_TYPE_WRITE_CACHE }); 1.765 + 1.766 + this.processQueue(); 1.767 + }, 1.768 + 1.769 + /* 1.770 + * 1.771 + */ 1.772 + writeCache: function writeCache(aStats, aCallback) { 1.773 + debug("saveStats: " + aStats.appId + " " + aStats.serviceType + " " + 1.774 + aStats.networkId + " " + aStats.networkType + " " + aStats.date + " " 1.775 + + aStats.date + " " + aStats.rxBytes + " " + aStats.txBytes); 1.776 + 1.777 + // Generate an unique key from |appId|, |serviceType| and |netId|, 1.778 + // which is used to retrieve data in |cachedStats|. 1.779 + let netId = this.getNetworkId(aStats.networkId, aStats.networkType); 1.780 + let key = aStats.appId + "" + aStats.serviceType + "" + netId; 1.781 + 1.782 + // |cachedStats| only keeps the data with the same date. 1.783 + // If the incoming date is different from |cachedStatsDate|, 1.784 + // both |cachedStats| and |cachedStatsDate| will get updated. 1.785 + let diff = (this._db.normalizeDate(aStats.date) - 1.786 + this._db.normalizeDate(this.cachedStatsDate)) / 1.787 + this._db.sampleRate; 1.788 + if (diff != 0) { 1.789 + this.updateCache(function onUpdated(success, message) { 1.790 + this.cachedStatsDate = aStats.date; 1.791 + this.cachedStats[key] = aStats; 1.792 + 1.793 + if (aCallback) { 1.794 + aCallback(true, "ok"); 1.795 + } 1.796 + }.bind(this)); 1.797 + return; 1.798 + } 1.799 + 1.800 + // Try to find the matched row in the cached by |appId| and |connectionType|. 1.801 + // If not found, save the incoming data into the cached. 1.802 + let cachedStats = this.cachedStats[key]; 1.803 + if (!cachedStats) { 1.804 + this.cachedStats[key] = aStats; 1.805 + if (aCallback) { 1.806 + aCallback(true, "ok"); 1.807 + } 1.808 + return; 1.809 + } 1.810 + 1.811 + // Find matched row, accumulate the traffic amount. 1.812 + cachedStats.rxBytes += aStats.rxBytes; 1.813 + cachedStats.txBytes += aStats.txBytes; 1.814 + 1.815 + // If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC 1.816 + // the corresponding row will be saved to indexedDB. 1.817 + // Then, the row will be removed from the cached. 1.818 + if (cachedStats.rxBytes > MAX_CACHED_TRAFFIC || 1.819 + cachedStats.txBytes > MAX_CACHED_TRAFFIC) { 1.820 + this._db.saveStats(cachedStats, function (error, result) { 1.821 + debug("Application stats inserted in indexedDB"); 1.822 + if (aCallback) { 1.823 + aCallback(true, "ok"); 1.824 + } 1.825 + }); 1.826 + delete this.cachedStats[key]; 1.827 + return; 1.828 + } 1.829 + 1.830 + if (aCallback) { 1.831 + aCallback(true, "ok"); 1.832 + } 1.833 + }, 1.834 + 1.835 + updateCachedStats: function updateCachedStats(aCallback) { 1.836 + this.updateQueue.push({ callbacks: [aCallback], 1.837 + queueType: QUEUE_TYPE_UPDATE_CACHE }); 1.838 + 1.839 + this.processQueue(); 1.840 + }, 1.841 + 1.842 + updateCache: function updateCache(aCallback) { 1.843 + debug("updateCache: " + this.cachedStatsDate); 1.844 + 1.845 + let stats = Object.keys(this.cachedStats); 1.846 + if (stats.length == 0) { 1.847 + // |cachedStats| is empty, no need to update. 1.848 + if (aCallback) { 1.849 + aCallback(true, "no need to update"); 1.850 + } 1.851 + return; 1.852 + } 1.853 + 1.854 + let index = 0; 1.855 + this._db.saveStats(this.cachedStats[stats[index]], 1.856 + function onSavedStats(error, result) { 1.857 + debug("Application stats inserted in indexedDB"); 1.858 + 1.859 + // Clean up the |cachedStats| after updating. 1.860 + if (index == stats.length - 1) { 1.861 + this.cachedStats = Object.create(null); 1.862 + 1.863 + if (aCallback) { 1.864 + aCallback(true, "ok"); 1.865 + } 1.866 + return; 1.867 + } 1.868 + 1.869 + // Update is not finished, keep updating. 1.870 + index += 1; 1.871 + this._db.saveStats(this.cachedStats[stats[index]], 1.872 + onSavedStats.bind(this, error, result)); 1.873 + }.bind(this)); 1.874 + }, 1.875 + 1.876 + get maxCachedTraffic () { 1.877 + return MAX_CACHED_TRAFFIC; 1.878 + }, 1.879 + 1.880 + logAllRecords: function logAllRecords() { 1.881 + this._db.logAllRecords(function onResult(aError, aResult) { 1.882 + if (aError) { 1.883 + debug("Error: " + aError); 1.884 + return; 1.885 + } 1.886 + 1.887 + debug("===== LOG ====="); 1.888 + debug("There are " + aResult.length + " items"); 1.889 + debug(JSON.stringify(aResult)); 1.890 + }); 1.891 + }, 1.892 + 1.893 + getAlarms: function getAlarms(mm, msg) { 1.894 + let self = this; 1.895 + let network = msg.data.network; 1.896 + let manifestURL = msg.data.manifestURL; 1.897 + 1.898 + if (network) { 1.899 + this.validateNetwork(network, function onValidateNetwork(aNetId) { 1.900 + if (!aNetId) { 1.901 + mm.sendAsyncMessage("NetworkStats:GetAlarms:Return", 1.902 + { id: msg.id, error: "InvalidInterface", result: null }); 1.903 + return; 1.904 + } 1.905 + 1.906 + self._getAlarms(mm, msg, aNetId, manifestURL); 1.907 + }); 1.908 + return; 1.909 + } 1.910 + 1.911 + this._getAlarms(mm, msg, null, manifestURL); 1.912 + }, 1.913 + 1.914 + _getAlarms: function _getAlarms(mm, msg, aNetId, aManifestURL) { 1.915 + let self = this; 1.916 + this._db.getAlarms(aNetId, aManifestURL, function onCompleted(error, result) { 1.917 + if (error) { 1.918 + mm.sendAsyncMessage("NetworkStats:GetAlarms:Return", 1.919 + { id: msg.id, error: error, result: result }); 1.920 + return; 1.921 + } 1.922 + 1.923 + let alarms = [] 1.924 + // NetworkStatsManager must return the network instead of the networkId. 1.925 + for (let i = 0; i < result.length; i++) { 1.926 + let alarm = result[i]; 1.927 + alarms.push({ id: alarm.id, 1.928 + network: self._networks[alarm.networkId].network, 1.929 + threshold: alarm.absoluteThreshold, 1.930 + data: alarm.data }); 1.931 + } 1.932 + 1.933 + mm.sendAsyncMessage("NetworkStats:GetAlarms:Return", 1.934 + { id: msg.id, error: null, result: alarms }); 1.935 + }); 1.936 + }, 1.937 + 1.938 + removeAlarms: function removeAlarms(mm, msg) { 1.939 + let alarmId = msg.data.alarmId; 1.940 + let manifestURL = msg.data.manifestURL; 1.941 + 1.942 + let self = this; 1.943 + let callback = function onRemove(error, result) { 1.944 + if (error) { 1.945 + mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return", 1.946 + { id: msg.id, error: error, result: result }); 1.947 + return; 1.948 + } 1.949 + 1.950 + for (let i in self._currentAlarms) { 1.951 + let currentAlarm = self._currentAlarms[i].alarm; 1.952 + if (currentAlarm && ((alarmId == currentAlarm.id) || 1.953 + (alarmId == -1 && currentAlarm.manifestURL == manifestURL))) { 1.954 + 1.955 + self._updateCurrentAlarm(currentAlarm.networkId); 1.956 + } 1.957 + } 1.958 + 1.959 + mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return", 1.960 + { id: msg.id, error: error, result: true }); 1.961 + }; 1.962 + 1.963 + if (alarmId == -1) { 1.964 + this._db.removeAlarms(manifestURL, callback); 1.965 + } else { 1.966 + this._db.removeAlarm(alarmId, manifestURL, callback); 1.967 + } 1.968 + }, 1.969 + 1.970 + /* 1.971 + * Function called from manager to set an alarm. 1.972 + */ 1.973 + setAlarm: function setAlarm(mm, msg) { 1.974 + let options = msg.data; 1.975 + let network = options.network; 1.976 + let threshold = options.threshold; 1.977 + 1.978 + debug("Set alarm at " + threshold + " for " + JSON.stringify(network)); 1.979 + 1.980 + if (threshold < 0) { 1.981 + mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", 1.982 + { id: msg.id, error: "InvalidThresholdValue", result: null }); 1.983 + return; 1.984 + } 1.985 + 1.986 + let self = this; 1.987 + this.validateNetwork(network, function onValidateNetwork(aNetId) { 1.988 + if (!aNetId) { 1.989 + mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", 1.990 + { id: msg.id, error: "InvalidiConnectionType", result: null }); 1.991 + return; 1.992 + } 1.993 + 1.994 + let newAlarm = { 1.995 + id: null, 1.996 + networkId: aNetId, 1.997 + absoluteThreshold: threshold, 1.998 + relativeThreshold: null, 1.999 + startTime: options.startTime, 1.1000 + data: options.data, 1.1001 + pageURL: options.pageURL, 1.1002 + manifestURL: options.manifestURL 1.1003 + }; 1.1004 + 1.1005 + self._getAlarmQuota(newAlarm, function onUpdate(error, quota) { 1.1006 + if (error) { 1.1007 + mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", 1.1008 + { id: msg.id, error: error, result: null }); 1.1009 + return; 1.1010 + } 1.1011 + 1.1012 + self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) { 1.1013 + if (error) { 1.1014 + mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", 1.1015 + { id: msg.id, error: error, result: null }); 1.1016 + return; 1.1017 + } 1.1018 + 1.1019 + newAlarm.id = newId; 1.1020 + self._setAlarm(newAlarm, function onSet(error, success) { 1.1021 + mm.sendAsyncMessage("NetworkStats:SetAlarm:Return", 1.1022 + { id: msg.id, error: error, result: newId }); 1.1023 + 1.1024 + if (error == "InvalidStateError") { 1.1025 + self._fireAlarm(newAlarm); 1.1026 + } 1.1027 + }); 1.1028 + }); 1.1029 + }); 1.1030 + }); 1.1031 + }, 1.1032 + 1.1033 + _setAlarm: function _setAlarm(aAlarm, aCallback) { 1.1034 + let currentAlarm = this._currentAlarms[aAlarm.networkId]; 1.1035 + if ((Object.getOwnPropertyNames(currentAlarm).length !== 0 && 1.1036 + aAlarm.relativeThreshold > currentAlarm.alarm.relativeThreshold) || 1.1037 + this._networks[aAlarm.networkId].status != NETWORK_STATUS_READY) { 1.1038 + aCallback(null, true); 1.1039 + return; 1.1040 + } 1.1041 + 1.1042 + let self = this; 1.1043 + 1.1044 + this._getAlarmQuota(aAlarm, function onUpdate(aError, aQuota) { 1.1045 + if (aError) { 1.1046 + aCallback(aError, null); 1.1047 + return; 1.1048 + } 1.1049 + 1.1050 + let callback = function onAlarmSet(aError) { 1.1051 + if (aError) { 1.1052 + debug("Set alarm error: " + aError); 1.1053 + aCallback("netdError", null); 1.1054 + return; 1.1055 + } 1.1056 + 1.1057 + self._currentAlarms[aAlarm.networkId].alarm = aAlarm; 1.1058 + 1.1059 + aCallback(null, true); 1.1060 + }; 1.1061 + 1.1062 + debug("Set alarm " + JSON.stringify(aAlarm)); 1.1063 + let interfaceName = self._networks[aAlarm.networkId].interfaceName; 1.1064 + if (interfaceName) { 1.1065 + networkService.setNetworkInterfaceAlarm(interfaceName, 1.1066 + aQuota, 1.1067 + callback); 1.1068 + return; 1.1069 + } 1.1070 + 1.1071 + aCallback(null, true); 1.1072 + }); 1.1073 + }, 1.1074 + 1.1075 + _getAlarmQuota: function _getAlarmQuota(aAlarm, aCallback) { 1.1076 + let self = this; 1.1077 + this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) { 1.1078 + self._db.getCurrentStats(self._networks[aAlarm.networkId].network, 1.1079 + aAlarm.startTime, 1.1080 + function onStatsFound(error, result) { 1.1081 + if (error) { 1.1082 + debug("Error getting stats for " + 1.1083 + JSON.stringify(self._networks[aAlarm.networkId]) + ": " + error); 1.1084 + aCallback(error, result); 1.1085 + return; 1.1086 + } 1.1087 + 1.1088 + let quota = aAlarm.absoluteThreshold - result.rxBytes - result.txBytes; 1.1089 + 1.1090 + // Alarm set to a threshold lower than current rx/tx bytes. 1.1091 + if (quota <= 0) { 1.1092 + aCallback("InvalidStateError", null); 1.1093 + return; 1.1094 + } 1.1095 + 1.1096 + aAlarm.relativeThreshold = aAlarm.startTime 1.1097 + ? result.rxTotalBytes + result.txTotalBytes + quota 1.1098 + : aAlarm.absoluteThreshold; 1.1099 + 1.1100 + aCallback(null, quota); 1.1101 + }); 1.1102 + }); 1.1103 + }, 1.1104 + 1.1105 + _fireAlarm: function _fireAlarm(aAlarm) { 1.1106 + debug("Fire alarm"); 1.1107 + 1.1108 + let self = this; 1.1109 + this._db.removeAlarm(aAlarm.id, null, function onRemove(aError, aResult){ 1.1110 + if (!aError && !aResult) { 1.1111 + return; 1.1112 + } 1.1113 + 1.1114 + self._fireSystemMessage(aAlarm); 1.1115 + self._updateCurrentAlarm(aAlarm.networkId); 1.1116 + }); 1.1117 + }, 1.1118 + 1.1119 + _updateCurrentAlarm: function _updateCurrentAlarm(aNetworkId) { 1.1120 + this._currentAlarms[aNetworkId] = Object.create(null); 1.1121 + 1.1122 + let self = this; 1.1123 + this._db.getFirstAlarm(aNetworkId, function onGet(error, result){ 1.1124 + if (error) { 1.1125 + debug("Error getting the first alarm"); 1.1126 + return; 1.1127 + } 1.1128 + 1.1129 + if (!result) { 1.1130 + let interfaceName = self._networks[aNetworkId].interfaceName; 1.1131 + networkService.setNetworkInterfaceAlarm(interfaceName, -1, 1.1132 + function onComplete(){}); 1.1133 + return; 1.1134 + } 1.1135 + 1.1136 + self._setAlarm(result, function onSet(error, success){ 1.1137 + if (error == "InvalidStateError") { 1.1138 + self._fireAlarm(result); 1.1139 + return; 1.1140 + } 1.1141 + }); 1.1142 + }); 1.1143 + }, 1.1144 + 1.1145 + _fireSystemMessage: function _fireSystemMessage(aAlarm) { 1.1146 + debug("Fire system message: " + JSON.stringify(aAlarm)); 1.1147 + 1.1148 + let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null); 1.1149 + let pageURI = Services.io.newURI(aAlarm.pageURL, null, null); 1.1150 + 1.1151 + let alarm = { "id": aAlarm.id, 1.1152 + "threshold": aAlarm.absoluteThreshold, 1.1153 + "data": aAlarm.data }; 1.1154 + messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI); 1.1155 + } 1.1156 +}; 1.1157 + 1.1158 +NetworkStatsService.init();