Bug 858005 - Part 3: Alarm implementation. r=gene, r=jshih
authorAlbert Crespell <acperez@tid.es>
Fri, 31 May 2013 07:21:29 +0200
changeset 174883 7cbdd95bf3715f83482c2f1fdaa5bf01aac67adf
parent 174882 97b690fd7806dfdb0c0ac9e8c0469d1b77e7018a
child 174884 34d915243969599116d8ec47c61082d54c51b7c8
push id445
push userffxbld
push dateMon, 10 Mar 2014 22:05:19 +0000
treeherdermozilla-release@dc38b741b04e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgene, jshih
bugs858005
milestone28.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 858005 - Part 3: Alarm implementation. r=gene, r=jshih
dom/messages/SystemMessagePermissionsChecker.jsm
dom/network/src/NetworkStatsDB.jsm
dom/network/src/NetworkStatsManager.js
dom/network/src/NetworkStatsManager.manifest
dom/network/src/NetworkStatsService.jsm
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -57,16 +57,19 @@ this.SystemMessagePermissionsTable = {
   },
   "connection": { },
   "dummy-system-message": { }, // for system message testing framework
   "headset-button": { },
   "icc-stkcommand": {
     "settings": ["read", "write"]
   },
   "media-button": { },
+  "networkstats-alarm": {
+    "networkstats-manage": []
+  },
   "notification": {
     "desktop-notification": []
   },
   "push": {
   	"push": []
   },
   "push-register": {
   	"push": []
--- a/dom/network/src/NetworkStatsDB.jsm
+++ b/dom/network/src/NetworkStatsDB.jsm
@@ -11,59 +11,60 @@ function debug(s) { dump("-*- NetworkSta
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/IndexedDBHelper.jsm");
 Cu.importGlobalProperties(["indexedDB"]);
 
 const DB_NAME = "net_stats";
-const DB_VERSION = 4;
-const STORE_NAME = "net_stats";
+const DB_VERSION = 5;
+const STATS_STORE_NAME = "net_stats";
+const ALARMS_STORE_NAME = "net_alarm";
 
 // 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() {
   if (DEBUG) {
     debug("Constructor");
   }
-  this.initDBHelper(DB_NAME, DB_VERSION, [STORE_NAME]);
+  this.initDBHelper(DB_NAME, DB_VERSION, [STATS_STORE_NAME, ALARMS_STORE_NAME]);
 }
 
 NetworkStatsDB.prototype = {
   __proto__: IndexedDBHelper.prototype,
 
-  dbNewTxn: function dbNewTxn(txn_type, callback, txnCb) {
+  dbNewTxn: function dbNewTxn(store_name, txn_type, callback, txnCb) {
     function successCb(result) {
       txnCb(null, result);
     }
     function errorCb(error) {
       txnCb(error, null);
     }
-    return this.newTxn(txn_type, STORE_NAME, 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;
     for (let currVersion = aOldVersion; currVersion < aNewVersion; currVersion++) {
       if (currVersion == 0) {
         /**
          * Create the initial database schema.
          */
 
-        objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
+        objectStore = db.createObjectStore(STATS_STORE_NAME, { keyPath: ["connectionType", "timestamp"] });
         objectStore.createIndex("connectionType", "connectionType", { 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 });
         if (DEBUG) {
           debug("Created object stores and indexes");
@@ -72,34 +73,34 @@ NetworkStatsDB.prototype = {
         // 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").
         // 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);
+        db.deleteObjectStore(STATS_STORE_NAME);
 
-        objectStore = db.createObjectStore(STORE_NAME, { keyPath: ["appId", "network", "timestamp"] });
+        objectStore = db.createObjectStore(STATS_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 });
 
         if (DEBUG) {
           debug("Created object stores and indexes for version 3");
         }
       } else if (currVersion == 3) {
         // Delete redundent indexes (leave "network" only).
-        objectStore = aTransaction.objectStore(STORE_NAME);
+        objectStore = aTransaction.objectStore(STATS_STORE_NAME);
         if (objectStore.indexNames.contains("appId")) {
           objectStore.deleteIndex("appId");
         }
         if (objectStore.indexNames.contains("networkType")) {
           objectStore.deleteIndex("networkType");
         }
         if (objectStore.indexNames.contains("timestamp")) {
           objectStore.deleteIndex("timestamp");
@@ -115,28 +116,92 @@ NetworkStatsDB.prototype = {
         }
         if (objectStore.indexNames.contains("txTotalBytes")) {
           objectStore.deleteIndex("txTotalBytes");
         }
 
         if (DEBUG) {
           debug("Deleted redundent indexes for version 4");
         }
+      } else if (currVersion == 4) {
+        // In order to manage alarms, it is necessary to use a global counter
+        // (totalBytes) that will increase regardless of the system reboot.
+        objectStore = aTransaction.objectStore(STATS_STORE_NAME);
+
+        // Now, systemBytes will hold the old totalBytes and totalBytes will
+        // keep the increasing counter. |counters| will keep the track of
+        // accumulated values.
+        let counters = {};
+
+        objectStore.openCursor().onsuccess = function(event) {
+          let cursor = event.target.result;
+          if (!cursor){
+            return;
+          }
+
+          cursor.value.rxSystemBytes = cursor.value.rxTotalBytes;
+          cursor.value.txSystemBytes = cursor.value.txTotalBytes;
+
+          if (cursor.value.appId == 0) {
+            let netId = cursor.value.network[0] + '' + cursor.value.network[1];
+            if (!counters[netId]) {
+              counters[netId] = {
+                rxCounter: 0,
+                txCounter: 0,
+                lastRx: 0,
+                lastTx: 0
+              };
+            }
+
+            let rxDiff = cursor.value.rxSystemBytes - counters[netId].lastRx;
+            let txDiff = cursor.value.txSystemBytes - counters[netId].lastTx;
+            if (rxDiff < 0 || txDiff < 0) {
+              // System reboot between samples, so take the current one.
+              rxDiff = cursor.value.rxSystemBytes;
+              txDiff = cursor.value.txSystemBytes;
+            }
+
+            counters[netId].rxCounter += rxDiff;
+            counters[netId].txCounter += txDiff;
+            cursor.value.rxTotalBytes = counters[netId].rxCounter;
+            cursor.value.txTotalBytes = counters[netId].txCounter;
+
+            counters[netId].lastRx = cursor.value.rxSystemBytes;
+            counters[netId].lastTx = cursor.value.txSystemBytes;
+          } else {
+            cursor.value.rxTotalBytes = cursor.value.rxSystemBytes;
+            cursor.value.txTotalBytes = cursor.value.txSystemBytes;
+          }
+
+          cursor.update(cursor.value);
+          cursor.continue();
+        };
+
+        // Create object store for alarms.
+        objectStore = db.createObjectStore(ALARMS_STORE_NAME, { keyPath: "id", autoIncrement: true });
+        objectStore.createIndex("alarm", ['networkId','threshold'], { unique: false });
+        objectStore.createIndex("manifestURL", "manifestURL", { unique: false });
+
+        if (DEBUG) {
+          debug("Created alarms store for version 5");
+        }
       }
     }
   },
 
   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 };
+    let stats = { appId:         aStats.appId,
+                  network:       [aStats.networkId, aStats.networkType],
+                  timestamp:     aStats.timestamp,
+                  rxBytes:       aStats.rxBytes,
+                  txBytes:       aStats.txBytes,
+                  rxSystemBytes: aStats.rxSystemBytes,
+                  txSystemBytes: aStats.txSystemBytes,
+                  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],
@@ -155,28 +220,30 @@ NetworkStatsDB.prototype = {
     let timestamp = aDate.getTime() - aDate.getTimezoneOffset() * 60 * 1000;
     timestamp = Math.floor(timestamp / SAMPLE_RATE) * SAMPLE_RATE;
     return timestamp;
   },
 
   saveStats: function saveStats(aStats, aResultCb) {
     let timestamp = this.normalizeDate(aStats.date);
 
-    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 };
+    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,
+                  rxSystemBytes: (aStats.appId == 0) ? aStats.rxBytes : 0,
+                  txSystemBytes: (aStats.appId == 0) ? aStats.txBytes : 0,
+                  rxTotalBytes:  (aStats.appId == 0) ? aStats.rxBytes : 0,
+                  txTotalBytes:  (aStats.appId == 0) ? aStats.txBytes : 0 };
 
     stats = this.importData(stats);
 
-    this.dbNewTxn("readwrite", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readwrite", function(aTxn, aStore) {
       if (DEBUG) {
         debug("Filtered time: " + new Date(timestamp));
         debug("New stats: " + JSON.stringify(stats));
       }
 
     let request = aStore.index("network").openCursor(stats.network, "prev");
       request.onsuccess = function onsuccess(event) {
         let cursor = event.target.result;
@@ -236,31 +303,39 @@ NetworkStatsDB.prototype = {
             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|.
+    let rxDiff = 0;
+    let txDiff = 0;
     if (aNewSample.appId == 0) {
-      let rxDiff = aNewSample.rxTotalBytes - lastSample.rxTotalBytes;
-      let txDiff = aNewSample.txTotalBytes - lastSample.txTotalBytes;
+      rxDiff = aNewSample.rxSystemBytes - lastSample.rxSystemBytes;
+      txDiff = aNewSample.txSystemBytes - lastSample.txSystemBytes;
       if (rxDiff < 0 || txDiff < 0) {
-        rxDiff = aNewSample.rxTotalBytes;
-        txDiff = aNewSample.txTotalBytes;
+        rxDiff = aNewSample.rxSystemBytes;
+        txDiff = aNewSample.txSystemBytes;
       }
       aNewSample.rxBytes = rxDiff;
       aNewSample.txBytes = txDiff;
+
+      aNewSample.rxTotalBytes = lastSample.rxTotalBytes + rxDiff;
+      aNewSample.txTotalBytes = lastSample.txTotalBytes + txDiff;
+    } else {
+      rxDiff = aNewSample.rxBytes;
+      txDiff = aNewSample.txBytes;
     }
 
     if (diff == 1) {
       // New element.
 
-      // If the incoming data is per-data data, new |rxTotalBytes|/|txTotalBytes|
+      // If the incoming data is per-app data, new |rxTotalBytes|/|txTotalBytes|
       // needs to be obtained by adding new |rxBytes|/|txBytes| to last
       // |rxTotalBytes|/|txTotalBytes|.
       if (aNewSample.appId != 0) {
         aNewSample.rxTotalBytes = aNewSample.rxBytes + lastSample.rxTotalBytes;
         aNewSample.txTotalBytes = aNewSample.txBytes + lastSample.txTotalBytes;
       }
 
       this._saveStats(aTxn, aStore, aNewSample);
@@ -272,53 +347,48 @@ NetworkStatsDB.prototype = {
       // 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 = 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 };
+        let sample = { appId:         aNewSample.appId,
+                       network:       aNewSample.network,
+                       timestamp:     time,
+                       rxBytes:       0,
+                       txBytes:       0,
+                       rxSystemBytes: lastSample.rxSystemBytes,
+                       txSystemBytes: lastSample.txSystemBytes,
+                       rxTotalBytes:  lastSample.rxTotalBytes,
+                       txTotalBytes:  lastSample.txTotalBytes };
 
         data.push(sample);
       }
 
       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 += aNewSample.rxBytes;
-      lastSample.txBytes += aNewSample.txBytes;
+      // 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.
 
-      // If incoming data is obtained from netd, last |rxTotalBytes|/|txTotalBytes|
-      // needs to get updated by replacing the new |rxTotalBytes|/|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 += aNewSample.rxBytes;
-        lastSample.txTotalBytes += aNewSample.txBytes;
-      }
+      // Old |rxTotalBytes|/|txTotalBytes| needs to get updated by adding the
+      // last |rxTotalBytes|/|txTotalBytes|.
+      lastSample.rxBytes += rxDiff;
+      lastSample.txBytes += txDiff;
+      lastSample.rxSystemBytes = aNewSample.rxSystemBytes;
+      lastSample.txSystemBytes = aNewSample.txSystemBytes;
+      lastSample.rxTotalBytes += rxDiff;
+      lastSample.txTotalBytes += txDiff;
+
       if (DEBUG) {
         debug("Update: " + JSON.stringify(lastSample));
       }
       let req = aLastSampleCursor.update(lastSample);
     }
   },
 
   _saveStats: function _saveStats(aTxn, aStore, aNetworkStats) {
@@ -374,17 +444,17 @@ NetworkStatsDB.prototype = {
     };
   },
 
   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) {
+    this.dbNewTxn(STATS_STORE_NAME, "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 && cursor.value.appId == 0) {
             sample = cursor.value;
           }
@@ -426,29 +496,56 @@ NetworkStatsDB.prototype = {
 
     if (!aNetworks[index]) {
       aResultCb(null, true);
       return;
     }
     this.clearInterfaceStats(aNetworks[index], callback);
   },
 
+  getCurrentStats: function getCurrentStats(aNetwork, aDate, aResultCb) {
+    if (DEBUG) {
+      debug("Get current stats for " + JSON.stringify(aNetwork) + " since " + aDate);
+    }
+
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(txn, store) {
+      let request = null;
+      let network = [aNetwork.id, aNetwork.type];
+      if (aDate) {
+        let start = this.normalizeDate(aDate);
+        let lowerFilter = [0, network, start];
+        let range = this.dbGlobal.IDBKeyRange.lowerBound(lowerFilter, false);
+        request = store.openCursor(range);
+      } else {
+        request = store.index("network").openCursor(network, "prev");
+      }
+
+      request.onsuccess = function onsuccess(event) {
+        txn.result = null;
+        let cursor = event.target.result;
+        if (cursor) {
+          txn.result = cursor.value;
+        }
+      };
+    }.bind(this), aResultCb);
+  },
+
   find: function find(aResultCb, aNetwork, aStart, aEnd, aAppId, aManifestURL) {
     let offset = (new Date()).getTimezoneOffset() * 60 * 1000;
     let start = this.normalizeDate(aStart);
     let end = this.normalizeDate(aEnd);
 
     if (DEBUG) {
       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(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "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 (!aTxn.result) {
@@ -498,17 +595,17 @@ NetworkStatsDB.prototype = {
     while (aEnd > aData[aData.length - 1].date.getTime()) {
       aData.push({ rxBytes: undefined,
                    txBytes: undefined,
                    date: new Date(aData[aData.length - 1].date.getTime() + SAMPLE_RATE) });
     }
   },
 
   getAvailableNetworks: function getAvailableNetworks(aResultCb) {
-    this.dbNewTxn("readonly", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
       if (!aTxn.result) {
         aTxn.result = [];
       }
 
       let request = aStore.index("network").openKeyCursor(null, "nextunique");
       request.onsuccess = function onsuccess(event) {
         let cursor = event.target.result;
         if (cursor) {
@@ -517,17 +614,17 @@ NetworkStatsDB.prototype = {
           cursor.continue();
           return;
         }
       };
     }, aResultCb);
   },
 
   isNetworkAvailable: function isNetworkAvailable(aNetwork, aResultCb) {
-    this.dbNewTxn("readonly", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
       if (!aTxn.result) {
         aTxn.result = false;
       }
 
       var network = [aNetwork.id, aNetwork.type];
       let request = aStore.index("network").openKeyCursor(IDBKeyRange.only(network));
       request.onsuccess = function onsuccess(event) {
         if (event.target.result) {
@@ -541,15 +638,164 @@ NetworkStatsDB.prototype = {
     return SAMPLE_RATE;
   },
 
   get maxStorageSamples () {
     return VALUES_MAX_LENGTH;
   },
 
   logAllRecords: function logAllRecords(aResultCb) {
-    this.dbNewTxn("readonly", function(aTxn, aStore) {
+    this.dbNewTxn(STATS_STORE_NAME, "readonly", function(aTxn, aStore) {
       aStore.mozGetAll().onsuccess = function onsuccess(event) {
         aTxn.result = event.target.result;
       };
     }, aResultCb);
   },
+
+  alarmToRecord: function alarmToRecord(aAlarm) {
+    let record = { networkId: aAlarm.networkId,
+                   threshold: aAlarm.threshold,
+                   data: aAlarm.data,
+                   manifestURL: aAlarm.manifestURL,
+                   pageURL: aAlarm.pageURL };
+
+    if (aAlarm.id) {
+      record.id = aAlarm.id;
+    }
+
+    return record;
+  },
+
+  recordToAlarm: function recordToalarm(aRecord) {
+    let alarm = { networkId: aRecord.networkId,
+                  threshold: aRecord.threshold,
+                  data: aRecord.data,
+                  manifestURL: aRecord.manifestURL,
+                  pageURL: aRecord.pageURL };
+
+    if (aRecord.id) {
+      alarm.id = aRecord.id;
+    }
+
+    return alarm;
+  },
+
+  addAlarm: function addAlarm(aAlarm, aResultCb) {
+    this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+      if (DEBUG) {
+        debug("Going to add " + JSON.stringify(aAlarm));
+      }
+
+      let record = this.alarmToRecord(aAlarm);
+      store.put(record).onsuccess = function setResult(aEvent) {
+        txn.result = aEvent.target.result;
+        if (DEBUG) {
+          debug("Request successful. New record ID: " + txn.result);
+        }
+      };
+    }.bind(this), aResultCb);
+  },
+
+  getFirstAlarm: function getFirstAlarm(aNetworkId, aResultCb) {
+    let self = this;
+
+    this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
+      if (DEBUG) {
+        debug("Get first alarm for network " + aNetworkId);
+      }
+
+      let lowerFilter = [aNetworkId, 0];
+      let upperFilter = [aNetworkId, ""];
+      let range = IDBKeyRange.bound(lowerFilter, upperFilter);
+
+      store.index("alarm").openCursor(range).onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        txn.result = null;
+        if (cursor) {
+          txn.result = self.recordToAlarm(cursor.value);
+        }
+      };
+    }, aResultCb);
+  },
+
+  removeAlarm: function removeAlarm(aAlarmId, aManifestURL, aResultCb) {
+    this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+      if (DEBUG) {
+        debug("Remove alarm " + aAlarmId);
+      }
+
+      store.get(aAlarmId).onsuccess = function onsuccess(event) {
+        let record = event.target.result;
+        txn.result = false;
+        if (!record || (aManifestURL && record.manifestURL != aManifestURL)) {
+          return;
+        }
+
+        store.delete(aAlarmId);
+        txn.result = true;
+      }
+    }, aResultCb);
+  },
+
+  removeAlarms: function removeAlarms(aManifestURL, aResultCb) {
+    this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+      if (DEBUG) {
+        debug("Remove alarms of " + aManifestURL);
+      }
+
+      store.index("manifestURL").openCursor(aManifestURL)
+                                .onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          cursor.delete();
+          cursor.continue();
+        }
+      }
+    }, aResultCb);
+  },
+
+  updateAlarm: function updateAlarm(aAlarm, aResultCb) {
+    let self = this;
+    this.dbNewTxn(ALARMS_STORE_NAME, "readwrite", function(txn, store) {
+      if (DEBUG) {
+        debug("Update alarm " + aAlarm.id);
+      }
+
+      let record = self.alarmToRecord(aAlarm);
+      store.openCursor(record.id).onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        txn.result = false;
+        if (cursor) {
+          cursor.update(record);
+          txn.result = true;
+        }
+      }
+    }, aResultCb);
+  },
+
+  getAlarms: function getAlarms(aNetworkId, aManifestURL, aResultCb) {
+    this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) {
+      if (DEBUG) {
+        debug("Get alarms for " + aManifestURL);
+      }
+
+      txn.result = [];
+      store.index("manifestURL").openCursor(aManifestURL)
+                                .onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (!cursor) {
+          return;
+        }
+
+        if (!aNetworkId || cursor.value.networkId == aNetworkId) {
+          let alarm = { id: cursor.value.id,
+                        networkId: cursor.value.networkId,
+                        threshold: cursor.value.threshold,
+                        data: cursor.value.data };
+
+          txn.result.push(alarm);
+        }
+
+        cursor.continue();
+      }
+    }, aResultCb);
+  }
 };
