Bug 887699 - Part 2/4 Multi SIM implementation. r=gene,jshih
authorAlbert Crespell <acperez@tid.es>
Tue, 08 Oct 2013 08:09:58 +0200
changeset 166977 78bdbfcca77d3da9566ea73f811fe7394b17acb0
parent 166976 53f2e5066e98d585d5ffec9df6bf7769167bd4d6
child 166978 7f85957616d4fdba57f9fee7fbdec3b2cec8f71b
push id428
push userbbajaj@mozilla.com
push dateTue, 28 Jan 2014 00:16:25 +0000
treeherdermozilla-release@cd72a7ff3a75 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgene, jshih
bugs887699
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 887699 - Part 2/4 Multi SIM implementation. r=gene,jshih
dom/network/src/NetworkStatsDB.jsm
dom/network/src/NetworkStatsManager.js
dom/network/src/NetworkStatsManager.manifest
dom/network/src/NetworkStatsService.jsm
dom/network/src/NetworkStatsServiceProxy.js
--- a/dom/network/src/NetworkStatsDB.jsm
+++ b/dom/network/src/NetworkStatsDB.jsm
@@ -10,46 +10,44 @@ const DEBUG = false;
 function debug(s) { dump("-*- NetworkStatsDB: " + s + "\n"); }
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 
 const DB_NAME = "net_stats";
-const DB_VERSION = 2;
-const STORE_NAME = "net_stats"; // Deprecated. Use "net_stats_v2" instead.
-const STORE_NAME_V2 = "net_stats_v2";
+const DB_VERSION = 3;
+const STORE_NAME = "net_stats";
 
 // Constant defining the maximum values allowed per interface. If more, older
 // will be erased.
 const VALUES_MAX_LENGTH = 6 * 30;
 
 // Constant defining the rate of the samples. Daily.
 const SAMPLE_RATE = 1000 * 60 * 60 * 24;
 
-this.NetworkStatsDB = function NetworkStatsDB(aConnectionTypes) {
+this.NetworkStatsDB = function NetworkStatsDB() {
   if (DEBUG) {
     debug("Constructor");
   }
-  this._connectionTypes = aConnectionTypes;
-  this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME_V2]);
+  this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME]);
 }
 
 NetworkStatsDB.prototype = {
   __proto__: IndexedDBHelper.prototype,
 
   dbNewTxn: function dbNewTxn(txn_type, callback, txnCb) {
     function successCb(result) {
       txnCb(null, result);
     }
     function errorCb(error) {
       txnCb(error, null);
     }
-    return this.newTxn(txn_type, STORE_NAME_V2, callback, successCb, errorCb);
+    return this.newTxn(txn_type, STORE_NAME, callback, successCb, errorCb);
   },
 
   upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion) {
     if (DEBUG) {
       debug("upgrade schema from: " + aOldVersion + " to " + aNewVersion + " called!");
     }
     let db = aDb;
     let objectStore;
@@ -64,371 +62,394 @@ NetworkStatsDB.prototype = {
         objectStore.createIndex("timestamp", "timestamp", { unique: false });
         objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
         objectStore.createIndex("txBytes", "txBytes", { unique: false });
         objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
         objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
         if (DEBUG) {
           debug("Created object stores and indexes");
         }
-
-        // There could be a time delay between the point when the network
-        // interface comes up and the point when the database is initialized.
-        // In this short interval some traffic data are generated but are not
-        // registered by the first sample. The initialization of the database
-        // should make up the missing sample.
-        let stats = [];
-        for (let connection in this._connectionTypes) {
-          let connectionType = this._connectionTypes[connection].name;
-          let timestamp = this.normalizeDate(new Date());
-          stats.push({ connectionType: connectionType,
-                       timestamp:      timestamp,
-                       rxBytes:        0,
-                       txBytes:        0,
-                       rxTotalBytes:   0,
-                       txTotalBytes:   0 });
-        }
-        this._saveStats(aTransaction, objectStore, stats);
-        if (DEBUG) {
-          debug("Database initialized");
-        }
-      } else if (currVersion == 1) {
+      } else if (currVersion == 2) {
         // In order to support per-app traffic data storage, the original
         // objectStore needs to be replaced by a new objectStore with new
         // key path ("appId") and new index ("appId").
-        let newObjectStore;
-        newObjectStore = db.createObjectStore(STORE_NAME_V2, { keyPath: ["appId", "connectionType", "timestamp"] });
-        newObjectStore.createIndex("appId", "appId", { unique: false });
-        newObjectStore.createIndex("connectionType", "connectionType", { unique: false });
-        newObjectStore.createIndex("timestamp", "timestamp", { unique: false });
-        newObjectStore.createIndex("rxBytes", "rxBytes", { unique: false });
-        newObjectStore.createIndex("txBytes", "txBytes", { unique: false });
-        newObjectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
-        newObjectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
-        if (DEBUG) {
-          debug("Created new object stores and indexes");
-        }
+        // Also, since now networks are identified by their
+        // [networkId, networkType] not just by their connectionType,
+        // to modify the keyPath is mandatory to delete the object store
+        // and create it again. Old data is going to be deleted because the
+        // networkId for each sample can not be set.
+        db.deleteObjectStore(STORE_NAME);
 
-        // Copy the data from the original objectStore to the new objectStore.
-        objectStore = aTransaction.objectStore(STORE_NAME);
-        objectStore.openCursor().onsuccess = function(event) {
-          let cursor = event.target.result;
-          if (!cursor) {
-            // Delete the original object store.
-            db.deleteObjectStore(STORE_NAME);
-            return;
-          }
+        objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
+        objectStore.createIndex("appId", "appId", { unique: false });
+        objectStore.createIndex("network", "network", { unique: false });
+        objectStore.createIndex("networkType", "networkType", { unique: false });
+        objectStore.createIndex("timestamp", "timestamp", { unique: false });
+        objectStore.createIndex("rxBytes", "rxBytes", { unique: false });
+        objectStore.createIndex("txBytes", "txBytes", { unique: false });
+        objectStore.createIndex("rxTotalBytes", "rxTotalBytes", { unique: false });
+        objectStore.createIndex("txTotalBytes", "txTotalBytes", { unique: false });
 
-          let oldStats = cursor.value;
-          let newStats = { appId:          0,
-                           connectionType: oldStats.connectionType,
-                           timestamp:      oldStats.timestamp,
-                           rxBytes:        oldStats.rxBytes,
-                           txBytes:        oldStats.txBytes,
-                           rxTotalBytes:   oldStats.rxTotalBytes,
-                           txTotalBytes:   oldStats.txTotalBytes };
-          this._saveStats(aTransaction, newObjectStore, newStats);
-          cursor.continue();
-        }.bind(this);
+        if (DEBUG) {
+          debug("Created object stores and indexes for version 3");
+        }
       }
     }
   },
 
+  importData: function importData(aStats) {
+    let stats = { appId:        aStats.appId,
+                  network:      [aStats.networkId, aStats.networkType],
+                  timestamp:    aStats.timestamp,
+                  rxBytes:      aStats.rxBytes,
+                  txBytes:      aStats.txBytes,
+                  rxTotalBytes: aStats.rxTotalBytes,
+                  txTotalBytes: aStats.txTotalBytes };
+
+    return stats;
+  },
+
+  exportData: function exportData(aStats) {
+    let stats = { appId:        aStats.appId,
+                  networkId:    aStats.network[0],
+                  networkType:  aStats.network[1],
+                  timestamp:    aStats.timestamp,
+                  rxBytes:      aStats.rxBytes,
+                  txBytes:      aStats.txBytes,
+                  rxTotalBytes: aStats.rxTotalBytes,
+                  txTotalBytes: aStats.txTotalBytes };
+
+    return stats;
+  },
+
   normalizeDate: function normalizeDate(aDate) {
     // Convert to UTC according to timezone and
     // filter timestamp to get SAMPLE_RATE precission
     let timestamp = aDate.getTime() - aDate.getTimezoneOffset() * 60 * 1000;
     timestamp = Math.floor(timestamp / SAMPLE_RATE) * SAMPLE_RATE;
     return timestamp;
   },
 
