From 86d64a008e613c848d611f35e6173451730c8d04 Mon Sep 17 00:00:00 2001 From: Georg Fritzsche Date: Wed, 6 May 2015 12:54:12 +0200 Subject: [PATCH] Bug 1140037 - Fix ping fuzzing calculations. r=dexter --- .../telemetry/TelemetryController.jsm | 32 ++++----- .../components/telemetry/TelemetrySession.jsm | 70 +++---------------- .../components/telemetry/TelemetryUtils.jsm | 66 +++++++++++++++++ toolkit/components/telemetry/moz.build | 1 + .../tests/unit/test_TelemetryController.js | 4 +- .../tests/unit/test_TelemetrySession.js | 2 +- 6 files changed, 94 insertions(+), 81 deletions(-) create mode 100644 toolkit/components/telemetry/TelemetryUtils.jsm diff --git a/toolkit/components/telemetry/TelemetryController.jsm b/toolkit/components/telemetry/TelemetryController.jsm index c1217c2e903..8fec203c9bf 100644 --- a/toolkit/components/telemetry/TelemetryController.jsm +++ b/toolkit/components/telemetry/TelemetryController.jsm @@ -22,6 +22,7 @@ Cu.import("resource://gre/modules/Task.jsm", this); Cu.import("resource://gre/modules/DeferredTask.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm"); Cu.import("resource://gre/modules/Timer.jsm"); +Cu.import("resource://gre/modules/TelemetryUtils.jsm", this); const LOGGER_NAME = "Toolkit.Telemetry"; const LOGGER_PREFIX = "TelemetryController::"; @@ -55,6 +56,8 @@ const PING_SUBMIT_TIMEOUT_MS = 2 * 60 * 1000; // We treat pings before midnight as happening "at midnight" with this tolerance. const MIDNIGHT_TOLERANCE_MS = 15 * 60 * 1000; +// For midnight fuzzing we want to affect pings around midnight with this tolerance. +const MIDNIGHT_TOLERANCE_FUZZ_MS = 5 * 60 * 1000; // We try to spread "midnight" pings out over this interval. const MIDNIGHT_FUZZING_INTERVAL_MS = 60 * 60 * 1000; const MIDNIGHT_FUZZING_DELAY_MS = Math.random() * MIDNIGHT_FUZZING_INTERVAL_MS; @@ -126,16 +129,6 @@ function isNewPingFormat(aPing) { ("version" in aPing) && (aPing.version >= 2); } -/** - * Takes a date and returns it trunctated to a date with daily precision. - */ -function truncateToDays(date) { - return new Date(date.getFullYear(), - date.getMonth(), - date.getDate(), - 0, 0, 0, 0); -} - function tomorrow(date) { let d = new Date(date); d.setDate(d.getDate() + 1); @@ -506,17 +499,20 @@ let Impl = { * @return Number The next time (ms from UNIX epoch) when we can send pings. */ _getNextPingSendTime: function(now) { - const todayDate = truncateToDays(now); - const tomorrowDate = tomorrow(todayDate); - const nextMidnightRangeStart = tomorrowDate.getTime() - MIDNIGHT_TOLERANCE_MS; - const currentMidnightRangeEnd = todayDate.getTime() - MIDNIGHT_TOLERANCE_MS + Policy.midnightPingFuzzingDelay(); + const midnight = TelemetryUtils.getNearestMidnight(now, MIDNIGHT_FUZZING_INTERVAL_MS); - if (now.getTime() < currentMidnightRangeEnd) { - return currentMidnightRangeEnd; + // Don't delay ping if we are not close to midnight. + if (!midnight) { + return now.getTime(); } - if (now.getTime() >= nextMidnightRangeStart) { - return nextMidnightRangeStart + Policy.midnightPingFuzzingDelay(); + // Delay ping send if we are within the midnight fuzzing range. + // This is from: |midnight - MIDNIGHT_TOLERANCE_FUZZ_MS| + // to: |midnight + MIDNIGHT_FUZZING_INTERVAL_MS| + const midnightRangeStart = midnight.getTime() - MIDNIGHT_TOLERANCE_FUZZ_MS; + if (now.getTime() >= midnightRangeStart) { + // We spread those ping sends out between |midnight| and |midnight + midnightPingFuzzingDelay|. + return midnight.getTime() + Policy.midnightPingFuzzingDelay(); } return now.getTime(); diff --git a/toolkit/components/telemetry/TelemetrySession.jsm b/toolkit/components/telemetry/TelemetrySession.jsm index 6f9e5814b0d..9109a199032 100644 --- a/toolkit/components/telemetry/TelemetrySession.jsm +++ b/toolkit/components/telemetry/TelemetrySession.jsm @@ -20,6 +20,7 @@ Cu.import("resource://gre/modules/DeferredTask.jsm", this); Cu.import("resource://gre/modules/Preferences.jsm"); Cu.import("resource://gre/modules/Task.jsm"); Cu.import("resource://gre/modules/Timer.jsm"); +Cu.import("resource://gre/modules/TelemetryUtils.jsm", this); const myScope = this; @@ -174,58 +175,6 @@ let Policy = { clearSchedulerTickTimeout: id => clearTimeout(id), }; -/** - * Takes a date and returns it trunctated to a date with daily precision. - */ -function truncateToDays(date) { - return new Date(date.getFullYear(), - date.getMonth(), - date.getDate(), - 0, 0, 0, 0); -} - -/** - * Check if the difference between the times is within the provided tolerance. - * @param {Number} t1 A time in milliseconds. - * @param {Number} t2 A time in milliseconds. - * @param {Number} tolerance The tolerance, in milliseconds. - * @return {Boolean} True if the absolute time difference is within the tolerance, false - * otherwise. - */ -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. - * @return {Object} The Date object representing the closes midnight, or null if midnight - * is not within the midnight tolerance. - */ -function getNearestMidnight(date) { - let lastMidnight = truncateToDays(date); - if (areTimesClose(date.getTime(), lastMidnight.getTime(), SCHEDULER_MIDNIGHT_TOLERANCE_MS)) { - return lastMidnight; - } - - const nextMidnightDate = getNextMidnight(date); - if (areTimesClose(date.getTime(), nextMidnightDate.getTime(), SCHEDULER_MIDNIGHT_TOLERANCE_MS)) { - return nextMidnightDate; - } - return null; -} - /** * Get the ping type based on the payload. * @param {Object} aPayload The ping payload. @@ -473,7 +422,7 @@ let TelemetryScheduler = { 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); + const nextMidnight = TelemetryUtils.getNextMidnight(now); timeout = Math.min(timeout, nextMidnight.getTime() - now.getTime()); } @@ -484,8 +433,8 @@ let TelemetryScheduler = { _sentDailyPingToday: function(nowDate) { // This is today's date and also the previous midnight (0:00). - const todayDate = truncateToDays(nowDate); - const nearestMidnight = getNearestMidnight(nowDate); + const todayDate = TelemetryUtils.truncateToDays(nowDate); + const nearestMidnight = TelemetryUtils.getNearestMidnight(nowDate, SCHEDULER_MIDNIGHT_TOLERANCE_MS); // 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. @@ -506,7 +455,7 @@ let TelemetryScheduler = { return false; } - const nearestMidnight = getNearestMidnight(nowDate); + const nearestMidnight = TelemetryUtils.getNearestMidnight(nowDate, SCHEDULER_MIDNIGHT_TOLERANCE_MS); 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?"); @@ -616,7 +565,8 @@ let TelemetryScheduler = { let nextSessionCheckpoint = this._lastSessionCheckpointTime + ABORTED_SESSION_UPDATE_INTERVAL_MS; let combineActions = (shouldSendDaily && isAbortedPingDue) || (shouldSendDaily && - areTimesClose(now, nextSessionCheckpoint, SCHEDULER_COALESCE_THRESHOLD_MS)); + TelemetryUtils.areTimesClose(now, nextSessionCheckpoint, + SCHEDULER_COALESCE_THRESHOLD_MS)); if (combineActions) { this._log.trace("_schedulerTickLogic - Combining pings."); @@ -661,7 +611,7 @@ let TelemetryScheduler = { // update the schedules. this._saveAbortedPing(now.getTime(), competingPayload); // If we're close to midnight, skip today's daily ping and reschedule it for tomorrow. - let nearestMidnight = getNearestMidnight(now); + let nearestMidnight = TelemetryUtils.getNearestMidnight(now, SCHEDULER_MIDNIGHT_TOLERANCE_MS); if (nearestMidnight) { this._lastDailyPingTime = now.getTime(); } @@ -1152,8 +1102,8 @@ let Impl = { getMetadata: function getMetadata(reason) { this._log.trace("getMetadata - Reason " + reason); - let sessionStartDate = toLocalTimeISOString(truncateToDays(this._sessionStartDate)); - let subsessionStartDate = toLocalTimeISOString(truncateToDays(this._subsessionStartDate)); + let sessionStartDate = toLocalTimeISOString(TelemetryUtils.truncateToDays(this._sessionStartDate)); + let subsessionStartDate = toLocalTimeISOString(TelemetryUtils.truncateToDays(this._subsessionStartDate)); // Compute the subsession length in milliseconds, then convert to seconds. let subsessionLength = Math.floor((Policy.now() - this._subsessionStartDate.getTime()) / 1000); diff --git a/toolkit/components/telemetry/TelemetryUtils.jsm b/toolkit/components/telemetry/TelemetryUtils.jsm new file mode 100644 index 00000000000..95d57154cc7 --- /dev/null +++ b/toolkit/components/telemetry/TelemetryUtils.jsm @@ -0,0 +1,66 @@ +/* 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/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = [ + "TelemetryUtils" +]; + +const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components; + +this.TelemetryUtils = { + /** + * Takes a date and returns it trunctated to a date with daily precision. + */ + truncateToDays: function(date) { + return new Date(date.getFullYear(), + date.getMonth(), + date.getDate(), + 0, 0, 0, 0); + }, + + /** + * Check if the difference between the times is within the provided tolerance. + * @param {Number} t1 A time in milliseconds. + * @param {Number} t2 A time in milliseconds. + * @param {Number} tolerance The tolerance, in milliseconds. + * @return {Boolean} True if the absolute time difference is within the tolerance, false + * otherwise. + */ + areTimesClose: function(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. + */ + getNextMidnight: function(date) { + let nextMidnight = new Date(this.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. + * @param {Number} tolerance The tolerance within we find the closest midnight. + * @return {Object} The Date object representing the closes midnight, or null if midnight + * is not within the midnight tolerance. + */ + getNearestMidnight: function(date, tolerance) { + let lastMidnight = this.truncateToDays(date); + if (this.areTimesClose(date.getTime(), lastMidnight.getTime(), tolerance)) { + return lastMidnight; + } + + const nextMidnightDate = this.getNextMidnight(date); + if (this.areTimesClose(date.getTime(), nextMidnightDate.getTime(), tolerance)) { + return nextMidnightDate; + } + return null; + }, +}; diff --git a/toolkit/components/telemetry/moz.build b/toolkit/components/telemetry/moz.build index b9bcd1014cf..f8ff35c81ba 100644 --- a/toolkit/components/telemetry/moz.build +++ b/toolkit/components/telemetry/moz.build @@ -34,6 +34,7 @@ EXTRA_JS_MODULES += [ 'TelemetryLog.jsm', 'TelemetryStopwatch.jsm', 'TelemetryStorage.jsm', + 'TelemetryUtils.jsm', 'ThirdPartyCookieProbe.jsm', 'UITelemetry.jsm', ] diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js index f8ee8aee933..2906db6dcce 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetryController.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetryController.js @@ -287,14 +287,14 @@ add_task(function* test_midnightPingSendFuzzing() { yield sendPing(true, true); Assert.ok(!!pingSendTimerCallback); - Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 0, 45, 0)); + Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 1, 0, 0)); // A ping after midnight within the fuzzing delay should also not get sent. now = new Date(2030, 5, 2, 0, 40, 0); fakeNow(now); pingSendTimeout = null; yield sendPing(true, true); - Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 0, 45, 0)); + Assert.deepEqual(futureDate(now, pingSendTimeout), new Date(2030, 5, 2, 1, 0, 0)); // The Request constructor restores the previous ping handler. gRequestIterator = Iterator(new Request()); diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js index bbe567daca5..152e8fd6743 100644 --- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js +++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js @@ -1672,7 +1672,7 @@ add_task(function* test_sendDailyOnIdle() { 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); + now = new Date(2040, 1, 2, 23, 54, 0); fakeNow(now); yield fakeIdleNotification("idle");