From aa521c403f380a89ea36c2304f36dd37f5f6ab82 Mon Sep 17 00:00:00 2001 From: Albert Crespell Date: Mon, 10 Feb 2014 09:14:45 +0100 Subject: [PATCH] Bug 965319 - Part 1: Wrong quota calculation for network alarms. r=gene, r=jshih --- dom/network/src/NetworkStatsDB.jsm | 184 +++++++++++++++++++++--- dom/network/src/NetworkStatsManager.js | 4 + dom/network/src/NetworkStatsService.jsm | 43 +++--- 3 files changed, 184 insertions(+), 47 deletions(-) diff --git a/dom/network/src/NetworkStatsDB.jsm b/dom/network/src/NetworkStatsDB.jsm index e27c1330734e..d7e12c9f6646 100644 --- a/dom/network/src/NetworkStatsDB.jsm +++ b/dom/network/src/NetworkStatsDB.jsm @@ -16,7 +16,7 @@ Cu.import("resource://gre/modules/IndexedDBHelper.jsm"); Cu.importGlobalProperties(["indexedDB"]); const DB_NAME = "net_stats"; -const DB_VERSION = 6; +const DB_VERSION = 7; const DEPRECATED_STORE_NAME = "net_stats"; const STATS_STORE_NAME = "net_stats_store"; const ALARMS_STORE_NAME = "net_alarm"; @@ -213,6 +213,94 @@ NetworkStatsDB.prototype = { if (DEBUG) { debug("Added new key 'serviceType' for version 6"); } + } else if (currVersion == 6) { + // Replace threshold attribute of alarm index by relativeThreshold in alarms DB. + // Now alarms are indexed by relativeThreshold, which is the threshold relative + // to current system stats. + let alarmsStore = aTransaction.objectStore(ALARMS_STORE_NAME); + + // Delete "alarm" index. + if (alarmsStore.indexNames.contains("alarm")) { + alarmsStore.deleteIndex("alarm"); + } + + // Create new "alarm" index. + alarmsStore.createIndex("alarm", ['networkId','relativeThreshold'], { unique: false }); + + // Populate new "alarm" index attributes. + alarmsStore.openCursor().onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + return; + } + + cursor.value.relativeThreshold = cursor.value.threshold; + cursor.value.absoluteThreshold = cursor.value.threshold; + delete cursor.value.threshold; + + cursor.update(cursor.value); + cursor.continue(); + } + + // Previous versions save accumulative totalBytes, increasing althought the system + // reboots or resets stats. But is necessary to reset the total counters when reset + // through 'clearInterfaceStats'. + let statsStore = aTransaction.objectStore(STATS_STORE_NAME); + let networks = []; + // Find networks stored in the database. + statsStore.index("network").openKeyCursor(null, "nextunique").onsuccess = function(event) { + let cursor = event.target.result; + if (cursor) { + networks.push(cursor.key); + cursor.continue(); + return; + } + + networks.forEach(function(network) { + let lowerFilter = [0, "", network, 0]; + let upperFilter = [0, "", network, ""]; + let range = IDBKeyRange.bound(lowerFilter, upperFilter, false, false); + + // Find number of samples for a given network. + statsStore.count(range).onsuccess = function(event) { + // If there are more samples than the max allowed, there is no way to know + // when does reset take place. + if (event.target.result >= VALUES_MAX_LENGTH) { + return; + } + + let last = null; + // Reset detected if the first sample totalCounters are different than bytes + // counters. If so, the total counters should be recalculated. + statsStore.openCursor(range).onsuccess = function(event) { + let cursor = event.target.result; + if (!cursor) { + return; + } + if (!last) { + if (cursor.value.rxTotalBytes == cursor.value.rxBytes && + cursor.value.txTotalBytes == cursor.value.txBytes) { + return; + } + + cursor.value.rxTotalBytes = cursor.value.rxBytes; + cursor.value.txTotalBytes = cursor.value.txBytes; + cursor.update(cursor.value); + last = cursor.value; + cursor.continue(); + return; + } + + // Recalculate the total counter for last / current sample + cursor.value.rxTotalBytes = last.rxTotalBytes + cursor.value.rxBytes; + cursor.value.txTotalBytes = last.txTotalBytes + cursor.value.txBytes; + cursor.update(cursor.value); + last = cursor.value; + cursor.continue(); + } + } + }, this); + }; } } }, @@ -515,6 +603,8 @@ NetworkStatsDB.prototype = { sample.serviceType = ""; sample.rxBytes = 0; sample.txBytes = 0; + sample.rxTotalBytes = 0; + sample.txTotalBytes = 0; self._saveStats(aTxn, aStore, sample); } @@ -550,24 +640,78 @@ NetworkStatsDB.prototype = { debug("Get current stats for " + JSON.stringify(aNetwork) + " since " + aDate); } + let network = [aNetwork.id, aNetwork.type]; + if (aDate) { + this._getCurrentStatsFromDate(network, aDate, aResultCb); + return; + } + + this._getCurrentStats(network, aResultCb); + }, + + _getCurrentStats: function _getCurrentStats(aNetwork, aResultCb) { 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"); - } + let upperFilter = [0, "", aNetwork, Date.now()]; + let range = IDBKeyRange.upperBound(upperFilter, false); + request = store.openCursor(range, "prev"); + + let result = { rxBytes: 0, txBytes: 0, + rxTotalBytes: 0, txTotalBytes: 0 }; request.onsuccess = function onsuccess(event) { - txn.result = null; let cursor = event.target.result; if (cursor) { - txn.result = cursor.value; + result.rxBytes = result.rxTotalBytes = cursor.value.rxTotalBytes; + result.txBytes = result.txTotalBytes = cursor.value.txTotalBytes; } + + txn.result = result; + }; + }.bind(this), aResultCb); + }, + + _getCurrentStatsFromDate: function _getCurrentStatsFromDate(aNetwork, aDate, aResultCb) { + aDate = new Date(aDate); + this.dbNewTxn(STATS_STORE_NAME, "readonly", function(txn, store) { + let request = null; + let start = this.normalizeDate(aDate); + let lowerFilter = [0, "", aNetwork, start]; + let upperFilter = [0, "", aNetwork, Date.now()]; + + let range = IDBKeyRange.upperBound(upperFilter, false); + + let result = { rxBytes: 0, txBytes: 0, + rxTotalBytes: 0, txTotalBytes: 0 }; + + request = store.openCursor(range, "prev"); + + request.onsuccess = function onsuccess(event) { + let cursor = event.target.result; + if (cursor) { + result.rxBytes = result.rxTotalBytes = cursor.value.rxTotalBytes; + result.txBytes = result.txTotalBytes = cursor.value.txTotalBytes; + } + + let timestamp = cursor.value.timestamp; + let range = IDBKeyRange.lowerBound(lowerFilter, false); + request = store.openCursor(range); + + request.onsuccess = function onsuccess(event) { + let cursor = event.target.result; + if (cursor) { + if (cursor.value.timestamp == timestamp) { + // There is one sample only. + result.rxBytes = cursor.value.rxBytes; + result.txBytes = cursor.value.txBytes; + } else { + result.rxBytes -= cursor.value.rxTotalBytes; + result.txBytes -= cursor.value.txTotalBytes; + } + } + + txn.result = result; + }; }; }.bind(this), aResultCb); }, @@ -671,7 +815,7 @@ NetworkStatsDB.prototype = { aTxn.result = false; } - var network = [aNetwork.id, aNetwork.type]; + let network = [aNetwork.id, aNetwork.type]; let request = aStore.index("network").openKeyCursor(IDBKeyRange.only(network)); request.onsuccess = function onsuccess(event) { if (event.target.result) { @@ -717,7 +861,8 @@ NetworkStatsDB.prototype = { alarmToRecord: function alarmToRecord(aAlarm) { let record = { networkId: aAlarm.networkId, - threshold: aAlarm.threshold, + absoluteThreshold: aAlarm.absoluteThreshold, + relativeThreshold: aAlarm.relativeThreshold, data: aAlarm.data, manifestURL: aAlarm.manifestURL, pageURL: aAlarm.pageURL }; @@ -731,7 +876,8 @@ NetworkStatsDB.prototype = { recordToAlarm: function recordToalarm(aRecord) { let alarm = { networkId: aRecord.networkId, - threshold: aRecord.threshold, + absoluteThreshold: aRecord.absoluteThreshold, + relativeThreshold: aRecord.relativeThreshold, data: aRecord.data, manifestURL: aRecord.manifestURL, pageURL: aRecord.pageURL }; @@ -837,6 +983,7 @@ NetworkStatsDB.prototype = { }, getAlarms: function getAlarms(aNetworkId, aManifestURL, aResultCb) { + let self = this; this.dbNewTxn(ALARMS_STORE_NAME, "readonly", function(txn, store) { if (DEBUG) { debug("Get alarms for " + aManifestURL); @@ -851,12 +998,7 @@ NetworkStatsDB.prototype = { } 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); + txn.result.push(self.recordToAlarm(cursor.value)); } cursor.continue(); diff --git a/dom/network/src/NetworkStatsManager.js b/dom/network/src/NetworkStatsManager.js index 33965f987214..d0023b51f36d 100644 --- a/dom/network/src/NetworkStatsManager.js +++ b/dom/network/src/NetworkStatsManager.js @@ -237,6 +237,10 @@ NetworkStatsManager.prototype = { aOptions = Object.create(null); } + if (aOptions.startTime && aOptions.startTime.constructor.name !== "Date") { + throw Components.results.NS_ERROR_INVALID_ARG; + } + let request = this.createRequest(); cpmm.sendAsyncMessage("NetworkStats:SetAlarm", {id: this.getRequestId(request), diff --git a/dom/network/src/NetworkStatsService.jsm b/dom/network/src/NetworkStatsService.jsm index 237332f69707..95d28ffc3fb3 100644 --- a/dom/network/src/NetworkStatsService.jsm +++ b/dom/network/src/NetworkStatsService.jsm @@ -331,10 +331,11 @@ this.NetworkStatsService = { this._networks[netId].status = NETWORK_STATUS_AWAY; this._currentAlarms[netId] = Object.create(null); aCallback(netId); + return; } aCallback(null); - }); + }.bind(this)); }, getAvailableNetworks: function getAvailableNetworks(mm, msg) { @@ -863,7 +864,7 @@ this.NetworkStatsService = { let alarm = result[i]; alarms.push({ id: alarm.id, network: self._networks[alarm.networkId].network, - threshold: alarm.threshold, + threshold: alarm.absoluteThreshold, data: alarm.data }); } @@ -931,22 +932,21 @@ this.NetworkStatsService = { let newAlarm = { id: null, networkId: aNetId, - threshold: threshold, - absoluteThreshold: null, + absoluteThreshold: threshold, + relativeThreshold: null, startTime: options.startTime, data: options.data, pageURL: options.pageURL, manifestURL: options.manifestURL }; - self._updateThreshold(newAlarm, function onUpdate(error, _threshold) { + self._getAlarmQuota(newAlarm, function onUpdate(error, quota) { 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", @@ -971,7 +971,7 @@ this.NetworkStatsService = { _setAlarm: function _setAlarm(aAlarm, aCallback) { let currentAlarm = this._currentAlarms[aAlarm.networkId]; if ((Object.getOwnPropertyNames(currentAlarm).length !== 0 && - aAlarm.absoluteThreshold > currentAlarm.alarm.absoluteThreshold) || + aAlarm.relativeThreshold > currentAlarm.alarm.relativeThreshold) || this._networks[aAlarm.networkId].status != NETWORK_STATUS_READY) { aCallback(null, true); return; @@ -979,7 +979,7 @@ this.NetworkStatsService = { let self = this; - this._updateThreshold(aAlarm, function onUpdate(aError, aThreshold) { + this._getAlarmQuota(aAlarm, function onUpdate(aError, aQuota) { if (aError) { aCallback(aError, null); return; @@ -1001,7 +1001,7 @@ this.NetworkStatsService = { let interfaceName = self._networks[aAlarm.networkId].interfaceName; if (interfaceName) { networkService.setNetworkInterfaceAlarm(interfaceName, - aThreshold.systemThreshold, + aQuota, callback); return; } @@ -1010,7 +1010,7 @@ this.NetworkStatsService = { }); }, - _updateThreshold: function _updateThreshold(aAlarm, aCallback) { + _getAlarmQuota: function _getAlarmQuota(aAlarm, aCallback) { let self = this; this.updateStats(aAlarm.networkId, function onStatsUpdated(aResult, aMessage) { self._db.getCurrentStats(self._networks[aAlarm.networkId].network, @@ -1023,28 +1023,19 @@ this.NetworkStatsService = { return; } - if (!result) { - // There are no stats for the network of the alarm, set them to default 0 in - // order to be able to calculate the offset, systemThreshold and - // absoluteThreshold. - result = { rxTotalBytes: 0, txTotalBytes: 0, - rxSystemBytes: 0, txSystemBytes: 0 }; - } - - let offset = aAlarm.threshold - result.rxTotalBytes - result.txTotalBytes; + let quota = aAlarm.absoluteThreshold - result.rxBytes - result.txBytes; // Alarm set to a threshold lower than current rx/tx bytes. - if (offset <= 0) { + if (quota <= 0) { aCallback("InvalidStateError", null); return; } - let threshold = { - systemThreshold: result.rxSystemBytes + result.txSystemBytes + offset, - absoluteThreshold: result.rxTotalBytes + result.txTotalBytes + offset - }; + aAlarm.relativeThreshold = aAlarm.startTime + ? result.rxTotalBytes + result.txTotalBytes + quota + : aAlarm.absoluteThreshold; - aCallback(null, threshold); + aCallback(null, quota); }); }); }, @@ -1096,7 +1087,7 @@ this.NetworkStatsService = { let pageURI = Services.io.newURI(aAlarm.pageURL, null, null); let alarm = { "id": aAlarm.id, - "threshold": aAlarm.threshold, + "threshold": aAlarm.absoluteThreshold, "data": aAlarm.data }; messenger.sendMessage("networkstats-alarm", alarm, pageURI, manifestURI); }