Backed out 14 changesets (bug 1143714, bug 1143796, bug 1139751, bug 1139460, bug 1140558) for Win PGO xpcshell failures

Backed out changeset d2567d89cda3 (bug 1139751)
Backed out changeset a8edee74d07f (bug 1139751)
Backed out changeset 613fd260f646 (bug 1143796)
Backed out changeset 7a6f6bdd6edf (bug 1143796)
Backed out changeset fdf9d0174142 (bug 1143714)
Backed out changeset 18989d1ebd43 (bug 1140558)
Backed out changeset a4f545b715ae (bug 1140558)
Backed out changeset b2e92f548736 (bug 1140558)
Backed out changeset a082c774db0a (bug 1140558)
Backed out changeset f9f66f6aaa86 (bug 1139460)
Backed out changeset d8b62b11c43e (bug 1139460)
Backed out changeset 77090798e88c (bug 1139460)
Backed out changeset ea6da072eb0c (bug 1139460)
Backed out changeset 58c2eb92a959 (bug 1139460)
This commit is contained in:
Wes Kocher 2015-04-01 20:52:33 -07:00
parent 520ca726f9
commit dac5cde09b
18 changed files with 831 additions and 1345 deletions

View File

@ -139,6 +139,28 @@ function configureLogging() {
}
}
// Takes an array of promises and returns a promise that is resolved once all of
// them are rejected or resolved.
function allResolvedOrRejected(promises) {
if (!promises.length) {
return Promise.resolve([]);
}
let countdown = promises.length;
let deferred = Promise.defer();
for (let p of promises) {
let helper = () => {
if (--countdown == 0) {
deferred.resolve();
}
};
Promise.resolve(p).then(helper, helper);
}
return deferred.promise;
}
// Loads a JSON file using OS.file. file is a string representing the path
// of the file to be read, options contains additional options to pass to
// OS.File.read.

View File

