Bug 887699 - Part 2: Multi SIM implementation. r=gene

This commit is contained in:
Albert Crespell 2013-10-08 08:09:58 +02:00
parent 1999531b78
commit a2c7f13eb0
5 changed files with 551 additions and 405 deletions

View File

@ -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);
},

View File

@ -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]);

View File

@ -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

View File

@ -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();

View File

@ -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,