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.

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

mercurial