--- a/dom/network/src/NetworkStatsManager.js
+++ b/dom/network/src/NetworkStatsManager.js
@@ -25,74 +25,74 @@ if (isParentProcess) {
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsISyncMessageSender");
 
 // NetworkStatsData
 const nsIClassInfo              = Ci.nsIClassInfo;
 const NETWORKSTATSDATA_CID      = Components.ID("{3b16fe17-5583-483a-b486-b64a3243221c}");
-const nsIDOMMozNetworkStatsData = Components.interfaces.nsIDOMMozNetworkStatsData;
+const nsIDOMMozNetworkStatsData = Ci.nsIDOMMozNetworkStatsData;
 
 function NetworkStatsData(aData) {
   this.rxBytes = aData.rxBytes;
   this.txBytes = aData.txBytes;
   this.date = aData.date;
 }
 
 NetworkStatsData.prototype = {
   __exposedProps__: {
-                      rxBytes: 'r',
-                      txBytes: 'r',
-                      date:  'r',
-                     },
+    rxBytes: 'r',
+    txBytes: 'r',
+    date:  'r',
+  },
 
   classID : NETWORKSTATSDATA_CID,
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSDATA_CID,
                                      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;
+const nsIDOMMozNetworkStatsInterface   = Ci.nsIDOMMozNetworkStatsInterface;
 
 function NetworkStatsInterface(aNetwork) {
   if (DEBUG) {
     debug("NetworkStatsInterface Constructor");
   }
   this.type = aNetwork.type;
   this.id = aNetwork.id;
 }
 
 NetworkStatsInterface.prototype = {
   __exposedProps__: {
-                      id: 'r',
-                      type: 'r',
-                    },
+    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("{b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}");
-const nsIDOMMozNetworkStats   = Components.interfaces.nsIDOMMozNetworkStats;
+const nsIDOMMozNetworkStats   = Ci.nsIDOMMozNetworkStats;
 
 function NetworkStats(aWindow, aStats) {
   if (DEBUG) {
     debug("NetworkStats Constructor");
   }
   this.manifestURL = aStats.manifestURL || null;
   this.network = new NetworkStatsInterface(aStats.network);
   this.start = aStats.start || null;
@@ -101,40 +101,69 @@ function NetworkStats(aWindow, aStats) {
   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',
-                      network: 'r',
-                      start: 'r',
-                      end:  'r',
-                      data:  'r',
-                    },
+    manifestURL: '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,
                                           nsIDOMMozNetworkStatsInterface])
 }
 