@ -17,7 +17,6 @@ this.EXPORTED_SYMBOLS = [
];
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
Components.utils.import("resource://gre/modules/ObjectUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
"resource://gre/modules/Promise.jsm");
@ -259,9 +258,95 @@ proto.notEqual = function notEqual(actual, expected, message) {
* (string) Short explanation of the expected result
*/
proto.deepEqual = function deepEqual(actual, expected, message) {
this.report(!ObjectUtils.deepEqual(actual, expected), actual, expected, message, "deepEqual");
this.report(!_deepEqual(actual, expected), actual, expected, message, "deepEqual");
};
function _deepEqual(actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
} else if (instanceOf(actual, "Date") && instanceOf(expected, "Date")) {
if (isNaN(actual.getTime()) && isNaN(expected.getTime()))
return true;
return actual.getTime() === expected.getTime();
// 7.3 If the expected value is a RegExp object, the actual value is
// equivalent if it is also a RegExp object with the same source and
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
} else if (instanceOf(actual, "RegExp") && instanceOf(expected, "RegExp")) {
return actual.source === expected.source &&
actual.global === expected.global &&
actual.multiline === expected.multiline &&
actual.lastIndex === expected.lastIndex &&
actual.ignoreCase === expected.ignoreCase;
// 7.4. Other pairs that do not both pass typeof value == "object",
// equivalence is determined by ==.
} else if (typeof actual != "object" && typeof expected != "object") {
return actual == expected;
// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected);
}
}
function isUndefinedOrNull(value) {
return value === null || value === undefined;
}
function isArguments(object) {
return instanceOf(object, "Arguments");
}
function objEquiv(a, b) {
if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
return false;
}
// An identical 'prototype' property.
if (a.prototype !== b.prototype) {
return false;
}
// Object.keys may be broken through screwy arguments passing. Converting to
// an array solves the problem.
if (isArguments(a)) {
if (!isArguments(b)) {
return false;
}
a = pSlice.call(a);
b = pSlice.call(b);
return _deepEqual(a, b);
}
let ka, kb, key, i;
try {
ka = Object.keys(a);
kb = Object.keys(b);
} catch (e) {
// Happens when one is a string literal and the other isn't
return false;
}
// Having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length != kb.length)
return false;
// The same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
// Equivalent values for every corresponding key, and possibly expensive deep
// test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
/**
* 8. The non-equivalence assertion tests for any deep inequality.
* assert.notDeepEqual(actual, expected, message_opt);

View File

@ -209,6 +209,24 @@ function run_test() {
}
});
// Make sure deepEqual doesn't loop forever on circular refs
let b = {};
b.b = b;
let c = {};
c.b = c;
let gotError = false;
try {
assert.deepEqual(b, c);
} catch (e) {
gotError = true;
}
dump("All OK\n");
assert.ok(gotError);
function testAssertionMessage(actual, expected) {
try {
assert.equal(actual, "");

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,6 @@ const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
const myScope = this;
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/debug.js", this);
@ -17,8 +16,6 @@ Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/PromiseUtils.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/DeferredTask.jsm", this);
Cu.import("resource://gre/modules/Preferences.jsm");
@ -42,8 +39,6 @@ const TELEMETRY_DELAY = 60000;
const TELEMETRY_TEST_DELAY = 100;
// The number of days to keep pings serialised on the disk in case of failures.
const DEFAULT_RETENTION_DAYS = 14;
// Timeout after which we consider a ping submission failed.
const PING_SUBMIT_TIMEOUT_MS = 2 * 60 * 1000;
XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
"@mozilla.org/base/telemetry;1",
@ -176,7 +171,6 @@ this.TelemetryPing = Object.freeze({
* id, false otherwise.
* @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
* environment data.
* @param {Object} [aOptions.overrideEnvironment=null] set to override the environment data.
* @returns {Promise} A promise that resolves when the ping is sent.
*/
send: function(aType, aPayload, aOptions = {}) {
@ -200,7 +194,6 @@ this.TelemetryPing = Object.freeze({
* id, false otherwise.
* @param {Boolean} [aOptions.addEnvironment=false] true if the ping should contain the
* environment data.
* @param {Object} [aOptions.overrideEnvironment=null] set to override the environment data.
* @returns {Promise} A promise that resolves when the pings are saved.
*/
savePendingPings: function(aType, aPayload, aOptions = {}) {
@ -226,7 +219,6 @@ this.TelemetryPing = Object.freeze({
* environment data.
* @param {Boolean} [aOptions.overwrite=false] true overwrites a ping with the same name,
* if found.
* @param {Object} [aOptions.overrideEnvironment=null] set to override the environment data.
* @param {String} [aOptions.filePath] The path to save the ping to. Will save to default
* ping location if not provided.
*
@ -274,15 +266,7 @@ let Impl = {
// The deferred promise resolved when the initialization task completes.
_delayedInitTaskDeferred: null,
// This is a public barrier Telemetry clients can use to add blockers to the shutdown
// of TelemetryPing.
// After this barrier, clients can not submit Telemetry pings anymore.
_shutdownBarrier: new AsyncShutdown.Barrier("TelemetryPing: Waiting for clients."),
// This is a private barrier blocked by pending async ping activity (sending & saving).
_connectionsBarrier: new AsyncShutdown.Barrier("TelemetryPing: Waiting for pending ping activity"),
// This tracks all pending ping requests to the server.
_pendingPingRequests: new Map(),
/**
* Get the data for the "application" section of the ping.
@ -326,7 +310,6 @@ let Impl = {
* id, false otherwise.
* @param {Boolean} aOptions.addEnvironment true if the ping should contain the
* environment data.
* @param {Object} [aOptions.overrideEnvironment=null] set to override the environment data.
*
* @returns Promise<Object> A promise that resolves when the ping is completely assembled.
*/
@ -334,11 +317,6 @@ let Impl = {
this._log.trace("assemblePing - Type " + aType + ", Server " + this._server +
", aOptions " + JSON.stringify(aOptions));
// Clone the payload data so we don't race against unexpected changes in subobjects that are
// still referenced by other code.
// We can't trust all callers to do this properly on their own.
let payload = Cu.cloneInto(aPayload, myScope);
// Fill the common ping fields.
let pingData = {
type: aType,
@ -354,10 +332,16 @@ let Impl = {
}
if (aOptions.addEnvironment) {
pingData.environment = aOptions.overrideEnvironment || TelemetryEnvironment.currentEnvironment;
return TelemetryEnvironment.getEnvironmentData().then(environment => {
pingData.environment = environment;
return pingData;
},
error => {
this._log.error("assemblePing - Rejection", error);
});
}
return pingData;
return Promise.resolve(pingData);
},
popPayloads: function popPayloads() {
@ -380,14 +364,6 @@ let Impl = {
this._server = aServer;
},
/**
* Track any pending ping send and save tasks through the promise passed here.
* This is needed to block shutdown on any outstanding ping activity.
*/
_trackPendingPingTask: function (aPromise) {
this._connectionsBarrier.client.addBlocker("Waiting for ping task", aPromise);
},
/**
* Adds a ping to the pending ping list by moving it to the saved pings directory
* and adding it to the pending ping list.
@ -418,7 +394,6 @@ let Impl = {
* false otherwise.
* @param {Boolean} aOptions.addEnvironment true if the ping should contain the
* environment data.
* @param {Object} aOptions.overrideEnvironment set to override the environment data.
*
* @returns {Promise} A promise that resolves when the ping is sent.
*/
@ -426,36 +401,28 @@ let Impl = {
this._log.trace("send - Type " + aType + ", Server " + this._server +
", aOptions " + JSON.stringify(aOptions));
let pingData = this.assemblePing(aType, aPayload, aOptions);
// Once ping is assembled, send it along with the persisted pings in the backlog.
let p = [
// Persist the ping if sending it fails.
this.doPing(pingData, false)
.catch(() => TelemetryFile.savePing(pingData, true)),
this.sendPersistedPings(),
];
let promise = Promise.all(p);
this._trackPendingPingTask(promise);
return promise;
return this.assemblePing(aType, aPayload, aOptions)
.then(pingData => {
// Once ping is assembled, send it along with the persisted ping in the backlog.
let p = [
// Persist the ping if sending it fails.
this.doPing(pingData, false)
.catch(() => TelemetryFile.savePing(pingData, true)),
this.sendPersistedPings(),
];
return Promise.all(p);
},
error => this._log.error("send - Rejection", error));
},
/**
* Send the persisted pings to the server.
*
* @return Promise A promise that is resolved when all pings finished sending or failed.
*/
sendPersistedPings: function sendPersistedPings() {
this._log.trace("sendPersistedPings");
let pingsIterator = Iterator(this.popPayloads());
let p = [for (data of pingsIterator) this.doPing(data, true).catch((e) => {
this._log.error("sendPersistedPings - doPing rejected", e);
})];
let promise = Promise.all(p);
this._trackPendingPingTask(promise);
return promise;
let p = [data for (data in pingsIterator)].map(data => this.doPing(data, true));
return Promise.all(p);
},
/**
@ -470,7 +437,6 @@ let Impl = {
* false otherwise.
* @param {Boolean} aOptions.addEnvironment true if the ping should contain the
* environment data.
* @param {Object} [aOptions.overrideEnvironment=null] set to override the environment data.
*
* @returns {Promise} A promise that resolves when all the pings are saved to disk.
*/
@ -478,8 +444,9 @@ let Impl = {
this._log.trace("savePendingPings - Type " + aType + ", Server " + this._server +
", aOptions " + JSON.stringify(aOptions));
let pingData = this.assemblePing(aType, aPayload, aOptions);
return TelemetryFile.savePendingPings(pingData);
return this.assemblePing(aType, aPayload, aOptions)
.then(pingData => TelemetryFile.savePendingPings(pingData),
error => this._log.error("savePendingPings - Rejection", error));
},
/**
@ -497,7 +464,6 @@ let Impl = {
* @param {Boolean} aOptions.overwrite true overwrites a ping with the same name, if found.
* @param {String} [aOptions.filePath] The path to save the ping to. Will save to default
* ping location if not provided.
* @param {Object} [aOptions.overrideEnvironment=null] set to override the environment data.
*
* @returns {Promise} A promise that resolves with the ping id when the ping is saved to
* disk.
@ -506,18 +472,20 @@ let Impl = {
this._log.trace("savePing - Type " + aType + ", Server " + this._server +
", aOptions " + JSON.stringify(aOptions));
let pingData = this.assemblePing(aType, aPayload, aOptions);
if ("filePath" in aOptions) {
return TelemetryFile.savePingToFile(pingData, aOptions.filePath, aOptions.overwrite)
.then(() => { return pingData.id; });
} else {
return TelemetryFile.savePing(pingData, aOptions.overwrite)
.then(() => { return pingData.id; });
}
return this.assemblePing(aType, aPayload, aOptions)
.then(pingData => {
if ("filePath" in aOptions) {
return TelemetryFile.savePingToFile(pingData, aOptions.filePath, aOptions.overwrite)
.then(() => { return pingData.id; });
} else {
return TelemetryFile.savePing(pingData, aOptions.overwrite)
.then(() => { return pingData.id; });
}
}, error => this._log.error("savePing - Rejection", error));
},
onPingRequestFinished: function(success, startTime, ping, isPersisted) {
this._log.trace("onPingRequestFinished - success: " + success + ", persisted: " + isPersisted);
finishPingRequest: function finishPingRequest(success, startTime, ping, isPersisted) {
this._log.trace("finishPingRequest - Success " + success + ", Persisted " + isPersisted);
let hping = Telemetry.getHistogramById("TELEMETRY_PING");
let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
@ -567,27 +535,21 @@ let Impl = {
doPing: function doPing(ping, isPersisted) {
this._log.trace("doPing - Server " + this._server + ", Persisted " + isPersisted);
const isNewPing = isNewPingFormat(ping);
const version = isNewPing ? PING_FORMAT_VERSION : 1;
const url = this._server + this.submissionPath(ping) + "?v=" + version;
let deferred = Promise.defer();
let isNewPing = isNewPingFormat(ping);
let version = isNewPing ? PING_FORMAT_VERSION : 1;
let url = this._server + this.submissionPath(ping) + "?v=" + version;
let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
.createInstance(Ci.nsIXMLHttpRequest);
request.mozBackgroundRequest = true;
request.timeout = PING_SUBMIT_TIMEOUT_MS;
request.open("POST", url, true);
request.overrideMimeType("text/plain");
request.setRequestHeader("Content-Type", "application/json; charset=UTF-8");
this._pendingPingRequests.set(url, request);
let startTime = new Date();
let deferred = PromiseUtils.defer();
let onRequestFinished = (success, event) => {
let onCompletion = () => {
function handler(success) {
let handleCompletion = event => {
if (success) {
deferred.resolve();
} else {
@ -595,51 +557,18 @@ let Impl = {
}
};
this._pendingPingRequests.delete(url);
this.onPingRequestFinished(success, startTime, ping, isPersisted)
.then(() => onCompletion(),
(error) => {
this._log.error("doPing - request success: " + success + ", error" + error);
onCompletion();
});
};
let errorhandler = (event) => {
this._log.error("doPing - error making request to " + url + ": " + event.type);
onRequestFinished(false, event);
};
request.onerror = errorhandler;
request.ontimeout = errorhandler;
request.onabort = errorhandler;
request.onload = (event) => {
let status = request.status;
let statusClass = status - (status % 100);
let success = false;
if (statusClass === 200) {
// We can treat all 2XX as success.
this._log.info("doPing - successfully loaded, status: " + status);
success = true;
} else if (statusClass === 400) {
// 4XX means that something with the request was broken.
this._log.error("doPing - error submitting to " + url + ", status: " + status
+ " - ping request broken?");
// TODO: we should handle this better, but for now we should avoid resubmitting
// broken requests by pretending success.
success = true;
} else if (statusClass === 500) {
// 5XX means there was a server-side error and we should try again later.
this._log.error("doPing - error submitting to " + url + ", status: " + status
+ " - server error, should retry later");
} else {
// We received an unexpected status codes.
this._log.error("doPing - error submitting to " + url + ", status: " + status
+ ", type: " + event.type);
}
onRequestFinished(success, event);
};
return function(event) {
this.finishPingRequest(success, startTime, ping, isPersisted)
.then(() => handleCompletion(event),
error => {
this._log.error("doPing - Request Success " + success + ", Error " +
error);
handleCompletion(event);
});
};
}
request.addEventListener("error", handler(false).bind(this), false);
request.addEventListener("load", handler(true).bind(this), false);
// If that's a legacy ping format, just send its payload.
let networkPayload = isNewPing ? ping : ping.payload;
@ -653,7 +582,6 @@ let Impl = {
.createInstance(Ci.nsIStringInputStream);
payloadStream.data = this.gzipCompressString(utf8Payload);
request.send(payloadStream);
return deferred.promise;
},
@ -770,6 +698,8 @@ let Impl = {
try {
this._initialized = true;
yield TelemetryEnvironment.init();
yield TelemetryFile.loadSavedPings();
// If we have any TelemetryPings lying around, we'll be aggressive
// and try to send them all off ASAP.
@ -812,39 +742,21 @@ let Impl = {
return this._delayedInitTaskDeferred.promise;
},
// Do proper shutdown waiting and cleanup.
_cleanupOnShutdown: Task.async(function*() {
if (!this._initialized) {
return;
}
// Abort any pending ping XHRs.
for (let [url, request] of this._pendingPingRequests) {
this._log.trace("_cleanupOnShutdown - aborting ping request for " + url);
try {
request.abort();
} catch (e) {
this._log.error("_cleanupOnShutdown - failed to abort request to " + url, e);
}
}
this._pendingPingRequests.clear();
// Now do an orderly shutdown.
try {
// First wait for clients processing shutdown.
yield this._shutdownBarrier.wait();
// Then wait for any outstanding async ping activity.
yield this._connectionsBarrier.wait();
} finally {
// Reset state.
this._initialized = false;
this._initStarted = false;
}
}),
shutdown: function() {
this._log.trace("shutdown");
let cleanup = () => {
if (!this._initialized) {
return;
}
let reset = () => {
this._initialized = false;
this._initStarted = false;
};
return this._shutdownBarrier.wait().then(
() => TelemetryEnvironment.shutdown().then(reset, reset));
};
// We can be in one the following states here:
// 1) setupTelemetry was never called
// or it was called and
@ -860,11 +772,11 @@ let Impl = {
// This handles 4).
if (!this._delayedInitTask) {
// We already ran the delayed initialization.
return this._cleanupOnShutdown();
return cleanup();
}
// This handles 2) and 3).
return this._delayedInitTask.finalize().then(() => this._cleanupOnShutdown());
return this._delayedInitTask.finalize().then(cleanup);
},
/**
@ -909,8 +821,6 @@ let Impl = {
initialized: this._initialized,
initStarted: this._initStarted,
haveDelayedInitTask: !!this._delayedInitTask,
shutdownBarrier: this._shutdownBarrier.state,
connectionsBarrier: this._connectionsBarrier.state,
};
},
};

View File

@ -47,7 +47,9 @@ const REASON_SHUTDOWN = "shutdown";
const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";
const MS_IN_ONE_HOUR = 60 * 60 * 1000;
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
const MIN_SUBSESSION_LENGTH_MS = 10 * 60 * 1000;
// This is the HG changeset of the Histogram.json file, used to associate
@ -84,8 +86,6 @@ const TELEMETRY_DELAY = 60000;
const TELEMETRY_TEST_DELAY = 100;
// Execute a scheduler tick every 5 minutes.
const SCHEDULER_TICK_INTERVAL_MS = 5 * 60 * 1000;
// When user is idle, execute a scheduler tick every 60 minutes.
const SCHEDULER_TICK_IDLE_INTERVAL_MS = 60 * 60 * 1000;
// The maximum number of times a scheduled operation can fail.
const SCHEDULER_RETRY_ATTEMPTS = 3;
@ -194,17 +194,6 @@ function areTimesClose(t1, t2, tolerance) {
return Math.abs(t1 - t2) <= tolerance;
}
/**
* Get the next midnight for a date.
* @param {Object} date The date object to check.
* @return {Object} The Date object representing the next midnight.
*/
function getNextMidnight(date) {
let nextMidnight = new Date(truncateToDays(date));
nextMidnight.setDate(nextMidnight.getDate() + 1);
return nextMidnight;
}
/**
* Get the midnight which is closer to the provided date.
* @param {Object} date The date object to check.
@ -217,7 +206,8 @@ function getNearestMidnight(date) {
return lastMidnight;
}
const nextMidnightDate = getNextMidnight(date);
let nextMidnightDate = new Date(lastMidnight);
nextMidnightDate.setDate(nextMidnightDate.getDate() + 1);
if (areTimesClose(date.getTime(), nextMidnightDate.getTime(), SCHEDULER_MIDNIGHT_TOLERANCE_MS)) {
return nextMidnightDate;
}
@ -426,10 +416,7 @@ let TelemetryScheduler = {
// The timer which drives the scheduler.
_schedulerTimer: null,
// The interval used by the scheduler timer.
_schedulerInterval: 0,
_shuttingDown: true,
_isUserIdle: false,
/**
* Initialises the scheduler and schedules the first daily/aborted session pings.
@ -438,21 +425,19 @@ let TelemetryScheduler = {
this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, "TelemetryScheduler::");
this._log.trace("init");
this._shuttingDown = false;
this._isUserIdle = false;
// Initialize the last daily ping and aborted session last due times to the current time.
// Otherwise, we might end up sending daily pings even if the subsession is not long enough.
let now = Policy.now();
this._lastDailyPingTime = now.getTime();
this._lastSessionCheckpointTime = now.getTime();
this._rescheduleTimeout();
idleService.addIdleObserver(this, IDLE_TIMEOUT_SECONDS);
},
/**
* Reschedules the tick timer.
*/
_rescheduleTimeout: function() {
this._log.trace("_rescheduleTimeout - isUserIdle: " + this._isUserIdle);
this._log.trace("_rescheduleTimeout");
if (this._shuttingDown) {
this._log.warn("_rescheduleTimeout - already shutdown");
return;
@ -462,31 +447,8 @@ let TelemetryScheduler = {
Policy.clearSchedulerTickTimeout(this._schedulerTimer);
}
const now = Policy.now();
let timeout = SCHEDULER_TICK_INTERVAL_MS;
// When the user is idle we want to fire the timer less often.
if (this._isUserIdle) {
timeout = SCHEDULER_TICK_IDLE_INTERVAL_MS;
// We need to make sure though that we don't miss sending pings around
// midnight when we use the longer idle intervals.
const nextMidnight = getNextMidnight(now);
timeout = Math.min(timeout, nextMidnight.getTime() - now.getTime());
}
this._log.trace("_rescheduleTimeout - scheduling next tick for " + new Date(now.getTime() + timeout));
this._schedulerTimer =
Policy.setSchedulerTickTimeout(() => this._onSchedulerTick(), timeout);
},
_sentDailyPingToday: function(nowDate) {
// This is today's date and also the previous midnight (0:00).
const todayDate = truncateToDays(nowDate);
const nearestMidnight = getNearestMidnight(nowDate);
// If we are close to midnight, we check against that, otherwise against the last midnight.
const checkDate = nearestMidnight || todayDate;
// We consider a ping sent for today if it occured after midnight, or prior within the tolerance.
return (this._lastDailyPingTime >= (checkDate.getTime() - SCHEDULER_MIDNIGHT_TOLERANCE_MS));
Policy.setSchedulerTickTimeout(() => this._onSchedulerTick(), SCHEDULER_TICK_INTERVAL_MS);
},
/**
@ -495,37 +457,31 @@ let TelemetryScheduler = {
* @return {Boolean} True if we can send the daily ping, false otherwise.
*/
_isDailyPingDue: function(nowDate) {
const sentPingToday = this._sentDailyPingToday(nowDate);
// The daily ping is not due if we already sent one today.
if (sentPingToday) {
this._log.trace("_isDailyPingDue - already sent one today");
return false;
}
const nearestMidnight = getNearestMidnight(nowDate);
if (!sentPingToday && !nearestMidnight) {
// Computer must have gone to sleep, the daily ping is overdue.
this._log.trace("_isDailyPingDue - daily ping is overdue... computer went to sleep?");
let nearestMidnight = getNearestMidnight(nowDate);
if (nearestMidnight) {
let subsessionLength = Math.abs(nowDate.getTime() - this._lastDailyPingTime);
if (subsessionLength < MIN_SUBSESSION_LENGTH_MS) {
// Generating a daily ping now would create a very short subsession.
return false;
} else if (areTimesClose(this._lastDailyPingTime, nearestMidnight.getTime(),
SCHEDULER_MIDNIGHT_TOLERANCE_MS)) {
// We've already sent a ping for this midnight.
return false;
}
return true;
}
// Avoid overly short sessions.
const timeSinceLastDaily = nowDate.getTime() - this._lastDailyPingTime;
if (timeSinceLastDaily < MIN_SUBSESSION_LENGTH_MS) {
this._log.trace("_isDailyPingDue - delaying daily to keep minimum session length");
return false;
let lastDailyPingDate = truncateToDays(new Date(this._lastDailyPingTime));
// This is today's date and also the previous midnight (0:00).
let todayDate = truncateToDays(nowDate);
// Check that _lastDailyPingTime isn't today nor within SCHEDULER_MIDNIGHT_TOLERANCE_MS of the
// *previous* midnight.
if ((lastDailyPingDate.getTime() != todayDate.getTime()) &&
!areTimesClose(this._lastDailyPingTime, todayDate.getTime(), SCHEDULER_MIDNIGHT_TOLERANCE_MS)) {
// Computer must have gone to sleep, the daily ping is overdue.
return true;
}
// To fight jank, we allow daily pings to be collected on user idle before midnight
// within the tolerance interval.
if (!this._isUserIdle && (nowDate.getTime() < nearestMidnight.getTime())) {
this._log.trace("_isDailyPingDue - waiting for user idle period");
return false;
}
this._log.trace("_isDailyPingDue - is due");
return true;
return false;
},
/**
@ -542,25 +498,6 @@ let TelemetryScheduler = {
.catch(e => this._log.error("_saveAbortedPing - Failed", e));
},
/**
* The notifications handler.
*/
observe: function(aSubject, aTopic, aData) {
this._log.trace("observe - aTopic: " + aTopic);
switch(aTopic) {
case "idle":
// If the user is idle, increase the tick interval.
this._isUserIdle = true;
return this._onSchedulerTick();
break;
case "active":
// User is back to work, restore the original tick interval.
this._isUserIdle = false;
return this._onSchedulerTick();
break;
}
},
/**
* Performs a scheduler tick. This function manages Telemetry recurring operations.
* @return {Promise} A promise, only used when testing, resolved when the scheduled
@ -569,7 +506,7 @@ let TelemetryScheduler = {
_onSchedulerTick: function() {
if (this._shuttingDown) {
this._log.warn("_onSchedulerTick - already shutdown.");
return Promise.reject(new Error("Already shutdown."));
return;
}
let promise = Promise.resolve();
@ -713,8 +650,6 @@ let TelemetryScheduler = {
this._schedulerTimer = null;
}
idleService.removeIdleObserver(this, IDLE_TIMEOUT_SECONDS);
this._shuttingDown = true;
}
};
@ -1582,7 +1517,7 @@ let Impl = {
yield this._checkAbortedSessionPing();
TelemetryEnvironment.registerChangeListener(ENVIRONMENT_CHANGE_LISTENER,
(reason, data) => this._onEnvironmentChange(reason, data));
() => this._onEnvironmentChange());
// Write the first aborted-session ping as early as possible. Just do that
// if we are not testing, since calling Telemetry.reset() will make a previous
// aborted ping a pending ping.
@ -1596,7 +1531,7 @@ let Impl = {
this._delayedInitTaskDeferred.resolve();
} catch (e) {
this._delayedInitTaskDeferred.reject(e);
this._delayedInitTaskDeferred.reject();
} finally {
this._delayedInitTask = null;
this._delayedInitTaskDeferred = null;
@ -1987,8 +1922,7 @@ let Impl = {
#if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
// If required, also save the payload as an aborted session.
if (saveAsAborted) {
let abortedPromise = this._saveAbortedSessionPing(payload);
promise = promise.then(() => abortedPromise);
return promise.then(() => this._saveAbortedSessionPing(payload));
}
#endif
return promise;
@ -2049,8 +1983,8 @@ let Impl = {
}
}),
_onEnvironmentChange: function(reason, oldEnvironment) {
this._log.trace("_onEnvironmentChange", reason);
_onEnvironmentChange: function() {
this._log.trace("_onEnvironmentChange");
let payload = this.getSessionPayload(REASON_ENVIRONMENT_CHANGE, true);
let clonedPayload = Cu.cloneInto(payload, myScope);
@ -2060,7 +1994,6 @@ let Impl = {
retentionDays: RETENTION_DAYS,
addClientId: true,
addEnvironment: true,
overrideEnvironment: oldEnvironment,
};
TelemetryPing.send(getPingType(payload), payload, options);
},
@ -2095,13 +2028,8 @@ let Impl = {
const FILE_PATH = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIRECTORY,
ABORTED_SESSION_FILE_NAME);
try {
this._log.trace("_removeAbortedSessionPing - success");
return OS.File.remove(FILE_PATH);
} catch (ex if ex.becauseNoSuchFile) {
this._log.trace("_removeAbortedSessionPing - no such file");
} catch (ex) {
this._log.error("_removeAbortedSessionPing - error removing ping", ex)
}
} catch (ex if ex.becauseNoSuchFile) { }
return Promise.resolve();
},

View File

@ -9,13 +9,6 @@ The environment data may also be submitted by other ping types.
*Note:* This is not submitted with all ping types due to privacy concerns. This and other data is inspected under the `data collection policy <https://wiki.mozilla.org/Firefox/Data_Collection>`_.
Some parts of the environment must be fetched asynchronously at startup. We don't want other Telemetry components to block on waiting for the environment, so some items may be missing from it until the async fetching finished.
This currently affects the following sections:
- profile
- addons
Structure::
{
@ -43,11 +36,8 @@ Structure::
autoDownload: <bool>, // true on failure
},
userPrefs: {
// Only prefs which are changed from the default value are listed
// in this block
"pref.name.value": value // some prefs send the value
"pref.name.url": "<user-set>" // For some privacy-sensitive prefs
// only the fact that the value has been changed is recorded
// Two possible behaviours: values of the whitelisted prefs, or for some prefs we
// only record they are present with value being set to null.
},
},
profile: { // This section is not available on Android.

View File

@ -19,12 +19,6 @@ If a ping failed to submit (e.g. because of missing internet connection), Teleme
*Note:* the :doc:`main pings <main-ping>` are kept locally even after successful submission to enable the HealthReport and SelfSupport features. They will be deleted after their retention period of 180 days.
The telemetry server team is working towards `the common services status codes <https://wiki.mozilla.org/CloudServices/DataPipeline/HTTPEdgeServerSpecification#Server_Responses>`_, but for now the following logic is sufficient for Telemetry:
* `2XX` - success, don't resubmit
* `4XX` - there was some problem with the request - the client should not try to resubmit as it would just receive the same response
* `5XX` - there was a server-side error, the client should try to resubmit later
Ping types
==========

View File

@ -9,12 +9,6 @@ const gIsMac = ("@mozilla.org/xpcom/mac-utils;1" in Components.classes);
const gIsAndroid = ("@mozilla.org/android/bridge;1" in Components.classes);
const gIsGonk = ("@mozilla.org/cellbroadcast/gonkservice;1" in Components.classes);
const MILLISECONDS_PER_MINUTE = 60 * 1000;
const MILLISECONDS_PER_HOUR = 60 * MILLISECONDS_PER_MINUTE;
const MILLISECONDS_PER_DAY = 24 * MILLISECONDS_PER_HOUR;
const HAS_DATAREPORTINGSERVICE = "@mozilla.org/datareporting/service;1" in Components.classes;
let gOldAppInfo = null;
let gGlobalScope = this;
@ -91,23 +85,6 @@ function fakeSchedulerTimer(set, clear) {
session.Policy.clearSchedulerTickTimeout = clear;
}
// Fake the current date.
function fakeNow(date) {
let session = Cu.import("resource://gre/modules/TelemetrySession.jsm");
session.Policy.now = () => date;
let environment = Cu.import("resource://gre/modules/TelemetryEnvironment.jsm");
environment.Policy.now = () => date;
}
// Return a date that is |offset| ms in the future from |date|.
function futureDate(date, offset) {
return new Date(date.getTime() + offset);
}
function truncateToDays(aMsec) {
return Math.floor(aMsec / MILLISECONDS_PER_DAY);
}
// Set logging preferences for all the tests.
Services.prefs.setCharPref("toolkit.telemetry.log.level", "Trace");
Services.prefs.setBoolPref("toolkit.telemetry.log.dump", true);

View File

@ -27,9 +27,6 @@ let gHttpRoot = null;
// The URL of the data directory, on the webserver.
let gDataRoot = null;
let gNow = new Date(2010, 1, 1, 12, 0, 0);
fakeNow(gNow);
const PLATFORM_VERSION = "1.9.2";
const APP_VERSION = "1";
const APP_ID = "xpcshell@tests.mozilla.org";
@ -46,6 +43,7 @@ const PARTNER_ID = "NicePartner-ID-3785";
const GFX_VENDOR_ID = "0xabcd";
const GFX_DEVICE_ID = "0x1234";
const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
// The profile reset date, in milliseconds (Today)
const PROFILE_RESET_DATE_MS = Date.now();
// The profile creation date, in milliseconds (Yesterday).
@ -177,6 +175,10 @@ function spoofPartnerInfo() {
}
}
function truncateToDays(aMsec) {
return Math.floor(aMsec / MILLISECONDS_PER_DAY);
}
/**
* Check that a value is a string and not empty.
*
@ -599,59 +601,105 @@ add_task(function* asyncSetup() {
yield spoofProfileReset();
});
add_task(function* test_initAndShutdown() {
// Check that init and shutdown work properly.
TelemetryEnvironment.init();
yield TelemetryEnvironment.shutdown();
TelemetryEnvironment.init();
yield TelemetryEnvironment.shutdown();
// A double init should be silently handled.
TelemetryEnvironment.init();
TelemetryEnvironment.init();
// getEnvironmentData should return a sane result.
let data = yield TelemetryEnvironment.getEnvironmentData();
Assert.ok(!!data);
// The change listener registration should silently fail after shutdown.
yield TelemetryEnvironment.shutdown();
TelemetryEnvironment.registerChangeListener("foo", () => {});
TelemetryEnvironment.unregisterChangeListener("foo");
// Shutting down again should be ignored.
yield TelemetryEnvironment.shutdown();
// Getting the environment data should reject after shutdown.
Assert.ok(yield isRejected(TelemetryEnvironment.getEnvironmentData()));
});
add_task(function* test_changeNotify() {
TelemetryEnvironment.init();
// Register some listeners
let results = new Array(4).fill(false);
for (let i=0; i<results.length; ++i) {
let k = i;
TelemetryEnvironment.registerChangeListener("test"+k, () => results[k] = true);
}
// Trigger environment change notifications.
// TODO: test with proper environment changes, not directly.
TelemetryEnvironment._onEnvironmentChange("foo");
Assert.ok(results.every(val => val), "All change listeners should have been notified.");
results.fill(false);
TelemetryEnvironment._onEnvironmentChange("bar");
Assert.ok(results.every(val => val), "All change listeners should have been notified.");
// Unregister listeners
for (let i=0; i<4; ++i) {
TelemetryEnvironment.unregisterChangeListener("test"+i);
}
});
add_task(function* test_checkEnvironment() {
let environmentData = yield TelemetryEnvironment.onInitialized();
yield TelemetryEnvironment.init();
let environmentData = yield TelemetryEnvironment.getEnvironmentData();
checkEnvironmentData(environmentData);
yield TelemetryEnvironment.shutdown();
});
add_task(function* test_prefWatchPolicies() {
const PREF_TEST_1 = "toolkit.telemetry.test.pref_new";
const PREF_TEST_2 = "toolkit.telemetry.test.pref1";
const PREF_TEST_3 = "toolkit.telemetry.test.pref2";
const PREF_TEST_4 = "toolkit.telemetry.test.pref_old";
const expectedValue = "some-test-value";
gNow = futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
let prefsToWatch = {};
prefsToWatch[PREF_TEST_1] = TelemetryEnvironment.RECORD_PREF_VALUE;
prefsToWatch[PREF_TEST_2] = TelemetryEnvironment.RECORD_PREF_STATE;
prefsToWatch[PREF_TEST_3] = TelemetryEnvironment.RECORD_PREF_STATE;
prefsToWatch[PREF_TEST_4] = TelemetryEnvironment.RECORD_PREF_VALUE;
Preferences.set(PREF_TEST_4, expectedValue);
yield TelemetryEnvironment.init();
// Set the Environment preferences to watch.
TelemetryEnvironment._watchPreferences(prefsToWatch);
let deferred = PromiseUtils.defer();
// Check that the pref values are missing or present as expected
Assert.strictEqual(TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST_1], undefined);
Assert.strictEqual(TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST_4], expectedValue);
TelemetryEnvironment.registerChangeListener("testWatchPrefs",
(reason, data) => deferred.resolve(data));
let oldEnvironmentData = TelemetryEnvironment.currentEnvironment;
TelemetryEnvironment.registerChangeListener("testWatchPrefs", deferred.resolve);
// Trigger a change in the watched preferences.
Preferences.set(PREF_TEST_1, expectedValue);
Preferences.set(PREF_TEST_2, false);
let eventEnvironmentData = yield deferred.promise;
yield deferred.promise;
// Unregister the listener.
TelemetryEnvironment.unregisterChangeListener("testWatchPrefs");
// Check environment contains the correct data.
Assert.deepEqual(oldEnvironmentData, eventEnvironmentData);
let userPrefs = TelemetryEnvironment.currentEnvironment.settings.userPrefs;
let environmentData = yield TelemetryEnvironment.getEnvironmentData();
let userPrefs = environmentData.settings.userPrefs;
Assert.equal(userPrefs[PREF_TEST_1], expectedValue,
"Environment contains the correct preference value.");
Assert.equal(userPrefs[PREF_TEST_2], "<user-set>",
"Report that the pref was user set but the value is not shown.");
Assert.equal(userPrefs[PREF_TEST_2], null,
"Report that the pref was user set and has no value.");
Assert.ok(!(PREF_TEST_3 in userPrefs),
"Do not report if preference not user set.");
yield TelemetryEnvironment.shutdown();
});
add_task(function* test_prefWatch_prefReset() {
@ -662,24 +710,20 @@ add_task(function* test_prefWatch_prefReset() {
// Set the preference to a non-default value.
Preferences.set(PREF_TEST, false);
gNow = futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
yield TelemetryEnvironment.init();
// Set the Environment preferences to watch.
TelemetryEnvironment._watchPreferences(prefsToWatch);
let deferred = PromiseUtils.defer();
TelemetryEnvironment.registerChangeListener("testWatchPrefs_reset", deferred.resolve);
Assert.strictEqual(TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST], "<user-set>");
// Trigger a change in the watched preferences.
Preferences.reset(PREF_TEST);
yield deferred.promise;
Assert.strictEqual(TelemetryEnvironment.currentEnvironment.settings.userPrefs[PREF_TEST], undefined);
// Unregister the listener.
TelemetryEnvironment.unregisterChangeListener("testWatchPrefs_reset");
yield TelemetryEnvironment.shutdown();
});
add_task(function* test_addonsWatch_InterestingChange() {
@ -688,15 +732,13 @@ add_task(function* test_addonsWatch_InterestingChange() {
// We only expect a single notification for each install, uninstall, enable, disable.
const EXPECTED_NOTIFICATIONS = 4;
yield TelemetryEnvironment.init();
let deferred = PromiseUtils.defer();
let receivedNotifications = 0;
let registerCheckpointPromise = (aExpected) => {
gNow = futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
return new Promise(resolve => TelemetryEnvironment.registerChangeListener(
"testWatchAddons_Changes" + aExpected, (reason, data) => {
Assert.equal(reason, "addons-changed");
"testWatchAddons_Changes" + aExpected, () => {
receivedNotifications++;
resolve();
}));
@ -712,26 +754,24 @@ add_task(function* test_addonsWatch_InterestingChange() {
yield AddonTestUtils.installXPIFromURL(ADDON_INSTALL_URL);
yield checkpointPromise;
assertCheckpoint(1);
Assert.ok(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons);
checkpointPromise = registerCheckpointPromise(2);
let addon = yield AddonTestUtils.getAddonById(ADDON_ID);
addon.userDisabled = true;
yield checkpointPromise;
assertCheckpoint(2);
Assert.ok(!(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons));
checkpointPromise = registerCheckpointPromise(3);
addon.userDisabled = false;
yield checkpointPromise;
assertCheckpoint(3);
Assert.ok(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons);
checkpointPromise = registerCheckpointPromise(4);
yield AddonTestUtils.uninstallAddonByID(ADDON_ID);
yield checkpointPromise;
assertCheckpoint(4);
Assert.ok(!(ADDON_ID in TelemetryEnvironment.currentEnvironment.addons.activeAddons));
yield TelemetryEnvironment.shutdown();
Assert.equal(receivedNotifications, EXPECTED_NOTIFICATIONS,
"We must only receive the notifications we expect.");
@ -743,19 +783,15 @@ add_task(function* test_pluginsWatch_Add() {
return;
}
gNow = futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
Assert.equal(TelemetryEnvironment.currentEnvironment.addons.activePlugins.length, 1);
yield TelemetryEnvironment.init();
let newPlugin = new PluginTag(PLUGIN2_NAME, PLUGIN2_DESC, PLUGIN2_VERSION, true);
gInstalledPlugins.push(newPlugin);
let deferred = PromiseUtils.defer();
let receivedNotifications = 0;
let callback = (reason, data) => {
let callback = () => {
receivedNotifications++;
Assert.equal(reason, "addons-changed");
deferred.resolve();
};
TelemetryEnvironment.registerChangeListener("testWatchPlugins_Add", callback);
@ -763,9 +799,8 @@ add_task(function* test_pluginsWatch_Add() {
Services.obs.notifyObservers(null, PLUGIN_UPDATED_TOPIC, null);
yield deferred.promise;
Assert.equal(TelemetryEnvironment.currentEnvironment.addons.activePlugins.length, 2);
TelemetryEnvironment.unregisterChangeListener("testWatchPlugins_Add");
yield TelemetryEnvironment.shutdown();
Assert.equal(receivedNotifications, 1, "We must only receive one notification.");
});
@ -776,8 +811,7 @@ add_task(function* test_pluginsWatch_Remove() {
return;
}
gNow = futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
yield TelemetryEnvironment.init();
// Find the test plugin.
let plugin = gInstalledPlugins.find(plugin => (plugin.name == PLUGIN2_NAME));
@ -798,6 +832,7 @@ add_task(function* test_pluginsWatch_Remove() {
yield deferred.promise;
TelemetryEnvironment.unregisterChangeListener("testWatchPlugins_Remove");
yield TelemetryEnvironment.shutdown();
Assert.equal(receivedNotifications, 1, "We must only receive one notification.");
});
@ -806,28 +841,19 @@ add_task(function* test_addonsWatch_NotInterestingChange() {
// We are not interested to dictionary addons changes.
const DICTIONARY_ADDON_INSTALL_URL = gDataRoot + "dictionary.xpi";
const INTERESTING_ADDON_INSTALL_URL = gDataRoot + "restartless.xpi";
yield TelemetryEnvironment.init();
gNow = futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
let receivedNotification = false;
let deferred = PromiseUtils.defer();
let receivedNotifications = 0;
TelemetryEnvironment.registerChangeListener("testNotInteresting",
() => {
Assert.ok(!receivedNotification, "Should not receive multiple notifications");
receivedNotification = true;
deferred.resolve();
});
() => receivedNotifications++);
yield AddonTestUtils.installXPIFromURL(DICTIONARY_ADDON_INSTALL_URL);
yield AddonTestUtils.installXPIFromURL(INTERESTING_ADDON_INSTALL_URL);
yield deferred.promise;
Assert.ok(!("telemetry-dictionary@tests.mozilla.org" in
TelemetryEnvironment.currentEnvironment.addons.activeAddons),
"Dictionaries should not appear in active addons.");
Assert.equal(receivedNotifications, 1, "We must receive only one notification.");
TelemetryEnvironment.unregisterChangeListener("testNotInteresting");
yield TelemetryEnvironment.shutdown();
});
add_task(function* test_addonsAndPlugins() {
@ -858,10 +884,12 @@ add_task(function* test_addonsAndPlugins() {
clicktoplay: true,
};
yield TelemetryEnvironment.init();
// Install an addon so we have some data.
yield AddonTestUtils.installXPIFromURL(ADDON_INSTALL_URL);
let data = TelemetryEnvironment.currentEnvironment;
let data = yield TelemetryEnvironment.getEnvironmentData();
checkEnvironmentData(data);
// Check addon data.
@ -891,45 +919,8 @@ add_task(function* test_addonsAndPlugins() {
let personaId = (gIsGonk) ? null : PERSONA_ID;
Assert.equal(data.addons.persona, personaId, "The correct Persona Id must be reported.");
});
add_task(function* test_changeThrottling() {
const PREF_TEST = "toolkit.telemetry.test.pref1";
let prefsToWatch = {};
prefsToWatch[PREF_TEST] = TelemetryEnvironment.RECORD_PREF_STATE;
Preferences.reset(PREF_TEST);
gNow = futureDate(gNow, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
// Set the Environment preferences to watch.
TelemetryEnvironment._watchPreferences(prefsToWatch);
let deferred = PromiseUtils.defer();
let changeCount = 0;
TelemetryEnvironment.registerChangeListener("testWatchPrefs_throttling", () => {
++changeCount;
deferred.resolve();
});
// The first pref change should trigger a notification.
Preferences.set(PREF_TEST, 1);
yield deferred.promise;
Assert.equal(changeCount, 1);
// We should only get a change notification for second of the following changes.
deferred = PromiseUtils.defer();
gNow = futureDate(gNow, MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
Preferences.set(PREF_TEST, 2);
gNow = futureDate(gNow, 5 * MILLISECONDS_PER_MINUTE);
fakeNow(gNow);
Preferences.set(PREF_TEST, 3);
yield deferred.promise;
Assert.equal(changeCount, 2);
// Unregister the listener.
TelemetryEnvironment.unregisterChangeListener("testWatchPrefs_throttling");
yield TelemetryEnvironment.shutdown();
});
add_task(function*() {

View File

@ -35,6 +35,8 @@ const PREF_ENABLED = PREF_BRANCH + "enabled";
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
const PREF_FHR_SERVICE_ENABLED = "datareporting.healthreport.service.enabled";
const HAS_DATAREPORTINGSERVICE = "@mozilla.org/datareporting/service;1" in Cc;
const Telemetry = Cc["@mozilla.org/base/telemetry;1"].getService(Ci.nsITelemetry);
let gHttpServer = new HttpServer();

View File

@ -1,73 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Test that TelemetryPing sends close to shutdown don't lead
// to AsyncShutdown timeouts.
"use strict";
const { utils: Cu, interfaces: Ci, classes: Cc } = Components;
Cu.import("resource://gre/modules/Services.jsm", this);
Cu.import("resource://gre/modules/TelemetryPing.jsm", this);
Cu.import("resource://gre/modules/Timer.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
Cu.import("resource://gre/modules/AsyncShutdown.jsm", this);
Cu.import("resource://testing-common/httpd.js", this);
const PREF_BRANCH = "toolkit.telemetry.";
const PREF_ENABLED = PREF_BRANCH + "enabled";
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
function contentHandler(metadata, response)
{
dump("contentHandler called for path: " + metadata._path + "\n");
// We intentionally don't finish writing the response here to let the
// client time out.
response.processAsync();
response.setHeader("Content-Type", "text/plain");
}
function run_test() {
// Addon manager needs a profile directory
do_get_profile();
loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
Services.prefs.setBoolPref(PREF_ENABLED, true);
Services.prefs.setBoolPref(PREF_FHR_UPLOAD_ENABLED, true);
// Send the needed startup notifications to the datareporting service
// to ensure that it has been initialized.
if (HAS_DATAREPORTINGSERVICE) {
let drs = Cc["@mozilla.org/datareporting/service;1"]
.getService(Ci.nsISupports)
.wrappedJSObject;
drs.observe(null, "app-startup", null);
drs.observe(null, "profile-after-change", null);
}
run_next_test();
}
add_task(function* test_sendTimeout() {
const TIMEOUT = 100;
// Enable testing mode for AsyncShutdown, otherwise some testing-only functionality
// is not available.
Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
Services.prefs.setIntPref("toolkit.asyncshutdown.crash_timeout", TIMEOUT);
let httpServer = new HttpServer();
httpServer.registerPrefixHandler("/", contentHandler);
httpServer.start(-1);
yield TelemetryPing.setup();
TelemetryPing.setServer("http://localhost:" + httpServer.identity.primaryPort);
TelemetryPing.send("test-ping-type", {});
// Trigger the AsyncShutdown phase TelemetryPing hangs off.
AsyncShutdown.profileBeforeChange._trigger();
AsyncShutdown.sendTelemetry._trigger();
// If we get here, we didn't time out in the shutdown routines.
Assert.ok(true, "Didn't time out on shutdown.");
});

View File

@ -61,8 +61,8 @@ const RW_OWNER = parseInt("0600", 8);
const NUMBER_OF_THREADS_TO_LAUNCH = 30;
let gNumberOfThreadsLaunched = 0;
const MS_IN_ONE_HOUR = 60 * 60 * 1000;
const MS_IN_ONE_DAY = 24 * MS_IN_ONE_HOUR;
const SEC_IN_ONE_DAY = 24 * 60 * 60;
const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000;
const PREF_BRANCH = "toolkit.telemetry.";
const PREF_ENABLED = PREF_BRANCH + "enabled";
@ -70,6 +70,7 @@ const PREF_SERVER = PREF_BRANCH + "server";
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
const PREF_FHR_SERVICE_ENABLED = "datareporting.healthreport.service.enabled";
const HAS_DATAREPORTINGSERVICE = "@mozilla.org/datareporting/service;1" in Cc;
const SESSION_RECORDER_EXPECTED = HAS_DATAREPORTINGSERVICE &&
Preferences.get(PREF_FHR_SERVICE_ENABLED, true);
@ -99,13 +100,6 @@ function generateUUID() {
return str.substring(1, str.length - 1);
}
function truncateDateToDays(date) {
return new Date(date.getFullYear(),
date.getMonth(),
date.getDate(),
0, 0, 0, 0);
}
function sendPing() {
TelemetrySession.gatherStartup();
if (gServerStarted) {
@ -130,17 +124,21 @@ function wrapWithExceptionHandler(f) {
return wrapper;
}
function futureDate(date, offset) {
return new Date(date.getTime() + offset);
}
function fakeNow(date) {
let session = Cu.import("resource://gre/modules/TelemetrySession.jsm");
session.Policy.now = () => date;
}
function fakeGenerateUUID(sessionFunc, subsessionFunc) {
let session = Cu.import("resource://gre/modules/TelemetrySession.jsm");
session.Policy.generateSessionUUID = sessionFunc;
session.Policy.generateSubsessionUUID = subsessionFunc;
}
function fakeIdleNotification(topic) {
let session = Cu.import("resource://gre/modules/TelemetrySession.jsm");
return session.TelemetryScheduler.observe(null, topic, null);
}
function registerPingHandler(handler) {
gHttpServer.registerPrefixHandler("/submit/telemetry/",
wrapWithExceptionHandler(handler));
@ -1023,10 +1021,8 @@ add_task(function* test_dailyDuplication() {
fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {});
yield TelemetrySession.setup();
// Make sure the daily ping gets triggered at midnight.
// We need to make sure that we trigger this after the period where we wait for
// the user to become idle.
let firstDailyDue = new Date(2030, 1, 2, 0, 0, 0);
// Make sure the daily ping gets triggered just before midnight.
let firstDailyDue = new Date(2030, 1, 1, 23, 45, 0);
fakeNow(firstDailyDue);
// Run a scheduler tick: it should trigger the daily ping.
@ -1048,6 +1044,7 @@ add_task(function* test_dailyDuplication() {
// Set the current time to a bit after midnight.
let secondDailyDue = new Date(firstDailyDue);
secondDailyDue.setDate(firstDailyDue.getDate() + 1);
secondDailyDue.setHours(0);
secondDailyDue.setMinutes(15);
fakeNow(secondDailyDue);
@ -1117,6 +1114,7 @@ add_task(function* test_environmentChange() {
}
let now = new Date(2040, 1, 1, 12, 0, 0);
let nowDay = new Date(2040, 1, 1, 0, 0, 0);
let timerCallback = null;
let timerDelay = null;
@ -1147,39 +1145,31 @@ add_task(function* test_environmentChange() {
keyed.add("b", 1);
// Trigger and collect environment-change ping.
let startDay = truncateDateToDays(now);
now = futureDate(now, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(now);
Preferences.set(PREF_TEST, 1);
let request = yield gRequestIterator.next();
Assert.ok(!!request);
let ping = decodeRequestPayload(request);
Assert.equal(ping.type, PING_TYPE_MAIN);
Assert.equal(ping.environment.settings.userPrefs[PREF_TEST], undefined);
Assert.equal(ping.environment.settings.userPrefs[PREF_TEST], 1);
Assert.equal(ping.payload.info.reason, REASON_ENVIRONMENT_CHANGE);
let subsessionStartDate = new Date(ping.payload.info.subsessionStartDate);
Assert.equal(subsessionStartDate.toISOString(), startDay.toISOString());
Assert.equal(subsessionStartDate.toISOString(), nowDay.toISOString());
Assert.equal(ping.payload.histograms[COUNT_ID].sum, 1);
Assert.equal(ping.payload.keyedHistograms[KEYED_ID]["a"].sum, 1);
// Trigger and collect another ping. The histograms should be reset.
startDay = truncateDateToDays(now);
now = futureDate(now, 10 * MILLISECONDS_PER_MINUTE);
fakeNow(now);
Preferences.set(PREF_TEST, 2);
request = yield gRequestIterator.next();
Assert.ok(!!request);
ping = decodeRequestPayload(request);
Assert.equal(ping.type, PING_TYPE_MAIN);
Assert.equal(ping.environment.settings.userPrefs[PREF_TEST], 1);
Assert.equal(ping.environment.settings.userPrefs[PREF_TEST], 2);
Assert.equal(ping.payload.info.reason, REASON_ENVIRONMENT_CHANGE);
subsessionStartDate = new Date(ping.payload.info.subsessionStartDate);
Assert.equal(subsessionStartDate.toISOString(), startDay.toISOString());
Assert.equal(subsessionStartDate.toISOString(), nowDay.toISOString());
Assert.equal(ping.payload.histograms[COUNT_ID].sum, 0);
Assert.deepEqual(ping.payload.keyedHistograms[KEYED_ID], {});
@ -1257,9 +1247,6 @@ add_task(function* test_savedSessionData() {
// Start TelemetrySession so that it loads the session data file.
yield TelemetrySession.reset();
// Watch a test preference, trigger and environment change and wait for it to propagate.
// _watchPreferences triggers a subsession notification
fakeNow(new Date(2050, 1, 1, 12, 0, 0));
TelemetryEnvironment._watchPreferences(prefsToWatch);
let changePromise = new Promise(resolve =>
TelemetryEnvironment.registerChangeListener("test_fake_change", resolve));
@ -1499,7 +1486,7 @@ add_task(function* test_schedulerEnvironmentReschedules() {
gRequestIterator = Iterator(new Request());
// Set a fake current date and start Telemetry.
let nowDate = new Date(2060, 10, 18, 0, 00, 0);
let nowDate = new Date(2009, 10, 18, 0, 00, 0);
fakeNow(nowDate);
let schedulerTickCallback = null;
fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {});
@ -1620,102 +1607,6 @@ add_task(function* test_pingExtendedStats() {
"UITelemetry must be sent if the extended set is on.");
});
add_task(function* test_schedulerUserIdle() {
if (gIsAndroid || gIsGonk) {
// We don't have the aborted session or the daily ping here.
return;
}
const SCHEDULER_TICK_INTERVAL_MS = 5 * 60 * 1000;
const SCHEDULER_TICK_IDLE_INTERVAL_MS = 60 * 60 * 1000;
let now = new Date(2010, 1, 1, 11, 0, 0);
fakeNow(now);
let schedulerTimeout = 0;
fakeSchedulerTimer((callback, timeout) => {
schedulerTimeout = timeout;
}, () => {});
yield TelemetrySession.reset();
gRequestIterator = Iterator(new Request());
// When not idle, the scheduler should have a 5 minutes tick interval.
Assert.equal(schedulerTimeout, SCHEDULER_TICK_INTERVAL_MS);
// Send an "idle" notification to the scheduler.
fakeIdleNotification("idle");
// When idle, the scheduler should have a 1hr tick interval.
Assert.equal(schedulerTimeout, SCHEDULER_TICK_IDLE_INTERVAL_MS);
// Send an "active" notification to the scheduler.
fakeIdleNotification("active");
// When user is back active, the scheduler tick should be 5 minutes again.
Assert.equal(schedulerTimeout, SCHEDULER_TICK_INTERVAL_MS);
// We should not miss midnight when going to idle.
now.setHours(23);
now.setMinutes(50);
fakeIdleNotification("idle");
Assert.equal(schedulerTimeout, 10 * 60 * 1000);
yield TelemetrySession.shutdown();
});
add_task(function* test_sendDailyOnIdle() {
if (gIsAndroid || gIsGonk) {
// We don't have the aborted session or the daily ping here.
return;
}
let now = new Date(2040, 1, 1, 11, 0, 0);
fakeNow(now);
let schedulerTickCallback = 0;
fakeSchedulerTimer((callback, timeout) => {
schedulerTickCallback = callback;
}, () => {});
yield TelemetrySession.reset();
// Make sure we are not sending a daily before midnight when active.
now = new Date(2040, 1, 1, 23, 55, 0);
fakeNow(now);
registerPingHandler((req, res) => {
Assert.ok(false, "No daily ping should be received yet when the user is active.");
});
yield fakeIdleNotification("active");
// The Request constructor restores the previous ping handler.
gRequestIterator = Iterator(new Request());
// We should receive a daily ping after midnight.
now = new Date(2040, 1, 2, 0, 05, 0);
fakeNow(now);
yield schedulerTickCallback();
let request = yield gRequestIterator.next();
Assert.ok(!!request);
let ping = decodeRequestPayload(request);
Assert.equal(ping.type, PING_TYPE_MAIN);
Assert.equal(ping.payload.info.reason, REASON_DAILY);
// We should also trigger a ping when going idle shortly before next midnight.
now = new Date(2040, 1, 2, 23, 55, 0);
fakeNow(now);
yield fakeIdleNotification("idle");
request = yield gRequestIterator.next();
Assert.ok(!!request);
ping = decodeRequestPayload(request);
Assert.equal(ping.type, PING_TYPE_MAIN);
Assert.equal(ping.payload.info.reason, REASON_DAILY);
yield TelemetrySession.shutdown();
});
add_task(function* stopServer(){
gHttpServer.stop(do_test_finished);
});

View File

@ -33,7 +33,6 @@ skip-if = android_version == "18"
# Bug 1144395: crash on Android 4.3
skip-if = android_version == "18"
[test_TelemetryPing_idle.js]
[test_TelemetryPingShutdown.js]
[test_TelemetryStopwatch.js]
[test_TelemetryPingBuildID.js]
# Bug 1144395: crash on Android 4.3

View File

@ -1,131 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// Portions of this file are originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
// MIT license: http://opensource.org/licenses/MIT
"use strict";
this.EXPORTED_SYMBOLS = [
"ObjectUtils"
];
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
this.ObjectUtils = {
/**
* This tests objects & values for deep equality.
*
* We check using the most exact approximation of equality between two objects
* to keep the chance of false positives to a minimum.
* `JSON.stringify` is not designed to be used for this purpose; objects may
* have ambiguous `toJSON()` implementations that would influence the test.
*
* @param a (mixed) Object or value to be compared.
* @param b (mixed) Object or value to be compared.
* @return Boolean Whether the objects are deep equal.
*/
deepEqual: function(a, b) {
return _deepEqual(a, b);
},
};
// ... Start of previously MIT-licensed code.
// This deepEqual implementation is originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
// MIT license: http://opensource.org/licenses/MIT
function _deepEqual(a, b) {
// The numbering below refers to sections in the CommonJS spec.
// 7.1 All identical values are equivalent, as determined by ===.
if (a === b) {
return true;
// 7.2 If the b value is a Date object, the a value is
// equivalent if it is also a Date object that refers to the same time.
} else if (instanceOf(a, "Date") && instanceOf(b, "Date")) {
if (isNaN(a.getTime()) && isNaN(b.getTime()))
return true;
return a.getTime() === b.getTime();
// 7.3 If the b value is a RegExp object, the a value is
// equivalent if it is also a RegExp object with the same source and
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
} else if (instanceOf(a, "RegExp") && instanceOf(b, "RegExp")) {
return a.source === b.source &&
a.global === b.global &&
a.multiline === b.multiline &&
a.lastIndex === b.lastIndex &&
a.ignoreCase === b.ignoreCase;
// 7.4 Other pairs that do not both pass typeof value == "object",
// equivalence is determined by ==.
} else if (typeof a != "object" && typeof b != "object") {
return a == b;
// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(a, b);
}
}
function instanceOf(object, type) {
return Object.prototype.toString.call(object) == "[object " + type + "]";
}
function isUndefinedOrNull(value) {
return value === null || value === undefined;
}
function isArguments(object) {
return instanceOf(object, "Arguments");
}
function objEquiv(a, b) {
if (isUndefinedOrNull(a) || isUndefinedOrNull(b)) {
return false;
}
// An identical 'prototype' property.
if (a.prototype !== b.prototype) {
return false;
}
// Object.keys may be broken through screwy arguments passing. Converting to
// an array solves the problem.
if (isArguments(a)) {
if (!isArguments(b)) {
return false;
}
a = pSlice.call(a);
b = pSlice.call(b);
return _deepEqual(a, b);
}
let ka, kb;
try {
ka = Object.keys(a);
kb = Object.keys(b);
} catch (e) {
// Happens when one is a string literal and the other isn't
return false;
}
// Having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length != kb.length)
return false;
// The same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
// Equivalent values for every corresponding key, and possibly expensive deep
// test
for (let key of ka) {
if (!_deepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
// ... End of previously MIT-licensed code.

View File

@ -30,7 +30,6 @@ EXTRA_JS_MODULES += [
'LoadContextInfo.jsm',
'Log.jsm',
'NewTabUtils.jsm',
'ObjectUtils.jsm',
'PageMenu.jsm',
'PageMetadata.jsm',
'PerformanceStats.jsm',

View File

@ -1,92 +0,0 @@
Components.utils.import("resource://gre/modules/ObjectUtils.jsm");
function run_test() {
run_next_test();
}
add_task(function* test_deepEqual() {
let deepEqual = ObjectUtils.deepEqual.bind(ObjectUtils);
// CommonJS 7.2
Assert.ok(deepEqual(new Date(2000, 3, 14), new Date(2000, 3, 14)), "deepEqual date");
Assert.ok(deepEqual(new Date(NaN), new Date(NaN)), "deepEqual invalid dates");
Assert.ok(!deepEqual(new Date(), new Date(2000, 3, 14)), "deepEqual date");
// 7.3
Assert.ok(deepEqual(/a/, /a/));
Assert.ok(deepEqual(/a/g, /a/g));
Assert.ok(deepEqual(/a/i, /a/i));
Assert.ok(deepEqual(/a/m, /a/m));
Assert.ok(deepEqual(/a/igm, /a/igm));
Assert.ok(!deepEqual(/ab/, /a/));
Assert.ok(!deepEqual(/a/g, /a/));
Assert.ok(!deepEqual(/a/i, /a/));
Assert.ok(!deepEqual(/a/m, /a/));
Assert.ok(!deepEqual(/a/igm, /a/im));
let re1 = /a/;
re1.lastIndex = 3;
Assert.ok(!deepEqual(re1, /a/));
// 7.4
Assert.ok(deepEqual(4, "4"), "deepEqual == check");
Assert.ok(deepEqual(true, 1), "deepEqual == check");
Assert.ok(!deepEqual(4, "5"), "deepEqual == check");
// 7.5
// having the same number of owned properties && the same set of keys
Assert.ok(deepEqual({a: 4}, {a: 4}));
Assert.ok(deepEqual({a: 4, b: "2"}, {a: 4, b: "2"}));
Assert.ok(deepEqual([4], ["4"]));
Assert.ok(!deepEqual({a: 4}, {a: 4, b: true}));
Assert.ok(deepEqual(["a"], {0: "a"}));
let a1 = [1, 2, 3];
let a2 = [1, 2, 3];
a1.a = "test";
a1.b = true;
a2.b = true;
a2.a = "test";
Assert.ok(!deepEqual(Object.keys(a1), Object.keys(a2)));
Assert.ok(deepEqual(a1, a2));
let nbRoot = {
toString: function() { return this.first + " " + this.last; }
};
function nameBuilder(first, last) {
this.first = first;
this.last = last;
return this;
}
nameBuilder.prototype = nbRoot;
function nameBuilder2(first, last) {
this.first = first;
this.last = last;
return this;
}
nameBuilder2.prototype = nbRoot;
let nb1 = new nameBuilder("Ryan", "Dahl");
let nb2 = new nameBuilder2("Ryan", "Dahl");
Assert.ok(deepEqual(nb1, nb2));
nameBuilder2.prototype = Object;
nb2 = new nameBuilder2("Ryan", "Dahl");
Assert.ok(!deepEqual(nb1, nb2));
// String literal + object
Assert.ok(!deepEqual("a", {}));
// Make sure deepEqual doesn't loop forever on circular refs
let b = {};
b.b = b;
let c = {};
c.b = c;
Assert.ok(!deepEqual(b, c));
});

View File

@ -16,7 +16,6 @@ support-files =
[test_Http.js]
[test_Log.js]
[test_NewTabUtils.js]
[test_ObjectUtils.js]
[test_PermissionsUtils.js]
[test_Preferences.js]
[test_Promise.js]