-  saveStats: function saveStats(stats, aResultCb) {
-    let timestamp = this.normalizeDate(stats.date);
+  saveStats: function saveStats(aStats, aResultCb) {
+    let timestamp = this.normalizeDate(aStats.date);
 
-    stats = { appId:          stats.appId,
-              connectionType: stats.connectionType,
-              timestamp:      timestamp,
-              rxBytes:        (stats.appId == 0) ? 0 : stats.rxBytes,
-              txBytes:        (stats.appId == 0) ? 0 : stats.txBytes,
-              rxTotalBytes:   (stats.appId == 0) ? stats.rxBytes : 0,
-              txTotalBytes:   (stats.appId == 0) ? stats.txBytes : 0 };
+    let stats = { appId:        aStats.appId,
+                  networkId:    aStats.networkId,
+                  networkType:  aStats.networkType,
+                  timestamp:    timestamp,
+                  rxBytes:      (aStats.appId == 0) ? 0 : aStats.rxBytes,
+                  txBytes:      (aStats.appId == 0) ? 0 : aStats.txBytes,
+                  rxTotalBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
+                  txTotalBytes: (aStats.appId == 0) ? aStats.txBytes : 0 };
 
-    this.dbNewTxn("readwrite", function(txn, store) {
+    stats = this.importData(stats);
+
+    this.dbNewTxn("readwrite", function(aTxn, aStore) {
       if (DEBUG) {
         debug("Filtered time: " + new Date(timestamp));
         debug("New stats: " + JSON.stringify(stats));
       }
 
-      let request = store.index("connectionType").openCursor(stats.connectionType, "prev");
+    let request = aStore.index("network").openCursor(stats.network, "prev");
       request.onsuccess = function onsuccess(event) {
         let cursor = event.target.result;
         if (!cursor) {
           // Empty, so save first element.
-          this._saveStats(txn, store, stats);
+
+          // There could be a time delay between the point when the network
+          // interface comes up and the point when the database is initialized.
+          // In this short interval some traffic data are generated but are not
+          // registered by the first sample.
+          if (stats.appId == 0) {
+            stats.rxBytes = stats.rxTotalBytes;
+            stats.txBytes = stats.txTotalBytes;
+          }
+
+          this._saveStats(aTxn, aStore, stats);
           return;
         }
 
         if (stats.appId != cursor.value.appId) {
           cursor.continue();
           return;
         }
 
         // There are old samples
         if (DEBUG) {
           debug("Last value " + JSON.stringify(cursor.value));
         }
 
         // Remove stats previous to now - VALUE_MAX_LENGTH
-        this._removeOldStats(txn, store, stats.appId, stats.connectionType, stats.timestamp);
+        this._removeOldStats(aTxn, aStore, stats.appId, stats.network, stats.timestamp);
 
         // Process stats before save
-        this._processSamplesDiff(txn, store, cursor, stats);
+        this._processSamplesDiff(aTxn, aStore, cursor, stats);
       }.bind(this);
     }.bind(this), aResultCb);
   },
 
   /*
    * This function check that stats are saved in the database following the sample rate.
    * In this way is easier to find elements when stats are requested.
    */
-  _processSamplesDiff: function _processSamplesDiff(txn, store, lastSampleCursor, newSample) {
-    let lastSample = lastSampleCursor.value;
+  _processSamplesDiff: function _processSamplesDiff(aTxn, aStore, aLastSampleCursor, aNewSample) {
+    let lastSample = aLastSampleCursor.value;
 
     // Get difference between last and new sample.
-    let diff = (newSample.timestamp - lastSample.timestamp) / SAMPLE_RATE;
+    let diff = (aNewSample.timestamp - lastSample.timestamp) / SAMPLE_RATE;
     if (diff % 1) {
       // diff is decimal, so some error happened because samples are stored as a multiple
       // of SAMPLE_RATE
-      txn.abort();
+      aTxn.abort();
       throw new Error("Error processing samples");
     }
 
     if (DEBUG) {
-      debug("New: " + newSample.timestamp + " - Last: " + lastSample.timestamp + " - diff: " + diff);
+      debug("New: " + aNewSample.timestamp + " - Last: " +
+            lastSample.timestamp + " - diff: " + diff);
     }
 
     // If the incoming data is obtained from netd (|newSample.appId| is 0),
     // the new |txBytes|/|rxBytes| is assigend by the differnce between the new
     // |txTotalBytes|/|rxTotalBytes| and the last |txTotalBytes|/|rxTotalBytes|.
     // Else, the incoming data is per-app data (|newSample.appId| is not 0),
     // the |txBytes|/|rxBytes| is directly the new |txBytes|/|rxBytes|.
-    if (newSample.appId == 0) {
-      let rxDiff = newSample.rxTotalBytes - lastSample.rxTotalBytes;
-      let txDiff = newSample.txTotalBytes - lastSample.txTotalBytes;
+    if (aNewSample.appId == 0) {
+      let rxDiff = aNewSample.rxTotalBytes - lastSample.rxTotalBytes;
+      let txDiff = aNewSample.txTotalBytes - lastSample.txTotalBytes;
       if (rxDiff < 0 || txDiff < 0) {
-        rxDiff = newSample.rxTotalBytes;
-        txDiff = newSample.txTotalBytes;
+        rxDiff = aNewSample.rxTotalBytes;
+        txDiff = aNewSample.txTotalBytes;
       }
-      newSample.rxBytes = rxDiff;
-      newSample.txBytes = txDiff;
+      aNewSample.rxBytes = rxDiff;
+      aNewSample.txBytes = txDiff;
     }
 
     if (diff == 1) {
       // New element.
 
       // If the incoming data is per-data data, new |rxTotalBytes|/|txTotalBytes|
       // needs to be obtained by adding new |rxBytes|/|txBytes| to last
       // |rxTotalBytes|/|txTotalBytes|.
-      if (newSample.appId != 0) {
-        newSample.rxTotalBytes = newSample.rxBytes + lastSample.rxTotalBytes;
-        newSample.txTotalBytes = newSample.txBytes + lastSample.txTotalBytes;
+      if (aNewSample.appId != 0) {
+        aNewSample.rxTotalBytes = aNewSample.rxBytes + lastSample.rxTotalBytes;
+        aNewSample.txTotalBytes = aNewSample.txBytes + lastSample.txTotalBytes;
       }
-      this._saveStats(txn, store, newSample);
+
+      this._saveStats(aTxn, aStore, aNewSample);
       return;
     }
     if (diff > 1) {
       // Some samples lost. Device off during one or more samplerate periods.
       // Time or timezone changed
       // Add lost samples with 0 bytes and the actual one.
       if (diff > VALUES_MAX_LENGTH) {
         diff = VALUES_MAX_LENGTH;
       }
 
       let data = [];
       for (let i = diff - 2; i >= 0; i--) {
-        let time = newSample.timestamp - SAMPLE_RATE * (i + 1);
-        let sample = {appId:          newSample.appId,
-                      connectionType: newSample.connectionType,
-                      timestamp:      time,
-                      rxBytes:        0,
-                      txBytes:        0,
-                      rxTotalBytes:   lastSample.rxTotalBytes,
-                      txTotalBytes:   lastSample.txTotalBytes};
+        let time = aNewSample.timestamp - SAMPLE_RATE * (i + 1);
+        let sample = { appId:        aNewSample.appId,
+                       network:      aNewSample.network,
+                       timestamp:    time,
+                       rxBytes:      0,
+                       txBytes:      0,
+                       rxTotalBytes: lastSample.rxTotalBytes,
+                       txTotalBytes: lastSample.txTotalBytes };
+
         data.push(sample);
       }
 
-      data.push(newSample);
-      this._saveStats(txn, store, data);
+      data.push(aNewSample);
+      this._saveStats(aTxn, aStore, data);
       return;
     }
     if (diff == 0 || diff < 0) {
       // New element received before samplerate period.
       // It means that device has been restarted (or clock / timezone change).
       // Update element.
 
       // If diff < 0, clock or timezone changed back. Place data in the last sample.
 
-      lastSample.rxBytes += newSample.rxBytes;
-      lastSample.txBytes += newSample.txBytes;
+      lastSample.rxBytes += aNewSample.rxBytes;
+      lastSample.txBytes += aNewSample.txBytes;
 
       // If incoming data is obtained from netd, last |rxTotalBytes|/|txTotalBytes|
       // needs to get updated by replacing the new |rxTotalBytes|/|txTotalBytes|.
-      if (newSample.appId == 0) {
-        lastSample.rxTotalBytes = newSample.rxTotalBytes;
-        lastSample.txTotalBytes = newSample.txTotalBytes;
+      if (aNewSample.appId == 0) {
+        lastSample.rxTotalBytes = aNewSample.rxTotalBytes;
+        lastSample.txTotalBytes = aNewSample.txTotalBytes;
       } else {
         // Else, the incoming data is per-app data, old |rxTotalBytes|/
         // |txTotalBytes| needs to get updated by adding the new
         // |rxBytes|/|txBytes| to last |rxTotalBytes|/|txTotalBytes|.
-        lastSample.rxTotalBytes += newSample.rxBytes;
-        lastSample.txTotalBytes += newSample.txBytes;
+        lastSample.rxTotalBytes += aNewSample.rxBytes;
+        lastSample.txTotalBytes += aNewSample.txBytes;
       }
       if (DEBUG) {
         debug("Update: " + JSON.stringify(lastSample));
       }
-      let req = lastSampleCursor.update(lastSample);
+      let req = aLastSampleCursor.update(lastSample);
     }
   },
 
-  _saveStats: function _saveStats(txn, store, networkStats) {
+  _saveStats: function _saveStats(aTxn, aStore, aNetworkStats) {
     if (DEBUG) {
-      debug("_saveStats: " + JSON.stringify(networkStats));
+      debug("_saveStats: " + JSON.stringify(aNetworkStats));
     }
 
-    if (Array.isArray(networkStats)) {
-      let len = networkStats.length - 1;
+    if (Array.isArray(aNetworkStats)) {
+      let len = aNetworkStats.length - 1;
       for (let i = 0; i <= len; i++) {
-        store.put(networkStats[i]);
+        aStore.put(aNetworkStats[i]);
       }
     } else {
-      store.put(networkStats);
+      aStore.put(aNetworkStats);
     }
   },
 
-  _removeOldStats: function _removeOldStats(txn, store, appId, connType, date) {
+  _removeOldStats: function _removeOldStats(aTxn, aStore, aAppId, aNetwork, aDate) {
     // Callback function to remove old items when new ones are added.
-    let filterDate = date - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
-    let lowerFilter = [appId, connType, 0];
-    let upperFilter = [appId, connType, filterDate];
+    let filterDate = aDate - (SAMPLE_RATE * VALUES_MAX_LENGTH - 1);
+    let lowerFilter = [aAppId, aNetwork, 0];
+    let upperFilter = [aAppId, aNetwork, filterDate];
     let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
-    store.openCursor(range).onsuccess = function(event) {
+    let lastSample = null;
+    let self = this;
+
+    aStore.openCursor(range).onsuccess = function(event) {
       var cursor = event.target.result;
       if (cursor) {
+        lastSample = cursor.value;
         cursor.delete();
         cursor.continue();
+        return;
       }
-    }.bind(this);
+
+      // If all samples for a network are removed, an empty sample
+      // has to be saved to keep the totalBytes in order to compute
+      // future samples because system counters are not set to 0.
+      // Thus, if there are no samples left, the last sample removed
+      // will be saved again after setting its bytes to 0.
+      let request = aStore.index("network").openCursor(aNetwork);
+      request.onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (!cursor && lastSample != null) {
+          let timestamp = new Date();
+          timestamp = self.normalizeDate(timestamp);
+          lastSample.timestamp = timestamp;
+          lastSample.rxBytes = 0;
+          lastSample.txBytes = 0;
+          self._saveStats(aTxn, aStore, lastSample);
+        }
+      };
+    };
   },
 
-  clear: function clear(aResultCb) {
-    this.dbNewTxn("readwrite", function(txn, store) {
-      if (DEBUG) {
-        debug("Going to clear all!");
-      }
-      store.clear();
+  clearInterfaceStats: function clearInterfaceStats(aNetwork, aResultCb) {
+    let network = [aNetwork.id, aNetwork.type];
+    let self = this;
+
+    // Clear and save an empty sample to keep sync with system counters
+    this.dbNewTxn("readwrite", function(aTxn, aStore) {
+      let sample = null;
+      let request = aStore.index("network").openCursor(network, "prev");
+      request.onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          if (!sample) {
+            sample = cursor.value;
+          }
+
+          cursor.delete();
+          cursor.continue();
+          return;
+        }
+
+        if (sample) {
+          let timestamp = new Date();
+          timestamp = self.normalizeDate(timestamp);
+          sample.timestamp = timestamp;
+          sample.appId = 0;
+          sample.rxBytes = 0;
+          sample.txBytes = 0;
+
+          self._saveStats(aTxn, aStore, sample);
+        }
+      };
     }, aResultCb);
   },
 
-  find: function find(aResultCb, aOptions) {
+  clearStats: function clearStats(aNetworks, aResultCb) {
+    let index = 0;
+    let stats = [];
+    let self = this;
+
+    let callback = function(aError, aResult) {
+      index++;
+
+      if (!aError && index < aNetworks.length) {
+        self.clearInterfaceStats(aNetworks[index], callback);
+        return;
+      }
+
+      aResultCb(aError, aResult);
+    };
+
+    if (!aNetworks[index]) {
+      aResultCb(null, true);
+      return;
+    }
+    this.clearInterfaceStats(aNetworks[index], callback);
+  },
+
+  find: function find(aResultCb, aNetwork, aStart, aEnd, aAppId, aManifestURL) {
     let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
-    let start = this.normalizeDate(aOptions.start);
-    let end = this.normalizeDate(aOptions.end);
+    let start = this.normalizeDate(aStart);
+    let end = this.normalizeDate(aEnd);
 
     if (DEBUG) {
-      debug("Find: appId: " + aOptions.appId + " connectionType:" +
-            aOptions.connectionType + " start: " + start + " end: " + end);
+      debug("Find samples for appId: " + aAppId + " network " +
+            JSON.stringify(aNetwork) + " from " + start + " until " + end);
       debug("Start time: " + new Date(start));
       debug("End time: " + new Date(end));
     }
 
-    this.dbNewTxn("readonly", function(txn, store) {
-      let lowerFilter = [aOptions.appId, aOptions.connectionType, start];
-      let upperFilter = [aOptions.appId, aOptions.connectionType, end];
+    this.dbNewTxn("readonly", function(aTxn, aStore) {
+      let network = [aNetwork.id, aNetwork.type];
+      let lowerFilter = [aAppId, network, start];
+      let upperFilter = [aAppId, network, end];
       let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
 
       let data = [];
 
-      if (!txn.result) {
-        txn.result = {};
+      if (!aTxn.result) {
+        aTxn.result = {};
       }
 
-      let request = store.openCursor(range).onsuccess = function(event) {
+      let request = aStore.openCursor(range).onsuccess = function(event) {
         var cursor = event.target.result;
         if (cursor){
           data.push({ rxBytes: cursor.value.rxBytes,
                       txBytes: cursor.value.txBytes,
                       date: new Date(cursor.value.timestamp + offset) });
           cursor.continue();
           return;
         }
 
         // When requested samples (start / end) are not in the range of now and
         // now - VALUES_MAX_LENGTH, fill with empty samples.
         this.fillResultSamples(start + offset, end + offset, data);
 
-        txn.result.manifestURL = aOptions.manifestURL;
-        txn.result.connectionType = aOptions.connectionType;
-        txn.result.start = aOptions.start;
-        txn.result.end = aOptions.end;
-        txn.result.data = data;
-      }.bind(this);
-    }.bind(this), aResultCb);
-  },
-
-  findAll: function findAll(aResultCb, aOptions) {
-    let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
-    let start = this.normalizeDate(aOptions.start);
-    let end = this.normalizeDate(aOptions.end);
-
-    if (DEBUG) {
-      debug("FindAll: appId: " + aOptions.appId +
-            " start: " + start + " end: " + end + "\n");
-    }
-
-    let self = this;
-    this.dbNewTxn("readonly", function(txn, store) {
-      let lowerFilter = start;
-      let upperFilter = end;
-      let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false);
-
-      let data = [];
-
-      if (!txn.result) {
-        txn.result = {};
-      }
-
-      let request = store.index("timestamp").openCursor(range).onsuccess = function(event) {
-        var cursor = event.target.result;
-        if (cursor) {
-          if (cursor.value.appId != aOptions.appId) {
-            cursor.continue();
-            return;
-          }
-
-          if (data.length > 0 &&
-              data[data.length - 1].date.getTime() == cursor.value.timestamp + offset) {
-            // Time is the same, so add values.
-            data[data.length - 1].rxBytes += cursor.value.rxBytes;
-            data[data.length - 1].txBytes += cursor.value.txBytes;
-          } else {
-            data.push({ rxBytes: cursor.value.rxBytes,
-                        txBytes: cursor.value.txBytes,
-                        date: new Date(cursor.value.timestamp + offset) });
-          }
-          cursor.continue();
-          return;
-        }
-
-        this.fillResultSamples(start + offset, end + offset, data);
-
-        txn.result.manifestURL = aOptions.manifestURL;
-        txn.result.connectionType = aOptions.connectionType;
-        txn.result.start = aOptions.start;
-        txn.result.end = aOptions.end;
-        txn.result.data = data;
+        aTxn.result.manifestURL = aManifestURL;
+        aTxn.result.network = aNetwork;
+        aTxn.result.start = aStart;
+        aTxn.result.end = aEnd;
+        aTxn.result.data = data;
       }.bind(this);
     }.bind(this), aResultCb);
   },
 
   /*
    * Fill data array (samples from database) with empty samples to match
    * requested start / end dates.
    */
@@ -456,15 +477,15 @@ NetworkStatsDB.prototype = {
     return SAMPLE_RATE;
   },
 
   get maxStorageSamples () {
     return VALUES_MAX_LENGTH;
   },
 
   logAllRecords: function logAllRecords(aResultCb) {
-    this.dbNewTxn("readonly", function(txn, store) {
-      store.mozGetAll().onsuccess = function onsuccess(event) {
-        txn.result = event.target.result;
+    this.dbNewTxn("readonly", function(aTxn, aStore) {
+      aStore.mozGetAll().onsuccess = function onsuccess(event) {
+        aTxn.result = event.target.result;
       };
     }, aResultCb);
   },
 };
--- a/dom/network/src/NetworkStatsManager.js
+++ b/dom/network/src/NetworkStatsManager.js
@@ -50,60 +50,90 @@ NetworkStatsData.prototype = {
                                      contractID:"@mozilla.org/networkstatsdata;1",
                                      classDescription: "NetworkStatsData",
                                      interfaces: [nsIDOMMozNetworkStatsData],
                                      flags: nsIClassInfo.DOM_OBJECT}),
 
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsData])
 };
 
+// NetworkStatsInterface
+const NETWORKSTATSINTERFACE_CONTRACTID = "@mozilla.org/networkstatsinterface;1";
+const NETWORKSTATSINTERFACE_CID        = Components.ID("{f540615b-d803-43ff-8200-2a9d145a5645}");
+const nsIDOMMozNetworkStatsInterface   = Components.interfaces.nsIDOMMozNetworkStatsInterface;
+
+function NetworkStatsInterface(aNetwork) {
+  if (DEBUG) {
+    debug("NetworkStatsInterface Constructor");
+  }
+  this.type = aNetwork.type;
+  this.id = aNetwork.id;
+}
+
+NetworkStatsInterface.prototype = {
+  __exposedProps__: {
+                      id: 'r',
+                      type: 'r',
+                    },
+
+  classID : NETWORKSTATSINTERFACE_CID,
+  classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSINTERFACE_CID,
+                                     contractID: NETWORKSTATSINTERFACE_CONTRACTID,
+                                     classDescription: "NetworkStatsInterface",
+                                     interfaces: [nsIDOMMozNetworkStatsInterface],
+                                     flags: nsIClassInfo.DOM_OBJECT}),
+
+  QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsInterface])
+}
+
 // NetworkStats
 const NETWORKSTATS_CONTRACTID = "@mozilla.org/networkstats;1";
-const NETWORKSTATS_CID        = Components.ID("{6613ea55-b99c-44f9-91bf-d07da10b9b74}");
+const NETWORKSTATS_CID        = Components.ID("{b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}");
 const nsIDOMMozNetworkStats   = Components.interfaces.nsIDOMMozNetworkStats;
 
 function NetworkStats(aWindow, aStats) {
   if (DEBUG) {
     debug("NetworkStats Constructor");
   }
   this.manifestURL = aStats.manifestURL || null;
-  this.connectionType = aStats.connectionType || null;
+  this.network = new NetworkStatsInterface(aStats.network);
   this.start = aStats.start || null;
   this.end = aStats.end || null;
 
   let samples = this.data = Cu.createArrayIn(aWindow);
   for (let i = 0; i < aStats.data.length; i++) {
     samples.push(new NetworkStatsData(aStats.data[i]));
   }
 }
 
 NetworkStats.prototype = {
   __exposedProps__: {
                       manifestURL: 'r',
-                      connectionType: 'r',
+                      network: 'r',
                       start: 'r',
                       end:  'r',
                       data:  'r',
                     },
 
   classID : NETWORKSTATS_CID,
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATS_CID,
                                      contractID: NETWORKSTATS_CONTRACTID,
                                      classDescription: "NetworkStats",
                                      interfaces: [nsIDOMMozNetworkStats],
                                      flags: nsIClassInfo.DOM_OBJECT}),
 
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStats,
-                                          nsIDOMMozNetworkStatsData])
+                                          nsIDOMMozNetworkStatsData,
+                                          nsIDOMMozNetworkStatsInterface])
 }
 
 // NetworkStatsManager
 
 const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
-const NETWORKSTATSMANAGER_CID        = Components.ID("{87529a6c-aef6-11e1-a595-4f034275cfa6}");
+const NETWORKSTATSMANAGER_CID        = Components.ID("{5fbdcae6-a2cd-47b3-929f-83ac75bd4881}");
 const nsIDOMMozNetworkStatsManager   = Components.interfaces.nsIDOMMozNetworkStatsManager;
 
 function NetworkStatsManager() {
   if (DEBUG) {
     debug("Constructor");
   }
 }
 
@@ -111,52 +141,79 @@ NetworkStatsManager.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   checkPrivileges: function checkPrivileges() {
     if (!this.hasPrivileges) {
       throw Components.Exception("Permission denied", Cr.NS_ERROR_FAILURE);
     }
   },
 
-  getNetworkStats: function getNetworkStats(aOptions) {
+  getSamples: function getSamples(aNetwork, aStart, aEnd, aManifestURL) {
     this.checkPrivileges();
 
-    if (!aOptions.start || !aOptions.end ||
-      aOptions.start > aOptions.end) {
+    if (aStart.constructor.name !== "Date" ||
+        aEnd.constructor.name !== "Date" ||
+        aStart > aEnd) {
       throw Components.results.NS_ERROR_INVALID_ARG;
     }
 
+    // TODO Bug 929410 Date object cannot correctly pass through cpmm/ppmm IPC
+    // This is just a work-around by passing timestamp numbers.
+    aStart = aStart.getTime();
+    aEnd = aEnd.getTime();
+
     let request = this.createRequest();
     cpmm.sendAsyncMessage("NetworkStats:Get",
-                          {data: aOptions, id: this.getRequestId(request)});
+                          { network: aNetwork,
+                            start: aStart,
+                            end: aEnd,
+                            manifestURL: aManifestURL,
+                            id: this.getRequestId(request) });
     return request;
   },
 
-  clearAllData: function clearAllData() {
+  clearStats: function clearStats(aNetwork) {
     this.checkPrivileges();
 
     let request = this.createRequest();
     cpmm.sendAsyncMessage("NetworkStats:Clear",
+                          { network: aNetwork,
+                            id: this.getRequestId(request) });
+    return request;
+  },
+
+  clearAllStats: function clearAllStats() {
+    this.checkPrivileges();
+
+    let request = this.createRequest();
+    cpmm.sendAsyncMessage("NetworkStats:ClearAll",
                           {id: this.getRequestId(request)});
     return request;
   },
 
-  get connectionTypes() {
+  get availableNetworks() {
     this.checkPrivileges();
-    return ObjectWrapper.wrap(cpmm.sendSyncMessage("NetworkStats:Types")[0], this._window);
+
+    let result = ObjectWrapper.wrap(cpmm.sendSyncMessage("NetworkStats:Networks")[0], this._window);
+    let networks = this.data = Cu.createArrayIn(this._window);
+    for (let i = 0; i < result.length; i++) {
+      networks.push(new NetworkStatsInterface(result[i]));
+    }
+
+    return networks;
   },
 
   get sampleRate() {
     this.checkPrivileges();
-    return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0] / 1000;
+    return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0];
   },
 
-  get maxStorageSamples() {
+  get maxStorageAge() {
     this.checkPrivileges();
-    return cpmm.sendSyncMessage("NetworkStats:MaxStorageSamples")[0];
+    return cpmm.sendSyncMessage("NetworkStats:MaxStorageAge")[0];
   },
 
   receiveMessage: function(aMessage) {
     if (DEBUG) {
       debug("NetworkStatsmanager::receiveMessage: " + aMessage.name);
     }
     let msg = aMessage.json;
 
@@ -178,16 +235,17 @@ NetworkStatsManager.prototype = {
         let result = new NetworkStats(this._window, msg.result);
         if (DEBUG) {
           debug("result: " + JSON.stringify(result));
         }
         Services.DOMRequest.fireSuccess(req, result);
         break;
 
       case "NetworkStats:Clear:Return":
+      case "NetworkStats:ClearAll:Return":
         if (msg.error) {
           Services.DOMRequest.fireError(req, msg.error);
           return;
         }
 
         Services.DOMRequest.fireSuccess(req, true);
         break;
 
@@ -217,17 +275,18 @@ NetworkStatsManager.prototype = {
       debug("has privileges: " + this.hasPrivileges);
     }
 
     if (!this.hasPrivileges) {
       return null;
     }
 
     this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
-                              "NetworkStats:Clear:Return"]);
+                                        "NetworkStats:Clear:Return",
+                                        "NetworkStats:ClearAll:Return"]);
   },
 
   // Called from DOMRequestIpcHelper
   uninit: function uninit() {
     if (DEBUG) {
       debug("uninit call");
     }
   },
@@ -240,10 +299,11 @@ NetworkStatsManager.prototype = {
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSMANAGER_CID,
                                      contractID: NETWORKSTATSMANAGER_CONTRACTID,
                                      classDescription: "NetworkStatsManager",
                                      interfaces: [nsIDOMMozNetworkStatsManager],
                                      flags: nsIClassInfo.DOM_OBJECT})
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsData,
+                                                     NetworkStatsInterface,
                                                      NetworkStats,
                                                      NetworkStatsManager]);