+// NetworkStatsAlarm
+const NETWORKSTATSALARM_CID      = Components.ID("{063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c}");
+const nsIDOMMozNetworkStatsAlarm = Ci.nsIDOMMozNetworkStatsAlarm;
+
+function NetworkStatsAlarm(aAlarm) {
+  this.alarmId = aAlarm.id;
+  this.network = new NetworkStatsInterface(aAlarm.network);
+  this.threshold = aAlarm.threshold;
+  this.data = aAlarm.data;
+}
+
+NetworkStatsAlarm.prototype = {
+  __exposedProps__: {
+    alarmId: 'r',
+    network: 'r',
+    threshold: 'r',
+    data: 'r',
+  },
+
+  classID : NETWORKSTATSALARM_CID,
+  classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSALARM_CID,
+                                     contractID:"@mozilla.org/networkstatsalarm;1",
+                                     classDescription: "NetworkStatsAlarm",
+                                     interfaces: [nsIDOMMozNetworkStatsAlarm],
+                                     flags: nsIClassInfo.DOM_OBJECT}),
+
+  QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsAlarm])
+};
+
 // NetworkStatsManager
 
 const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
-const NETWORKSTATSMANAGER_CID        = Components.ID("{5f033d31-c9a2-4e2d-83aa-6a807f1e0c11}");
-const nsIDOMMozNetworkStatsManager   = Components.interfaces.nsIDOMMozNetworkStatsManager;
+const NETWORKSTATSMANAGER_CID        = Components.ID("{50d109b8-0d7f-4208-81fe-5f07a759f159}");
+const nsIDOMMozNetworkStatsManager   = Ci.nsIDOMMozNetworkStatsManager;
 
 function NetworkStatsManager() {
   if (DEBUG) {
     debug("Constructor");
   }
 }
 
 NetworkStatsManager.prototype = {
@@ -184,16 +213,62 @@ NetworkStatsManager.prototype = {
     this.checkPrivileges();
 
     let request = this.createRequest();
     cpmm.sendAsyncMessage("NetworkStats:ClearAll",
                           {id: this.getRequestId(request)});
     return request;
   },
 
+  addAlarm: function addAlarm(aNetwork, aThreshold, aOptions) {
+    this.checkPrivileges();
+
+    if (!aOptions) {
+      aOptions = Object.create(null);
+    }
+
+    let request = this.createRequest();
+    cpmm.sendAsyncMessage("NetworkStats:SetAlarm",
+                          {id: this.getRequestId(request),
+                           data: {network: aNetwork,
+                                  threshold: aThreshold,
+                                  startTime: aOptions.startTime,
+                                  data: aOptions.data,
+                                  manifestURL: this.manifestURL,
+                                  pageURL: this.pageURL}});
+    return request;
+  },
+
+  getAllAlarms: function getAllAlarms(aNetwork) {
+    this.checkPrivileges();
+
+    let request = this.createRequest();
+    cpmm.sendAsyncMessage("NetworkStats:GetAlarms",
+                          {id: this.getRequestId(request),
+                           data: {network: aNetwork,
+                                  manifestURL: this.manifestURL}});
+    return request;
+  },
+
+  removeAlarms: function removeAlarms(aAlarmId) {
+    this.checkPrivileges();
+
+    if (aAlarmId == 0) {
+      aAlarmId = -1;
+    }
+
+    let request = this.createRequest();
+    cpmm.sendAsyncMessage("NetworkStats:RemoveAlarms",
+                          {id: this.getRequestId(request),
+                           data: {alarmId: aAlarmId,
+                                  manifestURL: this.manifestURL}});
+
+    return request;
+  },
+
   getAvailableNetworks: function getAvailableNetworks() {
     this.checkPrivileges();
 
     let request = this.createRequest();
     cpmm.sendAsyncMessage("NetworkStats:GetAvailableNetworks",
                           { id: this.getRequestId(request) });
     return request;
   },
@@ -207,18 +282,18 @@ NetworkStatsManager.prototype = {
     this.checkPrivileges();
     return cpmm.sendSyncMessage("NetworkStats:MaxStorageAge")[0];
   },
 
   receiveMessage: function(aMessage) {
     if (DEBUG) {
       debug("NetworkStatsmanager::receiveMessage: " + aMessage.name);
     }
+
     let msg = aMessage.json;
-
     let req = this.takeRequest(msg.id);
     if (!req) {
       if (DEBUG) {
         debug("No request stored with id " + msg.id);
       }
       return;
     }
 
@@ -255,16 +330,40 @@ NetworkStatsManager.prototype = {
         if (msg.error) {
           Services.DOMRequest.fireError(req, msg.error);
           return;
         }
 
         Services.DOMRequest.fireSuccess(req, true);
         break;
 
+      case "NetworkStats:SetAlarm:Return":
+      case "NetworkStats:RemoveAlarms:Return":
+        if (msg.error) {
+          Services.DOMRequest.fireError(req, msg.error);
+          return;
+        }
+
+        Services.DOMRequest.fireSuccess(req, msg.result);
+        break;
+
+      case "NetworkStats:GetAlarms:Return":
+        if (msg.error) {
+          Services.DOMRequest.fireError(req, msg.error);
+          return;
+        }
+
+        let alarms = Cu.createArrayIn(this._window);
+        for (let i = 0; i < msg.result.length; i++) {
+          alarms.push(new NetworkStatsAlarm(msg.result[i]));
+        }
+
+        Services.DOMRequest.fireSuccess(req, alarms);
+        break;
+
       default:
         if (DEBUG) {
           debug("Wrong message: " + aMessage.name);
         }
     }
   },
 
   init: function(aWindow) {
@@ -288,17 +387,31 @@ NetworkStatsManager.prototype = {
 
     if (!this.hasPrivileges) {
       return null;
     }
 
     this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
                                         "NetworkStats:GetAvailableNetworks:Return",
                                         "NetworkStats:Clear:Return",
-                                        "NetworkStats:ClearAll:Return"]);
+                                        "NetworkStats:ClearAll:Return",
+                                        "NetworkStats:SetAlarm:Return",
+                                        "NetworkStats:GetAlarms:Return",
+                                        "NetworkStats:RemoveAlarms:Return"]);
+
+    // Init app properties.
+    let appsService = Cc["@mozilla.org/AppsService;1"]
+                        .getService(Ci.nsIAppsService);
+
+    this.manifestURL = appsService.getManifestURLByLocalId(principal.appId);
+
+    let isApp = !!this.manifestURL.length;
+    if (isApp) {
+      this.pageURL = principal.URI.spec;
+    }
   },
 
   // Called from DOMRequestIpcHelper
   uninit: function uninit() {
     if (DEBUG) {
       debug("uninit call");
     }
   },
