1403 lines
43 KiB
JavaScript
1403 lines
43 KiB
JavaScript
/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
|
|
/* 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/. */
|
|
|
|
import { Log } from "resource://gre/modules/Log.sys.mjs";
|
|
import { TelemetryUtils } from "resource://gre/modules/TelemetryUtils.sys.mjs";
|
|
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs",
|
|
TelemetryController: "resource://gre/modules/TelemetryController.sys.mjs",
|
|
TelemetryEnvironment: "resource://gre/modules/TelemetryEnvironment.sys.mjs",
|
|
TelemetryReportingPolicy:
|
|
"resource://gre/modules/TelemetryReportingPolicy.sys.mjs",
|
|
TelemetryScheduler: "resource://gre/modules/TelemetryScheduler.sys.mjs",
|
|
TelemetryStorage: "resource://gre/modules/TelemetryStorage.sys.mjs",
|
|
});
|
|
|
|
const Utils = TelemetryUtils;
|
|
|
|
// When modifying the payload in incompatible ways, please bump this version number
|
|
const PAYLOAD_VERSION = 4;
|
|
const PING_TYPE_MAIN = "main";
|
|
const PING_TYPE_SAVED_SESSION = "saved-session";
|
|
|
|
const REASON_ABORTED_SESSION = "aborted-session";
|
|
const REASON_DAILY = "daily";
|
|
const REASON_SAVED_SESSION = "saved-session";
|
|
const REASON_GATHER_PAYLOAD = "gather-payload";
|
|
const REASON_TEST_PING = "test-ping";
|
|
const REASON_ENVIRONMENT_CHANGE = "environment-change";
|
|
const REASON_SHUTDOWN = "shutdown";
|
|
|
|
const ENVIRONMENT_CHANGE_LISTENER = "TelemetrySession::onEnvironmentChange";
|
|
|
|
const MIN_SUBSESSION_LENGTH_MS =
|
|
Services.prefs.getIntPref("toolkit.telemetry.minSubsessionLength", 5 * 60) *
|
|
1000;
|
|
|
|
const LOGGER_NAME = "Toolkit.Telemetry";
|
|
const LOGGER_PREFIX =
|
|
"TelemetrySession" + (Utils.isContentProcess ? "#content::" : "::");
|
|
|
|
// Whether the FHR/Telemetry unification features are enabled.
|
|
// Changing this pref requires a restart.
|
|
const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(
|
|
TelemetryUtils.Preferences.Unified,
|
|
false
|
|
);
|
|
|
|
var gWasDebuggerAttached = false;
|
|
|
|
function generateUUID() {
|
|
let str = Services.uuid.generateUUID().toString();
|
|
// strip {}
|
|
return str.substring(1, str.length - 1);
|
|
}
|
|
|
|
/**
|
|
* This is a policy object used to override behavior for testing.
|
|
*/
|
|
export var Policy = {
|
|
now: () => new Date(),
|
|
monotonicNow: Utils.monotonicNow,
|
|
generateSessionUUID: () => generateUUID(),
|
|
generateSubsessionUUID: () => generateUUID(),
|
|
};
|
|
|
|
/**
|
|
* Get the ping type based on the payload.
|
|
* @param {Object} aPayload The ping payload.
|
|
* @return {String} A string representing the ping type.
|
|
*/
|
|
function getPingType(aPayload) {
|
|
// To remain consistent with server-side ping handling, set "saved-session" as the ping
|
|
// type for "saved-session" payload reasons.
|
|
if (aPayload.info.reason == REASON_SAVED_SESSION) {
|
|
return PING_TYPE_SAVED_SESSION;
|
|
}
|
|
|
|
return PING_TYPE_MAIN;
|
|
}
|
|
|
|
/**
|
|
* Annotate the current session ID with the crash reporter to map potential
|
|
* crash pings with the related main ping.
|
|
*/
|
|
function annotateCrashReport(sessionId) {
|
|
try {
|
|
Services.appinfo.annotateCrashReport("TelemetrySessionId", sessionId);
|
|
} catch (e) {
|
|
// Ignore errors when crash reporting is disabled
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read current process I/O counters.
|
|
*/
|
|
var processInfo = {
|
|
_initialized: false,
|
|
_IO_COUNTERS: null,
|
|
_kernel32: null,
|
|
_GetProcessIoCounters: null,
|
|
_GetCurrentProcess: null,
|
|
getCounters() {
|
|
let isWindows = "@mozilla.org/windows-registry-key;1" in Cc;
|
|
if (isWindows) {
|
|
return this.getCounters_Windows();
|
|
}
|
|
return null;
|
|
},
|
|
getCounters_Windows() {
|
|
if (!this._initialized) {
|
|
var { ctypes } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ctypes.sys.mjs"
|
|
);
|
|
this._IO_COUNTERS = new ctypes.StructType("IO_COUNTERS", [
|
|
{ readOps: ctypes.unsigned_long_long },
|
|
{ writeOps: ctypes.unsigned_long_long },
|
|
{ otherOps: ctypes.unsigned_long_long },
|
|
{ readBytes: ctypes.unsigned_long_long },
|
|
{ writeBytes: ctypes.unsigned_long_long },
|
|
{ otherBytes: ctypes.unsigned_long_long },
|
|
]);
|
|
try {
|
|
this._kernel32 = ctypes.open("Kernel32.dll");
|
|
this._GetProcessIoCounters = this._kernel32.declare(
|
|
"GetProcessIoCounters",
|
|
ctypes.winapi_abi,
|
|
ctypes.bool, // return
|
|
ctypes.voidptr_t, // hProcess
|
|
this._IO_COUNTERS.ptr
|
|
); // lpIoCounters
|
|
this._GetCurrentProcess = this._kernel32.declare(
|
|
"GetCurrentProcess",
|
|
ctypes.winapi_abi,
|
|
ctypes.voidptr_t
|
|
); // return
|
|
this._initialized = true;
|
|
} catch (err) {
|
|
return null;
|
|
}
|
|
}
|
|
let io = new this._IO_COUNTERS();
|
|
if (!this._GetProcessIoCounters(this._GetCurrentProcess(), io.address())) {
|
|
return null;
|
|
}
|
|
return [parseInt(io.readBytes), parseInt(io.writeBytes)];
|
|
},
|
|
};
|
|
|
|
export var TelemetrySession = Object.freeze({
|
|
/**
|
|
* Send a ping to a test server. Used only for testing.
|
|
*/
|
|
testPing() {
|
|
return Impl.testPing();
|
|
},
|
|
/**
|
|
* Returns the current telemetry payload.
|
|
* @param reason Optional, the reason to trigger the payload.
|
|
* @param clearSubsession Optional, whether to clear subsession specific data.
|
|
* @returns Object
|
|
*/
|
|
getPayload(reason, clearSubsession = false) {
|
|
return Impl.getPayload(reason, clearSubsession);
|
|
},
|
|
/**
|
|
* Save the session state to a pending file.
|
|
* Used only for testing purposes.
|
|
*/
|
|
testSavePendingPing() {
|
|
return Impl.testSavePendingPing();
|
|
},
|
|
/**
|
|
* Collect and store information about startup.
|
|
*/
|
|
gatherStartup() {
|
|
return Impl.gatherStartup();
|
|
},
|
|
/**
|
|
* Inform the ping which AddOns are installed.
|
|
*
|
|
* @param aAddOns - The AddOns.
|
|
*/
|
|
setAddOns(aAddOns) {
|
|
return Impl.setAddOns(aAddOns);
|
|
},
|
|
/**
|
|
* Descriptive metadata
|
|
*
|
|
* @param reason
|
|
* The reason for the telemetry ping, this will be included in the
|
|
* returned metadata,
|
|
* @return The metadata as a JS object
|
|
*/
|
|
getMetadata(reason) {
|
|
return Impl.getMetadata(reason);
|
|
},
|
|
|
|
/**
|
|
* Reset the subsession and profile subsession counter.
|
|
* This should only be called when the profile should be considered completely new,
|
|
* e.g. after opting out of sending Telemetry
|
|
*/
|
|
resetSubsessionCounter() {
|
|
Impl._subsessionCounter = 0;
|
|
Impl._profileSubsessionCounter = 0;
|
|
},
|
|
|
|
/**
|
|
* Used only for testing purposes.
|
|
*/
|
|
testReset() {
|
|
Impl._newProfilePingSent = false;
|
|
Impl._sessionId = null;
|
|
Impl._subsessionId = null;
|
|
Impl._previousSessionId = null;
|
|
Impl._previousSubsessionId = null;
|
|
Impl._subsessionCounter = 0;
|
|
Impl._profileSubsessionCounter = 0;
|
|
Impl._subsessionStartActiveTicks = 0;
|
|
Impl._sessionActiveTicks = 0;
|
|
Impl._isUserActive = true;
|
|
Impl._subsessionStartTimeMonotonic = 0;
|
|
Impl._lastEnvironmentChangeDate = Policy.monotonicNow();
|
|
this.testUninstall();
|
|
},
|
|
/**
|
|
* Triggers shutdown of the module.
|
|
*/
|
|
shutdown() {
|
|
return Impl.shutdownChromeProcess();
|
|
},
|
|
/**
|
|
* Used only for testing purposes.
|
|
*/
|
|
testUninstall() {
|
|
try {
|
|
Impl.uninstall();
|
|
} catch (ex) {
|
|
// Ignore errors
|
|
}
|
|
},
|
|
/**
|
|
* Lightweight init function, called as soon as Firefox starts.
|
|
*/
|
|
earlyInit(aTesting = false) {
|
|
return Impl.earlyInit(aTesting);
|
|
},
|
|
/**
|
|
* Does the "heavy" Telemetry initialization later on, so we
|
|
* don't impact startup performance.
|
|
* @return {Promise} Resolved when the initialization completes.
|
|
*/
|
|
delayedInit() {
|
|
return Impl.delayedInit();
|
|
},
|
|
/**
|
|
* Send a notification.
|
|
*/
|
|
observe(aSubject, aTopic, aData) {
|
|
return Impl.observe(aSubject, aTopic, aData);
|
|
},
|
|
/**
|
|
* Marks the "new-profile" ping as sent in the telemetry state file.
|
|
* @return {Promise} A promise resolved when the new telemetry state is saved to disk.
|
|
*/
|
|
markNewProfilePingSent() {
|
|
return Impl.markNewProfilePingSent();
|
|
},
|
|
/**
|
|
* Returns if the "new-profile" ping has ever been sent for this profile.
|
|
* Please note that the returned value is trustworthy only after the delayed setup.
|
|
*
|
|
* @return {Boolean} True if the new profile ping was sent on this profile,
|
|
* false otherwise.
|
|
*/
|
|
get newProfilePingSent() {
|
|
return Impl._newProfilePingSent;
|
|
},
|
|
|
|
saveAbortedSessionPing(aProvidedPayload) {
|
|
return Impl._saveAbortedSessionPing(aProvidedPayload);
|
|
},
|
|
|
|
sendDailyPing() {
|
|
return Impl._sendDailyPing();
|
|
},
|
|
|
|
testOnEnvironmentChange(...args) {
|
|
return Impl._onEnvironmentChange(...args);
|
|
},
|
|
});
|
|
|
|
var Impl = {
|
|
_initialized: false,
|
|
_logger: null,
|
|
_slowSQLStartup: {},
|
|
// 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.
|
|
_previousBuildId: null,
|
|
// Unique id that identifies this session so the server can cope with duplicate
|
|
// submissions, orphaning and other oddities. The id is shared across subsessions.
|
|
_sessionId: null,
|
|
// Random subsession id.
|
|
_subsessionId: null,
|
|
// Session id of the previous session, null on first run.
|
|
_previousSessionId: null,
|
|
// Subsession id of the previous subsession (even if it was in a different session),
|
|
// null on first run.
|
|
_previousSubsessionId: null,
|
|
// The running no. of subsessions since the start of the browser session
|
|
_subsessionCounter: 0,
|
|
// The running no. of all subsessions for the whole profile life time
|
|
_profileSubsessionCounter: 0,
|
|
// Date of the last session split
|
|
_subsessionStartDate: null,
|
|
// Start time of the current subsession using a monotonic clock for the subsession
|
|
// length measurements.
|
|
_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,
|
|
_testing: false,
|
|
// An accumulator of total memory across all processes. Only valid once the final child reports.
|
|
_lastEnvironmentChangeDate: 0,
|
|
// We save whether the "new-profile" ping was sent yet, to
|
|
// survive profile refresh and migrations.
|
|
_newProfilePingSent: false,
|
|
// Keep track of the active observers
|
|
_observedTopics: new Set(),
|
|
|
|
addObserver(aTopic) {
|
|
Services.obs.addObserver(this, aTopic);
|
|
this._observedTopics.add(aTopic);
|
|
},
|
|
|
|
removeObserver(aTopic) {
|
|
Services.obs.removeObserver(this, aTopic);
|
|
this._observedTopics.delete(aTopic);
|
|
},
|
|
|
|
get _log() {
|
|
if (!this._logger) {
|
|
this._logger = Log.repository.getLoggerWithMessagePrefix(
|
|
LOGGER_NAME,
|
|
LOGGER_PREFIX
|
|
);
|
|
}
|
|
return this._logger;
|
|
},
|
|
|
|
/**
|
|
* Gets a series of simple measurements (counters). At the moment, this
|
|
* only returns startup data from nsIAppStartup.getStartupInfo().
|
|
* @param {Boolean} isSubsession True if this is a subsession, false otherwise.
|
|
* @param {Boolean} clearSubsession True if a new subsession is being started, false otherwise.
|
|
*
|
|
* @return simple measurements as a dictionary.
|
|
*/
|
|
getSimpleMeasurements: function getSimpleMeasurements(
|
|
forSavedSession,
|
|
isSubsession,
|
|
clearSubsession
|
|
) {
|
|
let si = Services.startup.getStartupInfo();
|
|
|
|
// Measurements common to chrome and content processes.
|
|
let elapsedTime = Date.now() - si.process;
|
|
var ret = {
|
|
totalTime: Math.round(elapsedTime / 1000), // totalTime, in seconds
|
|
};
|
|
|
|
// Look for app-specific timestamps
|
|
var appTimestamps = {};
|
|
try {
|
|
let { TelemetryTimestamps } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/TelemetryTimestamps.sys.mjs"
|
|
);
|
|
appTimestamps = TelemetryTimestamps.get();
|
|
} catch (ex) {}
|
|
|
|
// Only submit this if the extended set is enabled.
|
|
if (!Utils.isContentProcess && Services.telemetry.canRecordExtended) {
|
|
try {
|
|
ret.addonManager = lazy.AddonManagerPrivate.getSimpleMeasures();
|
|
} catch (ex) {}
|
|
}
|
|
|
|
if (si.process) {
|
|
for (let field of Object.keys(si)) {
|
|
if (field == "process") {
|
|
continue;
|
|
}
|
|
ret[field] = si[field] - si.process;
|
|
}
|
|
|
|
for (let p in appTimestamps) {
|
|
if (!(p in ret) && appTimestamps[p]) {
|
|
ret[p] = appTimestamps[p] - si.process;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Utils.isContentProcess) {
|
|
return ret;
|
|
}
|
|
|
|
// Measurements specific to chrome process
|
|
|
|
// Update debuggerAttached flag
|
|
let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(
|
|
Ci.nsIDebug2
|
|
);
|
|
let isDebuggerAttached = debugService.isDebuggerAttached;
|
|
gWasDebuggerAttached = gWasDebuggerAttached || isDebuggerAttached;
|
|
ret.debuggerAttached = Number(gWasDebuggerAttached);
|
|
|
|
let shutdownDuration = Services.telemetry.lastShutdownDuration;
|
|
if (shutdownDuration) {
|
|
ret.shutdownDuration = shutdownDuration;
|
|
}
|
|
|
|
let failedProfileLockCount = Services.telemetry.failedProfileLockCount;
|
|
if (failedProfileLockCount) {
|
|
ret.failedProfileLockCount = failedProfileLockCount;
|
|
}
|
|
|
|
for (let ioCounter in this._startupIO) {
|
|
ret[ioCounter] = this._startupIO[ioCounter];
|
|
}
|
|
|
|
let activeTicks = this._sessionActiveTicks;
|
|
if (isSubsession) {
|
|
activeTicks = this._sessionActiveTicks - this._subsessionStartActiveTicks;
|
|
}
|
|
|
|
if (clearSubsession) {
|
|
this._subsessionStartActiveTicks = this._sessionActiveTicks;
|
|
}
|
|
|
|
ret.activeTicks = activeTicks;
|
|
|
|
return ret;
|
|
},
|
|
|
|
getHistograms: function getHistograms(clearSubsession) {
|
|
return Services.telemetry.getSnapshotForHistograms(
|
|
"main",
|
|
clearSubsession,
|
|
!this._testing
|
|
);
|
|
},
|
|
|
|
getKeyedHistograms(clearSubsession) {
|
|
return Services.telemetry.getSnapshotForKeyedHistograms(
|
|
"main",
|
|
clearSubsession,
|
|
!this._testing
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Get a snapshot of the scalars and clear them.
|
|
* @param {subsession} If true, then we collect the data for a subsession.
|
|
* @param {clearSubsession} If true, we need to clear the subsession.
|
|
* @param {keyed} Take a snapshot of keyed or non keyed scalars.
|
|
* @return {Object} The scalar data as a Javascript object, including the
|
|
* data from child processes, in the following format:
|
|
* {'content': { 'scalarName': ... }, 'gpu': { ... } }
|
|
*/
|
|
getScalars(subsession, clearSubsession, keyed) {
|
|
if (!subsession) {
|
|
// We only support scalars for subsessions.
|
|
this._log.trace("getScalars - We only support scalars in subsessions.");
|
|
return {};
|
|
}
|
|
|
|
let scalarsSnapshot = keyed
|
|
? Services.telemetry.getSnapshotForKeyedScalars(
|
|
"main",
|
|
clearSubsession,
|
|
!this._testing
|
|
)
|
|
: Services.telemetry.getSnapshotForScalars(
|
|
"main",
|
|
clearSubsession,
|
|
!this._testing
|
|
);
|
|
|
|
return scalarsSnapshot;
|
|
},
|
|
|
|
/**
|
|
* Descriptive metadata
|
|
*
|
|
* @param reason
|
|
* The reason for the telemetry ping, this will be included in the
|
|
* returned metadata,
|
|
* @return The metadata as a JS object
|
|
*/
|
|
getMetadata: function getMetadata(reason) {
|
|
const sessionStartDate = Utils.toLocalTimeISOString(
|
|
Utils.truncateToHours(this._sessionStartDate)
|
|
);
|
|
const subsessionStartDate = Utils.toLocalTimeISOString(
|
|
Utils.truncateToHours(this._subsessionStartDate)
|
|
);
|
|
const monotonicNow = Policy.monotonicNow();
|
|
|
|
let ret = {
|
|
reason,
|
|
revision: AppConstants.SOURCE_REVISION_URL,
|
|
|
|
// Date.getTimezoneOffset() unintuitively returns negative values if we are ahead of
|
|
// UTC and vice versa (e.g. -60 for UTC+1). We invert the sign here.
|
|
timezoneOffset: -this._subsessionStartDate.getTimezoneOffset(),
|
|
previousBuildId: this._previousBuildId,
|
|
|
|
sessionId: this._sessionId,
|
|
subsessionId: this._subsessionId,
|
|
previousSessionId: this._previousSessionId,
|
|
previousSubsessionId: this._previousSubsessionId,
|
|
|
|
subsessionCounter: this._subsessionCounter,
|
|
profileSubsessionCounter: this._profileSubsessionCounter,
|
|
|
|
sessionStartDate,
|
|
subsessionStartDate,
|
|
|
|
// Compute the session and subsession length in seconds.
|
|
// We use monotonic clocks as Date() is affected by jumping clocks (leading
|
|
// to negative lengths and other issues).
|
|
sessionLength: Math.floor(monotonicNow / 1000),
|
|
subsessionLength: Math.floor(
|
|
(monotonicNow - this._subsessionStartTimeMonotonic) / 1000
|
|
),
|
|
};
|
|
|
|
// TODO: Remove this when bug 1201837 lands.
|
|
if (this._addons) {
|
|
ret.addons = this._addons;
|
|
}
|
|
|
|
return ret;
|
|
},
|
|
|
|
/**
|
|
* Get the current session's payload using the provided
|
|
* simpleMeasurements and info, which are typically obtained by a call
|
|
* to |this.getSimpleMeasurements| and |this.getMetadata|,
|
|
* respectively.
|
|
*/
|
|
assemblePayloadWithMeasurements(
|
|
simpleMeasurements,
|
|
info,
|
|
reason,
|
|
clearSubsession
|
|
) {
|
|
const isSubsession = IS_UNIFIED_TELEMETRY && !this._isClassicReason(reason);
|
|
clearSubsession = IS_UNIFIED_TELEMETRY && clearSubsession;
|
|
this._log.trace(
|
|
"assemblePayloadWithMeasurements - reason: " +
|
|
reason +
|
|
", submitting subsession data: " +
|
|
isSubsession
|
|
);
|
|
|
|
// This allows wrapping data retrieval calls in a try-catch block so that
|
|
// failures don't break the rest of the ping assembly.
|
|
const protect = (fn, defaultReturn = null) => {
|
|
try {
|
|
return fn();
|
|
} catch (ex) {
|
|
this._log.error(
|
|
"assemblePayloadWithMeasurements - caught exception",
|
|
ex
|
|
);
|
|
return defaultReturn;
|
|
}
|
|
};
|
|
|
|
// Payload common to chrome and content processes.
|
|
let payloadObj = {
|
|
ver: PAYLOAD_VERSION,
|
|
simpleMeasurements,
|
|
};
|
|
|
|
// Add extended set measurements common to chrome & content processes
|
|
if (Services.telemetry.canRecordExtended) {
|
|
payloadObj.log = [];
|
|
}
|
|
|
|
if (Utils.isContentProcess) {
|
|
return payloadObj;
|
|
}
|
|
|
|
// Additional payload for chrome process.
|
|
let measurements = {
|
|
histograms: protect(() => this.getHistograms(clearSubsession), {}),
|
|
keyedHistograms: protect(
|
|
() => this.getKeyedHistograms(clearSubsession),
|
|
{}
|
|
),
|
|
scalars: protect(
|
|
() => this.getScalars(isSubsession, clearSubsession),
|
|
{}
|
|
),
|
|
keyedScalars: protect(
|
|
() => this.getScalars(isSubsession, clearSubsession, true),
|
|
{}
|
|
),
|
|
};
|
|
|
|
let measurementsContainGPU = Object.keys(measurements).some(
|
|
key => "gpu" in measurements[key]
|
|
);
|
|
|
|
let measurementsContainSocket = Object.keys(measurements).some(
|
|
key => "socket" in measurements[key]
|
|
);
|
|
|
|
let measurementsContainUtility = Object.keys(measurements).some(
|
|
key => "utility" in measurements[key]
|
|
);
|
|
|
|
payloadObj.processes = {};
|
|
let processTypes = ["parent", "content", "extension", "dynamic"];
|
|
// Only include the GPU process if we've accumulated data for it.
|
|
if (measurementsContainGPU) {
|
|
processTypes.push("gpu");
|
|
}
|
|
if (measurementsContainSocket) {
|
|
processTypes.push("socket");
|
|
}
|
|
if (measurementsContainUtility) {
|
|
processTypes.push("utility");
|
|
}
|
|
|
|
// Collect per-process measurements.
|
|
for (const processType of processTypes) {
|
|
let processPayload = {};
|
|
|
|
for (const key in measurements) {
|
|
let payloadLoc = processPayload;
|
|
// Parent histograms are added to the top-level payload object instead of the process payload.
|
|
if (
|
|
processType == "parent" &&
|
|
(key == "histograms" || key == "keyedHistograms")
|
|
) {
|
|
payloadLoc = payloadObj;
|
|
}
|
|
// The Dynamic process only collects scalars and keyed scalars.
|
|
if (
|
|
processType == "dynamic" &&
|
|
key !== "scalars" &&
|
|
key !== "keyedScalars"
|
|
) {
|
|
continue;
|
|
}
|
|
|
|
// Process measurements can be empty, set a default value.
|
|
payloadLoc[key] = measurements[key][processType] || {};
|
|
}
|
|
|
|
// Add process measurements to payload.
|
|
payloadObj.processes[processType] = processPayload;
|
|
}
|
|
|
|
payloadObj.info = info;
|
|
|
|
// Add extended set measurements for chrome process.
|
|
if (Services.telemetry.canRecordExtended) {
|
|
payloadObj.slowSQL = protect(() => Services.telemetry.slowSQL);
|
|
payloadObj.fileIOReports = protect(
|
|
() => Services.telemetry.fileIOReports
|
|
);
|
|
payloadObj.lateWrites = protect(() => Services.telemetry.lateWrites);
|
|
|
|
payloadObj.addonDetails = protect(() =>
|
|
lazy.AddonManagerPrivate.getTelemetryDetails()
|
|
);
|
|
|
|
if (
|
|
this._slowSQLStartup &&
|
|
!!Object.keys(this._slowSQLStartup).length &&
|
|
(Object.keys(this._slowSQLStartup.mainThread).length ||
|
|
Object.keys(this._slowSQLStartup.otherThreads).length)
|
|
) {
|
|
payloadObj.slowSQLStartup = this._slowSQLStartup;
|
|
}
|
|
}
|
|
|
|
return payloadObj;
|
|
},
|
|
|
|
/**
|
|
* Start a new subsession.
|
|
*/
|
|
startNewSubsession() {
|
|
this._subsessionStartDate = Policy.now();
|
|
this._subsessionStartTimeMonotonic = Policy.monotonicNow();
|
|
this._previousSubsessionId = this._subsessionId;
|
|
this._subsessionId = Policy.generateSubsessionUUID();
|
|
this._subsessionCounter++;
|
|
this._profileSubsessionCounter++;
|
|
},
|
|
|
|
getSessionPayload: function getSessionPayload(reason, clearSubsession) {
|
|
this._log.trace(
|
|
"getSessionPayload - reason: " +
|
|
reason +
|
|
", clearSubsession: " +
|
|
clearSubsession
|
|
);
|
|
|
|
let payload;
|
|
try {
|
|
const isMobile = AppConstants.platform == "android";
|
|
const isSubsession = isMobile ? false : !this._isClassicReason(reason);
|
|
|
|
// The order of the next two msSinceProcessStart* calls is somewhat
|
|
// important. In theory, `session_time_including_suspend` is supposed to
|
|
// ALWAYS be lower or equal than `session_time_excluding_suspend` (because
|
|
// the former is a temporal superset of the latter). When a device has not
|
|
// been suspended since boot, we want the previous property to hold,
|
|
// regardless of the delay during or between the two
|
|
// `msSinceProcessStart*` calls.
|
|
Glean.browserEngagement.sessionTimeExcludingSuspend.set(
|
|
Services.telemetry.msSinceProcessStartExcludingSuspend()
|
|
);
|
|
Glean.browserEngagement.sessionTimeIncludingSuspend.set(
|
|
Services.telemetry.msSinceProcessStartIncludingSuspend()
|
|
);
|
|
|
|
if (isMobile) {
|
|
clearSubsession = false;
|
|
}
|
|
|
|
let measurements = this.getSimpleMeasurements(
|
|
reason == REASON_SAVED_SESSION,
|
|
isSubsession,
|
|
clearSubsession
|
|
);
|
|
let info = !Utils.isContentProcess ? this.getMetadata(reason) : null;
|
|
payload = this.assemblePayloadWithMeasurements(
|
|
measurements,
|
|
info,
|
|
reason,
|
|
clearSubsession
|
|
);
|
|
} finally {
|
|
if (!Utils.isContentProcess && clearSubsession) {
|
|
this.startNewSubsession();
|
|
// Persist session data to disk (don't wait until it completes).
|
|
let sessionData = this._getSessionDataObject();
|
|
lazy.TelemetryStorage.saveSessionData(sessionData);
|
|
|
|
// Notify that there was a subsession split in the parent process. This is an
|
|
// internal topic and is only meant for internal Telemetry usage.
|
|
Services.obs.notifyObservers(
|
|
null,
|
|
"internal-telemetry-after-subsession-split"
|
|
);
|
|
}
|
|
}
|
|
|
|
return payload;
|
|
},
|
|
|
|
/**
|
|
* Send data to the server. Record success/send-time in histograms
|
|
*/
|
|
send: async function send(reason) {
|
|
this._log.trace("send - Reason " + reason);
|
|
// populate histograms one last time
|
|
await Services.telemetry.gatherMemory();
|
|
|
|
const isSubsession = !this._isClassicReason(reason);
|
|
let payload = this.getSessionPayload(reason, isSubsession);
|
|
let options = {
|
|
addClientId: true,
|
|
addEnvironment: true,
|
|
};
|
|
return lazy.TelemetryController.submitExternalPing(
|
|
getPingType(payload),
|
|
payload,
|
|
options
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Attaches the needed observers during Telemetry early init, in the
|
|
* chrome process.
|
|
*/
|
|
attachEarlyObservers() {
|
|
this.addObserver("sessionstore-windows-restored");
|
|
if (AppConstants.platform === "android") {
|
|
this.addObserver("application-background");
|
|
}
|
|
this.addObserver("xul-window-visible");
|
|
|
|
// Attach the active-ticks related observers.
|
|
this.addObserver("user-interaction-active");
|
|
this.addObserver("user-interaction-inactive");
|
|
},
|
|
|
|
/**
|
|
* Lightweight init function, called as soon as Firefox starts.
|
|
*/
|
|
earlyInit(testing) {
|
|
this._log.trace("earlyInit");
|
|
|
|
this._initStarted = true;
|
|
this._testing = testing;
|
|
|
|
if (this._initialized && !testing) {
|
|
this._log.error("earlyInit - already initialized");
|
|
return;
|
|
}
|
|
|
|
if (!Services.telemetry.canRecordBase && !testing) {
|
|
this._log.config(
|
|
"earlyInit - Telemetry recording is disabled, skipping Chrome process setup."
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Generate a unique id once per session so the server can cope with duplicate
|
|
// submissions, orphaning and other oddities. The id is shared across subsessions.
|
|
this._sessionId = Policy.generateSessionUUID();
|
|
this.startNewSubsession();
|
|
// startNewSubsession sets |_subsessionStartDate| to the current date/time. Use
|
|
// the very same value for |_sessionStartDate|.
|
|
this._sessionStartDate = this._subsessionStartDate;
|
|
|
|
annotateCrashReport(this._sessionId);
|
|
|
|
// Record old value and update build ID preference if this is the first
|
|
// run with a new build ID.
|
|
let previousBuildId = Services.prefs.getStringPref(
|
|
TelemetryUtils.Preferences.PreviousBuildID,
|
|
null
|
|
);
|
|
let thisBuildID = Services.appinfo.appBuildID;
|
|
// If there is no previousBuildId preference, we send null to the server.
|
|
if (previousBuildId != thisBuildID) {
|
|
this._previousBuildId = previousBuildId;
|
|
Services.prefs.setStringPref(
|
|
TelemetryUtils.Preferences.PreviousBuildID,
|
|
thisBuildID
|
|
);
|
|
}
|
|
|
|
this.attachEarlyObservers();
|
|
},
|
|
|
|
/**
|
|
* Does the "heavy" Telemetry initialization later on, so we
|
|
* don't impact startup performance.
|
|
* @return {Promise} Resolved when the initialization completes.
|
|
*/
|
|
delayedInit() {
|
|
this._log.trace("delayedInit");
|
|
|
|
this._delayedInitTask = (async () => {
|
|
try {
|
|
this._initialized = true;
|
|
|
|
await this._loadSessionData();
|
|
// Update the session data to keep track of new subsessions created before
|
|
// the initialization.
|
|
await lazy.TelemetryStorage.saveSessionData(
|
|
this._getSessionDataObject()
|
|
);
|
|
|
|
this.addObserver("idle-daily");
|
|
await Services.telemetry.gatherMemory();
|
|
|
|
Services.telemetry.asyncFetchTelemetryData(function () {});
|
|
|
|
if (IS_UNIFIED_TELEMETRY) {
|
|
// Check for a previously written aborted session ping.
|
|
await lazy.TelemetryController.checkAbortedSessionPing();
|
|
|
|
// 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.
|
|
if (!this._testing) {
|
|
await this._saveAbortedSessionPing();
|
|
}
|
|
|
|
// The last change date for the environment, used to throttle environment changes.
|
|
this._lastEnvironmentChangeDate = Policy.monotonicNow();
|
|
lazy.TelemetryEnvironment.registerChangeListener(
|
|
ENVIRONMENT_CHANGE_LISTENER,
|
|
(reason, data) => this._onEnvironmentChange(reason, data)
|
|
);
|
|
|
|
// Start the scheduler.
|
|
// We skip this if unified telemetry is off, so we don't
|
|
// trigger the new unified ping types.
|
|
lazy.TelemetryScheduler.init();
|
|
}
|
|
|
|
this._delayedInitTask = null;
|
|
} catch (e) {
|
|
this._delayedInitTask = null;
|
|
throw e;
|
|
}
|
|
})();
|
|
|
|
return this._delayedInitTask;
|
|
},
|
|
|
|
/**
|
|
* On Desktop: Save the "shutdown" ping to disk.
|
|
* On Android: Save the "saved-session" ping to disk.
|
|
* This needs to be called after TelemetrySend shuts down otherwise pings
|
|
* would be sent instead of getting persisted to disk.
|
|
*/
|
|
saveShutdownPings() {
|
|
this._log.trace("saveShutdownPings");
|
|
|
|
// We append the promises to this list and wait
|
|
// on all pings to be saved after kicking off their collection.
|
|
let p = [];
|
|
|
|
if (IS_UNIFIED_TELEMETRY) {
|
|
let shutdownPayload = this.getSessionPayload(REASON_SHUTDOWN, false);
|
|
|
|
// Only send the shutdown ping using the pingsender from the second
|
|
// browsing session on, to mitigate issues with "bot" profiles (see bug 1354482).
|
|
const sendOnThisSession =
|
|
Services.prefs.getBoolPref(
|
|
Utils.Preferences.ShutdownPingSenderFirstSession,
|
|
false
|
|
) || !lazy.TelemetryReportingPolicy.isFirstRun();
|
|
let sendWithPingsender =
|
|
Services.prefs.getBoolPref(
|
|
TelemetryUtils.Preferences.ShutdownPingSender,
|
|
false
|
|
) && sendOnThisSession;
|
|
|
|
let options = {
|
|
addClientId: true,
|
|
addEnvironment: true,
|
|
usePingSender: sendWithPingsender,
|
|
};
|
|
p.push(
|
|
lazy.TelemetryController.submitExternalPing(
|
|
getPingType(shutdownPayload),
|
|
shutdownPayload,
|
|
options
|
|
).catch(e =>
|
|
this._log.error(
|
|
"saveShutdownPings - failed to submit shutdown ping",
|
|
e
|
|
)
|
|
)
|
|
);
|
|
|
|
// Send a duplicate of first-shutdown pings as a new ping type, in order to properly
|
|
// evaluate first session profiles (see bug 1390095).
|
|
const sendFirstShutdownPing =
|
|
Services.prefs.getBoolPref(
|
|
Utils.Preferences.ShutdownPingSender,
|
|
false
|
|
) &&
|
|
Services.prefs.getBoolPref(
|
|
Utils.Preferences.FirstShutdownPingEnabled,
|
|
false
|
|
) &&
|
|
lazy.TelemetryReportingPolicy.isFirstRun();
|
|
|
|
if (sendFirstShutdownPing) {
|
|
let options = {
|
|
addClientId: true,
|
|
addEnvironment: true,
|
|
usePingSender: true,
|
|
};
|
|
p.push(
|
|
lazy.TelemetryController.submitExternalPing(
|
|
"first-shutdown",
|
|
shutdownPayload,
|
|
options
|
|
).catch(e =>
|
|
this._log.error(
|
|
"saveShutdownPings - failed to submit first shutdown ping",
|
|
e
|
|
)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
if (
|
|
AppConstants.platform == "android" &&
|
|
Services.telemetry.canRecordExtended
|
|
) {
|
|
let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
|
|
|
|
let options = {
|
|
addClientId: true,
|
|
addEnvironment: true,
|
|
};
|
|
p.push(
|
|
lazy.TelemetryController.submitExternalPing(
|
|
getPingType(payload),
|
|
payload,
|
|
options
|
|
).catch(e =>
|
|
this._log.error(
|
|
"saveShutdownPings - failed to submit saved-session ping",
|
|
e
|
|
)
|
|
)
|
|
);
|
|
}
|
|
|
|
// Wait on pings to be saved.
|
|
return Promise.all(p);
|
|
},
|
|
|
|
testSavePendingPing() {
|
|
let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
|
|
let options = {
|
|
addClientId: true,
|
|
addEnvironment: true,
|
|
overwrite: true,
|
|
};
|
|
return lazy.TelemetryController.addPendingPing(
|
|
getPingType(payload),
|
|
payload,
|
|
options
|
|
);
|
|
},
|
|
|
|
/**
|
|
* Do some shutdown work that is common to all process types.
|
|
*/
|
|
uninstall() {
|
|
for (let topic of this._observedTopics) {
|
|
try {
|
|
// Tests may flip Telemetry.canRecordExtended on and off. It can be the case
|
|
// that the observer TOPIC_CYCLE_COLLECTOR_BEGIN was not added.
|
|
this.removeObserver(topic);
|
|
} catch (e) {
|
|
this._log.warn("uninstall - Failed to remove " + topic, e);
|
|
}
|
|
}
|
|
},
|
|
|
|
getPayload: function getPayload(reason, clearSubsession) {
|
|
this._log.trace("getPayload - clearSubsession: " + clearSubsession);
|
|
reason = reason || REASON_GATHER_PAYLOAD;
|
|
// This function returns the current Telemetry payload to the caller.
|
|
// We only gather startup info once.
|
|
if (!Object.keys(this._slowSQLStartup).length) {
|
|
this._slowSQLStartup = Services.telemetry.slowSQL;
|
|
}
|
|
Services.telemetry.gatherMemory();
|
|
return this.getSessionPayload(reason, clearSubsession);
|
|
},
|
|
|
|
gatherStartup: function gatherStartup() {
|
|
this._log.trace("gatherStartup");
|
|
let counters = processInfo.getCounters();
|
|
if (counters) {
|
|
[
|
|
this._startupIO.startupSessionRestoreReadBytes,
|
|
this._startupIO.startupSessionRestoreWriteBytes,
|
|
] = counters;
|
|
}
|
|
this._slowSQLStartup = Services.telemetry.slowSQL;
|
|
},
|
|
|
|
setAddOns: function setAddOns(aAddOns) {
|
|
this._addons = aAddOns;
|
|
},
|
|
|
|
testPing: function testPing() {
|
|
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++;
|
|
Glean.browserEngagement.activeTicks.add(1);
|
|
// GLAM EXPERIMENT
|
|
// This metric is temporary, disabled by default, and will be enabled only
|
|
// for the purpose of experimenting with client-side sampling of data for
|
|
// GLAM use. See Bug 1947604 for more information.
|
|
Glean.glamExperiment.activeTicks.add(1);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* This observer drives telemetry.
|
|
*/
|
|
observe(aSubject, aTopic) {
|
|
this._log.trace("observe - " + aTopic + " notified.");
|
|
|
|
switch (aTopic) {
|
|
case "xul-window-visible":
|
|
this.removeObserver("xul-window-visible");
|
|
var counters = processInfo.getCounters();
|
|
if (counters) {
|
|
[
|
|
this._startupIO.startupWindowVisibleReadBytes,
|
|
this._startupIO.startupWindowVisibleWriteBytes,
|
|
] = counters;
|
|
}
|
|
break;
|
|
case "sessionstore-windows-restored":
|
|
this.removeObserver("sessionstore-windows-restored");
|
|
// Check whether debugger was attached during startup
|
|
let debugService = Cc["@mozilla.org/xpcom/debug;1"].getService(
|
|
Ci.nsIDebug2
|
|
);
|
|
gWasDebuggerAttached = debugService.isDebuggerAttached;
|
|
this.gatherStartup();
|
|
break;
|
|
case "idle-daily":
|
|
// Enqueue to main-thread, otherwise components may be inited by the
|
|
// idle-daily category and miss the gather-telemetry notification.
|
|
Services.tm.dispatchToMainThread(function () {
|
|
// Notify that data should be gathered now.
|
|
// TODO: We are keeping this behaviour for now but it will be removed as soon as
|
|
// bug 1127907 lands.
|
|
Services.obs.notifyObservers(null, "gather-telemetry");
|
|
});
|
|
break;
|
|
|
|
case "application-background":
|
|
if (AppConstants.platform !== "android") {
|
|
break;
|
|
}
|
|
// On Android, we can get killed without warning once we are in the background,
|
|
// but we may also submit data and/or come back into the foreground without getting
|
|
// killed. To deal with this, we save the current session data to file when we are
|
|
// put into the background. This handles the following post-backgrounding scenarios:
|
|
// 1) We are killed immediately. In this case the current session data (which we
|
|
// save to a file) will be loaded and submitted on a future run.
|
|
// 2) We submit the data while in the background, and then are killed. In this case
|
|
// the file that we saved will be deleted by the usual process in
|
|
// finishPingRequest after it is submitted.
|
|
// 3) We submit the data, and then come back into the foreground. Same as case (2).
|
|
// 4) We do not submit the data, but come back into the foreground. In this case
|
|
// we have the option of either deleting the file that we saved (since we will either
|
|
// send the live data while in the foreground, or create the file again on the next
|
|
// backgrounding), or not (in which case we will delete it on submit, or overwrite
|
|
// it on the next backgrounding). Not deleting it is faster, so that's what we do.
|
|
let payload = this.getSessionPayload(REASON_SAVED_SESSION, false);
|
|
let options = {
|
|
addClientId: true,
|
|
addEnvironment: true,
|
|
overwrite: true,
|
|
};
|
|
lazy.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;
|
|
},
|
|
|
|
/**
|
|
* This tells TelemetrySession to uninitialize and save any pending pings.
|
|
*/
|
|
shutdownChromeProcess() {
|
|
this._log.trace("shutdownChromeProcess");
|
|
|
|
let cleanup = () => {
|
|
if (IS_UNIFIED_TELEMETRY) {
|
|
lazy.TelemetryEnvironment.unregisterChangeListener(
|
|
ENVIRONMENT_CHANGE_LISTENER
|
|
);
|
|
lazy.TelemetryScheduler.shutdown();
|
|
}
|
|
this.uninstall();
|
|
|
|
let reset = () => {
|
|
this._initStarted = false;
|
|
this._initialized = false;
|
|
};
|
|
|
|
return (async () => {
|
|
await this.saveShutdownPings();
|
|
|
|
if (IS_UNIFIED_TELEMETRY) {
|
|
await lazy.TelemetryController.removeAbortedSessionPing();
|
|
}
|
|
|
|
reset();
|
|
})();
|
|
};
|
|
|
|
// We can be in one the following states here:
|
|
// 1) delayedInit was never called
|
|
// or it was called and
|
|
// 2) _delayedInitTask is running now.
|
|
// 3) _delayedInitTask finished running already.
|
|
|
|
// This handles 1).
|
|
if (!this._initStarted) {
|
|
return Promise.resolve();
|
|
}
|
|
|
|
// This handles 3).
|
|
if (!this._delayedInitTask) {
|
|
// We already ran the delayed initialization.
|
|
return cleanup();
|
|
}
|
|
|
|
// This handles 2).
|
|
return this._delayedInitTask.then(cleanup);
|
|
},
|
|
|
|
/**
|
|
* Gather and send a daily ping.
|
|
* @return {Promise} Resolved when the ping is sent.
|
|
*/
|
|
_sendDailyPing() {
|
|
this._log.trace("_sendDailyPing");
|
|
let payload = this.getSessionPayload(REASON_DAILY, true);
|
|
|
|
let options = {
|
|
addClientId: true,
|
|
addEnvironment: true,
|
|
};
|
|
|
|
let promise = lazy.TelemetryController.submitExternalPing(
|
|
getPingType(payload),
|
|
payload,
|
|
options
|
|
);
|
|
|
|
// Also save the payload as an aborted session. If we delay this, aborted-session can
|
|
// lag behind for the profileSubsessionCounter and other state, complicating analysis.
|
|
if (IS_UNIFIED_TELEMETRY) {
|
|
this._saveAbortedSessionPing(payload).catch(e =>
|
|
this._log.error(
|
|
"_sendDailyPing - Failed to save the aborted session ping",
|
|
e
|
|
)
|
|
);
|
|
}
|
|
|
|
return promise;
|
|
},
|
|
|
|
/** Loads session data from the session data file.
|
|
* @return {Promise<object>} A promise which is resolved with an object when
|
|
* loading has completed, with null otherwise.
|
|
*/
|
|
async _loadSessionData() {
|
|
let data = await lazy.TelemetryStorage.loadSessionData();
|
|
|
|
if (!data) {
|
|
return null;
|
|
}
|
|
|
|
if (
|
|
!("profileSubsessionCounter" in data) ||
|
|
!(typeof data.profileSubsessionCounter == "number") ||
|
|
!("subsessionId" in data) ||
|
|
!("sessionId" in data)
|
|
) {
|
|
this._log.error("_loadSessionData - session data is invalid");
|
|
return null;
|
|
}
|
|
|
|
this._previousSessionId = data.sessionId;
|
|
this._previousSubsessionId = data.subsessionId;
|
|
// Add |_subsessionCounter| to the |_profileSubsessionCounter| to account for
|
|
// new subsession while loading still takes place. This will always be exactly
|
|
// 1 - the current subsessions.
|
|
this._profileSubsessionCounter =
|
|
data.profileSubsessionCounter + this._subsessionCounter;
|
|
// If we don't have this flag in the state file, it means that this is an old profile.
|
|
// We don't want to send the "new-profile" ping on new profile, so se this to true.
|
|
this._newProfilePingSent =
|
|
"newProfilePingSent" in data ? data.newProfilePingSent : true;
|
|
return data;
|
|
},
|
|
|
|
/**
|
|
* Get the session data object to serialise to disk.
|
|
*/
|
|
_getSessionDataObject() {
|
|
return {
|
|
sessionId: this._sessionId,
|
|
subsessionId: this._subsessionId,
|
|
profileSubsessionCounter: this._profileSubsessionCounter,
|
|
newProfilePingSent: this._newProfilePingSent,
|
|
};
|
|
},
|
|
|
|
_onEnvironmentChange(reason, oldEnvironment) {
|
|
this._log.trace("_onEnvironmentChange", reason);
|
|
|
|
let now = Policy.monotonicNow();
|
|
let timeDelta = now - this._lastEnvironmentChangeDate;
|
|
if (timeDelta <= MIN_SUBSESSION_LENGTH_MS) {
|
|
this._log.trace(
|
|
`_onEnvironmentChange - throttling; last change was ${Math.round(
|
|
timeDelta / 1000
|
|
)}s ago.`
|
|
);
|
|
return;
|
|
}
|
|
|
|
this._lastEnvironmentChangeDate = now;
|
|
let payload = this.getSessionPayload(REASON_ENVIRONMENT_CHANGE, true);
|
|
lazy.TelemetryScheduler.rescheduleDailyPing(payload);
|
|
|
|
let options = {
|
|
addClientId: true,
|
|
addEnvironment: true,
|
|
overrideEnvironment: oldEnvironment,
|
|
};
|
|
lazy.TelemetryController.submitExternalPing(
|
|
getPingType(payload),
|
|
payload,
|
|
options
|
|
);
|
|
},
|
|
|
|
_isClassicReason(reason) {
|
|
const classicReasons = [
|
|
REASON_SAVED_SESSION,
|
|
REASON_GATHER_PAYLOAD,
|
|
REASON_TEST_PING,
|
|
];
|
|
return classicReasons.includes(reason);
|
|
},
|
|
|
|
/**
|
|
* Get an object describing the current state of this module for AsyncShutdown diagnostics.
|
|
*/
|
|
_getState() {
|
|
return {
|
|
initialized: this._initialized,
|
|
initStarted: this._initStarted,
|
|
haveDelayedInitTask: !!this._delayedInitTask,
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Saves the aborted session ping to disk.
|
|
* @param {Object} [aProvidedPayload=null] A payload object to be used as an aborted
|
|
* session ping. The reason of this payload is changed to aborted-session.
|
|
* If not provided, a new payload is gathered.
|
|
*/
|
|
_saveAbortedSessionPing(aProvidedPayload = null) {
|
|
this._log.trace("_saveAbortedSessionPing");
|
|
|
|
let payload = null;
|
|
if (aProvidedPayload) {
|
|
payload = Cu.cloneInto(aProvidedPayload, {});
|
|
// Overwrite the original reason.
|
|
payload.info.reason = REASON_ABORTED_SESSION;
|
|
} else {
|
|
payload = this.getSessionPayload(REASON_ABORTED_SESSION, false);
|
|
}
|
|
|
|
return lazy.TelemetryController.saveAbortedSessionPing(payload);
|
|
},
|
|
|
|
async markNewProfilePingSent() {
|
|
this._log.trace("markNewProfilePingSent");
|
|
this._newProfilePingSent = true;
|
|
return lazy.TelemetryStorage.saveSessionData(this._getSessionDataObject());
|
|
},
|
|
};
|