Bug 1795467 - Include some targeting in background update Glean telemetry. r=bytesized,chutten,application-update-reviewers

This includes some key metrics from the default profile targeting
state in the background update Glean telemetry.  It will allow us to
measure the incidence of missing and malformed targeting state JSON,
and how many background update clients have been lapsed for how long.
This in turn will help us understand if the lapsed experiment
targeting is working correctly.

Differential Revision: https://phabricator.services.mozilla.com/D164026
This commit is contained in:
Nick Alexander 2022-12-13 22:18:16 +00:00
Родитель 639cbc34a2
Коммит 17aeb39742
5 изменённых файлов: 313 добавлений и 18 удалений

Просмотреть файл

@ -231,7 +231,7 @@ export var BackgroundTasksUtils = {
* read from it. If `lock` is given, read from the given lock's directory.
*
* @param {nsIProfileLock} [lock] optional lock to use
* @returns {string}
* @returns {object}
*/
async readFirefoxMessagingSystemTargetingSnapshot(lock = null) {
if (!lock) {

Просмотреть файл

@ -273,22 +273,10 @@ export async function runBackgroundTask(commandLine) {
);
Glean.backgroundUpdate.clientId.set(telemetryClientID);
try {
defaultProfileTargetingSnapshot = await lazy.BackgroundTasksUtils.readFirefoxMessagingSystemTargetingSnapshot(
lock
);
} catch (f) {
if (DOMException.isInstance(f) && f.name === "NotFoundError") {
lazy.log.info(
`${SLUG}: no default profile targeting snapshot exists`
);
} else {
lazy.log.warn(
`${SLUG}: ignoring exception reading default profile targeting snapshot`,
f
);
}
}
// Read targeting snapshot, collect background update specific telemetry. Never throws.
defaultProfileTargetingSnapshot = await BackgroundUpdate.readFirefoxMessagingSystemTargetingSnapshot(
lock
);
});
for (let [name, value] of Object.entries(defaultProfilePrefs)) {

Просмотреть файл

@ -693,6 +693,71 @@ var BackgroundUpdate = {
Ci.nsITimer.TYPE_REPEATING_SLACK_LOW_PRIORITY
);
},
/**
* Reads the snapshotted Firefox Messaging System targeting out of a profile.
* Collects background update specific telemetry. Never throws.
*
* If no `lock` is given, the default profile is locked and the preferences
* read from it. If `lock` is given, read from the given lock's directory.
*
* @param {nsIProfileLock} [lock] optional lock to use
* @returns {object} possibly empty targeting snapshot.
*/
async readFirefoxMessagingSystemTargetingSnapshot(lock = null) {
let SLUG = "readFirefoxMessagingSystemTargetingSnapshot";
let defaultProfileTargetingSnapshot = {};
Glean.backgroundUpdate.targetingExists.set(false);
Glean.backgroundUpdate.targetingException.set(true);
try {
defaultProfileTargetingSnapshot = await lazy.BackgroundTasksUtils.readFirefoxMessagingSystemTargetingSnapshot(
lock
);
Glean.backgroundUpdate.targetingExists.set(true);
Glean.backgroundUpdate.targetingException.set(false);
if (defaultProfileTargetingSnapshot?.version) {
Glean.backgroundUpdate.targetingVersion.set(
defaultProfileTargetingSnapshot.version
);
}
if (defaultProfileTargetingSnapshot?.environment?.firefoxVersion) {
Glean.backgroundUpdate.targetingEnvFirefoxVersion.set(
defaultProfileTargetingSnapshot.environment.firefoxVersion
);
}
if (defaultProfileTargetingSnapshot?.environment?.currentDate) {
Glean.backgroundUpdate.targetingEnvCurrentDate.set(
// Glean date times are provided in nanoseconds, `getTime()` yields
// milliseconds (after the Unix epoch).
new Date(
defaultProfileTargetingSnapshot.environment.currentDate
).getTime() * 1000
);
}
if (defaultProfileTargetingSnapshot?.environment?.profileAgeCreated) {
Glean.backgroundUpdate.targetingEnvProfileAge.set(
// Glean date times are provided in nanoseconds, `profileAgeCreated`
// is in milliseconds (after the Unix epoch).
defaultProfileTargetingSnapshot.environment.profileAgeCreated * 1000
);
}
} catch (f) {
if (DOMException.isInstance(f) && f.name === "NotFoundError") {
Glean.backgroundUpdate.targetingException.set(false);
lazy.log.info(`${SLUG}: no default profile targeting snapshot exists`);
} else {
lazy.log.warn(
`${SLUG}: ignoring exception reading default profile targeting snapshot`,
f
);
}
}
return defaultProfileTargetingSnapshot;
},
};
BackgroundUpdate.REASON = {

Просмотреть файл

@ -46,6 +46,116 @@ background_update:
- events
- baseline
targeting_exists:
type: boolean
description: >
True if the default profile had a targeting snapshot serialized to disk,
and there was no exception thrown reading it.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_sensitivity:
- technical
notification_emails:
- install-update@mozilla.com
expires: never
send_in_pings:
- background-update
targeting_exception:
type: boolean
description: >
True if the default profile had a targeting snapshot serialized to disk,
but an exception was thrown reading it.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_sensitivity:
- technical
notification_emails:
- install-update@mozilla.com
expires: never
send_in_pings:
- background-update
targeting_version:
type: quantity
unit: version number
description: >
If the default profile had a targeting snapshot serialized to disk, the
`version` of the snapshot.
This version number does not have a physical unit: it's only useful to
compare between versions.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_sensitivity:
- technical
notification_emails:
- install-update@mozilla.com
expires: never
send_in_pings:
- background-update
targeting_env_firefox_version:
type: quantity
unit: version number
description: >
The `environment.firefoxVersion` of the default profile's serialized
targeting snapshot. At the time of writing, this version is an integer
representing the Firefox major version, e.g., `109`.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_sensitivity:
- interaction
notification_emails:
- install-update@mozilla.com
expires: never
send_in_pings:
- background-update
targeting_env_current_date:
type: datetime
time_unit: day
description: >
The `environment.currentDate` of the default profile's serialized
targeting snapshot.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_sensitivity:
- interaction
notification_emails:
- install-update@mozilla.com
expires: never
send_in_pings:
- background-update
targeting_env_profile_age:
type: datetime
time_unit: day
description: >
The `environment.profileAgeCreated` of the default profile's serialized
targeting snapshot.
bugs:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_reviews:
- https://bugzilla.mozilla.org/show_bug.cgi?id=1795467
data_sensitivity:
- interaction
notification_emails:
- install-update@mozilla.com
expires: never
send_in_pings:
- background-update
final_state:
type: string
description: >

Просмотреть файл

@ -6,6 +6,10 @@
"use strict";
const { ASRouterTargeting } = ChromeUtils.import(
"resource://activity-stream/lib/ASRouterTargeting.jsm"
);
const { BackgroundUpdate } = ChromeUtils.import(
"resource://gre/modules/BackgroundUpdate.jsm"
);
@ -21,12 +25,14 @@ XPCOMUtils.defineLazyServiceGetter(
"nsIApplicationUpdateService"
);
add_task(function test_setup() {
add_setup(function test_setup() {
// FOG needs a profile directory to put its data in.
do_get_profile();
// We need to initialize it once, otherwise operations will be stuck in the pre-init queue.
Services.fog.initializeFOG();
setupProfileService();
});
add_task(async function test_record_update_environment() {
@ -88,3 +94,129 @@ add_task(async function test_record_update_environment() {
ok(pingSubmitted, "'background-update' ping was submitted");
});
async function do_readTargeting(content, beforeNextSubmitCallback) {
let profileService = Cc["@mozilla.org/toolkit/profile-service;1"].getService(
Ci.nsIToolkitProfileService
);
let file = do_get_profile();
file.append("profile_cannot_be_locked");
let profile = profileService.createUniqueProfile(
file,
"test_default_profile"
);
let targetingSnapshot = profile.rootDir.clone();
targetingSnapshot.append("targeting.snapshot.json");
if (content) {
await IOUtils.writeUTF8(targetingSnapshot.path, content);
}
let lock = profile.lock({});
Services.fog.testResetFOG();
try {
await BackgroundUpdate.readFirefoxMessagingSystemTargetingSnapshot(lock);
} finally {
lock.unlock();
}
let pingSubmitted = false;
GleanPings.backgroundUpdate.testBeforeNextSubmit(reason => {
pingSubmitted = true;
return beforeNextSubmitCallback(reason);
});
// There's nothing async in this function atm, but it's annotated async, so..
await maybeSubmitBackgroundUpdatePing();
ok(pingSubmitted, "'background-update' ping was submitted");
}
// Missing targeting is anticipated.
add_task(async function test_targeting_missing() {
await do_readTargeting(null, reason => {
Assert.equal(false, Glean.backgroundUpdate.targetingExists.testGetValue());
Assert.equal(
false,
Glean.backgroundUpdate.targetingException.testGetValue()
);
});
});
// Malformed JSON yields an exception.
add_task(async function test_targeting_exception() {
await do_readTargeting("{", reason => {
Assert.equal(false, Glean.backgroundUpdate.targetingExists.testGetValue());
Assert.equal(
true,
Glean.backgroundUpdate.targetingException.testGetValue()
);
});
});
// Well formed targeting values are reflected into the Glean telemetry.
add_task(async function test_targeting_exists() {
// We can't take a full environment snapshot under `xpcshell`; these are just
// the items we need.
let target = {
currentDate: ASRouterTargeting.Environment.currentDate,
profileAgeCreated: ASRouterTargeting.Environment.profileAgeCreated,
firefoxVersion: ASRouterTargeting.Environment.firefoxVersion,
};
let targetSnapshot = await ASRouterTargeting.getEnvironmentSnapshot(target);
await do_readTargeting(JSON.stringify(targetSnapshot), reason => {
Assert.equal(true, Glean.backgroundUpdate.targetingExists.testGetValue());
Assert.equal(
false,
Glean.backgroundUpdate.targetingException.testGetValue()
);
// `environment.firefoxVersion` is a positive integer.
Assert.ok(
Glean.backgroundUpdate.targetingEnvFirefoxVersion.testGetValue() > 0
);
Assert.equal(
targetSnapshot.environment.firefoxVersion,
Glean.backgroundUpdate.targetingEnvFirefoxVersion.testGetValue()
);
let profileAge = Glean.backgroundUpdate.targetingEnvProfileAge.testGetValue();
Assert.ok(profileAge instanceof Date);
Assert.ok(0 < profileAge.getTime());
Assert.ok(profileAge.getTime() < Date.now());
// `environment.profileAgeCreated` is an integer, milliseconds since the
// Unix epoch.
let targetProfileAge = new Date(
targetSnapshot.environment.profileAgeCreated
);
// Our `time_unit: day` has Glean round to the nearest day *in the local
// timezone*, so we must do the same.
targetProfileAge.setHours(0, 0, 0, 0);
Assert.equal(targetProfileAge.toISOString(), profileAge.toISOString());
let currentDate = Glean.backgroundUpdate.targetingEnvCurrentDate.testGetValue();
Assert.ok(0 < currentDate.getTime());
Assert.ok(currentDate.getTime() < Date.now());
// `environment.currentDate` is in ISO string format.
let targetCurrentDate = new Date(targetSnapshot.environment.currentDate);
// Our `time_unit: day` has Glean round to the nearest day *in the local
// timezone*, so we must do the same.
targetCurrentDate.setHours(0, 0, 0, 0);
Assert.equal(targetCurrentDate.toISOString(), currentDate.toISOString());
});
});