--- a/dom/network/src/NetworkStatsManager.manifest
+++ b/dom/network/src/NetworkStatsManager.manifest
@@ -1,9 +1,12 @@
 component {3b16fe17-5583-483a-b486-b64a3243221c} NetworkStatsManager.js
 contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
 
-component {6613ea55-b99c-44f9-91bf-d07da10b9b74} NetworkStatsManager.js
-contract @mozilla.org/networkStats;1 {6613ea55-b99c-44f9-91bf-d07da10b9b74}
+component {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe} NetworkStatsManager.js
+contract @mozilla.org/networkStats;1 {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}
 
-component {87529a6c-aef6-11e1-a595-4f034275cfa6} NetworkStatsManager.js
-contract @mozilla.org/networkStatsManager;1 {87529a6c-aef6-11e1-a595-4f034275cfa6}
+component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
+contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
+
+component {5fbdcae6-a2cd-47b3-929f-83ac75bd4881} NetworkStatsManager.js
+contract @mozilla.org/networkStatsManager;1 {5fbdcae6-a2cd-47b3-929f-83ac75bd4881}
 category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
--- a/dom/network/src/NetworkStatsService.jsm
+++ b/dom/network/src/NetworkStatsService.jsm
@@ -1,80 +1,108 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const DEBUG = false;
-function debug(s) { dump("-*- NetworkStatsService: " + s + "\n"); }
+function debug(s) {
+  if (DEBUG) {
+    dump("-*- NetworkStatsService: " + s + "\n");
+  }
+}
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 this.EXPORTED_SYMBOLS = ["NetworkStatsService"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
 
 const NET_NETWORKSTATSSERVICE_CONTRACTID = "@mozilla.org/network/netstatsservice;1";
 const NET_NETWORKSTATSSERVICE_CID = Components.ID("{18725604-e9ac-488a-8aa0-2471e7f6c0a4}");
 
 const TOPIC_INTERFACE_REGISTERED   = "network-interface-registered";
 const TOPIC_INTERFACE_UNREGISTERED = "network-interface-unregistered";
 const NET_TYPE_WIFI = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
 const NET_TYPE_MOBILE = Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE;
-const NET_TYPE_UNKNOWN = Ci.nsINetworkInterface.NETWORK_TYPE_UNKNOWN;
 
 // The maximum traffic amount can be saved in the |cachedAppStats|.
 const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageListenerManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "networkManager",
                                    "@mozilla.org/network/manager;1",
                                    "nsINetworkManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
                                    "@mozilla.org/AppsService;1",
                                    "nsIAppsService");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
