mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 887699 - Part 2: Multi SIM implementation. r=gene
This commit is contained in:
parent
1999531b78
commit
a2c7f13eb0
@ -16,8 +16,7 @@ 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 STORE_NAME = "net_stats";
|
||||
|
||||
// Constant defining the maximum values allowed per interface. If more, older
|
||||
// will be erased.
|
||||
@ -26,12 +25,11 @@ 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 = {
|
||||
@ -44,7 +42,7 @@ NetworkStatsDB.prototype = {
|
||||
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) {
|
||||
@ -69,69 +67,57 @@ NetworkStatsDB.prototype = {
|
||||
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) {
|
||||
// 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);
|
||||
debug("Created object stores and indexes for version 2");
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
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
|
||||
@ -140,29 +126,42 @@ NetworkStatsDB.prototype = {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -177,10 +176,10 @@ NetworkStatsDB.prototype = {
|
||||
}
|
||||
|
||||
// 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);
|
||||
},
|
||||
@ -189,20 +188,21 @@ NetworkStatsDB.prototype = {
|
||||
* 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),
|
||||
@ -210,15 +210,15 @@ NetworkStatsDB.prototype = {
|
||||
// |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) {
|
||||
@ -227,11 +227,12 @@ NetworkStatsDB.prototype = {
|
||||
// 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) {
|
||||
@ -244,19 +245,20 @@ NetworkStatsDB.prototype = {
|
||||
|
||||
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) {
|
||||
@ -266,91 +268,163 @@ NetworkStatsDB.prototype = {
|
||||
|
||||
// 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,
|
||||
@ -364,66 +438,11 @@ NetworkStatsDB.prototype = {
|
||||
// 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);
|
||||
},
|
||||
@ -461,9 +480,9 @@ NetworkStatsDB.prototype = {
|
||||
},
|
||||
|
||||
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);
|
||||
},
|
||||
|
@ -55,9 +55,38 @@ NetworkStatsData.prototype = {
|
||||
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) {
|
||||
@ -65,7 +94,7 @@ function NetworkStats(aWindow, aStats) {
|
||||
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;
|
||||
|
||||
@ -78,7 +107,7 @@ function NetworkStats(aWindow, aStats) {
|
||||
NetworkStats.prototype = {
|
||||
__exposedProps__: {
|
||||
manifestURL: 'r',
|
||||
connectionType: 'r',
|
||||
network: 'r',
|
||||
start: 'r',
|
||||
end: 'r',
|
||||
data: 'r',
|
||||
@ -92,13 +121,14 @@ NetworkStats.prototype = {
|
||||
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() {
|
||||
@ -116,42 +146,64 @@ NetworkStatsManager.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -183,6 +235,7 @@ NetworkStatsManager.prototype = {
|
||||
break;
|
||||
|
||||
case "NetworkStats:Clear:Return":
|
||||
case "NetworkStats:ClearAll:Return":
|
||||
if (msg.error) {
|
||||
Services.DOMRequest.fireError(req, msg.error);
|
||||
return;
|
||||
@ -222,7 +275,8 @@ NetworkStatsManager.prototype = {
|
||||
}
|
||||
|
||||
this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
|
||||
"NetworkStats:Clear:Return"]);
|
||||
"NetworkStats:Clear:Return",
|
||||
"NetworkStats:ClearAll:Return"]);
|
||||
},
|
||||
|
||||
// Called from DOMRequestIpcHelper
|
||||
@ -245,5 +299,6 @@ NetworkStatsManager.prototype = {
|
||||
}
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NetworkStatsData,
|
||||
NetworkStatsInterface,
|
||||
NetworkStats,
|
||||
NetworkStatsManager]);
|
||||
|
@ -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
|
||||
|
@ -5,7 +5,11 @@
|
||||
"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;
|
||||
|
||||
@ -22,7 +26,6 @@ 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
|
||||
@ -39,11 +42,19 @@ 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);
|
||||
@ -52,24 +63,41 @@ this.NetworkStatsService = {
|
||||
|
||||
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,
|
||||
@ -88,58 +116,56 @@ this.NetworkStatsService = {
|
||||
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");
|
||||
}
|
||||
if (this._connectionTypes[network.type]) {
|
||||
this._connectionTypes[network.type].network = network;
|
||||
this.updateStats(network.type);
|
||||
}
|
||||
break;
|
||||
case "xpcom-shutdown":
|
||||
if (DEBUG) {
|
||||
debug("Service shutdown");
|
||||
// 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.messages.forEach(function(msgName) {
|
||||
ppmm.removeMessageListener(msgName, this);
|
||||
this.updateStats(netId);
|
||||
break;
|
||||
case "xpcom-shutdown":
|
||||
debug("Service shutdown");
|
||||
|
||||
this.messages.forEach(function(aMsgName) {
|
||||
ppmm.removeMessageListener(aMsgName, this);
|
||||
}, this);
|
||||
|
||||
Services.obs.removeObserver(this, "xpcom-shutdown");
|
||||
@ -160,10 +186,53 @@ this.NetworkStatsService = {
|
||||
* 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
|
||||
@ -172,69 +241,71 @@ this.NetworkStatsService = {
|
||||
* 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);
|
||||
}
|
||||
|
||||
if (!appId) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid manifestURL", result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
options.appId = appId;
|
||||
options.manifestURL = manifestURL;
|
||||
}
|
||||
|
||||
if (DEBUG) {
|
||||
debug("getStats for options: " + JSON.stringify(options));
|
||||
}
|
||||
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
getSamples: function getSamples(mm, msg) {
|
||||
let self = this;
|
||||
let network = msg.network;
|
||||
let netId = this.getNetworkId(network.id, network.type);
|
||||
|
||||
if (!this._networks[netId]) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid connectionType", result: null });
|
||||
return;
|
||||
}
|
||||
|
||||
}.bind(this));
|
||||
},
|
||||
let appId = 0;
|
||||
let manifestURL = msg.manifestURL;
|
||||
if (manifestURL) {
|
||||
appId = appsService.getAppLocalIdByManifestURL(manifestURL);
|
||||
|
||||
if (!appId) {
|
||||
mm.sendAsyncMessage("NetworkStats:Get:Return",
|
||||
{ id: msg.id, error: "Invalid manifestURL", result: null });
|
||||
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);
|
||||
|
||||
clearDB: function clearDB(mm, msg) {
|
||||
this._db.clear(function onDBCleared(error, result) {
|
||||
mm.sendAsyncMessage("NetworkStats:Clear:Return",
|
||||
{ id: msg.id, error: error, result: result });
|
||||
});
|
||||
},
|
||||
|
||||
updateAllStats: function updateAllStats(callback) {
|
||||
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);
|
||||
|
||||
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) {
|
||||
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(aCallback) {
|
||||
// Update |cachedAppStats|.
|
||||
this.updateCachedAppStats();
|
||||
|
||||
@ -247,18 +318,19 @@ this.NetworkStatsService = {
|
||||
// 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
|
||||
@ -266,16 +338,14 @@ this.NetworkStatsService = {
|
||||
// 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.
|
||||
@ -286,14 +356,14 @@ this.NetworkStatsService = {
|
||||
}
|
||||
},
|
||||
|
||||
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.
|
||||
@ -301,16 +371,11 @@ this.NetworkStatsService = {
|
||||
},
|
||||
|
||||
/*
|
||||
* 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);
|
||||
},
|
||||
|
||||
/*
|
||||
@ -347,64 +412,64 @@ this.NetworkStatsService = {
|
||||
}
|
||||
|
||||
// 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");
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -412,26 +477,34 @@ this.NetworkStatsService = {
|
||||
/*
|
||||
* 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|,
|
||||
@ -478,19 +551,15 @@ this.NetworkStatsService = {
|
||||
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) {
|
||||
@ -509,16 +578,16 @@ this.NetworkStatsService = {
|
||||
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;
|
||||
}
|
||||
|
||||
@ -534,17 +603,17 @@ this.NetworkStatsService = {
|
||||
},
|
||||
|
||||
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();
|
||||
|
@ -29,15 +29,15 @@ 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,
|
||||
aRxBytes, aTxBytes, aCallback);
|
||||
NetworkStatsService.saveAppStats(aAppId, aNetwork, aTimeStamp,
|
||||
aRxBytes, aTxBytes, aCallback);
|
||||
},
|
||||
|
||||
classID : NETWORKSTATSSERVICEPROXY_CID,
|
||||
|
Loading…
Reference in New Issue
Block a user