@@ -311,12 +424,13 @@ NetworkStatsManager.prototype = {
 
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSMANAGER_CID,
                                      contractID: NETWORKSTATSMANAGER_CONTRACTID,
                                      classDescription: "NetworkStatsManager",
                                      interfaces: [nsIDOMMozNetworkStatsManager],
                                      flags: nsIClassInfo.DOM_OBJECT})
 }
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsData,
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsAlarm,
+                                                     NetworkStatsData,
                                                      NetworkStatsInterface,
                                                      NetworkStats,
                                                      NetworkStatsManager]);
--- a/dom/network/src/NetworkStatsManager.manifest
+++ b/dom/network/src/NetworkStatsManager.manifest
@@ -2,11 +2,14 @@ component {3b16fe17-5583-483a-b486-b64a3
 contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
 
 component {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe} NetworkStatsManager.js
 contract @mozilla.org/networkStats;1 {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}
 
 component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
 contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
 
-component {5f033d31-c9a2-4e2d-83aa-6a807f1e0c11} NetworkStatsManager.js
-contract @mozilla.org/networkStatsManager;1 {5f033d31-c9a2-4e2d-83aa-6a807f1e0c11}
+component {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c} NetworkStatsManager.js
+contract @mozilla.org/networkstatsalarm;1 {063ebeb2-5c6e-47ae-bdcd-5e6ebdc7a68c}
+
+component {50d109b8-0d7f-4208-81fe-5f07a759f159} NetworkStatsManager.js
+contract @mozilla.org/networkStatsManager;1 {50d109b8-0d7f-4208-81fe-5f07a759f159}
 category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
--- a/dom/network/src/NetworkStatsService.jsm
+++ b/dom/network/src/NetworkStatsService.jsm
@@ -17,16 +17,18 @@ this.EXPORTED_SYMBOLS = ["NetworkStatsSe
 
 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_BANDWIDTH_CONTROL = "netd-bandwidth-control"
+
 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;
 
 // The maximum traffic amount can be saved in the |cachedAppStats|.
 const MAX_CACHED_TRAFFIC = 500 * 1000 * 1000; // 500 MB
 
@@ -45,23 +47,28 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
                                    "@mozilla.org/AppsService;1",
                                    "nsIAppsService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
                                    "@mozilla.org/settingsService;1",
                                    "nsISettingsService");
 
+XPCOMUtils.defineLazyServiceGetter(this, "messenger",
+                                   "@mozilla.org/system-message-internal;1",
+                                   "nsISystemMessagesInternal");
+
 this.NetworkStatsService = {
   init: function() {
     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, TOPIC_BANDWIDTH_CONTROL, false);
     Services.obs.addObserver(this, "profile-after-change", false);
 
     this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 
     // 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,
@@ -82,16 +89,19 @@ this.NetworkStatsService = {
     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:ClearAll",
+                     "NetworkStats:SetAlarm",
+                     "NetworkStats:GetAlarms",
+                     "NetworkStats:RemoveAlarms",
                      "NetworkStats:GetAvailableNetworks",
                      "NetworkStats:SampleRate",
                      "NetworkStats:MaxStorageAge"];
 
     this.messages.forEach(function(aMsgName) {
       ppmm.addMessageListener(aMsgName, this);
     }, this);
 
@@ -102,16 +112,19 @@ this.NetworkStatsService = {
                                 Ci.nsITimer.TYPE_REPEATING_PRECISE);
 
     // App stats are firstly stored in the cached.
     this.cachedAppStats = Object.create(null);
     this.cachedAppStatsDate = new Date();
 
     this.updateQueue = [];
     this.isQueueRunning = false;
+
+    this._currentAlarms = {};
+    this.initAlarms();
   },
 
   receiveMessage: function(aMessage) {
     if (!aMessage.target.assertPermission("networkstats-manage")) {
       return;
     }
 
     debug("receiveMessage " + aMessage.name);
@@ -124,16 +137,25 @@ this.NetworkStatsService = {
         this.getSamples(mm, msg);
         break;
       case "NetworkStats:Clear":
         this.clearInterfaceStats(mm, msg);
         break;
       case "NetworkStats:ClearAll":
         this.clearDB(mm, msg);
         break;
+      case "NetworkStats:SetAlarm":
+        this.setAlarm(mm, msg);
+        break;
+      case "NetworkStats:GetAlarms":
+        this.getAlarms(mm, msg);
+        break;
+      case "NetworkStats:RemoveAlarms":
+        this.removeAlarms(mm, msg);
+        break;
       case "NetworkStats:GetAvailableNetworks":
         this.getAvailableNetworks(mm, msg);
         break;
       case "NetworkStats:SampleRate":
         // This message is sync.
         return this._db.sampleRate;
       case "NetworkStats:MaxStorageAge":
         // This message is sync.
@@ -141,42 +163,61 @@ this.NetworkStatsService = {
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case TOPIC_INTERFACE_REGISTERED:
       case TOPIC_INTERFACE_UNREGISTERED:
 
-        // If new interface is registered (notified from NetworkManager),
+        // If new interface is registered (notified from NetworkService),
         // the stats are updated for the new interface without waiting to
         // 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;
         }
 
+        this._updateCurrentAlarm(netId);
+
         debug("NetId: " + netId);
         this.updateStats(netId);
         break;
+
+      case TOPIC_BANDWIDTH_CONTROL:
+        debug("Bandwidth message from netd: " + JSON.stringify(aData));
+
+        let interfaceName = aData.substring(aData.lastIndexOf(" ") + 1);
+        for (let networkId in this._networks) {
+          if (interfaceName == this._networks[networkId].interfaceName) {
+            let currentAlarm = this._currentAlarms[networkId];
+            if (Object.getOwnPropertyNames(currentAlarm).length !== 0) {
+              this._fireAlarm(currentAlarm.alarm);
+            }
+            break;
+          }
+        }
+        break;
+
       case "xpcom-shutdown":
         debug("Service shutdown");
 
         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);
+        Services.obs.removeObserver(this, TOPIC_BANDWIDTH_CONTROL);
 
         this.timer.cancel();
         this.timer = null;
 
         // Update stats before shutdown
         this.updateAllStats();
         break;
     }
@@ -261,16 +302,35 @@ this.NetworkStatsService = {
         }
       }
 
       mm.sendAsyncMessage("NetworkStats:GetAvailableNetworks:Return",
                           { id: msg.id, error: aError, result: aResult });
     });
   },
 
