зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1346223 - Remove SessionRecorder.jsm. r=gfritzsche
This patch also moves the activeTicks logic to TelemetrySession.jsm along with the related test coverage. MozReview-Commit-ID: 8vXffqo2V85 --HG-- extra : rebase_source : f681b06b48a56e2890af98fd3a1b2dc21a44a77c
This commit is contained in:
Родитель
b84f7fe3cf
Коммит
de1f4c78e1
|
@ -34,7 +34,6 @@ const PREF_LOG_LEVEL = PREF_BRANCH_LOG + "level";
|
|||
const PREF_LOG_DUMP = PREF_BRANCH_LOG + "dump";
|
||||
const PREF_CACHED_CLIENTID = PREF_BRANCH + "cachedClientID";
|
||||
const PREF_FHR_UPLOAD_ENABLED = "datareporting.healthreport.uploadEnabled";
|
||||
const PREF_SESSIONS_BRANCH = "datareporting.sessions.";
|
||||
const PREF_UNIFIED = PREF_BRANCH + "unified";
|
||||
|
||||
// Whether the FHR/Telemetry unification features are enabled.
|
||||
|
@ -69,8 +68,6 @@ XPCOMUtils.defineLazyModuleGetter(this, "ThirdPartyCookieProbe",
|
|||
"resource://gre/modules/ThirdPartyCookieProbe.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
|
||||
"resource://gre/modules/TelemetryEnvironment.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "SessionRecorder",
|
||||
"resource://gre/modules/SessionRecorder.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "UpdateUtils",
|
||||
"resource://gre/modules/UpdateUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "TelemetryArchive",
|
||||
|
@ -300,14 +297,6 @@ this.TelemetryController = Object.freeze({
|
|||
return Impl.savePing(aType, aPayload, aFilePath, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* The session recorder instance managed by Telemetry.
|
||||
* @return {Object} The active SessionRecorder instance or null if not available.
|
||||
*/
|
||||
getSessionRecorder() {
|
||||
return Impl._sessionRecorder;
|
||||
},
|
||||
|
||||
/**
|
||||
* Allows waiting for TelemetryControllers delayed initialization to complete.
|
||||
* The returned promise is guaranteed to resolve before TelemetryController is shutting down.
|
||||
|
@ -333,8 +322,6 @@ var Impl = {
|
|||
// The deferred promise resolved when the initialization task completes.
|
||||
_delayedInitTaskDeferred: null,
|
||||
|
||||
// The session recorder, shared with FHR and the Data Reporting Service.
|
||||
_sessionRecorder: null,
|
||||
// This is a public barrier Telemetry clients can use to add blockers to the shutdown
|
||||
// of TelemetryController.
|
||||
// After this barrier, clients can not submit Telemetry pings anymore.
|
||||
|
@ -692,12 +679,6 @@ var Impl = {
|
|||
return Promise.resolve();
|
||||
}
|
||||
|
||||
// Initialize the session recorder.
|
||||
if (!this._sessionRecorder) {
|
||||
this._sessionRecorder = new SessionRecorder(PREF_SESSIONS_BRANCH);
|
||||
this._sessionRecorder.onStartup();
|
||||
}
|
||||
|
||||
this._attachObservers();
|
||||
|
||||
// Perform a lightweight, early initialization for the component, just registering
|
||||
|
|
|
@ -605,6 +605,8 @@ this.TelemetrySession = Object.freeze({
|
|||
Impl._subsessionCounter = 0;
|
||||
Impl._profileSubsessionCounter = 0;
|
||||
Impl._subsessionStartActiveTicks = 0;
|
||||
Impl._sessionActiveTicks = 0;
|
||||
Impl._isUserActive = true;
|
||||
Impl._subsessionStartTimeMonotonic = 0;
|
||||
Impl._lastEnvironmentChangeDate = Policy.monotonicNow();
|
||||
this.testUninstall();
|
||||
|
@ -661,6 +663,10 @@ var Impl = {
|
|||
_slowSQLStartup: {},
|
||||
_hasWindowRestoredObserver: false,
|
||||
_hasXulWindowVisibleObserver: false,
|
||||
_hasActiveTicksObservers: false,
|
||||
// The activity state for the user. If false, don't count the next
|
||||
// active tick. Otherwise, increment the active ticks as usual.
|
||||
_isUserActive: true,
|
||||
_startupIO: {},
|
||||
// The previous build ID, if this is the first run with a new build.
|
||||
// Null if this is the first run, or the previous build ID is unknown.
|
||||
|
@ -702,6 +708,8 @@ var Impl = {
|
|||
_subsessionStartTimeMonotonic: 0,
|
||||
// The active ticks counted when the subsession starts
|
||||
_subsessionStartActiveTicks: 0,
|
||||
// Active ticks in the whole session.
|
||||
_sessionActiveTicks: 0,
|
||||
// A task performing delayed initialization of the chrome process
|
||||
_delayedInitTask: null,
|
||||
// Need a timeout in case children are tardy in giving back their memory reports.
|
||||
|
@ -805,21 +813,17 @@ var Impl = {
|
|||
|
||||
ret.savedPings = TelemetryStorage.pendingPingCount;
|
||||
|
||||
ret.activeTicks = -1;
|
||||
let sr = TelemetryController.getSessionRecorder();
|
||||
if (sr) {
|
||||
let activeTicks = sr.activeTicks;
|
||||
if (isSubsession) {
|
||||
activeTicks = sr.activeTicks - this._subsessionStartActiveTicks;
|
||||
}
|
||||
|
||||
if (clearSubsession) {
|
||||
this._subsessionStartActiveTicks = activeTicks;
|
||||
}
|
||||
|
||||
ret.activeTicks = activeTicks;
|
||||
let activeTicks = this._sessionActiveTicks;
|
||||
if (isSubsession) {
|
||||
activeTicks = this._sessionActiveTicks - this._subsessionStartActiveTicks;
|
||||
}
|
||||
|
||||
if (clearSubsession) {
|
||||
this._subsessionStartActiveTicks = activeTicks;
|
||||
}
|
||||
|
||||
ret.activeTicks = activeTicks;
|
||||
|
||||
ret.pingsOverdue = TelemetrySend.overduePingsCount;
|
||||
|
||||
return ret;
|
||||
|
@ -1417,6 +1421,25 @@ var Impl = {
|
|||
return TelemetryController.submitExternalPing(getPingType(payload), payload, options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Attaches the needed observers during Telemetry early init, in the
|
||||
* chrome process.
|
||||
*/
|
||||
attachEarlyObservers() {
|
||||
Services.obs.addObserver(this, "sessionstore-windows-restored");
|
||||
if (AppConstants.platform === "android") {
|
||||
Services.obs.addObserver(this, "application-background");
|
||||
}
|
||||
Services.obs.addObserver(this, "xul-window-visible");
|
||||
this._hasWindowRestoredObserver = true;
|
||||
this._hasXulWindowVisibleObserver = true;
|
||||
|
||||
// Attach the active-ticks related observers.
|
||||
Services.obs.addObserver(this, "user-interaction-active");
|
||||
Services.obs.addObserver(this, "user-interaction-inactive");
|
||||
this._hasActiveTicksObservers = true;
|
||||
},
|
||||
|
||||
attachObservers: function attachObservers() {
|
||||
if (!this._initialized)
|
||||
return;
|
||||
|
@ -1482,13 +1505,7 @@ var Impl = {
|
|||
Preferences.set(PREF_PREVIOUS_BUILDID, thisBuildID);
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, "sessionstore-windows-restored");
|
||||
if (AppConstants.platform === "android") {
|
||||
Services.obs.addObserver(this, "application-background");
|
||||
}
|
||||
Services.obs.addObserver(this, "xul-window-visible");
|
||||
this._hasWindowRestoredObserver = true;
|
||||
this._hasXulWindowVisibleObserver = true;
|
||||
this.attachEarlyObservers();
|
||||
|
||||
ppml.addMessageListener(MESSAGE_TELEMETRY_PAYLOAD, this);
|
||||
ppml.addMessageListener(MESSAGE_TELEMETRY_THREAD_HANGS, this);
|
||||
|
@ -1804,7 +1821,6 @@ var Impl = {
|
|||
return Promise.all(p);
|
||||
},
|
||||
|
||||
|
||||
testSavePendingPing() {
|
||||
let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
|
||||
let options = {
|
||||
|
@ -1831,6 +1847,11 @@ var Impl = {
|
|||
if (AppConstants.platform === "android") {
|
||||
Services.obs.removeObserver(this, "application-background");
|
||||
}
|
||||
if (this._hasActiveTicksObservers) {
|
||||
Services.obs.removeObserver(this, "user-interaction-active");
|
||||
Services.obs.removeObserver(this, "user-interaction-inactive");
|
||||
this._hasActiveTicksObservers = false;
|
||||
}
|
||||
GCTelemetry.shutdown();
|
||||
},
|
||||
|
||||
|
@ -1908,6 +1929,20 @@ var Impl = {
|
|||
return this.send(REASON_TEST_PING);
|
||||
},
|
||||
|
||||
/**
|
||||
* Tracks the number of "ticks" the user was active in.
|
||||
*/
|
||||
_onActiveTick(aUserActive) {
|
||||
const needsUpdate = aUserActive && this._isUserActive;
|
||||
this._isUserActive = aUserActive;
|
||||
|
||||
// Don't count the first active tick after we get out of
|
||||
// inactivity, because it is just the start of this active tick.
|
||||
if (needsUpdate) {
|
||||
this._sessionActiveTicks++;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This observer drives telemetry.
|
||||
*/
|
||||
|
@ -1988,6 +2023,12 @@ var Impl = {
|
|||
};
|
||||
TelemetryController.addPendingPing(getPingType(payload), payload, options);
|
||||
break;
|
||||
case "user-interaction-active":
|
||||
this._onActiveTick(true);
|
||||
break;
|
||||
case "user-interaction-inactive":
|
||||
this._onActiveTick(false);
|
||||
break;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
|
|
|
@ -932,12 +932,12 @@ add_task(function* test_checkSubsessionData() {
|
|||
}
|
||||
|
||||
// Keep track of the active ticks count if the session recorder is available.
|
||||
let sessionRecorder = TelemetryController.getSessionRecorder();
|
||||
let activeTicksAtSubsessionStart = sessionRecorder.activeTicks;
|
||||
let getActiveTicks = () => TelemetrySession.getPayload().simpleMeasurements.activeTicks;
|
||||
let activeTicksAtSubsessionStart = getActiveTicks();
|
||||
let expectedActiveTicks = activeTicksAtSubsessionStart;
|
||||
|
||||
let incrementActiveTicks = () => {
|
||||
sessionRecorder.incrementActiveTicks();
|
||||
TelemetrySession.observe(null, "user-interaction-active");
|
||||
++expectedActiveTicks;
|
||||
}
|
||||
|
||||
|
@ -954,7 +954,7 @@ add_task(function* test_checkSubsessionData() {
|
|||
|
||||
// Start a new subsession and check that the active ticks are correctly reported.
|
||||
incrementActiveTicks();
|
||||
activeTicksAtSubsessionStart = sessionRecorder.activeTicks;
|
||||
activeTicksAtSubsessionStart = getActiveTicks();
|
||||
classic = TelemetrySession.getPayload();
|
||||
subsession = TelemetrySession.getPayload("environment-change", true);
|
||||
Assert.equal(classic.simpleMeasurements.activeTicks, expectedActiveTicks,
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
|
||||
Cu.import("resource://gre/modules/TelemetrySession.jsm", this);
|
||||
|
||||
|
||||
add_task(function* test_setup() {
|
||||
// Addon manager needs a profile directory
|
||||
do_get_profile();
|
||||
loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
|
||||
// Make sure we don't generate unexpected pings due to pref changes.
|
||||
yield setEmptyPrefWatchlist();
|
||||
|
||||
Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
|
||||
});
|
||||
|
||||
add_task(function* test_record_activeTicks() {
|
||||
yield TelemetryController.testSetup();
|
||||
|
||||
let checkActiveTicks = (expected) => {
|
||||
let payload = TelemetrySession.getPayload();
|
||||
Assert.equal(payload.simpleMeasurements.activeTicks, expected,
|
||||
"TelemetrySession must record the expected number of active ticks.");
|
||||
};
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
Services.obs.notifyObservers(null, "user-interaction-active");
|
||||
}
|
||||
checkActiveTicks(3);
|
||||
|
||||
// Now send inactive. This must not increment the active ticks.
|
||||
Services.obs.notifyObservers(null, "user-interaction-inactive");
|
||||
checkActiveTicks(3);
|
||||
|
||||
// If we send active again, this should be counted as inactive.
|
||||
Services.obs.notifyObservers(null, "user-interaction-active");
|
||||
checkActiveTicks(3);
|
||||
|
||||
// If we send active again, this should be counted as active.
|
||||
Services.obs.notifyObservers(null, "user-interaction-active");
|
||||
checkActiveTicks(4);
|
||||
|
||||
Services.obs.notifyObservers(null, "user-interaction-active");
|
||||
checkActiveTicks(5);
|
||||
|
||||
yield TelemetryController.testShutdown();
|
||||
});
|
|
@ -53,6 +53,7 @@ skip-if = os == "android" # Disabled due to intermittent orange on Android
|
|||
tags = addons
|
||||
[test_TelemetrySession.js]
|
||||
tags = addons
|
||||
[test_TelemetrySession_activeTicks.js]
|
||||
[test_ThreadHangStats.js]
|
||||
run-sequentially = Bug 1046307, test can fail intermittently when CPU load is high
|
||||
[test_TelemetrySend.js]
|
||||
|
|
|
@ -1,403 +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/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"SessionRecorder",
|
||||
];
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Preferences.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
// We automatically prune sessions older than this.
|
||||
const MAX_SESSION_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days.
|
||||
const STARTUP_RETRY_INTERVAL_MS = 5000;
|
||||
|
||||
// Wait up to 5 minutes for startup measurements before giving up.
|
||||
const MAX_STARTUP_TRIES = 300000 / STARTUP_RETRY_INTERVAL_MS;
|
||||
|
||||
const LOGGER_NAME = "Toolkit.Telemetry";
|
||||
const LOGGER_PREFIX = "SessionRecorder::";
|
||||
|
||||
/**
|
||||
* Records information about browser sessions.
|
||||
*
|
||||
* This serves as an interface to both current session information as
|
||||
* well as a history of previous sessions.
|
||||
*
|
||||
* Typically only one instance of this will be installed in an
|
||||
* application. It is typically managed by an XPCOM service. The
|
||||
* instance is instantiated at application start; onStartup is called
|
||||
* once the profile is installed; onShutdown is called during shutdown.
|
||||
*
|
||||
* We currently record state in preferences. However, this should be
|
||||
* invisible to external consumers. We could easily swap in a different
|
||||
* storage mechanism if desired.
|
||||
*
|
||||
* Please note the different semantics for storing times and dates in
|
||||
* preferences. Full dates (notably the session start time) are stored
|
||||
* as strings because preferences have a 32-bit limit on integer values
|
||||
* and milliseconds since UNIX epoch would overflow. Many times are
|
||||
* stored as integer offsets from the session start time because they
|
||||
* should not overflow 32 bits.
|
||||
*
|
||||
* Since this records history of all sessions, there is a possibility
|
||||
* for unbounded data aggregation. This is curtailed through:
|
||||
*
|
||||
* 1) An "idle-daily" observer which delete sessions older than
|
||||
* MAX_SESSION_AGE_MS.
|
||||
* 2) The creator of this instance explicitly calling
|
||||
* `pruneOldSessions`.
|
||||
*
|
||||
* @param branch
|
||||
* (string) Preferences branch on which to record state.
|
||||
*/
|
||||
this.SessionRecorder = function(branch) {
|
||||
if (!branch) {
|
||||
throw new Error("branch argument must be defined.");
|
||||
}
|
||||
|
||||
if (!branch.endsWith(".")) {
|
||||
throw new Error("branch argument must end with '.': " + branch);
|
||||
}
|
||||
|
||||
this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, LOGGER_PREFIX);
|
||||
|
||||
this._prefs = new Preferences(branch);
|
||||
this._lastActivityWasInactive = false;
|
||||
this._activeTicks = 0;
|
||||
this.fineTotalTime = 0;
|
||||
this._started = false;
|
||||
this._timer = null;
|
||||
this._startupFieldTries = 0;
|
||||
|
||||
this._os = Cc["@mozilla.org/observer-service;1"]
|
||||
.getService(Ci.nsIObserverService);
|
||||
|
||||
};
|
||||
|
||||
SessionRecorder.prototype = Object.freeze({
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
|
||||
|
||||
STARTUP_RETRY_INTERVAL_MS,
|
||||
|
||||
get _currentIndex() {
|
||||
return this._prefs.get("currentIndex", 0);
|
||||
},
|
||||
|
||||
set _currentIndex(value) {
|
||||
this._prefs.set("currentIndex", value);
|
||||
},
|
||||
|
||||
get _prunedIndex() {
|
||||
return this._prefs.get("prunedIndex", 0);
|
||||
},
|
||||
|
||||
set _prunedIndex(value) {
|
||||
this._prefs.set("prunedIndex", value);
|
||||
},
|
||||
|
||||
get startDate() {
|
||||
return CommonUtils.getDatePref(this._prefs, "current.startTime");
|
||||
},
|
||||
|
||||
set _startDate(value) {
|
||||
CommonUtils.setDatePref(this._prefs, "current.startTime", value);
|
||||
},
|
||||
|
||||
get activeTicks() {
|
||||
return this._prefs.get("current.activeTicks", 0);
|
||||
},
|
||||
|
||||
incrementActiveTicks() {
|
||||
this._prefs.set("current.activeTicks", ++this._activeTicks);
|
||||
},
|
||||
|
||||
/**
|
||||
* Total time of this session in integer seconds.
|
||||
*
|
||||
* See also fineTotalTime for the time in milliseconds.
|
||||
*/
|
||||
get totalTime() {
|
||||
return this._prefs.get("current.totalTime", 0);
|
||||
},
|
||||
|
||||
updateTotalTime() {
|
||||
// We store millisecond precision internally to prevent drift from
|
||||
// repeated rounding.
|
||||
this.fineTotalTime = Date.now() - this.startDate;
|
||||
this._prefs.set("current.totalTime", Math.floor(this.fineTotalTime / 1000));
|
||||
},
|
||||
|
||||
get main() {
|
||||
return this._prefs.get("current.main", -1);
|
||||
},
|
||||
|
||||
set _main(value) {
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error("main time must be an integer.");
|
||||
}
|
||||
|
||||
this._prefs.set("current.main", value);
|
||||
},
|
||||
|
||||
get firstPaint() {
|
||||
return this._prefs.get("current.firstPaint", -1);
|
||||
},
|
||||
|
||||
set _firstPaint(value) {
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error("firstPaint must be an integer.");
|
||||
}
|
||||
|
||||
this._prefs.set("current.firstPaint", value);
|
||||
},
|
||||
|
||||
get sessionRestored() {
|
||||
return this._prefs.get("current.sessionRestored", -1);
|
||||
},
|
||||
|
||||
set _sessionRestored(value) {
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error("sessionRestored must be an integer.");
|
||||
}
|
||||
|
||||
this._prefs.set("current.sessionRestored", value);
|
||||
},
|
||||
|
||||
getPreviousSessions() {
|
||||
let result = {};
|
||||
|
||||
for (let i = this._prunedIndex; i < this._currentIndex; i++) {
|
||||
let s = this.getPreviousSession(i);
|
||||
if (!s) {
|
||||
continue;
|
||||
}
|
||||
|
||||
result[i] = s;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
getPreviousSession(index) {
|
||||
return this._deserialize(this._prefs.get("previous." + index));
|
||||
},
|
||||
|
||||
/**
|
||||
* Prunes old, completed sessions that started earlier than the
|
||||
* specified date.
|
||||
*/
|
||||
pruneOldSessions(date) {
|
||||
for (let i = this._prunedIndex; i < this._currentIndex; i++) {
|
||||
let s = this.getPreviousSession(i);
|
||||
if (!s) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (s.startDate >= date) {
|
||||
continue;
|
||||
}
|
||||
|
||||
this._log.debug("Pruning session #" + i + ".");
|
||||
this._prefs.reset("previous." + i);
|
||||
this._prunedIndex = i;
|
||||
}
|
||||
},
|
||||
|
||||
recordStartupFields() {
|
||||
let si = this._getStartupInfo();
|
||||
|
||||
if (!si.process) {
|
||||
throw new Error("Startup info not available.");
|
||||
}
|
||||
|
||||
let missing = false;
|
||||
|
||||
for (let field of ["main", "firstPaint", "sessionRestored"]) {
|
||||
if (!(field in si)) {
|
||||
this._log.debug("Missing startup field: " + field);
|
||||
missing = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
this["_" + field] = si[field].getTime() - si.process.getTime();
|
||||
}
|
||||
|
||||
if (!missing || this._startupFieldTries > MAX_STARTUP_TRIES) {
|
||||
this._clearStartupTimer();
|
||||
return;
|
||||
}
|
||||
|
||||
// If we have missing fields, install a timer and keep waiting for
|
||||
// data.
|
||||
this._startupFieldTries++;
|
||||
|
||||
if (!this._timer) {
|
||||
this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||
this._timer.initWithCallback({
|
||||
notify: this.recordStartupFields.bind(this),
|
||||
}, this.STARTUP_RETRY_INTERVAL_MS, this._timer.TYPE_REPEATING_SLACK);
|
||||
}
|
||||
},
|
||||
|
||||
_clearStartupTimer() {
|
||||
if (this._timer) {
|
||||
this._timer.cancel();
|
||||
delete this._timer;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform functionality on application startup.
|
||||
*
|
||||
* This is typically called in a "profile-do-change" handler.
|
||||
*/
|
||||
onStartup() {
|
||||
if (this._started) {
|
||||
throw new Error("onStartup has already been called.");
|
||||
}
|
||||
|
||||
let si = this._getStartupInfo();
|
||||
if (!si.process) {
|
||||
throw new Error("Process information not available. Misconfigured app?");
|
||||
}
|
||||
|
||||
this._started = true;
|
||||
|
||||
this._os.addObserver(this, "profile-before-change");
|
||||
this._os.addObserver(this, "user-interaction-active");
|
||||
this._os.addObserver(this, "user-interaction-inactive");
|
||||
this._os.addObserver(this, "idle-daily");
|
||||
|
||||
// This has the side-effect of clearing current session state.
|
||||
this._moveCurrentToPrevious();
|
||||
|
||||
this._startDate = si.process;
|
||||
this._prefs.set("current.activeTicks", 0);
|
||||
this.updateTotalTime();
|
||||
|
||||
this.recordStartupFields();
|
||||
},
|
||||
|
||||
/**
|
||||
* Record application activity.
|
||||
*/
|
||||
onActivity(active) {
|
||||
let updateActive = active && !this._lastActivityWasInactive;
|
||||
this._lastActivityWasInactive = !active;
|
||||
|
||||
this.updateTotalTime();
|
||||
|
||||
if (updateActive) {
|
||||
this.incrementActiveTicks();
|
||||
}
|
||||
},
|
||||
|
||||
onShutdown() {
|
||||
this._log.info("Recording clean session shutdown.");
|
||||
this._prefs.set("current.clean", true);
|
||||
this.updateTotalTime();
|
||||
this._clearStartupTimer();
|
||||
|
||||
this._os.removeObserver(this, "profile-before-change");
|
||||
this._os.removeObserver(this, "user-interaction-active");
|
||||
this._os.removeObserver(this, "user-interaction-inactive");
|
||||
this._os.removeObserver(this, "idle-daily");
|
||||
},
|
||||
|
||||
_CURRENT_PREFS: [
|
||||
"current.startTime",
|
||||
"current.activeTicks",
|
||||
"current.totalTime",
|
||||
"current.main",
|
||||
"current.firstPaint",
|
||||
"current.sessionRestored",
|
||||
"current.clean",
|
||||
],
|
||||
|
||||
// This is meant to be called only during onStartup().
|
||||
_moveCurrentToPrevious() {
|
||||
try {
|
||||
if (!this.startDate.getTime()) {
|
||||
this._log.info("No previous session. Is this first app run?");
|
||||
return;
|
||||
}
|
||||
|
||||
let clean = this._prefs.get("current.clean", false);
|
||||
|
||||
let count = this._currentIndex++;
|
||||
let obj = {
|
||||
s: this.startDate.getTime(),
|
||||
a: this.activeTicks,
|
||||
t: this.totalTime,
|
||||
c: clean,
|
||||
m: this.main,
|
||||
fp: this.firstPaint,
|
||||
sr: this.sessionRestored,
|
||||
};
|
||||
|
||||
this._log.debug("Recording last sessions as #" + count + ".");
|
||||
this._prefs.set("previous." + count, JSON.stringify(obj));
|
||||
} catch (ex) {
|
||||
this._log.warn("Exception when migrating last session", ex);
|
||||
} finally {
|
||||
this._log.debug("Resetting prefs from last session.");
|
||||
for (let pref of this._CURRENT_PREFS) {
|
||||
this._prefs.reset(pref);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_deserialize(s) {
|
||||
let o;
|
||||
try {
|
||||
o = JSON.parse(s);
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
startDate: new Date(o.s),
|
||||
activeTicks: o.a,
|
||||
totalTime: o.t,
|
||||
clean: !!o.c,
|
||||
main: o.m,
|
||||
firstPaint: o.fp,
|
||||
sessionRestored: o.sr,
|
||||
};
|
||||
},
|
||||
|
||||
// Implemented as a function to allow for monkeypatching in tests.
|
||||
_getStartupInfo() {
|
||||
return Cc["@mozilla.org/toolkit/app-startup;1"]
|
||||
.getService(Ci.nsIAppStartup)
|
||||
.getStartupInfo();
|
||||
},
|
||||
|
||||
observe(subject, topic, data) {
|
||||
switch (topic) {
|
||||
case "profile-before-change":
|
||||
this.onShutdown();
|
||||
break;
|
||||
|
||||
case "user-interaction-active":
|
||||
this.onActivity(true);
|
||||
break;
|
||||
|
||||
case "user-interaction-inactive":
|
||||
this.onActivity(false);
|
||||
break;
|
||||
|
||||
case "idle-daily":
|
||||
this.pruneOldSessions(new Date(Date.now() - MAX_SESSION_AGE_MS));
|
||||
break;
|
||||
}
|
||||
},
|
||||
});
|
|
@ -49,9 +49,6 @@ with Files('tests/xpcshell/test_UpdateUtils*.js'):
|
|||
with Files('tests/xpcshell/test_client_id.js'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Telemetry')
|
||||
|
||||
with Files('tests/xpcshell/test_session_recorder.js'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Telemetry')
|
||||
|
||||
with Files('AsyncPrefs.jsm'):
|
||||
BUG_COMPONENT = ('Core', 'Security: Process Sandboxing')
|
||||
|
||||
|
@ -142,9 +139,6 @@ with Files('RemoteWebProgress.jsm'):
|
|||
with Files('ResponsivenessMonitor.jsm'):
|
||||
BUG_COMPONENT = ('Firefox', 'Migration')
|
||||
|
||||
with Files('SessionRecorder.jsm'):
|
||||
BUG_COMPONENT = ('Toolkit', 'Telemetry')
|
||||
|
||||
with Files('ShortcutUtils.jsm'):
|
||||
BUG_COMPONENT = ('Firefox', 'Toolbars and Customization')
|
||||
|
||||
|
@ -241,7 +235,6 @@ EXTRA_JS_MODULES += [
|
|||
'SelectParentHelper.jsm',
|
||||
'ServiceRequest.jsm',
|
||||
'Services.jsm',
|
||||
'SessionRecorder.jsm',
|
||||
'sessionstore/FormData.jsm',
|
||||
'sessionstore/ScrollPosition.jsm',
|
||||
'sessionstore/XPathGenerator.jsm',
|
||||
|
|
|
@ -1,305 +0,0 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
var {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
Cu.import("resource://gre/modules/SessionRecorder.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://services-common/utils.js");
|
||||
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
function monkeypatchStartupInfo(recorder, start = new Date(), offset = 500) {
|
||||
Object.defineProperty(recorder, "_getStartupInfo", {
|
||||
value: function _getStartupInfo() {
|
||||
return {
|
||||
process: start,
|
||||
main: new Date(start.getTime() + offset),
|
||||
firstPaint: new Date(start.getTime() + 2 * offset),
|
||||
sessionRestored: new Date(start.getTime() + 3 * offset),
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sleep(wait) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
CommonUtils.namedTimer(function onTimer() {
|
||||
deferred.resolve();
|
||||
}, wait, deferred.promise, "_sleepTimer");
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
function getRecorder(name, start, offset) {
|
||||
let recorder = new SessionRecorder("testing." + name + ".");
|
||||
monkeypatchStartupInfo(recorder, start, offset);
|
||||
|
||||
return recorder;
|
||||
}
|
||||
|
||||
add_test(function test_basic() {
|
||||
let recorder = getRecorder("basic");
|
||||
recorder.onStartup();
|
||||
recorder.onShutdown();
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function* test_current_properties() {
|
||||
let now = new Date();
|
||||
let recorder = getRecorder("current_properties", now);
|
||||
yield sleep(25);
|
||||
recorder.onStartup();
|
||||
|
||||
do_check_eq(recorder.startDate.getTime(), now.getTime());
|
||||
do_check_eq(recorder.activeTicks, 0);
|
||||
do_check_true(recorder.fineTotalTime > 0);
|
||||
do_check_eq(recorder.main, 500);
|
||||
do_check_eq(recorder.firstPaint, 1000);
|
||||
do_check_eq(recorder.sessionRestored, 1500);
|
||||
|
||||
recorder.incrementActiveTicks();
|
||||
do_check_eq(recorder.activeTicks, 1);
|
||||
|
||||
recorder._startDate = new Date(Date.now() - 1000);
|
||||
recorder.updateTotalTime();
|
||||
do_check_eq(recorder.totalTime, 1);
|
||||
|
||||
recorder.onShutdown();
|
||||
});
|
||||
|
||||
// If startup info isn't present yet, we should install a timer and get
|
||||
// it eventually.
|
||||
add_task(function* test_current_availability() {
|
||||
let recorder = new SessionRecorder("testing.current_availability.");
|
||||
let now = new Date();
|
||||
|
||||
Object.defineProperty(recorder, "_getStartupInfo", {
|
||||
value: function _getStartupInfo() {
|
||||
return {
|
||||
process: now,
|
||||
main: new Date(now.getTime() + 500),
|
||||
firstPaint: new Date(now.getTime() + 1000),
|
||||
};
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
|
||||
Object.defineProperty(recorder, "STARTUP_RETRY_INTERVAL_MS", {
|
||||
value: 100,
|
||||
});
|
||||
|
||||
let oldRecord = recorder.recordStartupFields;
|
||||
let recordCount = 0;
|
||||
|
||||
Object.defineProperty(recorder, "recordStartupFields", {
|
||||
value() {
|
||||
recordCount++;
|
||||
return oldRecord.call(recorder);
|
||||
}
|
||||
});
|
||||
|
||||
do_check_null(recorder._timer);
|
||||
recorder.onStartup();
|
||||
do_check_eq(recordCount, 1);
|
||||
do_check_eq(recorder.sessionRestored, -1);
|
||||
do_check_neq(recorder._timer, null);
|
||||
|
||||
yield sleep(125);
|
||||
do_check_eq(recordCount, 2);
|
||||
yield sleep(100);
|
||||
do_check_eq(recordCount, 3);
|
||||
do_check_eq(recorder.sessionRestored, -1);
|
||||
|
||||
monkeypatchStartupInfo(recorder, now);
|
||||
yield sleep(100);
|
||||
do_check_eq(recordCount, 4);
|
||||
do_check_eq(recorder.sessionRestored, 1500);
|
||||
|
||||
// The timer should be removed and we should not fire again.
|
||||
do_check_null(recorder._timer);
|
||||
yield sleep(100);
|
||||
do_check_eq(recordCount, 4);
|
||||
|
||||
recorder.onShutdown();
|
||||
});
|
||||
|
||||
add_test(function test_timer_clear_on_shutdown() {
|
||||
let recorder = new SessionRecorder("testing.timer_clear_on_shutdown.");
|
||||
let now = new Date();
|
||||
|
||||
Object.defineProperty(recorder, "_getStartupInfo", {
|
||||
value: function _getStartupInfo() {
|
||||
return {
|
||||
process: now,
|
||||
main: new Date(now.getTime() + 500),
|
||||
firstPaint: new Date(now.getTime() + 1000),
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
do_check_null(recorder._timer);
|
||||
recorder.onStartup();
|
||||
do_check_neq(recorder._timer, null);
|
||||
|
||||
recorder.onShutdown();
|
||||
do_check_null(recorder._timer);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_task(function* test_previous_clean() {
|
||||
let now = new Date();
|
||||
let recorder = getRecorder("previous_clean", now);
|
||||
yield sleep(25);
|
||||
recorder.onStartup();
|
||||
|
||||
recorder.incrementActiveTicks();
|
||||
recorder.incrementActiveTicks();
|
||||
|
||||
yield sleep(25);
|
||||
recorder.onShutdown();
|
||||
|
||||
let total = recorder.totalTime;
|
||||
|
||||
yield sleep(25);
|
||||
let now2 = new Date();
|
||||
let recorder2 = getRecorder("previous_clean", now2, 100);
|
||||
yield sleep(25);
|
||||
recorder2.onStartup();
|
||||
|
||||
do_check_eq(recorder2.startDate.getTime(), now2.getTime());
|
||||
do_check_eq(recorder2.main, 100);
|
||||
do_check_eq(recorder2.firstPaint, 200);
|
||||
do_check_eq(recorder2.sessionRestored, 300);
|
||||
|
||||
let sessions = recorder2.getPreviousSessions();
|
||||
do_check_eq(Object.keys(sessions).length, 1);
|
||||
do_check_true(0 in sessions);
|
||||
let session = sessions[0];
|
||||
do_check_true(session.clean);
|
||||
do_check_eq(session.startDate.getTime(), now.getTime());
|
||||
do_check_eq(session.main, 500);
|
||||
do_check_eq(session.firstPaint, 1000);
|
||||
do_check_eq(session.sessionRestored, 1500);
|
||||
do_check_eq(session.totalTime, total);
|
||||
do_check_eq(session.activeTicks, 2);
|
||||
|
||||
recorder2.onShutdown();
|
||||
});
|
||||
|
||||
add_task(function* test_previous_abort() {
|
||||
let now = new Date();
|
||||
let recorder = getRecorder("previous_abort", now);
|
||||
yield sleep(25);
|
||||
recorder.onStartup();
|
||||
recorder.incrementActiveTicks();
|
||||
yield sleep(25);
|
||||
let total = recorder.totalTime;
|
||||
yield sleep(25);
|
||||
|
||||
let now2 = new Date();
|
||||
let recorder2 = getRecorder("previous_abort", now2);
|
||||
yield sleep(25);
|
||||
recorder2.onStartup();
|
||||
|
||||
let sessions = recorder2.getPreviousSessions();
|
||||
do_check_eq(Object.keys(sessions).length, 1);
|
||||
do_check_true(0 in sessions);
|
||||
let session = sessions[0];
|
||||
do_check_false(session.clean);
|
||||
do_check_eq(session.totalTime, total);
|
||||
|
||||
recorder.onShutdown();
|
||||
recorder2.onShutdown();
|
||||
});
|
||||
|
||||
add_task(function* test_multiple_sessions() {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
let recorder = getRecorder("multiple_sessions");
|
||||
yield sleep(25);
|
||||
recorder.onStartup();
|
||||
for (let j = 0; j < i; j++) {
|
||||
recorder.incrementActiveTicks();
|
||||
}
|
||||
yield sleep(25);
|
||||
recorder.onShutdown();
|
||||
yield sleep(25);
|
||||
}
|
||||
|
||||
let recorder = getRecorder("multiple_sessions");
|
||||
recorder.onStartup();
|
||||
|
||||
let sessions = recorder.getPreviousSessions();
|
||||
do_check_eq(Object.keys(sessions).length, 10);
|
||||
|
||||
for (let [i, session] of Object.entries(sessions)) {
|
||||
do_check_eq(session.activeTicks, i);
|
||||
|
||||
if (i > 0) {
|
||||
do_check_true(session.startDate.getTime() > sessions[i - 1].startDate.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
// #6 is preserved since >=.
|
||||
let threshold = sessions[6].startDate;
|
||||
recorder.pruneOldSessions(threshold);
|
||||
|
||||
sessions = recorder.getPreviousSessions();
|
||||
do_check_eq(Object.keys(sessions).length, 4);
|
||||
|
||||
recorder.pruneOldSessions(threshold);
|
||||
sessions = recorder.getPreviousSessions();
|
||||
do_check_eq(Object.keys(sessions).length, 4);
|
||||
do_check_eq(recorder._prunedIndex, 5);
|
||||
|
||||
recorder.onShutdown();
|
||||
});
|
||||
|
||||
add_task(function* test_record_activity() {
|
||||
let recorder = getRecorder("record_activity");
|
||||
yield sleep(25);
|
||||
recorder.onStartup();
|
||||
let total = recorder.totalTime;
|
||||
yield sleep(25);
|
||||
|
||||
for (let i = 0; i < 3; i++) {
|
||||
Services.obs.notifyObservers(null, "user-interaction-active");
|
||||
yield sleep(25);
|
||||
do_check_true(recorder.fineTotalTime > total);
|
||||
total = recorder.fineTotalTime;
|
||||
}
|
||||
|
||||
do_check_eq(recorder.activeTicks, 3);
|
||||
|
||||
// Now send inactive. We should increment total time but not active.
|
||||
Services.obs.notifyObservers(null, "user-interaction-inactive");
|
||||
do_check_eq(recorder.activeTicks, 3);
|
||||
do_check_true(recorder.fineTotalTime > total);
|
||||
total = recorder.fineTotalTime;
|
||||
yield sleep(25);
|
||||
|
||||
// If we send active again, this should be counted as inactive.
|
||||
Services.obs.notifyObservers(null, "user-interaction-active");
|
||||
do_check_eq(recorder.activeTicks, 3);
|
||||
do_check_true(recorder.fineTotalTime > total);
|
||||
total = recorder.fineTotalTime;
|
||||
yield sleep(25);
|
||||
|
||||
// If we send active again, this should be counted as active.
|
||||
Services.obs.notifyObservers(null, "user-interaction-active");
|
||||
do_check_eq(recorder.activeTicks, 4);
|
||||
|
||||
Services.obs.notifyObservers(null, "user-interaction-active");
|
||||
do_check_eq(recorder.activeTicks, 5);
|
||||
|
||||
recorder.onShutdown();
|
||||
});
|
|
@ -54,8 +54,6 @@ skip-if = toolkit == 'android'
|
|||
skip-if = toolkit == 'android'
|
||||
[test_Services.js]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_session_recorder.js]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_sqlite.js]
|
||||
skip-if = toolkit == 'android'
|
||||
[test_sqlite_shutdown.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче