dom/network/src/NetworkStatsService.jsm

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 "use strict";
michael@0 6
michael@0 7 const DEBUG = false;
michael@0 8 function debug(s) {
michael@0 9 if (DEBUG) {
michael@0 10 dump("-*- NetworkStatsService: " + s + "\n");
michael@0 11 }
michael@0 12 }
michael@0 13
michael@0 14 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
michael@0 15
michael@0 16 this.EXPORTED_SYMBOLS = ["NetworkStatsService"];
michael@0 17
michael@0 18 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 19 Cu.import("resource://gre/modules/Services.jsm");
michael@0 20 Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
michael@0 21
michael@0 22 const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
michael@0 23 const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
michael@0 24
michael@0 25 const TOPIC_BANDWIDTH_CONTROL = "netd-bandwidth-control"
michael@0 26
michael@0 27 const TOPIC_INTERFACE_REGISTERED = "network-interface-registered";
michael@0 28 const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
michael@0 29 const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
michael@0 30 const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
michael@0 31
michael@0 32 // Networks have different status that NetworkStats API needs to be aware of.
michael@0 33 // Network is present and ready, so NetworkManager provides the whole info.
michael@0 34 const NETWORK_STATUS_READY = 0;
michael@0 35 // Network is present but hasn't established a connection yet (e.g. SIM that has not
michael@0 36 // enabled 3G since boot).
michael@0 37 const NETWORK_STATUS_STANDBY = 1;
michael@0 38 // Network is not present, but stored in database by the previous connections.
michael@0 39 const NETWORK_STATUS_AWAY = 2;
michael@0 40
michael@0 41 // The maximum traffic amount can be saved in the |cachedStats|.
michael@0 42 const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
michael@0 43
michael@0 44 const QUEUE_TYPE_UPDATE_STATS = 0;
michael@0 45 const QUEUE_TYPE_UPDATE_CACHE = 1;
michael@0 46 const QUEUE_TYPE_WRITE_CACHE = 2;
michael@0 47
michael@0 48 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
michael@0 49 "@mozilla.org/parentprocessmessagemanager;1",
michael@0 50 "nsIMessageListenerManager");
michael@0 51
michael@0 52 XPCOMUtils.defineLazyServiceGetter(this, "gRil",
michael@0 53 "@mozilla.org/ril;1",
michael@0 54 "nsIRadioInterfaceLayer");
michael@0 55
michael@0 56 XPCOMUtils.defineLazyServiceGetter(this, "networkService",
michael@0 57 "@mozilla.org/network/service;1",
michael@0 58 "nsINetworkService");
michael@0 59
michael@0 60 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
michael@0 61 "@mozilla.org/AppsService;1",
michael@0 62 "nsIAppsService");
michael@0 63
michael@0 64 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
michael@0 65 "@mozilla.org/settingsService;1",
michael@0 66 "nsISettingsService");
michael@0 67
michael@0 68 XPCOMUtils.defineLazyServiceGetter(this, "messenger",
michael@0 69 "@mozilla.org/system-message-internal;1",
michael@0 70 "nsISystemMessagesInternal");
michael@0 71
michael@0 72 this.NetworkStatsService = {
michael@0 73 init: function() {
michael@0 74 debug("Service started");
michael@0 75
michael@0 76 Services.obs.addObserver(this, "xpcom-shutdown", false);
michael@0 77 Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false);
michael@0 78 Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false);
michael@0 79 Services.obs.addObserver(this, TOPIC_BANDWIDTH_CONTROL, false);
michael@0 80 Services.obs.addObserver(this, "profile-after-change", false);
michael@0 81
michael@0 82 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
michael@0 83
michael@0 84 // Object to store network interfaces, each network interface is composed
michael@0 85 // by a network object (network type and network Id) and a interfaceName
michael@0 86 // that contains the name of the physical interface (wlan0, rmnet0, etc.).
michael@0 87 // The network type can be 0 for wifi or 1 for mobile. On the other hand,
michael@0 88 // the network id is '0' for wifi or the iccid for mobile (SIM).
michael@0 89 // Each networkInterface is placed in the _networks object by the index of
michael@0 90 // 'networkId + networkType'.
michael@0 91 //
michael@0 92 // _networks object allows to map available network interfaces at low level
michael@0 93 // (wlan0, rmnet0, etc.) to a network. It's not mandatory to have a
michael@0 94 // networkInterface per network but can't exist a networkInterface not
michael@0 95 // being mapped to a network.
michael@0 96
michael@0 97 this._networks = Object.create(null);
michael@0 98
michael@0 99 // There is no way to know a priori if wifi connection is available,
michael@0 100 // just when the wifi driver is loaded, but it is unloaded when
michael@0 101 // wifi is switched off. So wifi connection is hardcoded
michael@0 102 let netId = this.getNetworkId('0', NET_TYPE_WIFI);
michael@0 103 this._networks[netId] = { network: { id: '0',
michael@0 104 type: NET_TYPE_WIFI },
michael@0 105 interfaceName: null,
michael@0 106 status: NETWORK_STATUS_STANDBY };
michael@0 107
michael@0 108 this.messages = ["NetworkStats:Get",
michael@0 109 "NetworkStats:Clear",
michael@0 110 "NetworkStats:ClearAll",
michael@0 111 "NetworkStats:SetAlarm",
michael@0 112 "NetworkStats:GetAlarms",
michael@0 113 "NetworkStats:RemoveAlarms",
michael@0 114 "NetworkStats:GetAvailableNetworks",
michael@0 115 "NetworkStats:GetAvailableServiceTypes",
michael@0 116 "NetworkStats:SampleRate",
michael@0 117 "NetworkStats:MaxStorageAge"];
michael@0 118
michael@0 119 this.messages.forEach(function(aMsgName) {
michael@0 120 ppmm.addMessageListener(aMsgName, this);
michael@0 121 }, this);
michael@0 122
michael@0 123 this._db = new NetworkStatsDB();
michael@0 124
michael@0 125 // Stats for all interfaces are updated periodically
michael@0 126 this.timer.initWithCallback(this, this._db.sampleRate,
michael@0 127 Ci.nsITimer.TYPE_REPEATING_PRECISE);
michael@0 128
michael@0 129 // Stats not from netd are firstly stored in the cached.
michael@0 130 this.cachedStats = Object.create(null);
michael@0 131 this.cachedStatsDate = new Date();
michael@0 132
michael@0 133 this.updateQueue = [];
michael@0 134 this.isQueueRunning = false;
michael@0 135
michael@0 136 this._currentAlarms = {};
michael@0 137 this.initAlarms();
michael@0 138 },
michael@0 139
michael@0 140 receiveMessage: function(aMessage) {
michael@0 141 if (!aMessage.target.assertPermission("networkstats-manage")) {
michael@0 142 return;
michael@0 143 }
michael@0 144
michael@0 145 debug("receiveMessage " + aMessage.name);
michael@0 146
michael@0 147 let mm = aMessage.target;
michael@0 148 let msg = aMessage.json;
michael@0 149
michael@0 150 switch (aMessage.name) {
michael@0 151 case "NetworkStats:Get":
michael@0 152 this.getSamples(mm, msg);
michael@0 153 break;
michael@0 154 case "NetworkStats:Clear":
michael@0 155 this.clearInterfaceStats(mm, msg);
michael@0 156 break;
michael@0 157 case "NetworkStats:ClearAll":
michael@0 158 this.clearDB(mm, msg);
michael@0 159 break;
michael@0 160 case "NetworkStats:SetAlarm":
michael@0 161 this.setAlarm(mm, msg);
michael@0 162 break;
michael@0 163 case "NetworkStats:GetAlarms":
michael@0 164 this.getAlarms(mm, msg);
michael@0 165 break;
michael@0 166 case "NetworkStats:RemoveAlarms":
michael@0 167 this.removeAlarms(mm, msg);
michael@0 168 break;
michael@0 169 case "NetworkStats:GetAvailableNetworks":
michael@0 170 this.getAvailableNetworks(mm, msg);
michael@0 171 break;
michael@0 172 case "NetworkStats:GetAvailableServiceTypes":
michael@0 173 this.getAvailableServiceTypes(mm, msg);
michael@0 174 break;
michael@0 175 case "NetworkStats:SampleRate":
michael@0 176 // This message is sync.
michael@0 177 return this._db.sampleRate;
michael@0 178 case "NetworkStats:MaxStorageAge":
michael@0 179 // This message is sync.
michael@0 180 return this._db.maxStorageSamples * this._db.sampleRate;
michael@0 181 }
michael@0 182 },
michael@0 183
michael@0 184 observe: function observe(aSubject, aTopic, aData) {
michael@0 185 switch (aTopic) {
michael@0 186 case TOPIC_INTERFACE_REGISTERED:
michael@0 187 case TOPIC_INTERFACE_UNREGISTERED:
michael@0 188
michael@0 189 // If new interface is registered (notified from NetworkService),
michael@0 190 // the stats are updated for the new interface without waiting to
michael@0 191 // complete the updating period.
michael@0 192
michael@0 193 let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
michael@0 194 debug("Network " + network.name + " of type " + network.type + " status change");
michael@0 195
michael@0 196 let netId = this.convertNetworkInterface(network);
michael@0 197 if (!netId) {
michael@0 198 break;
michael@0 199 }
michael@0 200
michael@0 201 this._updateCurrentAlarm(netId);
michael@0 202
michael@0 203 debug("NetId: " + netId);
michael@0 204 this.updateStats(netId);
michael@0 205 break;
michael@0 206
michael@0 207 case TOPIC_BANDWIDTH_CONTROL:
michael@0 208 debug("Bandwidth message from netd: " + JSON.stringify(aData));
michael@0 209
michael@0 210 let interfaceName = aData.substring(aData.lastIndexOf(" ") + 1);
michael@0 211 for (let networkId in this._networks) {
michael@0 212 if (interfaceName == this._networks[networkId].interfaceName) {
michael@0 213 let currentAlarm = this._currentAlarms[networkId];
michael@0 214 if (Object.getOwnPropertyNames(currentAlarm).length !== 0) {
michael@0 215 this._fireAlarm(currentAlarm.alarm);
michael@0 216 }
michael@0 217 break;
michael@0 218 }
michael@0 219 }
michael@0 220 break;
michael@0 221
michael@0 222 case "xpcom-shutdown":
michael@0 223 debug("Service shutdown");
michael@0 224
michael@0 225 this.messages.forEach(function(aMsgName) {
michael@0 226 ppmm.removeMessageListener(aMsgName, this);
michael@0 227 }, this);
michael@0 228
michael@0 229 Services.obs.removeObserver(this, "xpcom-shutdown");
michael@0 230 Services.obs.removeObserver(this, "profile-after-change");
michael@0 231 Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED);
michael@0 232 Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED);
michael@0 233 Services.obs.removeObserver(this, TOPIC_BANDWIDTH_CONTROL);
michael@0 234
michael@0 235 this.timer.cancel();
michael@0 236 this.timer = null;
michael@0 237
michael@0 238 // Update stats before shutdown
michael@0 239 this.updateAllStats();
michael@0 240 break;
michael@0 241 }
michael@0 242 },
michael@0 243
michael@0 244 /*
michael@0 245 * nsITimerCallback
michael@0 246 * Timer triggers the update of all stats
michael@0 247 */
michael@0 248 notify: function(aTimer) {
michael@0 249 this.updateAllStats();
michael@0 250 },
michael@0 251
michael@0 252 /*
michael@0 253 * nsINetworkStatsService
michael@0 254 */
michael@0 255 getRilNetworks: function() {
michael@0 256 let networks = {};
michael@0 257 let numRadioInterfaces = gRil.numRadioInterfaces;
michael@0 258 for (let i = 0; i < numRadioInterfaces; i++) {
michael@0 259 let radioInterface = gRil.getRadioInterface(i);
michael@0 260 if (radioInterface.rilContext.iccInfo) {
michael@0 261 let netId = this.getNetworkId(radioInterface.rilContext.iccInfo.iccid,
michael@0 262 NET_TYPE_MOBILE);
michael@0 263 networks[netId] = { id : radioInterface.rilContext.iccInfo.iccid,
michael@0 264 type: NET_TYPE_MOBILE };
michael@0 265 }
michael@0 266 }
michael@0 267 return networks;
michael@0 268 },
michael@0 269
michael@0 270 convertNetworkInterface: function(aNetwork) {
michael@0 271 if (aNetwork.type != NET_TYPE_MOBILE &&
michael@0 272 aNetwork.type != NET_TYPE_WIFI) {
michael@0 273 return null;
michael@0 274 }
michael@0 275
michael@0 276 let id = '0';
michael@0 277 if (aNetwork.type == NET_TYPE_MOBILE) {
michael@0 278 if (!(aNetwork instanceof Ci.nsIRilNetworkInterface)) {
michael@0 279 debug("Error! Mobile network should be an nsIRilNetworkInterface!");
michael@0 280 return null;
michael@0 281 }
michael@0 282
michael@0 283 let rilNetwork = aNetwork.QueryInterface(Ci.nsIRilNetworkInterface);
michael@0 284 id = rilNetwork.iccId;
michael@0 285 }
michael@0 286
michael@0 287 let netId = this.getNetworkId(id, aNetwork.type);
michael@0 288
michael@0 289 if (!this._networks[netId]) {
michael@0 290 this._networks[netId] = Object.create(null);
michael@0 291 this._networks[netId].network = { id: id,
michael@0 292 type: aNetwork.type };
michael@0 293 }
michael@0 294
michael@0 295 this._networks[netId].status = NETWORK_STATUS_READY;
michael@0 296 this._networks[netId].interfaceName = aNetwork.name;
michael@0 297 return netId;
michael@0 298 },
michael@0 299
michael@0 300 getNetworkId: function getNetworkId(aIccId, aNetworkType) {
michael@0 301 return aIccId + '' + aNetworkType;
michael@0 302 },
michael@0 303
michael@0 304 /* Function to ensure that one network is valid. The network is valid if its status is
michael@0 305 * NETWORK_STATUS_READY, NETWORK_STATUS_STANDBY or NETWORK_STATUS_AWAY.
michael@0 306 *
michael@0 307 * The result is |netId| or null in case of a non-valid network
michael@0 308 * aCallback is signatured as |function(netId)|.
michael@0 309 */
michael@0 310 validateNetwork: function validateNetwork(aNetwork, aCallback) {
michael@0 311 let netId = this.getNetworkId(aNetwork.id, aNetwork.type);
michael@0 312
michael@0 313 if (this._networks[netId]) {
michael@0 314 aCallback(netId);
michael@0 315 return;
michael@0 316 }
michael@0 317
michael@0 318 // Check if network is valid (RIL entry) but has not established a connection yet.
michael@0 319 // If so add to networks list with empty interfaceName.
michael@0 320 let rilNetworks = this.getRilNetworks();
michael@0 321 if (rilNetworks[netId]) {
michael@0 322 this._networks[netId] = Object.create(null);
michael@0 323 this._networks[netId].network = rilNetworks[netId];
michael@0 324 this._networks[netId].status = NETWORK_STATUS_STANDBY;
michael@0 325 this._currentAlarms[netId] = Object.create(null);
michael@0 326 aCallback(netId);
michael@0 327 return;
michael@0 328 }
michael@0 329
michael@0 330 // Check if network is available in the DB.
michael@0 331 this._db.isNetworkAvailable(aNetwork, function(aError, aResult) {
michael@0 332 if (aResult) {
michael@0 333 this._networks[netId] = Object.create(null);
michael@0 334 this._networks[netId].network = aNetwork;
michael@0 335 this._networks[netId].status = NETWORK_STATUS_AWAY;
michael@0 336 this._currentAlarms[netId] = Object.create(null);
michael@0 337 aCallback(netId);
michael@0 338 return;
michael@0 339 }
michael@0 340
michael@0 341 aCallback(null);
michael@0 342 }.bind(this));
michael@0 343 },
michael@0 344
michael@0 345 getAvailableNetworks: function getAvailableNetworks(mm, msg) {
michael@0 346 let self = this;
michael@0 347 let rilNetworks = this.getRilNetworks();
michael@0 348 this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
michael@0 349
michael@0 350 // Also return the networks that are valid but have not
michael@0 351 // established connections yet.
michael@0 352 for (let netId in rilNetworks) {
michael@0 353 let found = false;
michael@0 354 for (let i = 0; i < aResult.length; i++) {
michael@0 355 if (netId == self.getNetworkId(aResult[i].id, aResult[i].type)) {
michael@0 356 found = true;
michael@0 357 break;
michael@0 358 }
michael@0 359 }
michael@0 360 if (!found) {
michael@0 361 aResult.push(rilNetworks[netId]);
michael@0 362 }
michael@0 363 }
michael@0 364
michael@0 365 mm.sendAsyncMessage("NetworkStats:GetAvailableNetworks:Return",
michael@0 366 { id: msg.id, error: aError, result: aResult });
michael@0 367 });
michael@0 368 },
michael@0 369
michael@0 370 getAvailableServiceTypes: function getAvailableServiceTypes(mm, msg) {
michael@0 371 this._db.getAvailableServiceTypes(function onGetServiceTypes(aError, aResult) {
michael@0 372 mm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes:Return",
michael@0 373 { id: msg.id, error: aError, result: aResult });
michael@0 374 });
michael@0 375 },
michael@0 376
michael@0 377 initAlarms: function initAlarms() {
michael@0 378 debug("Init usage alarms");
michael@0 379 let self = this;
michael@0 380
michael@0 381 for (let netId in this._networks) {
michael@0 382 this._currentAlarms[netId] = Object.create(null);
michael@0 383
michael@0 384 this._db.getFirstAlarm(netId, function getResult(error, result) {
michael@0 385 if (!error && result) {
michael@0 386 self._setAlarm(result, function onSet(error, success) {
michael@0 387 if (error == "InvalidStateError") {
michael@0 388 self._fireAlarm(result);
michael@0 389 }
michael@0 390 });
michael@0 391 }
michael@0 392 });
michael@0 393 }
michael@0 394 },
michael@0 395
michael@0 396 /*
michael@0 397 * Function called from manager to get stats from database.
michael@0 398 * In order to return updated stats, first is performed a call to
michael@0 399 * updateAllStats function, which will get last stats from netd
michael@0 400 * and update the database.
michael@0 401 * Then, depending on the request (stats per appId or total stats)
michael@0 402 * it retrieve them from database and return to the manager.
michael@0 403 */
michael@0 404 getSamples: function getSamples(mm, msg) {
michael@0 405 let network = msg.network;
michael@0 406 let netId = this.getNetworkId(network.id, network.type);
michael@0 407
michael@0 408 let appId = 0;
michael@0 409 let appManifestURL = msg.appManifestURL;
michael@0 410 if (appManifestURL) {
michael@0 411 appId = appsService.getAppLocalIdByManifestURL(appManifestURL);
michael@0 412
michael@0 413 if (!appId) {
michael@0 414 mm.sendAsyncMessage("NetworkStats:Get:Return",
michael@0 415 { id: msg.id,
michael@0 416 error: "Invalid appManifestURL", result: null });
michael@0 417 return;
michael@0 418 }
michael@0 419 }
michael@0 420
michael@0 421 let serviceType = msg.serviceType || "";
michael@0 422
michael@0 423 let start = new Date(msg.start);
michael@0 424 let end = new Date(msg.end);
michael@0 425
michael@0 426 let callback = (function (aError, aResult) {
michael@0 427 this._db.find(function onStatsFound(aError, aResult) {
michael@0 428 mm.sendAsyncMessage("NetworkStats:Get:Return",
michael@0 429 { id: msg.id, error: aError, result: aResult });
michael@0 430 }, appId, serviceType, network, start, end, appManifestURL);
michael@0 431 }).bind(this);
michael@0 432
michael@0 433 this.validateNetwork(network, function onValidateNetwork(aNetId) {
michael@0 434 if (!aNetId) {
michael@0 435 mm.sendAsyncMessage("NetworkStats:Get:Return",
michael@0 436 { id: msg.id, error: "Invalid connectionType", result: null });
michael@0 437 return;
michael@0 438 }
michael@0 439
michael@0 440 // If network is currently active we need to update the cached stats first before
michael@0 441 // retrieving stats from the DB.
michael@0 442 if (this._networks[aNetId].status == NETWORK_STATUS_READY) {
michael@0 443 debug("getstats for network " + network.id + " of type " + network.type);
michael@0 444 debug("appId: " + appId + " from appManifestURL: " + appManifestURL);
michael@0 445 debug("serviceType: " + serviceType);
michael@0 446
michael@0 447 if (appId || serviceType) {
michael@0 448 this.updateCachedStats(callback);
michael@0 449 return;
michael@0 450 }
michael@0 451
michael@0 452 this.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) {
michael@0 453 this.updateCachedStats(callback);
michael@0 454 }.bind(this));
michael@0 455 return;
michael@0 456 }
michael@0 457
michael@0 458 // Network not active, so no need to update
michael@0 459 this._db.find(function onStatsFound(aError, aResult) {
michael@0 460 mm.sendAsyncMessage("NetworkStats:Get:Return",
michael@0 461 { id: msg.id, error: aError, result: aResult });
michael@0 462 }, appId, serviceType, network, start, end, appManifestURL);
michael@0 463 }.bind(this));
michael@0 464 },
michael@0 465
michael@0 466 clearInterfaceStats: function clearInterfaceStats(mm, msg) {
michael@0 467 let self = this;
michael@0 468 let network = msg.network;
michael@0 469
michael@0 470 debug("clear stats for network " + network.id + " of type " + network.type);
michael@0 471
michael@0 472 this.validateNetwork(network, function onValidateNetwork(aNetId) {
michael@0 473 if (!aNetId) {
michael@0 474 mm.sendAsyncMessage("NetworkStats:Clear:Return",
michael@0 475 { id: msg.id, error: "Invalid connectionType", result: null });
michael@0 476 return;
michael@0 477 }
michael@0 478
michael@0 479 network = {network: network, networkId: aNetId};
michael@0 480 self.updateStats(aNetId, function onUpdate(aResult, aMessage) {
michael@0 481 if (!aResult) {
michael@0 482 mm.sendAsyncMessage("NetworkStats:Clear:Return",
michael@0 483 { id: msg.id, error: aMessage, result: null });
michael@0 484 return;
michael@0 485 }
michael@0 486
michael@0 487 self._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
michael@0 488 self._updateCurrentAlarm(aNetId);
michael@0 489 mm.sendAsyncMessage("NetworkStats:Clear:Return",
michael@0 490 { id: msg.id, error: aError, result: aResult });
michael@0 491 });
michael@0 492 });
michael@0 493 });
michael@0 494 },
michael@0 495
michael@0 496 clearDB: function clearDB(mm, msg) {
michael@0 497 let self = this;
michael@0 498 this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
michael@0 499 if (aError) {
michael@0 500 mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
michael@0 501 { id: msg.id, error: aError, result: aResult });
michael@0 502 return;
michael@0 503 }
michael@0 504
michael@0 505 let networks = aResult;
michael@0 506 networks.forEach(function(network, index) {
michael@0 507 networks[index] = {network: network, networkId: self.getNetworkId(network.id, network.type)};
michael@0 508 }, self);
michael@0 509
michael@0 510 self.updateAllStats(function onUpdate(aResult, aMessage){
michael@0 511 if (!aResult) {
michael@0 512 mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
michael@0 513 { id: msg.id, error: aMessage, result: null });
michael@0 514 return;
michael@0 515 }
michael@0 516
michael@0 517 self._db.clearStats(networks, function onDBCleared(aError, aResult) {
michael@0 518 networks.forEach(function(network, index) {
michael@0 519 self._updateCurrentAlarm(network.networkId);
michael@0 520 }, self);
michael@0 521 mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
michael@0 522 { id: msg.id, error: aError, result: aResult });
michael@0 523 });
michael@0 524 });
michael@0 525 });
michael@0 526 },
michael@0 527
michael@0 528 updateAllStats: function updateAllStats(aCallback) {
michael@0 529 let elements = [];
michael@0 530 let lastElement = null;
michael@0 531 let callback = (function (success, message) {
michael@0 532 this.updateCachedStats(aCallback);
michael@0 533 }).bind(this);
michael@0 534
michael@0 535 // For each connectionType create an object containning the type
michael@0 536 // and the 'queueIndex', the 'queueIndex' is an integer representing
michael@0 537 // the index of a connection type in the global queue array. So, if
michael@0 538 // the connection type is already in the queue it is not appended again,
michael@0 539 // else it is pushed in 'elements' array, which later will be pushed to
michael@0 540 // the queue array.
michael@0 541 for (let netId in this._networks) {
michael@0 542 if (this._networks[netId].status != NETWORK_STATUS_READY) {
michael@0 543 continue;
michael@0 544 }
michael@0 545
michael@0 546 lastElement = { netId: netId,
michael@0 547 queueIndex: this.updateQueueIndex(netId) };
michael@0 548
michael@0 549 if (lastElement.queueIndex == -1) {
michael@0 550 elements.push({ netId: lastElement.netId,
michael@0 551 callbacks: [],
michael@0 552 queueType: QUEUE_TYPE_UPDATE_STATS });
michael@0 553 }
michael@0 554 }
michael@0 555
michael@0 556 if (!lastElement) {
michael@0 557 // No elements need to be updated, probably because status is different than
michael@0 558 // NETWORK_STATUS_READY.
michael@0 559 if (aCallback) {
michael@0 560 aCallback(true, "OK");
michael@0 561 }
michael@0 562 return;
michael@0 563 }
michael@0 564
michael@0 565 if (elements.length > 0) {
michael@0 566 // If length of elements is greater than 0, callback is set to
michael@0 567 // the last element.
michael@0 568 elements[elements.length - 1].callbacks.push(callback);
michael@0 569 this.updateQueue = this.updateQueue.concat(elements);
michael@0 570 } else {
michael@0 571 // Else, it means that all connection types are already in the queue to
michael@0 572 // be updated, so callback for this request is added to
michael@0 573 // the element in the main queue with the index of the last 'lastElement'.
michael@0 574 // But before is checked that element is still in the queue because it can
michael@0 575 // be processed while generating 'elements' array.
michael@0 576 let element = this.updateQueue[lastElement.queueIndex];
michael@0 577 if (aCallback &&
michael@0 578 (!element || element.netId != lastElement.netId)) {
michael@0 579 aCallback();
michael@0 580 return;
michael@0 581 }
michael@0 582
michael@0 583 this.updateQueue[lastElement.queueIndex].callbacks.push(callback);
michael@0 584 }
michael@0 585
michael@0 586 // Call the function that process the elements of the queue.
michael@0 587 this.processQueue();
michael@0 588
michael@0 589 if (DEBUG) {
michael@0 590 this.logAllRecords();
michael@0 591 }
michael@0 592 },
michael@0 593
michael@0 594 updateStats: function updateStats(aNetId, aCallback) {
michael@0 595 // Check if the connection is in the main queue, push a new element
michael@0 596 // if it is not being processed or add a callback if it is.
michael@0 597 let index = this.updateQueueIndex(aNetId);
michael@0 598 if (index == -1) {
michael@0 599 this.updateQueue.push({ netId: aNetId,
michael@0 600 callbacks: [aCallback],
michael@0 601 queueType: QUEUE_TYPE_UPDATE_STATS });
michael@0 602 } else {
michael@0 603 this.updateQueue[index].callbacks.push(aCallback);
michael@0 604 return;
michael@0 605 }
michael@0 606
michael@0 607 // Call the function that process the elements of the queue.
michael@0 608 this.processQueue();
michael@0 609 },
michael@0 610
michael@0 611 /*
michael@0 612 * Find if a connection is in the main queue array and return its
michael@0 613 * index, if it is not in the array return -1.
michael@0 614 */
michael@0 615 updateQueueIndex: function updateQueueIndex(aNetId) {
michael@0 616 return this.updateQueue.map(function(e) { return e.netId; }).indexOf(aNetId);
michael@0 617 },
michael@0 618
michael@0 619 /*
michael@0 620 * Function responsible of process all requests in the queue.
michael@0 621 */
michael@0 622 processQueue: function processQueue(aResult, aMessage) {
michael@0 623 // If aResult is not undefined, the caller of the function is the result
michael@0 624 // of processing an element, so remove that element and call the callbacks
michael@0 625 // it has.
michael@0 626 if (aResult != undefined) {
michael@0 627 let item = this.updateQueue.shift();
michael@0 628 for (let callback of item.callbacks) {
michael@0 629 if (callback) {
michael@0 630 callback(aResult, aMessage);
michael@0 631 }
michael@0 632 }
michael@0 633 } else {
michael@0 634 // The caller is a function that has pushed new elements to the queue,
michael@0 635 // if isQueueRunning is false it means there is no processing currently
michael@0 636 // being done, so start.
michael@0 637 if (this.isQueueRunning) {
michael@0 638 return;
michael@0 639 } else {
michael@0 640 this.isQueueRunning = true;
michael@0 641 }
michael@0 642 }
michael@0 643
michael@0 644 // Check length to determine if queue is empty and stop processing.
michael@0 645 if (this.updateQueue.length < 1) {
michael@0 646 this.isQueueRunning = false;
michael@0 647 return;
michael@0 648 }
michael@0 649
michael@0 650 // Call the update function for the next element.
michael@0 651 switch (this.updateQueue[0].queueType) {
michael@0 652 case QUEUE_TYPE_UPDATE_STATS:
michael@0 653 this.update(this.updateQueue[0].netId, this.processQueue.bind(this));
michael@0 654 break;
michael@0 655 case QUEUE_TYPE_UPDATE_CACHE:
michael@0 656 this.updateCache(this.processQueue.bind(this));
michael@0 657 break;
michael@0 658 case QUEUE_TYPE_WRITE_CACHE:
michael@0 659 this.writeCache(this.updateQueue[0].stats, this.processQueue.bind(this));
michael@0 660 break;
michael@0 661 }
michael@0 662 },
michael@0 663
michael@0 664 update: function update(aNetId, aCallback) {
michael@0 665 // Check if connection type is valid.
michael@0 666 if (!this._networks[aNetId]) {
michael@0 667 if (aCallback) {
michael@0 668 aCallback(false, "Invalid network " + aNetId);
michael@0 669 }
michael@0 670 return;
michael@0 671 }
michael@0 672
michael@0 673 let interfaceName = this._networks[aNetId].interfaceName;
michael@0 674 debug("Update stats for " + interfaceName);
michael@0 675
michael@0 676 // Request stats to NetworkService, which will get stats from netd, passing
michael@0 677 // 'networkStatsAvailable' as a callback.
michael@0 678 if (interfaceName) {
michael@0 679 networkService.getNetworkInterfaceStats(interfaceName,
michael@0 680 this.networkStatsAvailable.bind(this, aCallback, aNetId));
michael@0 681 return;
michael@0 682 }
michael@0 683
michael@0 684 if (aCallback) {
michael@0 685 aCallback(true, "ok");
michael@0 686 }
michael@0 687 },
michael@0 688
michael@0 689 /*
michael@0 690 * Callback of request stats. Store stats in database.
michael@0 691 */
michael@0 692 networkStatsAvailable: function networkStatsAvailable(aCallback, aNetId,
michael@0 693 aResult, aRxBytes,
michael@0 694 aTxBytes, aDate) {
michael@0 695 if (!aResult) {
michael@0 696 if (aCallback) {
michael@0 697 aCallback(false, "Netd IPC error");
michael@0 698 }
michael@0 699 return;
michael@0 700 }
michael@0 701
michael@0 702 let stats = { appId: 0,
michael@0 703 serviceType: "",
michael@0 704 networkId: this._networks[aNetId].network.id,
michael@0 705 networkType: this._networks[aNetId].network.type,
michael@0 706 date: aDate,
michael@0 707 rxBytes: aTxBytes,
michael@0 708 txBytes: aRxBytes,
michael@0 709 isAccumulative: true };
michael@0 710
michael@0 711 debug("Update stats for: " + JSON.stringify(stats));
michael@0 712
michael@0 713 this._db.saveStats(stats, function onSavedStats(aError, aResult) {
michael@0 714 if (aCallback) {
michael@0 715 if (aError) {
michael@0 716 aCallback(false, aError);
michael@0 717 return;
michael@0 718 }
michael@0 719
michael@0 720 aCallback(true, "OK");
michael@0 721 }
michael@0 722 });
michael@0 723 },
michael@0 724
michael@0 725 /*
michael@0 726 * Function responsible for receiving stats which are not from netd.
michael@0 727 */
michael@0 728 saveStats: function saveStats(aAppId, aServiceType, aNetwork, aTimeStamp,
michael@0 729 aRxBytes, aTxBytes, aIsAccumulative,
michael@0 730 aCallback) {
michael@0 731 let netId = this.convertNetworkInterface(aNetwork);
michael@0 732 if (!netId) {
michael@0 733 if (aCallback) {
michael@0 734 aCallback(false, "Invalid network type");
michael@0 735 }
michael@0 736 return;
michael@0 737 }
michael@0 738
michael@0 739 // Check if |aConnectionType|, |aAppId| and |aServiceType| are valid.
michael@0 740 // There are two invalid cases for the combination of |aAppId| and
michael@0 741 // |aServiceType|:
michael@0 742 // a. Both |aAppId| is non-zero and |aServiceType| is non-empty.
michael@0 743 // b. Both |aAppId| is zero and |aServiceType| is empty.
michael@0 744 if (!this._networks[netId] || (aAppId && aServiceType) ||
michael@0 745 (!aAppId && !aServiceType)) {
michael@0 746 debug("Invalid network interface, appId or serviceType");
michael@0 747 return;
michael@0 748 }
michael@0 749
michael@0 750 let stats = { appId: aAppId,
michael@0 751 serviceType: aServiceType,
michael@0 752 networkId: this._networks[netId].network.id,
michael@0 753 networkType: this._networks[netId].network.type,
michael@0 754 date: new Date(aTimeStamp),
michael@0 755 rxBytes: aRxBytes,
michael@0 756 txBytes: aTxBytes,
michael@0 757 isAccumulative: aIsAccumulative };
michael@0 758
michael@0 759 this.updateQueue.push({ stats: stats,
michael@0 760 callbacks: [aCallback],
michael@0 761 queueType: QUEUE_TYPE_WRITE_CACHE });
michael@0 762
michael@0 763 this.processQueue();
michael@0 764 },
michael@0 765
michael@0 766 /*
michael@0 767 *
michael@0 768 */
michael@0 769 writeCache: function writeCache(aStats, aCallback) {
michael@0 770 debug("saveStats: " + aStats.appId + " " + aStats.serviceType + " " +
michael@0 771 aStats.networkId + " " + aStats.networkType + " " + aStats.date + " "
michael@0 772 + aStats.date + " " + aStats.rxBytes + " " + aStats.txBytes);
michael@0 773
michael@0 774 // Generate an unique key from |appId|, |serviceType| and |netId|,
michael@0 775 // which is used to retrieve data in |cachedStats|.
michael@0 776 let netId = this.getNetworkId(aStats.networkId, aStats.networkType);
michael@0 777 let key = aStats.appId + "" + aStats.serviceType + "" + netId;
michael@0 778
michael@0 779 // |cachedStats| only keeps the data with the same date.
michael@0 780 // If the incoming date is different from |cachedStatsDate|,
michael@0 781 // both |cachedStats| and |cachedStatsDate| will get updated.
michael@0 782 let diff = (this._db.normalizeDate(aStats.date) -
michael@0 783 this._db.normalizeDate(this.cachedStatsDate)) /
michael@0 784 this._db.sampleRate;
michael@0 785 if (diff != 0) {
michael@0 786 this.updateCache(function onUpdated(success, message) {
michael@0 787 this.cachedStatsDate = aStats.date;
michael@0 788 this.cachedStats[key] = aStats;
michael@0 789
michael@0 790 if (aCallback) {
michael@0 791 aCallback(true, "ok");
michael@0 792 }
michael@0 793 }.bind(this));
michael@0 794 return;
michael@0 795 }
michael@0 796
michael@0 797 // Try to find the matched row in the cached by |appId| and |connectionType|.
michael@0 798 // If not found, save the incoming data into the cached.
michael@0 799 let cachedStats = this.cachedStats[key];
michael@0 800 if (!cachedStats) {
michael@0 801 this.cachedStats[key] = aStats;
michael@0 802 if (aCallback) {
michael@0 803 aCallback(true, "ok");
michael@0 804 }
michael@0 805 return;
michael@0 806 }
michael@0 807
michael@0 808 // Find matched row, accumulate the traffic amount.
michael@0 809 cachedStats.rxBytes += aStats.rxBytes;
michael@0 810 cachedStats.txBytes += aStats.txBytes;
michael@0 811
michael@0 812 // If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC
michael@0 813 // the corresponding row will be saved to indexedDB.
michael@0 814 // Then, the row will be removed from the cached.
michael@0 815 if (cachedStats.rxBytes > MAX_CACHED_TRAFFIC ||
michael@0 816 cachedStats.txBytes > MAX_CACHED_TRAFFIC) {
michael@0 817 this._db.saveStats(cachedStats, function (error, result) {
michael@0 818 debug("Application stats inserted in indexedDB");
michael@0 819 if (aCallback) {
michael@0 820 aCallback(true, "ok");
michael@0 821 }
michael@0 822 });
michael@0 823 delete this.cachedStats[key];
michael@0 824 return;
michael@0 825 }
michael@0 826
michael@0 827 if (aCallback) {
michael@0 828 aCallback(true, "ok");
michael@0 829 }
michael@0 830 },
michael@0 831
michael@0 832 updateCachedStats: function updateCachedStats(aCallback) {
michael@0 833 this.updateQueue.push({ callbacks: [aCallback],
michael@0 834 queueType: QUEUE_TYPE_UPDATE_CACHE });
michael@0 835
michael@0 836 this.processQueue();
michael@0 837 },
michael@0 838
michael@0 839 updateCache: function updateCache(aCallback) {
michael@0 840 debug("updateCache: " + this.cachedStatsDate);
michael@0 841
michael@0 842 let stats = Object.keys(this.cachedStats);
michael@0 843 if (stats.length == 0) {
michael@0 844 // |cachedStats| is empty, no need to update.
michael@0 845 if (aCallback) {
michael@0 846 aCallback(true, "no need to update");
michael@0 847 }
michael@0 848 return;
michael@0 849 }
michael@0 850
michael@0 851 let index = 0;
michael@0 852 this._db.saveStats(this.cachedStats[stats[index]],
michael@0 853 function onSavedStats(error, result) {
michael@0 854 debug("Application stats inserted in indexedDB");
michael@0 855
michael@0 856 // Clean up the |cachedStats| after updating.
michael@0 857 if (index == stats.length - 1) {
michael@0 858 this.cachedStats = Object.create(null);
michael@0 859
michael@0 860 if (aCallback) {
michael@0 861 aCallback(true, "ok");
michael@0 862 }
michael@0 863 return;
michael@0 864 }
michael@0 865
michael@0 866 // Update is not finished, keep updating.
michael@0 867 index += 1;
michael@0 868 this._db.saveStats(this.cachedStats[stats[index]],
michael@0 869 onSavedStats.bind(this, error, result));
michael@0 870 }.bind(this));
michael@0 871 },
michael@0 872
michael@0 873 get maxCachedTraffic () {
michael@0 874 return MAX_CACHED_TRAFFIC;
michael@0 875 },
michael@0 876
michael@0 877 logAllRecords: function logAllRecords() {
michael@0 878 this._db.logAllRecords(function onResult(aError, aResult) {
michael@0 879 if (aError) {
michael@0 880 debug("Error: " + aError);
michael@0 881 return;
michael@0 882 }
michael@0 883
michael@0 884 debug("===== LOG =====");
michael@0 885 debug("There are " + aResult.length + " items");
michael@0 886 debug(JSON.stringify(aResult));
michael@0 887 });
michael@0 888 },
michael@0 889
michael@0 890 getAlarms: function getAlarms(mm, msg) {
michael@0 891 let self = this;
michael@0 892 let network = msg.data.network;
michael@0 893 let manifestURL = msg.data.manifestURL;
michael@0 894
michael@0 895 if (network) {
michael@0 896 this.validateNetwork(network, function onValidateNetwork(aNetId) {
michael@0 897 if (!aNetId) {
michael@0 898 mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
michael@0 899 { id: msg.id, error: "InvalidInterface", result: null });
michael@0 900 return;
michael@0 901 }
michael@0 902
michael@0 903 self._getAlarms(mm, msg, aNetId, manifestURL);
michael@0 904 });
michael@0 905 return;
michael@0 906 }
michael@0 907
michael@0 908 this._getAlarms(mm, msg, null, manifestURL);
michael@0 909 },
michael@0 910
michael@0 911 _getAlarms: function _getAlarms(mm, msg, aNetId, aManifestURL) {
michael@0 912 let self = this;
michael@0 913 this._db.getAlarms(aNetId, aManifestURL, function onCompleted(error, result) {
michael@0 914 if (error) {
michael@0 915 mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
michael@0 916 { id: msg.id, error: error, result: result });
michael@0 917 return;
michael@0 918 }
michael@0 919
michael@0 920 let alarms = []
michael@0 921 // NetworkStatsManager must return the network instead of the networkId.
michael@0 922 for (let i = 0; i < result.length; i++) {
michael@0 923 let alarm = result[i];
michael@0 924 alarms.push({ id: alarm.id,
michael@0 925 network: self._networks[alarm.networkId].network,
michael@0 926 threshold: alarm.absoluteThreshold,
michael@0 927 data: alarm.data });
michael@0 928 }
michael@0 929
michael@0 930 mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
michael@0 931 { id: msg.id, error: null, result: alarms });
michael@0 932 });
michael@0 933 },
michael@0 934
michael@0 935 removeAlarms: function removeAlarms(mm, msg) {
michael@0 936 let alarmId = msg.data.alarmId;
michael@0 937 let manifestURL = msg.data.manifestURL;
michael@0 938
michael@0 939 let self = this;
michael@0 940 let callback = function onRemove(error, result) {
michael@0 941 if (error) {
michael@0 942 mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
michael@0 943 { id: msg.id, error: error, result: result });
michael@0 944 return;
michael@0 945 }
michael@0 946
michael@0 947 for (let i in self._currentAlarms) {
michael@0 948 let currentAlarm = self._currentAlarms[i].alarm;
michael@0 949 if (currentAlarm && ((alarmId == currentAlarm.id) ||
michael@0 950 (alarmId == -1 && currentAlarm.manifestURL == manifestURL))) {
michael@0 951
michael@0 952 self._updateCurrentAlarm(currentAlarm.networkId);
michael@0 953 }
michael@0 954 }
michael@0 955
michael@0 956 mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
michael@0 957 { id: msg.id, error: error, result: true });
michael@0 958 };
michael@0 959
michael@0 960 if (alarmId == -1) {
michael@0 961 this._db.removeAlarms(manifestURL, callback);
michael@0 962 } else {
michael@0 963 this._db.removeAlarm(alarmId, manifestURL, callback);
michael@0 964 }
michael@0 965 },
michael@0 966
michael@0 967 /*
michael@0 968 * Function called from manager to set an alarm.
michael@0 969 */
michael@0 970 setAlarm: function setAlarm(mm, msg) {
michael@0 971 let options = msg.data;
michael@0 972 let network = options.network;
michael@0 973 let threshold = options.threshold;
michael@0 974
michael@0 975 debug("Set alarm at " + threshold + " for " + JSON.stringify(network));
michael@0 976
michael@0 977 if (threshold < 0) {
michael@0 978 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
michael@0 979 { id: msg.id, error: "InvalidThresholdValue", result: null });
michael@0 980 return;
michael@0 981 }
michael@0 982
michael@0 983 let self = this;
michael@0 984 this.validateNetwork(network, function onValidateNetwork(aNetId) {
michael@0 985 if (!aNetId) {
michael@0 986 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
michael@0 987 { id: msg.id, error: "InvalidiConnectionType", result: null });
michael@0 988 return;
michael@0 989 }
michael@0 990
michael@0 991 let newAlarm = {
michael@0 992 id: null,
michael@0 993 networkId: aNetId,
michael@0 994 absoluteThreshold: threshold,
michael@0 995 relativeThreshold: null,
michael@0 996 startTime: options.startTime,
michael@0 997 data: options.data,
michael@0 998 pageURL: options.pageURL,
michael@0 999 manifestURL: options.manifestURL
michael@0 1000 };
michael@0 1001
michael@0 1002 self._getAlarmQuota(newAlarm, function onUpdate(error, quota) {
michael@0 1003 if (error) {
michael@0 1004 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
michael@0 1005 { id: msg.id, error: error, result: null });
michael@0 1006 return;
michael@0 1007 }
michael@0 1008
michael@0 1009 self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
michael@0 1010 if (error) {
michael@0 1011 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
michael@0 1012 { id: msg.id, error: error, result: null });
michael@0 1013 return;
michael@0 1014 }
michael@0 1015
michael@0 1016 newAlarm.id = newId;
michael@0 1017 self._setAlarm(newAlarm, function onSet(error, success) {
michael@0 1018 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
michael@0 1019 { id: msg.id, error: error, result: newId });
michael@0 1020
michael@0 1021 if (error == "InvalidStateError") {
michael@0 1022 self._fireAlarm(newAlarm);
michael@0 1023 }
michael@0 1024 });
michael@0 1025 });
michael@0 1026 });
michael@0 1027 });
michael@0 1028 },
michael@0 1029
michael@0 1030 _setAlarm: function _setAlarm(aAlarm, aCallback) {
michael@0 1031 let currentAlarm = this._currentAlarms[aAlarm.networkId];
michael@0 1032 if ((Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
michael@0 1033 aAlarm.relativeThreshold > currentAlarm.alarm.relativeThreshold) ||
michael@0 1034 this._networks[aAlarm.networkId].status != NETWORK_STATUS_READY) {
michael@0 1035 aCallback(null, true);
michael@0 1036 return;
michael@0 1037 }
michael@0 1038
michael@0 1039 let self = this;
michael@0 1040
michael@0 1041 this._getAlarmQuota(aAlarm, function onUpdate(aError, aQuota) {
michael@0 1042 if (aError) {
michael@0 1043 aCallback(aError, null);
michael@0 1044 return;
michael@0 1045 }
michael@0 1046
michael@0 1047 let callback = function onAlarmSet(aError) {
michael@0 1048 if (aError) {
michael@0 1049 debug("Set alarm error: " + aError);
michael@0 1050 aCallback("netdError", null);
michael@0 1051 return;
michael@0 1052 }
michael@0 1053
michael@0 1054 self._currentAlarms[aAlarm.networkId].alarm = aAlarm;
michael@0 1055
michael@0 1056 aCallback(null, true);
michael@0 1057 };
michael@0 1058
michael@0 1059 debug("Set alarm " + JSON.stringify(aAlarm));
michael@0 1060 let interfaceName = self._networks[aAlarm.networkId].interfaceName;
michael@0 1061 if (interfaceName) {
michael@0 1062 networkService.setNetworkInterfaceAlarm(interfaceName,
michael@0 1063 aQuota,
michael@0 1064 callback);
michael@0 1065 return;
michael@0 1066 }
michael@0 1067
michael@0 1068 aCallback(null, true);
michael@0 1069 });
michael@0 1070 },
michael@0 1071
michael@0 1072 _getAlarmQuota: function _getAlarmQuota(aAlarm, aCallback) {
michael@0 1073 let self = this;
michael@0 1074 this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) {
michael@0 1075 self._db.getCurrentStats(self._networks[aAlarm.networkId].network,
michael@0 1076 aAlarm.startTime,
michael@0 1077 function onStatsFound(error, result) {
michael@0 1078 if (error) {
michael@0 1079 debug("Error getting stats for " +
michael@0 1080 JSON.stringify(self._networks[aAlarm.networkId]) + ": " + error);
michael@0 1081 aCallback(error, result);
michael@0 1082 return;
michael@0 1083 }
michael@0 1084
michael@0 1085 let quota = aAlarm.absoluteThreshold - result.rxBytes - result.txBytes;
michael@0 1086
michael@0 1087 // Alarm set to a threshold lower than current rx/tx bytes.
michael@0 1088 if (quota <= 0) {
michael@0 1089 aCallback("InvalidStateError", null);
michael@0 1090 return;
michael@0 1091 }
michael@0 1092
michael@0 1093 aAlarm.relativeThreshold = aAlarm.startTime
michael@0 1094 ? result.rxTotalBytes + result.txTotalBytes + quota
michael@0 1095 : aAlarm.absoluteThreshold;
michael@0 1096
michael@0 1097 aCallback(null, quota);
michael@0 1098 });
michael@0 1099 });
michael@0 1100 },
michael@0 1101
michael@0 1102 _fireAlarm: function _fireAlarm(aAlarm) {
michael@0 1103 debug("Fire alarm");
michael@0 1104
michael@0 1105 let self = this;
michael@0 1106 this._db.removeAlarm(aAlarm.id, null, function onRemove(aError, aResult){
michael@0 1107 if (!aError && !aResult) {
michael@0 1108 return;
michael@0 1109 }
michael@0 1110
michael@0 1111 self._fireSystemMessage(aAlarm);
michael@0 1112 self._updateCurrentAlarm(aAlarm.networkId);
michael@0 1113 });
michael@0 1114 },
michael@0 1115
michael@0 1116 _updateCurrentAlarm: function _updateCurrentAlarm(aNetworkId) {
michael@0 1117 this._currentAlarms[aNetworkId] = Object.create(null);
michael@0 1118
michael@0 1119 let self = this;
michael@0 1120 this._db.getFirstAlarm(aNetworkId, function onGet(error, result){
michael@0 1121 if (error) {
michael@0 1122 debug("Error getting the first alarm");
michael@0 1123 return;
michael@0 1124 }
michael@0 1125
michael@0 1126 if (!result) {
michael@0 1127 let interfaceName = self._networks[aNetworkId].interfaceName;
michael@0 1128 networkService.setNetworkInterfaceAlarm(interfaceName, -1,
michael@0 1129 function onComplete(){});
michael@0 1130 return;
michael@0 1131 }
michael@0 1132
michael@0 1133 self._setAlarm(result, function onSet(error, success){
michael@0 1134 if (error == "InvalidStateError") {
michael@0 1135 self._fireAlarm(result);
michael@0 1136 return;
michael@0 1137 }
michael@0 1138 });
michael@0 1139 });
michael@0 1140 },
michael@0 1141
michael@0 1142 _fireSystemMessage: function _fireSystemMessage(aAlarm) {
michael@0 1143 debug("Fire system message: " + JSON.stringify(aAlarm));
michael@0 1144
michael@0 1145 let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
michael@0 1146 let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
michael@0 1147
michael@0 1148 let alarm = { "id": aAlarm.id,
michael@0 1149 "threshold": aAlarm.absoluteThreshold,
michael@0 1150 "data": aAlarm.data };
michael@0 1151 messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI);
michael@0 1152 }
michael@0 1153 };
michael@0 1154
michael@0 1155 NetworkStatsService.init();

mercurial