+  initAlarms: function initAlarms() {
+    debug("Init usage alarms");
+    let self = this;
+
+    for (let netId in this._networks) {
+      this._currentAlarms[netId] = Object.create(null);
+
+      this._db.getFirstAlarm(netId, function getResult(error, result) {
+        if (!error && result) {
+          self._setAlarm(result, function onSet(error, success) {
+            if (error == "InvalidStateError") {
+              self._fireAlarm(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.
    */
@@ -477,18 +537,18 @@ this.NetworkStatsService = {
       let item = this.updateQueue.shift();
       for (let callback of item.callbacks) {
         if(callback) {
           callback(aResult, aMessage);
         }
       }
     } else {
       // The caller is a function that has pushed new elements to the queue,
-      // if isQueueRunning is false it means there is no processing currently being
-      // done, so start.
+      // if isQueueRunning is false it means there is no processing currently
+      // being done, so start.
       if (this.isQueueRunning) {
         if(this.updateQueue.length > 1) {
           return;
         }
       } else {
         this.isQueueRunning = true;
       }
     }
@@ -510,17 +570,17 @@ this.NetworkStatsService = {
         aCallback(false, "Invalid network " + aNetId);
       }
       return;
     }
 
     let interfaceName = this._networks[aNetId].interfaceName;
     debug("Update stats for " + interfaceName);
 
-    // Request stats to NetworkManager, which will get stats from netd, passing
+    // Request stats to NetworkService, which will get stats from netd, passing
     // 'networkStatsAvailable' as a callback.
     if (interfaceName) {
       networkService.getNetworkInterfaceStats(interfaceName,
                 this.networkStatsAvailable.bind(this, aCallback, aNetId));
       return;
     }
 
     if (aCallback) {
@@ -701,11 +761,267 @@ this.NetworkStatsService = {
         return;
       }
 
       debug("===== LOG =====");
       debug("There are " + aResult.length + " items");
       debug(JSON.stringify(aResult));
     });
   },
+
+  getAlarms: function getAlarms(mm, msg) {
+    let network = msg.data.network;
+    let manifestURL = msg.data.manifestURL;
+
+    let netId = null;
+    if (network) {
+      netId = this.getNetworkId(network.id, network.type);
+      if (!this._networks[netId]) {
+        mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+                            { id: msg.id, error: "InvalidInterface", result: null });
+        return;
+      }
+    }
+
+    let self = this;
+    this._db.getAlarms(netId, manifestURL, function onCompleted(error, result) {
+      if (error) {
+        mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+                            { id: msg.id, error: error, result: result });
+        return;
+      }
+
+      let alarms = []
+      // NetworkStatsManager must return the network instead of the networkId.
+      for (let i = 0; i < result.length; i++) {
+        let alarm = result[i];
+        alarms.push({ id: alarm.id,
+                      network: self._networks[alarm.networkId].network,
+                      threshold: alarm.threshold,
+                      data: alarm.data });
+      }
+
+      mm.sendAsyncMessage("NetworkStats:GetAlarms:Return",
+                          { id: msg.id, error: null, result: alarms });
+    });
+  },
+
+  removeAlarms: function removeAlarms(mm, msg) {
+    let alarmId = msg.data.alarmId;
+    let manifestURL = msg.data.manifestURL;
+
+    let self = this;
+    let callback = function onRemove(error, result) {
+      if (error) {
+        mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
+                            { id: msg.id, error: error, result: result });
+        return;
+      }
+
+      for (let i in self._currentAlarms) {
+        let currentAlarm = self._currentAlarms[i].alarm;
+        if (currentAlarm && ((alarmId == currentAlarm.id) ||
+            (alarmId == -1 && currentAlarm.manifestURL == manifestURL))) {
+
+          self._updateCurrentAlarm(currentAlarm.networkId);
+        }
+      }
+
+      mm.sendAsyncMessage("NetworkStats:RemoveAlarms:Return",
+                          { id: msg.id, error: error, result: true });
+    };
+
+    if (alarmId == -1) {
+      this._db.removeAlarms(manifestURL, callback);
+    } else {
+      this._db.removeAlarm(alarmId, manifestURL, callback);
+    }
+  },
+
+  /*
+   * Function called from manager to set an alarm.
+   */
+  setAlarm: function setAlarm(mm, msg) {
+    let options = msg.data;
+    let network = options.network;
+    let threshold = options.threshold;
+
+    debug("Set alarm at " + threshold + " for " + JSON.stringify(network));
+
+    if (threshold < 0) {
+      mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                          { id: msg.id, error: "InvalidThresholdValue", result: null });
+      return;
+    }
+
+    let netId = this.getNetworkId(network.id, network.type);
+    if (!this._networks[netId]) {
+      mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                          { id: msg.id, error: "InvalidiConnectionType", result: null });
+      return;
+    }
+
+    let newAlarm = {
+      id: null,
+      networkId: netId,
+      threshold: threshold,
+      absoluteThreshold: null,
+      startTime: options.startTime,
+      data: options.data,
+      pageURL: options.pageURL,
+      manifestURL: options.manifestURL
+    };
+
+    let self = this;
+    this._updateThreshold(newAlarm, function onUpdate(error, _threshold) {
+      if (error) {
+        mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                            { id: msg.id, error: error, result: null });
+        return;
+      }
+
+      newAlarm.absoluteThreshold = _threshold.absoluteThreshold;
+      self._db.addAlarm(newAlarm, function addSuccessCb(error, newId) {
+        if (error) {
+          mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                              { id: msg.id, error: error, result: null });
+          return;
+        }
+
+        newAlarm.id = newId;
+        self._setAlarm(newAlarm, function onSet(error, success) {
+          mm.sendAsyncMessage("NetworkStats:SetAlarm:Return",
+                              { id: msg.id, error: error, result: newId });
+
+          if (error == "InvalidStateError") {
+            self._fireAlarm(newAlarm);
+          }
+        });
+      });
+    });
+  },
+
+  _setAlarm: function _setAlarm(aAlarm, aCallback) {
+    let currentAlarm = this._currentAlarms[aAlarm.networkId];
+    if (Object.getOwnPropertyNames(currentAlarm).length !== 0 &&
+        aAlarm.absoluteThreshold > currentAlarm.alarm.absoluteThreshold) {
+      aCallback(null, true);
+      return;
+    }
+
+    let self = this;
+
+    this._updateThreshold(aAlarm, function onUpdate(aError, aThreshold) {
+      if (aError) {
+        aCallback(aError, null);
+        return;
+      }
+
+      let callback = function onAlarmSet(aError) {
+        if (aError) {
+          debug("Set alarm error: " + aError);
+          aCallback("netdError", null);
+          return;
+        }
+
+        self._currentAlarms[aAlarm.networkId].alarm = aAlarm;
+
+        aCallback(null, true);
+      };
+
+      debug("Set alarm " + JSON.stringify(aAlarm));
+      let interfaceName = self._networks[aAlarm.networkId].interfaceName;
+      if (interfaceName) {
+        networkService.setNetworkInterfaceAlarm(interfaceName,
+                                                aThreshold.systemThreshold,
+                                                callback);
+        return;
+      }
+
+      aCallback(null, true);
+    });
+  },
+
+  _updateThreshold: function _updateThreshold(aAlarm, aCallback) {
+    let self = this;
+    this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) {
+      self._db.getCurrentStats(self._networks[aAlarm.networkId].network,
+                               aAlarm.startTime,
+                               function onStatsFound(error, result) {
+        if (error) {
+          debug("Error getting stats for " +
+                JSON.stringify(self._networks[aAlarm.networkId]) + ": " + error);
+          aCallback(error, result);
+          return;
+        }
+
+        let offset = aAlarm.threshold - result.rxTotalBytes - result.txTotalBytes;
+
+        // Alarm set to a threshold lower than current rx/tx bytes.
+        if (offset <= 0) {
+          aCallback("InvalidStateError", null);
+          return;
+        }
+
+        let threshold = {
+          systemThreshold: result.rxSystemBytes + result.txSystemBytes + offset,
+          absoluteThreshold: result.rxTotalBytes + result.txTotalBytes + offset
+        };
+
+        aCallback(null, threshold);
+      });
+    });
+  },
+
+  _fireAlarm: function _fireAlarm(aAlarm) {
+    debug("Fire alarm");
+
+    let self = this;
+    this._db.removeAlarm(aAlarm.id, null, function onRemove(aError, aResult){
+      if (!aError && !aResult) {
+        return;
+      }
+
+      self._fireSystemMessage(aAlarm);
+      self._updateCurrentAlarm(aAlarm.networkId);
+    });
+  },
+
+  _updateCurrentAlarm: function _updateCurrentAlarm(aNetworkId) {
+    this._currentAlarms[aNetworkId] = Object.create(null);
+
+    let self = this;
+    this._db.getFirstAlarm(aNetworkId, function onGet(error, result){
+      if (error) {
+        debug("Error getting the first alarm");
+        return;
+      }
+
+      if (!result) {
+        let interfaceName = self._networks[aNetworkId].interfaceName;
+        networkService.setNetworkInterfaceAlarm(interfaceName, -1,
+                                                function onComplete(){});
+        return;
+      }
+
+      self._setAlarm(result, function onSet(error, success){
+        if (error == "InvalidStateError") {
+          self._fireAlarm(result);
+          return;
+        }
+      });
+    });
+  },
+
+  _fireSystemMessage: function _fireSystemMessage(aAlarm) {
+    debug("Fire system message: " + JSON.stringify(aAlarm));
+
+    let manifestURI = Services.io.newURI(aAlarm.manifestURL, null, null);
+    let pageURI = Services.io.newURI(aAlarm.pageURL, null, null);
+
+    let alarm = { "id":        aAlarm.id,
+                  "threshold": aAlarm.threshold,
+                  "data":      aAlarm.data };
+    messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI);
+  }
 };
 
 NetworkStatsService.init();