+                                   "@mozilla.org/settingsService;1",
+                                   "nsISettingsService");
+
+XPCOMUtils.defineLazyGetter(this, "gRadioInterface", function () {
+  let ril = Cc["@mozilla.org/ril;1"].getService(Ci["nsIRadioInterfaceLayer"]);
+  // TODO: Bug 923382 - B2G Multi-SIM: support multiple SIM cards for network metering.
+  return ril.getRadioInterface(0);
+});
+
 this.NetworkStatsService = {
   init: function() {
-    if (DEBUG) {
-      debug("Service started");
-    }
+    debug("Service started");
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     Services.obs.addObserver(this, TOPIC_INTERFACE_REGISTERED, false);
     Services.obs.addObserver(this, TOPIC_INTERFACE_UNREGISTERED, false);
     Services.obs.addObserver(this, "profile-after-change", false);
 
     this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
-    this._connectionTypes = Object.create(null);
-    this._connectionTypes[NET_TYPE_WIFI] = { name: "wifi",
-                                             network: Object.create(null) };
-    this._connectionTypes[NET_TYPE_MOBILE] = { name: "mobile",
-                                               network: Object.create(null) };
+    // Object to store network interfaces, each network interface is composed
+    // by a network object (network type and network Id) and a interfaceName
+    // that contains the name of the physical interface (wlan0, rmnet0, etc.).
+    // The network type can be 0 for wifi or 1 for mobile. On the other hand,
+    // the network id is '0' for wifi or the iccid for mobile (SIM).
+    // Each networkInterface is placed in the _networks object by the index of
+    // 'networkId + networkType'.
+    //
+    // _networks object allows to map available network interfaces at low level
+    // (wlan0, rmnet0, etc.) to a network. It's not mandatory to have a
+    // networkInterface per network but can't exist a networkInterface not
+    // being mapped to a network.
 
+    this._networks = Object.create(null);
+
+    // There is no way to know a priori if wifi connection is available,
+    // just when the wifi driver is loaded, but it is unloaded when
+    // wifi is switched off. So wifi connection is hardcoded
+    let netId = this.getNetworkId('0', NET_TYPE_WIFI);
+    this._networks[netId] = { network:       { id: '0',
+                                               type: NET_TYPE_WIFI },
+                              interfaceName: null };
 
     this.messages = ["NetworkStats:Get",
                      "NetworkStats:Clear",
-                     "NetworkStats:Types",
+                     "NetworkStats:ClearAll",
+                     "NetworkStats:Networks",
                      "NetworkStats:SampleRate",
-                     "NetworkStats:MaxStorageSamples"];
+                     "NetworkStats:MaxStorageAge"];
 
-    this.messages.forEach(function(msgName) {
-      ppmm.addMessageListener(msgName, this);
+    this.messages.forEach(function(aMsgName) {
+      ppmm.addMessageListener(aMsgName, this);
     }, this);
 
-    this._db = new NetworkStatsDB(this._connectionTypes);
+    this._db = new NetworkStatsDB();
 
     // Stats for all interfaces are updated periodically
     this.timer.initWithCallback(this, this._db.sampleRate,
                                 Ci.nsITimer.TYPE_REPEATING_PRECISE);
 
     // App stats are firstly stored in the cached.
     this.cachedAppStats = Object.create(null);
     this.cachedAppStatsDate = new Date();
@@ -83,68 +111,66 @@ this.NetworkStatsService = {
     this.isQueueRunning = false;
   },
 
   receiveMessage: function(aMessage) {
     if (!aMessage.target.assertPermission("networkstats-manage")) {
       return;
     }
 
-    if (DEBUG) {
-      debug("receiveMessage " + aMessage.name);
-    }
+    debug("receiveMessage " + aMessage.name);
+
     let mm = aMessage.target;
     let msg = aMessage.json;
 
     switch (aMessage.name) {
       case "NetworkStats:Get":
-        this.getStats(mm, msg);
+        this.getSamples(mm, msg);
         break;
       case "NetworkStats:Clear":
+        this.clearInterfaceStats(mm, msg);
+        break;
+      case "NetworkStats:ClearAll":
         this.clearDB(mm, msg);
         break;
-      case "NetworkStats:Types":
-        // This message is sync.
-        let types = [];
-        for (let i in this._connectionTypes) {
-          types.push(this._connectionTypes[i].name);
-        }
-        return types;
+      case "NetworkStats:Networks":
+        return this.availableNetworks();
       case "NetworkStats:SampleRate":
         // This message is sync.
         return this._db.sampleRate;
-      case "NetworkStats:MaxStorageSamples":
+      case "NetworkStats:MaxStorageAge":
         // This message is sync.
-        return this._db.maxStorageSamples;
+        return this._db.maxStorageSamples * this._db.sampleRate;
     }
   },
 
-  observe: function observe(subject, topic, data) {
-    switch (topic) {
+  observe: function observe(aSubject, aTopic, aData) {
+    switch (aTopic) {
       case TOPIC_INTERFACE_REGISTERED:
       case TOPIC_INTERFACE_UNREGISTERED:
+
         // If new interface is registered (notified from NetworkManager),
         // the stats are updated for the new interface without waiting to
-        // complete the updating period
-        let network = subject.QueryInterface(Ci.nsINetworkInterface);
-        if (DEBUG) {
-          debug("Network " + network.name + " of type " + network.type + " status change");
+        // complete the updating period.
+
+        let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
+        debug("Network " + network.name + " of type " + network.type + " status change");
+
+        let netId = this.convertNetworkInterface(network);
+        if (!netId) {
+          break;
         }
-        if (this._connectionTypes[network.type]) {
-          this._connectionTypes[network.type].network = network;
-          this.updateStats(network.type);
-        }
+
+        this.updateStats(netId);
         break;
       case "xpcom-shutdown":
-        if (DEBUG) {
-          debug("Service shutdown");
-        }
+        debug("Service shutdown");
 
-        this.messages.forEach(function(msgName) {
-          ppmm.removeMessageListener(msgName, this);
+        this.messages.forEach(function(aMsgName) {
+          ppmm.removeMessageListener(aMsgName, this);
         }, this);
 
         Services.obs.removeObserver(this, "xpcom-shutdown");
         Services.obs.removeObserver(this, "profile-after-change");
         Services.obs.removeObserver(this, TOPIC_INTERFACE_REGISTERED);
         Services.obs.removeObserver(this, TOPIC_INTERFACE_UNREGISTERED);
 
         this.timer.cancel();
@@ -155,167 +181,206 @@ this.NetworkStatsService = {
         break;
     }
   },
 
   /*
    * nsITimerCallback
    * Timer triggers the update of all stats
    */
-  notify: function(timer) {
+  notify: function(aTimer) {
     this.updateAllStats();
   },
 
   /*
+   * nsINetworkStatsService
+   */
+
+  convertNetworkInterface: function(aNetwork) {
+    if (aNetwork.type != NET_TYPE_MOBILE &&
+        aNetwork.type != NET_TYPE_WIFI) {
+      return null;
+    }
+
+    let id = '0';
+    if (aNetwork.type == NET_TYPE_MOBILE) {
+      // Bug 904542 will provide the serviceId to map the iccId with the
+      // nsINetworkInterface of the NetworkManager. Now, lets assume that
+      // network is mapped with the current iccId of the single SIM.
+      id = gRadioInterface.rilContext.iccInfo.iccid;
+    }
+
+    let netId = this.getNetworkId(id, aNetwork.type);
+
+    if (!this._networks[netId]) {
+      this._networks[netId] = Object.create(null);
+      this._networks[netId].network = { id: id,
+                                        type: aNetwork.type };
+    }
+
+    this._networks[netId].interfaceName = aNetwork.name;
+    return netId;
+  },
+
+  getNetworkId: function getNetworkId(aIccId, aNetworkType) {
+    return aIccId + '' + aNetworkType;
+  },
+
+  availableNetworks: function availableNetworks() {
+    let result = [];
+    for (let netId in this._networks) {
+      result.push(this._networks[netId].network);
+    }
+
+    return result;
+  },
+
+  /*
    * Function called from manager to get stats from database.
    * In order to return updated stats, first is performed a call to
    * updateAllStats function, which will get last stats from netd
    * and update the database.
    * Then, depending on the request (stats per appId or total stats)
    * it retrieve them from database and return to the manager.
    */
-  getStats: function getStats(mm, msg) {
-    this.updateAllStats(function onStatsUpdated(aResult, aMessage) {
-
-      let data = msg.data;
-
-      let options = { appId:          0,
-                      connectionType: data.connectionType,
-                      start:          data.start,
-                      end:            data.end };
-
-      let manifestURL = data.manifestURL;
-      if (manifestURL) {
-        let appId = appsService.getAppLocalIdByManifestURL(manifestURL);
-        if (DEBUG) {
-          debug("get appId: " + appId + " from manifestURL: " + manifestURL);
-        }
+  getSamples: function getSamples(mm, msg) {
+    let self = this;
+    let network = msg.network;
+    let netId = this.getNetworkId(network.id, network.type);
 
-        if (!appId) {
-          mm.sendAsyncMessage("NetworkStats:Get:Return",
-                              { id: msg.id, error: "Invalid manifestURL", result: null });
-          return;
-        }
-
-        options.appId = appId;
-        options.manifestURL = manifestURL;
-      }
+    if (!this._networks[netId]) {
+      mm.sendAsyncMessage("NetworkStats:Get:Return",
+                          { id: msg.id, error: "Invalid connectionType", result: null });
+      return;
+    }
 
-      if (DEBUG) {
-        debug("getStats for options: " + JSON.stringify(options));
-      }
+    let appId = 0;
+    let manifestURL = msg.manifestURL;
+    if (manifestURL) {
+      appId = appsService.getAppLocalIdByManifestURL(manifestURL);
 
-      if (!options.connectionType || options.connectionType.length == 0) {
-        this._db.findAll(function onStatsFound(error, result) {
-          mm.sendAsyncMessage("NetworkStats:Get:Return",
-                              { id: msg.id, error: error, result: result });
-        }, options);
+      if (!appId) {
+        mm.sendAsyncMessage("NetworkStats:Get:Return",
+                            { id: msg.id, error: "Invalid manifestURL", result: null });
         return;
       }
+    }
 
-      for (let i in this._connectionTypes) {
-        if (this._connectionTypes[i].name == options.connectionType) {
-          this._db.find(function onStatsFound(error, result) {
-            mm.sendAsyncMessage("NetworkStats:Get:Return",
-                                { id: msg.id, error: error, result: result });
-          }, options);
-          return;
-        }
-      }
+    let start = new Date(msg.start);
+    let end = new Date(msg.end);
+
+    this.updateStats(netId, function onStatsUpdated(aResult, aMessage) {
+      debug("getstats for network " + network.id + " of type " + network.type);
+      debug("appId: " + appId + " from manifestURL: " + manifestURL);
+
+      self._db.find(function onStatsFound(aError, aResult) {
+        mm.sendAsyncMessage("NetworkStats:Get:Return",
+                            { id: msg.id, error: aError, result: aResult });
+      }, network, start, end, appId, manifestURL);
+
+    });
+  },
 
-      mm.sendAsyncMessage("NetworkStats:Get:Return",
-                          { id: msg.id, error: "Invalid connectionType", result: null });
+  clearInterfaceStats: function clearInterfaceStats(mm, msg) {
+    let network = msg.network;
+    let netId = this.getNetworkId(network.id, network.type);
+
+    debug("clear stats for network " + network.id + " of type " + network.type);
 
-    }.bind(this));
+    if (!this._networks[netId]) {
+      mm.sendAsyncMessage("NetworkStats:Clear:Return",
+                          { id: msg.id, error: "Invalid networkType", result: null });
+      return;
+    }
+
+    this._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
+        mm.sendAsyncMessage("NetworkStats:Clear:Return",
+                            { id: msg.id, error: aError, result: aResult });
+    });
   },
 
   clearDB: function clearDB(mm, msg) {
-    this._db.clear(function onDBCleared(error, result) {
-      mm.sendAsyncMessage("NetworkStats:Clear:Return",
-                          { id: msg.id, error: error, result: result });
+    let networks = this.availableNetworks();
+    this._db.clearStats(networks, function onDBCleared(aError, aResult) {
+      mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
+                          { id: msg.id, error: aError, result: aResult });
     });
   },
 
-  updateAllStats: function updateAllStats(callback) {
+  updateAllStats: function updateAllStats(aCallback) {
     // Update |cachedAppStats|.
     this.updateCachedAppStats();
 
     let elements = [];
     let lastElement;
 
     // For each connectionType create an object containning the type
     // and the 'queueIndex', the 'queueIndex' is an integer representing
     // the index of a connection type in the global queue array. So, if
     // the connection type is already in the queue it is not appended again,
     // else it is pushed in 'elements' array, which later will be pushed to
     // the queue array.
-    for (let i in this._connectionTypes) {
-      lastElement = { type: i,
-                      queueIndex: this.updateQueueIndex(i)};
+    for (let netId in this._networks) {
+      lastElement = { netId: netId,
+                      queueIndex: this.updateQueueIndex(netId)};
+
       if (lastElement.queueIndex == -1) {
-        elements.push({type: lastElement.type, callbacks: []});
+        elements.push({netId: lastElement.netId, callbacks: []});
       }
     }
 
     if (elements.length > 0) {
       // If length of elements is greater than 0, callback is set to
       // the last element.
-      elements[elements.length - 1].callbacks.push(callback);
+      elements[elements.length - 1].callbacks.push(aCallback);
       this.updateQueue = this.updateQueue.concat(elements);
     } else {
       // Else, it means that all connection types are already in the queue to
       // be updated, so callback for this request is added to
       // the element in the main queue with the index of the last 'lastElement'.
       // But before is checked that element is still in the queue because it can
       // be processed while generating 'elements' array.
-
-      if (!this.updateQueue[lastElement.queueIndex] ||
-          this.updateQueue[lastElement.queueIndex].type != lastElement.queueIndex) {
-        if (callback) {
-          callback();
-        }
+      let element = this.updateQueue[lastElement.queueIndex];
+      if (aCallback &&
+         (!element || element.netId != lastElement.netId)) {
+        aCallback();
         return;
       }
 
-      this.updateQueue[lastElement.queueIndex].callbacks.push(callback);
+      this.updateQueue[lastElement.queueIndex].callbacks.push(aCallback);
     }
 
     // Call the function that process the elements of the queue.
     this.processQueue();
 
     if (DEBUG) {
       this.logAllRecords();
     }
   },
 
-  updateStats: function updateStats(connectionType, callback) {
-    // Check if the connection type is in the main queue, push a new element
+  updateStats: function updateStats(aNetId, aCallback) {
+    // Check if the connection is in the main queue, push a new element
     // if it is not being processed or add a callback if it is.
-    let index = this.updateQueueIndex(connectionType);
+    let index = this.updateQueueIndex(aNetId);
     if (index == -1) {
-      this.updateQueue.push({type: connectionType, callbacks: [callback]});
+      this.updateQueue.push({netId: aNetId, callbacks: [aCallback]});
     } else {
-      this.updateQueue[index].callbacks.push(callback);
+      this.updateQueue[index].callbacks.push(aCallback);
     }
 
     // Call the function that process the elements of the queue.
     this.processQueue();
   },
 
   /*
-   * Find if a connection type is in the main queue array and return its
+   * Find if a connection is in the main queue array and return its
    * index, if it is not in the array return -1.
    */
-  updateQueueIndex: function updateQueueIndex(type) {
-    for (let i in this.updateQueue) {
-      if (this.updateQueue[i].type == type) {
-        return i;
-      }
-    }
-    return -1;
+  updateQueueIndex: function updateQueueIndex(aNetId) {
+    return this.updateQueue.map(function(e) { return e.netId; }).indexOf(aNetId);
   },
 
   /*
    * Function responsible of process all requests in the queue.
    */
   processQueue: function processQueue(aResult, aMessage) {
     // If aResult is not undefined, the caller of the function is the result
     // of processing an element, so remove that element and call the callbacks
@@ -342,101 +407,109 @@ this.NetworkStatsService = {
 
     // Check length to determine if queue is empty and stop processing.
     if (this.updateQueue.length < 1) {
       this.isQueueRunning = false;
       return;
     }
 
     // Call the update function for the next element.
-    this.update(this.updateQueue[0].type, this.processQueue.bind(this));
+    this.update(this.updateQueue[0].netId, this.processQueue.bind(this));
   },
 
-  update: function update(connectionType, callback) {
+  update: function update(aNetId, aCallback) {
     // Check if connection type is valid.
-    if (!this._connectionTypes[connectionType]) {
-      if (callback) {
-        callback(false, "Invalid network type " + connectionType);
+    if (!this._networks[aNetId]) {
+      if (aCallback) {
+        aCallback(false, "Invalid network " + aNetId);
       }
       return;
     }
 
-    if (DEBUG) {
-      debug("Update stats for " + this._connectionTypes[connectionType].name);
-    }
+    let interfaceName = this._networks[aNetId].interfaceName;
+    debug("Update stats for " + interfaceName);
 
     // Request stats to NetworkManager, which will get stats from netd, passing
     // 'networkStatsAvailable' as a callback.
-    let networkName = this._connectionTypes[connectionType].network.name;
-    if (networkName) {
-      networkManager.getNetworkInterfaceStats(networkName,
-                this.networkStatsAvailable.bind(this, callback, connectionType));
+    if (interfaceName) {
+      networkManager.getNetworkInterfaceStats(interfaceName,
+                this.networkStatsAvailable.bind(this, aCallback, aNetId));
       return;
     }
-    if (callback) {
-      callback(true, "ok");
+
+    if (aCallback) {
+      aCallback(true, "ok");
     }
   },
 
   /*
    * Callback of request stats. Store stats in database.
    */
-  networkStatsAvailable: function networkStatsAvailable(callback, connType, result, rxBytes, txBytes, date) {
-    if (!result) {
-      if (callback) {
-        callback(false, "Netd IPC error");
+  networkStatsAvailable: function networkStatsAvailable(aCallback, aNetId,
+                                                        aResult, aRxBytes,
+                                                        aTxBytes, aDate) {
+    if (!aResult) {
+      if (aCallback) {
+        aCallback(false, "Netd IPC error");
       }
       return;
     }
 
-    let stats = { appId:          0,
-                  connectionType: this._connectionTypes[connType].name,
-                  date:           date,
-                  rxBytes:        rxBytes,
-                  txBytes:        txBytes };
+    let stats = { appId:       0,
+                  networkId:   this._networks[aNetId].network.id,
+                  networkType: this._networks[aNetId].network.type,
+                  date:        aDate,
+                  rxBytes:     aTxBytes,
+                  txBytes:     aRxBytes };
 
-    if (DEBUG) {
-      debug("Update stats for " + stats.connectionType + ": rx=" + stats.rxBytes +
-            " tx=" + stats.txBytes + " timestamp=" + stats.date);
-    }
-    this._db.saveStats(stats, function onSavedStats(error, result) {
-      if (callback) {
-        if (error) {
-          callback(false, error);
+    debug("Update stats for: " + JSON.stringify(stats));
+
+    this._db.saveStats(stats, function onSavedStats(aError, aResult) {
+      if (aCallback) {
+        if (aError) {
+          aCallback(false, aError);
           return;
         }
 
-        callback(true, "OK");
+        aCallback(true, "OK");
       }
     });
   },
 
   /*
    * Function responsible for receiving per-app stats.
    */
-  saveAppStats: function saveAppStats(aAppId, aConnectionType, aTimeStamp, aRxBytes, aTxBytes, aCallback) {
-    if (DEBUG) {
-      debug("saveAppStats: " + aAppId + " " + aConnectionType + " " +
-            aTimeStamp + " " + aRxBytes + " " + aTxBytes);
+  saveAppStats: function saveAppStats(aAppId, aNetwork, aTimeStamp, aRxBytes, aTxBytes, aCallback) {
+    let netId = this.convertNetworkInterface(aNetwork);
+    if (!netId) {
+      if (aCallback) {
+        aCallback.notify(false, "Invalid network type");
+      }
+      return;
     }
 
+    debug("saveAppStats: " + aAppId + " " + netId + " " +
+          aTimeStamp + " " + aRxBytes + " " + aTxBytes);
+
     // Check if |aAppId| and |aConnectionType| are valid.
-    if (!aAppId || aConnectionType == NET_TYPE_UNKNOWN) {
+    if (!aAppId || !this._networks[netId]) {
+      debug("Invalid appId or network interface");
       return;
     }
 
     let stats = { appId: aAppId,
-                  connectionType: this._connectionTypes[aConnectionType].name,
+                  networkId: this._networks[netId].network.id,
+                  networkType: this._networks[netId].network.type,
                   date: new Date(aTimeStamp),
                   rxBytes: aRxBytes,
                   txBytes: aTxBytes };
 
     // Generate an unique key from |appId| and |connectionType|,
     // which is used to retrieve data in |cachedAppStats|.
-    let key = stats.appId + stats.connectionType;
+    let key = stats.appId + "" + netId;
 
     // |cachedAppStats| only keeps the data with the same date.
     // If the incoming date is different from |cachedAppStatsDate|,
     // both |cachedAppStats| and |cachedAppStatsDate| will get updated.
     let diff = (this._db.normalizeDate(stats.date) -
                 this._db.normalizeDate(this.cachedAppStatsDate)) /
                this._db.sampleRate;
     if (diff != 0) {
@@ -473,29 +546,25 @@ this.NetworkStatsService = {
 
     // If new rxBytes or txBytes exceeds MAX_CACHED_TRAFFIC
     // the corresponding row will be saved to indexedDB.
     // Then, the row will be removed from the cached.
     if (appStats.rxBytes > MAX_CACHED_TRAFFIC ||
         appStats.txBytes > MAX_CACHED_TRAFFIC) {
       this._db.saveStats(appStats,
         function (error, result) {
-          if (DEBUG) {
-            debug("Application stats inserted in indexedDB");
-          }
+          debug("Application stats inserted in indexedDB");
         }
       );
       delete this.cachedAppStats[key];
     }
   },
 
-  updateCachedAppStats: function updateCachedAppStats(callback) {
-    if (DEBUG) {
-      debug("updateCachedAppStats: " + this.cachedAppStatsDate);
-    }
+  updateCachedAppStats: function updateCachedAppStats(aCallback) {
+    debug("updateCachedAppStats: " + this.cachedAppStatsDate);
 
     let stats = Object.keys(this.cachedAppStats);
     if (stats.length == 0) {
       // |cachedAppStats| is empty, no need to update.
       return;
     }
 
     let index = 0;
@@ -504,47 +573,47 @@ this.NetworkStatsService = {
         if (DEBUG) {
           debug("Application stats inserted in indexedDB");
         }
 
         // Clean up the |cachedAppStats| after updating.
         if (index == stats.length - 1) {
           this.cachedAppStats = Object.create(null);
 
-          if (!callback) {
+          if (!aCallback) {
             return;
           }
 
           if (error) {
-            callback(false, error);
+            aCallback(false, error);
             return;
           }
 
-          callback(true, "ok");
+          aCallback(true, "ok");
           return;
         }
 
         // Update is not finished, keep updating.
         index += 1;
         this._db.saveStats(this.cachedAppStats[stats[index]],
                            onSavedStats.bind(this, error, result));
       }.bind(this));
   },
 
   get maxCachedTraffic () {
     return MAX_CACHED_TRAFFIC;
   },
 
   logAllRecords: function logAllRecords() {
-    this._db.logAllRecords(function onResult(error, result) {
-      if (error) {
-        debug("Error: " + error);
+    this._db.logAllRecords(function onResult(aError, aResult) {
+      if (aError) {
+        debug("Error: " + aError);
         return;
       }
 
       debug("===== LOG =====");
-      debug("There are " + result.length + " items");
-      debug(JSON.stringify(result));
+      debug("There are " + aResult.length + " items");
+      debug(JSON.stringify(aResult));
     });
-  }
+  },
 };
 
 NetworkStatsService.init();
--- a/dom/network/src/NetworkStatsServiceProxy.js
+++ b/dom/network/src/NetworkStatsServiceProxy.js
@@ -24,24 +24,24 @@ function NetworkStatsServiceProxy() {
   }
 }
 
 NetworkStatsServiceProxy.prototype = {
   /*
    * Function called in the protocol layer (HTTP, FTP, WebSocket ...etc)
    * to pass the per-app stats to NetworkStatsService.
    */
-  saveAppStats: function saveAppStats(aAppId, aConnectionType, aTimeStamp,
+  saveAppStats: function saveAppStats(aAppId, aNetwork, aTimeStamp,
                                       aRxBytes, aTxBytes, aCallback) {
     if (DEBUG) {
-      debug("saveAppStats: " + aAppId + " " + aConnectionType + " " +
-            aTimeStamp + " " + aRxBytes + " " + aTxBytes);
+      debug("saveAppStats: " + aAppId + " connectionType " + aNetwork.type +
+            " " + aTimeStamp + " " + aRxBytes + " " + aTxBytes);
     }
 
-    NetworkStatsService.saveAppStats(aAppId, aConnectionType, aTimeStamp,
+    NetworkStatsService.saveAppStats(aAppId, aNetwork, aTimeStamp,
                                      aRxBytes, aTxBytes, aCallback);
   },
 
   classID : NETWORKSTATSSERVICEPROXY_CID,
   QueryInterface : XPCOMUtils.generateQI([nsINetworkStatsServiceProxy]),
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsServiceProxy]);