1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
10 dump("-*- NetworkStatsService: " + s + "\n");
14 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
16 this.EXPORTED_SYMBOLS = ["NetworkStatsService"];
18 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
19 Cu.import("resource://gre/modules/Services.jsm");
20 Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
21 Cu.import("resource://gre/modules/Timer.jsm");
23 const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
24 const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
26 const TOPIC_BANDWIDTH_CONTROL = "netd-bandwidth-control"
28 const TOPIC_CONNECTION_STATE_CHANGED = "network-connection-state-changed";
29 const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
30 const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
32 // Networks have different status that NetworkStats API needs to be aware of.
33 // Network is present and ready, so NetworkManager provides the whole info.
34 const NETWORK_STATUS_READY = 0;
35 // Network is present but hasn't established a connection yet (e.g. SIM that has not
36 // enabled 3G since boot).
37 const NETWORK_STATUS_STANDBY = 1;
38 // Network is not present, but stored in database by the previous connections.
39 const NETWORK_STATUS_AWAY = 2;
41 // The maximum traffic amount can be saved in the |cachedStats|.
42 const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
44 const QUEUE_TYPE_UPDATE_STATS = 0;
45 const QUEUE_TYPE_UPDATE_CACHE = 1;
46 const QUEUE_TYPE_WRITE_CACHE = 2;
48 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
49 "@mozilla.org/parentprocessmessagemanager;1",
50 "nsIMessageListenerManager");
52 XPCOMUtils.defineLazyServiceGetter(this, "gRil",
54 "nsIRadioInterfaceLayer");
56 XPCOMUtils.defineLazyServiceGetter(this, "networkService",
57 "@mozilla.org/network/service;1",
60 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
61 "@mozilla.org/AppsService;1",
64 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
65 "@mozilla.org/settingsService;1",
66 "nsISettingsService");
68 XPCOMUtils.defineLazyServiceGetter(this, "messenger",
69 "@mozilla.org/system-message-internal;1",
70 "nsISystemMessagesInternal");
72 this.NetworkStatsService = {
74 debug("Service started");
76 Services.obs.addObserver(this, "xpcom-shutdown", false);
77 Services.obs.addObserver(this, TOPIC_CONNECTION_STATE_CHANGED, false);
78 Services.obs.addObserver(this, TOPIC_BANDWIDTH_CONTROL, false);
79 Services.obs.addObserver(this, "profile-after-change", false);
81 this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
83 // Object to store network interfaces, each network interface is composed
84 // by a network object (network type and network Id) and a interfaceName
85 // that contains the name of the physical interface (wlan0, rmnet0, etc.).
86 // The network type can be 0 for wifi or 1 for mobile. On the other hand,
87 // the network id is '0' for wifi or the iccid for mobile (SIM).
88 // Each networkInterface is placed in the _networks object by the index of
89 // 'networkId + networkType'.
91 // _networks object allows to map available network interfaces at low level
92 // (wlan0, rmnet0, etc.) to a network. It's not mandatory to have a
93 // networkInterface per network but can't exist a networkInterface not
94 // being mapped to a network.
96 this._networks = Object.create(null);
98 // There is no way to know a priori if wifi connection is available,
99 // just when the wifi driver is loaded, but it is unloaded when
100 // wifi is switched off. So wifi connection is hardcoded
101 let netId = this.getNetworkId('0', NET_TYPE_WIFI);
102 this._networks[netId] = { network: { id: '0',
103 type: NET_TYPE_WIFI },
105 status: NETWORK_STATUS_STANDBY };
107 this.messages = ["NetworkStats:Get",
108 "NetworkStats:Clear",
109 "NetworkStats:ClearAll",
110 "NetworkStats:SetAlarm",
111 "NetworkStats:GetAlarms",
112 "NetworkStats:RemoveAlarms",
113 "NetworkStats:GetAvailableNetworks",
114 "NetworkStats:GetAvailableServiceTypes",
115 "NetworkStats:SampleRate",
116 "NetworkStats:MaxStorageAge"];
118 this.messages.forEach(function(aMsgName) {
119 ppmm.addMessageListener(aMsgName, this);
122 this._db = new NetworkStatsDB();
124 // Stats for all interfaces are updated periodically
125 this.timer.initWithCallback(this, this._db.sampleRate,
126 Ci.nsITimer.TYPE_REPEATING_PRECISE);
128 // Stats not from netd are firstly stored in the cached.
129 this.cachedStats = Object.create(null);
130 this.cachedStatsDate = new Date();
132 this.updateQueue = [];
133 this.isQueueRunning = false;
135 this._currentAlarms = {};
139 receiveMessage: function(aMessage) {
140 if (!aMessage.target.assertPermission("networkstats-manage")) {
144 debug("receiveMessage " + aMessage.name);
146 let mm = aMessage.target;
147 let msg = aMessage.json;
149 switch (aMessage.name) {
150 case "NetworkStats:Get":
151 this.getSamples(mm, msg);
153 case "NetworkStats:Clear":
154 this.clearInterfaceStats(mm, msg);
156 case "NetworkStats:ClearAll":
157 this.clearDB(mm, msg);
159 case "NetworkStats:SetAlarm":
160 this.setAlarm(mm, msg);
162 case "NetworkStats:GetAlarms":
163 this.getAlarms(mm, msg);
165 case "NetworkStats:RemoveAlarms":
166 this.removeAlarms(mm, msg);
168 case "NetworkStats:GetAvailableNetworks":
169 this.getAvailableNetworks(mm, msg);
171 case "NetworkStats:GetAvailableServiceTypes":
172 this.getAvailableServiceTypes(mm, msg);
174 case "NetworkStats:SampleRate":
175 // This message is sync.
176 return this._db.sampleRate;
177 case "NetworkStats:MaxStorageAge":
178 // This message is sync.
179 return this._db.maxStorageSamples * this._db.sampleRate;
183 observe: function observe(aSubject, aTopic, aData) {
185 case TOPIC_CONNECTION_STATE_CHANGED:
187 // If new interface is registered (notified from NetworkService),
188 // the stats are updated for the new interface without waiting to
189 // complete the updating period.
191 let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
192 debug("Network " + network.name + " of type " + network.type + " status change");
194 let netId = this.convertNetworkInterface(network);
199 this._updateCurrentAlarm(netId);
201 debug("NetId: " + netId);
202 this.updateStats(netId);
205 case TOPIC_BANDWIDTH_CONTROL:
206 debug("Bandwidth message from netd: " + JSON.stringify(aData));
208 let interfaceName = aData.substring(aData.lastIndexOf(" ") + 1);
209 for (let networkId in this._networks) {
210 if (interfaceName == this._networks[networkId].interfaceName) {
211 let currentAlarm = this._currentAlarms[networkId];
212 if (Object.getOwnPropertyNames(currentAlarm).length !== 0) {
213 this._fireAlarm(currentAlarm.alarm);
220 case "xpcom-shutdown":
221 debug("Service shutdown");
223 this.messages.forEach(function(aMsgName) {
224 ppmm.removeMessageListener(aMsgName, this);
227 Services.obs.removeObserver(this, "xpcom-shutdown");
228 Services.obs.removeObserver(this, "profile-after-change");
229 Services.obs.removeObserver(this, TOPIC_CONNECTION_STATE_CHANGED);
230 Services.obs.removeObserver(this, TOPIC_BANDWIDTH_CONTROL);
235 // Update stats before shutdown
236 this.updateAllStats();
243 * Timer triggers the update of all stats
245 notify: function(aTimer) {
246 this.updateAllStats();
250 * nsINetworkStatsService
252 getRilNetworks: function() {
254 let numRadioInterfaces = gRil.numRadioInterfaces;
255 for (let i = 0; i < numRadioInterfaces; i++) {
256 let radioInterface = gRil.getRadioInterface(i);
257 if (radioInterface.rilContext.iccInfo) {
258 let netId = this.getNetworkId(radioInterface.rilContext.iccInfo.iccid,
260 networks[netId] = { id : radioInterface.rilContext.iccInfo.iccid,
261 type: NET_TYPE_MOBILE };
267 convertNetworkInterface: function(aNetwork) {
268 if (aNetwork.type != NET_TYPE_MOBILE &&
269 aNetwork.type != NET_TYPE_WIFI) {
274 if (aNetwork.type == NET_TYPE_MOBILE) {
275 if (!(aNetwork instanceof Ci.nsIRilNetworkInterface)) {
276 debug("Error! Mobile network should be an nsIRilNetworkInterface!");
280 let rilNetwork = aNetwork.QueryInterface(Ci.nsIRilNetworkInterface);
281 id = rilNetwork.iccId;
284 let netId = this.getNetworkId(id, aNetwork.type);
286 if (!this._networks[netId]) {
287 this._networks[netId] = Object.create(null);
288 this._networks[netId].network = { id: id,
289 type: aNetwork.type };
292 this._networks[netId].status = NETWORK_STATUS_READY;
293 this._networks[netId].interfaceName = aNetwork.name;
297 getNetworkId: function getNetworkId(aIccId, aNetworkType) {
298 return aIccId + '' + aNetworkType;
301 /* Function to ensure that one network is valid. The network is valid if its status is
302 * NETWORK_STATUS_READY, NETWORK_STATUS_STANDBY or NETWORK_STATUS_AWAY.
304 * The result is |netId| or null in case of a non-valid network
305 * aCallback is signatured as |function(netId)|.
307 validateNetwork: function validateNetwork(aNetwork, aCallback) {
308 let netId = this.getNetworkId(aNetwork.id, aNetwork.type);
310 if (this._networks[netId]) {
315 // Check if network is valid (RIL entry) but has not established a connection yet.
316 // If so add to networks list with empty interfaceName.
317 let rilNetworks = this.getRilNetworks();
318 if (rilNetworks[netId]) {
319 this._networks[netId] = Object.create(null);
320 this._networks[netId].network = rilNetworks[netId];
321 this._networks[netId].status = NETWORK_STATUS_STANDBY;
322 this._currentAlarms[netId] = Object.create(null);
327 // Check if network is available in the DB.
328 this._db.isNetworkAvailable(aNetwork, function(aError, aResult) {
330 this._networks[netId] = Object.create(null);
331 this._networks[netId].network = aNetwork;
332 this._networks[netId].status = NETWORK_STATUS_AWAY;
333 this._currentAlarms[netId] = Object.create(null);
342 getAvailableNetworks: function getAvailableNetworks(mm, msg) {
344 let rilNetworks = this.getRilNetworks();
345 this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
347 // Also return the networks that are valid but have not
348 // established connections yet.
349 for (let netId in rilNetworks) {
351 for (let i = 0; i < aResult.length; i++) {
352 if (netId == self.getNetworkId(aResult[i].id, aResult[i].type)) {
358 aResult.push(rilNetworks[netId]);
362 mm.sendAsyncMessage("NetworkStats:GetAvailableNetworks:Return",
363 { id: msg.id, error: aError, result: aResult });
367 getAvailableServiceTypes: function getAvailableServiceTypes(mm, msg) {
368 this._db.getAvailableServiceTypes(function onGetServiceTypes(aError, aResult) {
369 mm.sendAsyncMessage("NetworkStats:GetAvailableServiceTypes:Return",
370 { id: msg.id, error: aError, result: aResult });
374 initAlarms: function initAlarms() {
375 debug("Init usage alarms");
378 for (let netId in this._networks) {
379 this._currentAlarms[netId] = Object.create(null);
381 this._db.getFirstAlarm(netId, function getResult(error, result) {
382 if (!error && result) {
383 self._setAlarm(result, function onSet(error, success) {
384 if (error == "InvalidStateError") {
385 self._fireAlarm(result);
394 * Function called from manager to get stats from database.
395 * In order to return updated stats, first is performed a call to
396 * updateAllStats function, which will get last stats from netd
397 * and update the database.
398 * Then, depending on the request (stats per appId or total stats)
399 * it retrieve them from database and return to the manager.
401 getSamples: function getSamples(mm, msg) {
402 let network = msg.network;
403 let netId = this.getNetworkId(network.id, network.type);
406 let appManifestURL = msg.appManifestURL;
407 if (appManifestURL) {
408 appId = appsService.getAppLocalIdByManifestURL(appManifestURL);
411 mm.sendAsyncMessage("NetworkStats:Get:Return",
413 error: "Invalid appManifestURL", result: null });
418 let browsingTrafficOnly = msg.browsingTrafficOnly || false;
419 let serviceType = msg.serviceType || "";
421 let start = new Date(msg.start);
422 let end = new Date(msg.end);
424 let callback = (function (aError, aResult) {
425 this._db.find(function onStatsFound(aError, aResult) {
426 mm.sendAsyncMessage("NetworkStats:Get:Return",
427 { id: msg.id, error: aError, result: aResult });
428 }, appId, browsingTrafficOnly, serviceType, network, start, end, appManifestURL);
431 this.validateNetwork(network, function onValidateNetwork(aNetId) {
433 mm.sendAsyncMessage("NetworkStats:Get:Return",
434 { id: msg.id, error: "Invalid connectionType", result: null });
438 // If network is currently active we need to update the cached stats first before
439 // retrieving stats from the DB.
440 if (this._networks[aNetId].status == NETWORK_STATUS_READY) {
441 debug("getstats for network " + network.id + " of type " + network.type);
442 debug("appId: " + appId + " from appManifestURL: " + appManifestURL);
443 debug("browsingTrafficOnly: " + browsingTrafficOnly);
444 debug("serviceType: " + serviceType);
446 if (appId || serviceType) {
447 this.updateCachedStats(callback);
451 this.updateStats(aNetId, function onStatsUpdated(aResult, aMessage) {
452 this.updateCachedStats(callback);
457 // Network not active, so no need to update
458 this._db.find(function onStatsFound(aError, aResult) {
459 mm.sendAsyncMessage("NetworkStats:Get:Return",
460 { id: msg.id, error: aError, result: aResult });
461 }, appId, browsingTrafficOnly, serviceType, network, start, end, appManifestURL);
465 clearInterfaceStats: function clearInterfaceStats(mm, msg) {
467 let network = msg.network;
469 debug("clear stats for network " + network.id + " of type " + network.type);
471 this.validateNetwork(network, function onValidateNetwork(aNetId) {
473 mm.sendAsyncMessage("NetworkStats:Clear:Return",
474 { id: msg.id, error: "Invalid connectionType", result: null });
478 network = {network: network, networkId: aNetId};
479 self.updateStats(aNetId, function onUpdate(aResult, aMessage) {
481 mm.sendAsyncMessage("NetworkStats:Clear:Return",
482 { id: msg.id, error: aMessage, result: null });
486 self._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
487 self._updateCurrentAlarm(aNetId);
488 mm.sendAsyncMessage("NetworkStats:Clear:Return",
489 { id: msg.id, error: aError, result: aResult });
495 clearDB: function clearDB(mm, msg) {
497 this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
499 mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
500 { id: msg.id, error: aError, result: aResult });
504 let networks = aResult;
505 networks.forEach(function(network, index) {
506 networks[index] = {network: network, networkId: self.getNetworkId(network.id, network.type)};
509 self.updateAllStats(function onUpdate(aResult, aMessage){
511 mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
512 { id: msg.id, error: aMessage, result: null });
516 self._db.clearStats(networks, function onDBCleared(aError, aResult) {
517 networks.forEach(function(network, index) {
518 self._updateCurrentAlarm(network.networkId);
520 mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
521 { id: msg.id, error: aError, result: aResult });
527 updateAllStats: function updateAllStats(aCallback) {
529 let lastElement = null;
530 let callback = (function (success, message) {
531 this.updateCachedStats(aCallback);
534 // For each connectionType create an object containning the type
535 // and the 'queueIndex', the 'queueIndex' is an integer representing
536 // the index of a connection type in the global queue array. So, if
537 // the connection type is already in the queue it is not appended again,
538 // else it is pushed in 'elements' array, which later will be pushed to
540 for (let netId in this._networks) {
541 if (this._networks[netId].status != NETWORK_STATUS_READY) {
545 lastElement = { netId: netId,
546 queueIndex: this.updateQueueIndex(netId) };
548 if (lastElement.queueIndex == -1) {
549 elements.push({ netId: lastElement.netId,
551 queueType: QUEUE_TYPE_UPDATE_STATS });
556 // No elements need to be updated, probably because status is different than
557 // NETWORK_STATUS_READY.
559 aCallback(true, "OK");
564 if (elements.length > 0) {
565 // If length of elements is greater than 0, callback is set to
567 elements[elements.length - 1].callbacks.push(callback);
568 this.updateQueue = this.updateQueue.concat(elements);
570 // Else, it means that all connection types are already in the queue to
571 // be updated, so callback for this request is added to
572 // the element in the main queue with the index of the last 'lastElement'.
573 // But before is checked that element is still in the queue because it can
574 // be processed while generating 'elements' array.
575 let element = this.updateQueue[lastElement.queueIndex];
577 (!element || element.netId != lastElement.netId)) {
582 this.updateQueue[lastElement.queueIndex].callbacks.push(callback);
585 // Call the function that process the elements of the queue.
589 this.logAllRecords();
593 updateStats: function updateStats(aNetId, aCallback) {
594 // Check if the connection is in the main queue, push a new element
595 // if it is not being processed or add a callback if it is.
596 let index = this.updateQueueIndex(aNetId);
598 this.updateQueue.push({ netId: aNetId,
599 callbacks: [aCallback],
600 queueType: QUEUE_TYPE_UPDATE_STATS });
602 this.updateQueue[index].callbacks.push(aCallback);
606 // Call the function that process the elements of the queue.
611 * Find if a connection is in the main queue array and return its
612 * index, if it is not in the array return -1.
614 updateQueueIndex: function updateQueueIndex(aNetId) {
615 return this.updateQueue.map(function(e) { return e.netId; }).indexOf(aNetId);
619 * Function responsible of process all requests in the queue.
621 processQueue: function processQueue(aResult, aMessage) {
622 // If aResult is not undefined, the caller of the function is the result
623 // of processing an element, so remove that element and call the callbacks
627 if (aResult != undefined) {
628 let item = this.updateQueue.shift();
629 for (let callback of item.callbacks) {
631 callback(aResult, aMessage);
635 // The caller is a function that has pushed new elements to the queue,
636 // if isQueueRunning is false it means there is no processing currently
637 // being done, so start.
638 if (this.isQueueRunning) {
641 this.isQueueRunning = true;
645 // Check length to determine if queue is empty and stop processing.
646 if (this.updateQueue.length < 1) {
647 this.isQueueRunning = false;
651 // Process the next item as soon as possible.
652 setTimeout(function () {
653 self.run(self.updateQueue[0]);
657 run: function run(item) {
658 switch (item.queueType) {
659 case QUEUE_TYPE_UPDATE_STATS:
660 this.update(item.netId, this.processQueue.bind(this));
662 case QUEUE_TYPE_UPDATE_CACHE:
663 this.updateCache(this.processQueue.bind(this));
665 case QUEUE_TYPE_WRITE_CACHE:
666 this.writeCache(item.stats, this.processQueue.bind(this));
671 update: function update(aNetId, aCallback) {
672 // Check if connection type is valid.
673 if (!this._networks[aNetId]) {
675 aCallback(false, "Invalid network " + aNetId);
680 let interfaceName = this._networks[aNetId].interfaceName;
681 debug("Update stats for " + interfaceName);
683 // Request stats to NetworkService, which will get stats from netd, passing
684 // 'networkStatsAvailable' as a callback.
686 networkService.getNetworkInterfaceStats(interfaceName,
687 this.networkStatsAvailable.bind(this, aCallback, aNetId));
692 aCallback(true, "ok");
697 * Callback of request stats. Store stats in database.
699 networkStatsAvailable: function networkStatsAvailable(aCallback, aNetId,
701 aTxBytes, aTimestamp) {
704 aCallback(false, "Netd IPC error");
709 let stats = { appId: 0,
712 networkId: this._networks[aNetId].network.id,
713 networkType: this._networks[aNetId].network.type,
714 date: new Date(aTimestamp),
717 isAccumulative: true };
719 debug("Update stats for: " + JSON.stringify(stats));
721 this._db.saveStats(stats, function onSavedStats(aError, aResult) {
724 aCallback(false, aError);
728 aCallback(true, "OK");
734 * Function responsible for receiving stats which are not from netd.
736 saveStats: function saveStats(aAppId, aIsInBrowser, aServiceType, aNetwork,
737 aTimeStamp, aRxBytes, aTxBytes, aIsAccumulative,
739 let netId = this.convertNetworkInterface(aNetwork);
742 aCallback(false, "Invalid network type");
747 // Check if |aConnectionType|, |aAppId| and |aServiceType| are valid.
748 // There are two invalid cases for the combination of |aAppId| and
750 // a. Both |aAppId| is non-zero and |aServiceType| is non-empty.
751 // b. Both |aAppId| is zero and |aServiceType| is empty.
752 if (!this._networks[netId] || (aAppId && aServiceType) ||
753 (!aAppId && !aServiceType)) {
754 debug("Invalid network interface, appId or serviceType");
758 let stats = { appId: aAppId,
759 isInBrowser: aIsInBrowser,
760 serviceType: aServiceType,
761 networkId: this._networks[netId].network.id,
762 networkType: this._networks[netId].network.type,
763 date: new Date(aTimeStamp),
766 isAccumulative: aIsAccumulative };
768 this.updateQueue.push({ stats: stats,
769 callbacks: [aCallback],
770 queueType: QUEUE_TYPE_WRITE_CACHE });
778 writeCache: function writeCache(aStats, aCallback) {
779 debug("saveStats: " + aStats.appId + " " + aStats.isInBrowser + " " +
780 aStats.serviceType + " " + aStats.networkId + " " +
781 aStats.networkType + " " + aStats.date + " " +
782 aStats.rxBytes + " " + aStats.txBytes);
784 // Generate an unique key from |appId|, |isInBrowser|, |serviceType| and
785 // |netId|, which is used to retrieve data in |cachedStats|.
786 let netId = this.getNetworkId(aStats.networkId, aStats.networkType);
787 let key = aStats.appId + "" + aStats.isInBrowser + "" +
788 aStats.serviceType + "" + netId;
790 // |cachedStats| only keeps the data with the same date.
791 // If the incoming date is different from |cachedStatsDate|,
792 // both |cachedStats| and |cachedStatsDate| will get updated.
793 let diff = (this._db.normalizeDate(aStats.date) -
794 this._db.normalizeDate(this.cachedStatsDate)) /
797 this.updateCache(function onUpdated(success, message) {
798 this.cachedStatsDate = aStats.date;
799 this.cachedStats[key] = aStats;
802 aCallback(true, "ok");
808 // Try to find the matched row in the cached by |appId| and |connectionType|.
809 // If not found, save the incoming data into the cached.
810 let cachedStats = this.cachedStats[key];
812 this.cachedStats[key] = aStats;
814 aCallback(true, "ok");
819 // Find matched row, accumulate the traffic amount.
820 cachedStats.rxBytes += aStats.rxBytes;
821 cachedStats.txBytes += aStats.txBytes;
823 // If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC
824 // the corresponding row will be saved to indexedDB.
825 // Then, the row will be removed from the cached.
826 if (cachedStats.rxBytes > MAX_CACHED_TRAFFIC ||
827 cachedStats.txBytes > MAX_CACHED_TRAFFIC) {
828 this._db.saveStats(cachedStats, function (error, result) {
829 debug("Application stats inserted in indexedDB");
831 aCallback(true, "ok");
834 delete this.cachedStats[key];
839 aCallback(true, "ok");
843 updateCachedStats: function updateCachedStats(aCallback) {
844 this.updateQueue.push({ callbacks: [aCallback],
845 queueType: QUEUE_TYPE_UPDATE_CACHE });
850 updateCache: function updateCache(aCallback) {
851 debug("updateCache: " + this.cachedStatsDate);
853 let stats = Object.keys(this.cachedStats);
854 if (stats.length == 0) {
855 // |cachedStats| is empty, no need to update.
857 aCallback(true, "no need to update");
863 this._db.saveStats(this.cachedStats[stats[index]],
864 function onSavedStats(error, result) {
865 debug("Application stats inserted in indexedDB");
867 // Clean up the |cachedStats| after updating.
868 if (index == stats.length - 1) {
869 this.cachedStats = Object.create(null);
872 aCallback(true, "ok");
877 // Update is not finished, keep updating.
879 this._db.saveStats(this.cachedStats[stats[index]],
880 onSavedStats.bind(this, error, result));
884 get maxCachedTraffic () {
885 return MAX_CACHED_TRAFFIC;
888 logAllRecords: function logAllRecords() {
889 this._db.logAllRecords(function onResult(aError, aResult) {
891 debug("Error: " + aError);
895 debug("===== LOG =====");
896 debug("There are " + aResult.length + " items");
897 debug(JSON.stringify(aResult));
901 getAlarms: function getAlarms(mm, msg) {
903 let network = msg.data.network;
904 let manifestURL = msg.data.manifestURL;
907 this.validateNetwork(network, function onValidateNetwork(aNetId) {
909 mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
910 { id: msg.id, error: "InvalidInterface", result: null });
914 self._getAlarms(mm, msg, aNetId, manifestURL);
919 this._getAlarms(mm, msg, null, manifestURL);
922 _getAlarms: function _getAlarms(mm, msg, aNetId, aManifestURL) {
924 this._db.getAlarms(aNetId, aManifestURL, function onCompleted(error, result) {
926 mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
927 { id: msg.id, error: error, result: result });
932 // NetworkStatsManager must return the network instead of the networkId.
933 for (let i = 0; i < result.length; i++) {
934 let alarm = result[i];
935 alarms.push({ id: alarm.id,
936 network: self._networks[alarm.networkId].network,
937 threshold: alarm.absoluteThreshold,
941 mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
942 { id: msg.id, error: null, result: alarms });
946 removeAlarms: function removeAlarms(mm, msg) {
947 let alarmId = msg.data.alarmId;
948 let manifestURL = msg.data.manifestURL;
951 let callback = function onRemove(error, result) {
953 mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
954 { id: msg.id, error: error, result: result });
958 for (let i in self._currentAlarms) {
959 let currentAlarm = self._currentAlarms[i].alarm;
960 if (currentAlarm && ((alarmId == currentAlarm.id) ||
961 (alarmId == -1 && currentAlarm.manifestURL == manifestURL))) {
963 self._updateCurrentAlarm(currentAlarm.networkId);
967 mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
968 { id: msg.id, error: error, result: true });
972 this._db.removeAlarms(manifestURL, callback);
974 this._db.removeAlarm(alarmId, manifestURL, callback);
979 * Function called from manager to set an alarm.
981 setAlarm: function setAlarm(mm, msg) {
982 let options = msg.data;
983 let network = options.network;
984 let threshold = options.threshold;
986 debug("Set alarm at " + threshold + " for " + JSON.stringify(network));
989 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
990 { id: msg.id, error: "InvalidThresholdValue", result: null });
995 this.validateNetwork(network, function onValidateNetwork(aNetId) {
997 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
998 { id: msg.id, error: "InvalidiConnectionType", result: null });
1005 absoluteThreshold: threshold,
1006 relativeThreshold: null,
1007 startTime: options.startTime,
1009 pageURL: options.pageURL,
1010 manifestURL: options.manifestURL
1013 self._getAlarmQuota(newAlarm, function onUpdate(error, quota) {
1015 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
1016 { id: msg.id, error: error, result: null });
1020 self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
1022 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
1023 { id: msg.id, error: error, result: null });
1027 newAlarm.id = newId;
1028 self._setAlarm(newAlarm, function onSet(error, success) {
1029 mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
1030 { id: msg.id, error: error, result: newId });
1032 if (error == "InvalidStateError") {
1033 self._fireAlarm(newAlarm);
1041 _setAlarm: function _setAlarm(aAlarm, aCallback) {
1042 let currentAlarm = this._currentAlarms[aAlarm.networkId];
1043 if ((Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
1044 aAlarm.relativeThreshold > currentAlarm.alarm.relativeThreshold) ||
1045 this._networks[aAlarm.networkId].status != NETWORK_STATUS_READY) {
1046 aCallback(null, true);
1052 this._getAlarmQuota(aAlarm, function onUpdate(aError, aQuota) {
1054 aCallback(aError, null);
1058 let callback = function onAlarmSet(aError) {
1060 debug("Set alarm error: " + aError);
1061 aCallback("netdError", null);
1065 self._currentAlarms[aAlarm.networkId].alarm = aAlarm;
1067 aCallback(null, true);
1070 debug("Set alarm " + JSON.stringify(aAlarm));
1071 let interfaceName = self._networks[aAlarm.networkId].interfaceName;
1072 if (interfaceName) {
1073 networkService.setNetworkInterfaceAlarm(interfaceName,
1079 aCallback(null, true);
1083 _getAlarmQuota: function _getAlarmQuota(aAlarm, aCallback) {
1085 this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) {
1086 self._db.getCurrentStats(self._networks[aAlarm.networkId].network,
1088 function onStatsFound(error, result) {
1090 debug("Error getting stats for " +
1091 JSON.stringify(self._networks[aAlarm.networkId]) + ": " + error);
1092 aCallback(error, result);
1096 let quota = aAlarm.absoluteThreshold - result.rxBytes - result.txBytes;
1098 // Alarm set to a threshold lower than current rx/tx bytes.
1100 aCallback("InvalidStateError", null);
1104 aAlarm.relativeThreshold = aAlarm.startTime
1105 ? result.rxTotalBytes + result.txTotalBytes + quota
1106 : aAlarm.absoluteThreshold;
1108 aCallback(null, quota);
1113 _fireAlarm: function _fireAlarm(aAlarm) {
1114 debug("Fire alarm");
1117 this._db.removeAlarm(aAlarm.id, null, function onRemove(aError, aResult){
1118 if (!aError && !aResult) {
1122 self._fireSystemMessage(aAlarm);
1123 self._updateCurrentAlarm(aAlarm.networkId);
1127 _updateCurrentAlarm: function _updateCurrentAlarm(aNetworkId) {
1128 this._currentAlarms[aNetworkId] = Object.create(null);
1131 this._db.getFirstAlarm(aNetworkId, function onGet(error, result){
1133 debug("Error getting the first alarm");
1138 let interfaceName = self._networks[aNetworkId].interfaceName;
1139 networkService.setNetworkInterfaceAlarm(interfaceName, -1,
1140 function onComplete(){});
1144 self._setAlarm(result, function onSet(error, success){
1145 if (error == "InvalidStateError") {
1146 self._fireAlarm(result);
1153 _fireSystemMessage: function _fireSystemMessage(aAlarm) {
1154 debug("Fire system message: " + JSON.stringify(aAlarm));
1156 let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
1157 let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
1159 let alarm = { "id": aAlarm.id,
1160 "threshold": aAlarm.absoluteThreshold,
1161 "data": aAlarm.data };
1162 messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI);
1166 NetworkStatsService.init();