Bug 1743465: send additional information about existing installations in installation.first_seen Telemetry event. r=nalexander

This patch does two things:
1) Adds a few new pieces of information to the installation "first_seen" event. Specifically:
    * other_inst - true when any non-MSIX installation other than the running one was present when this event was prepared
    * other_msix_inst - true when any MSIX installation other that the running one was present when this event was prepared
2) Begins sending this event for MSIX installations

Differential Revision: https://phabricator.services.mozilla.com/D134326
This commit is contained in:
Ben Hearsum 2022-01-20 23:34:52 +00:00
parent b9f8a703ea
commit 3e28c91b15
3 changed files with 149 additions and 47 deletions

View File

@ -24,6 +24,8 @@ XPCOMUtils.defineLazyModuleGetters(this, {
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.jsm",
Services: "resource://gre/modules/Services.jsm",
WindowsInstallsInfo:
"resource://gre/modules/components-utils/WindowsInstallsInfo.jsm",
setTimeout: "resource://gre/modules/Timer.jsm",
setInterval: "resource://gre/modules/Timer.jsm",
clearTimeout: "resource://gre/modules/Timer.jsm",
@ -1196,72 +1198,148 @@ let BrowserUsageTelemetry = {
* if so then send installation telemetry.
*
* @param {nsIFile} [dataPathOverride] Optional, full data file path, for tests.
* @param {Array<string>} [msixPackagePrefixes] Optional, list of prefixes to
consider "existing" installs when looking at installed MSIX packages.
Defaults to prefixes for builds produced in Firefox automation.
* @return {Promise}
* @resolves When the event has been recorded, or if the data file was not found.
* @rejects JavaScript exception on any failure.
*/
async reportInstallationTelemetry(dataPathOverride) {
async reportInstallationTelemetry(
dataPathOverride,
msixPackagePrefixes = ["Mozilla.Firefox", "Mozilla.MozillaFirefox"]
) {
if (AppConstants.platform != "win") {
// This is a windows-only feature.
return;
}
let dataPath = dataPathOverride;
if (!dataPath) {
dataPath = Services.dirsvc.get("GreD", Ci.nsIFile);
dataPath.append("installation_telemetry.json");
}
let dataBytes;
try {
dataBytes = await IOUtils.read(dataPath.path);
} catch (ex) {
if (ex.name == "NotFoundError") {
// Many systems will not have the data file, return silently if not found as
// there is nothing to record.
return;
}
throw ex;
}
const dataString = new TextDecoder("utf-16").decode(dataBytes);
const data = JSON.parse(dataString);
const TIMESTAMP_PREF = "app.installation.timestamp";
const lastInstallTime = Services.prefs.getStringPref(TIMESTAMP_PREF, null);
const wpm = Cc["@mozilla.org/windows-package-manager;1"].createInstance(
Ci.nsIWindowsPackageManager
);
let installer_type = "";
let pfn;
try {
pfn = Services.sysinfo.getProperty("winPackageFamilyName");
} catch (e) {}
if (lastInstallTime && data.install_timestamp == lastInstallTime) {
// We've already seen this install
return;
function getInstallData() {
// We only care about where _any_ other install existed - no
// need to count more than 1.
const installPaths = WindowsInstallsInfo.getInstallPaths(
1,
new Set([Services.dirsvc.get("GreBinD", Ci.nsIFile).path])
);
const msixInstalls = new Set();
// We're just going to eat all errors here -- we don't want the event
// to go unsent if we were unable to look for MSIX installs.
try {
wpm
.findUserInstalledPackages(msixPackagePrefixes)
.forEach(i => msixInstalls.add(i));
if (pfn) {
msixInstalls.delete(pfn);
}
} catch (ex) {}
return {
installPaths,
msixInstalls,
};
}
// First time seeing this install, record the timestamp.
Services.prefs.setStringPref(TIMESTAMP_PREF, data.install_timestamp);
let extra = {};
// Installation timestamp is not intended to be sent with telemetry,
// remove it to emphasize this point.
delete data.install_timestamp;
if (pfn) {
if (lastInstallTime != null) {
// We've already seen this install
return;
}
// Build the extra event data
let extra = {
version: data.version,
build_id: data.build_id,
admin_user: data.admin_user.toString(),
install_existed: data.install_existed.toString(),
profdir_existed: data.profdir_existed.toString(),
};
// First time seeing this install, record the timestamp.
Services.prefs.setStringPref(TIMESTAMP_PREF, wpm.getInstalledDate());
let install_data = getInstallData();
if (data.installer_type == "full") {
extra.silent = data.silent.toString();
extra.from_msi = data.from_msi.toString();
extra.default_path = data.default_path.toString();
installer_type = "msix";
// Build the extra event data
extra.version = AppConstants.MOZ_APP_VERSION;
extra.build_id = AppConstants.MOZ_BUILDID;
// The next few keys are static for the reasons described
// No way to detect whether or not we were installed by an admin
extra.admin_user = "false";
// Always false at the moment, because we create a new profile
// on first launch
extra.profdir_existed = "false";
// Obviously false for MSIX installs
extra.from_msi = "false";
// We have no way of knowing whether we were installed via the GUI,
// through the command line, or some Enterprise management tool.
extra.silent = "false";
// There's no way to change the install path for an MSIX package
extra.default_path = "true";
extra.install_existed = install_data.msixInstalls.has(pfn).toString();
install_data.msixInstalls.delete(pfn);
extra.other_inst = (!!install_data.installPaths.size).toString();
extra.other_msix_inst = (!!install_data.msixInstalls.size).toString();
} else {
let dataPath = dataPathOverride;
if (!dataPath) {
dataPath = Services.dirsvc.get("GreD", Ci.nsIFile);
dataPath.append("installation_telemetry.json");
}
let dataBytes;
try {
dataBytes = await IOUtils.read(dataPath.path);
} catch (ex) {
if (ex.name == "NotFoundError") {
// Many systems will not have the data file, return silently if not found as
// there is nothing to record.
return;
}
throw ex;
}
const dataString = new TextDecoder("utf-16").decode(dataBytes);
const data = JSON.parse(dataString);
if (lastInstallTime && data.install_timestamp == lastInstallTime) {
// We've already seen this install
return;
}
// First time seeing this install, record the timestamp.
Services.prefs.setStringPref(TIMESTAMP_PREF, data.install_timestamp);
let install_data = getInstallData();
installer_type = data.installer_type;
// Installation timestamp is not intended to be sent with telemetry,
// remove it to emphasize this point.
delete data.install_timestamp;
// Build the extra event data
extra.version = data.version;
extra.build_id = data.build_id;
extra.admin_user = data.admin_user.toString();
extra.install_existed = data.install_existed.toString();
extra.profdir_existed = data.profdir_existed.toString();
extra.other_inst = (!!install_data.installPaths.size).toString();
extra.other_msix_inst = (!!install_data.msixInstalls.size).toString();
if (data.installer_type == "full") {
extra.silent = data.silent.toString();
extra.from_msi = data.from_msi.toString();
extra.default_path = data.default_path.toString();
}
}
// Record the event
Services.telemetry.setEventRecordingEnabled("installation", true);
Services.telemetry.recordEvent(
"installation",
"first_seen",
data.installer_type,
installer_type,
null,
extra
);

View File

@ -3,6 +3,9 @@
*/
"use strict";
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { BrowserUsageTelemetry } = ChromeUtils.import(
"resource:///modules/BrowserUsageTelemetry.jsm"
);
@ -36,7 +39,7 @@ function writeJsonUtf16(fileName, obj) {
async function runReport(
dataFile,
installType,
{ clearTS, setTS, assertRejects, expectExtra, expectTS }
{ clearTS, setTS, assertRejects, expectExtra, expectTS, msixPrefixes }
) {
// Setup timestamp
if (clearTS) {
@ -55,8 +58,13 @@ async function runReport(
BrowserUsageTelemetry.reportInstallationTelemetry(dataFile),
assertRejects
);
} else {
} else if (!msixPrefixes) {
await BrowserUsageTelemetry.reportInstallationTelemetry(dataFile);
} else {
await BrowserUsageTelemetry.reportInstallationTelemetry(
dataFile,
msixPrefixes
);
}
// Check events
@ -105,6 +113,8 @@ add_task(async function testInstallationTelemetry() {
build_id: "123",
admin_user: "true",
install_existed: "false",
other_inst: "false",
other_msix_inst: "false",
profdir_existed: "false",
};
@ -145,6 +155,8 @@ add_task(async function testInstallationTelemetry() {
build_id: "123",
admin_user: "false",
install_existed: "true",
other_inst: "false",
other_msix_inst: "false",
profdir_existed: "true",
silent: "false",
from_msi: "false",
@ -161,12 +173,18 @@ add_task(async function testInstallationTelemetry() {
// Check that it doesn't generate another event when the timestamp is unchanged
await runReport(dataFile, "full", { expectTS: "1" });
// New timestamp
// New timestamp and a check to make sure we can find installed MSIX packages
// by overriding the prefixes a bit further down.
fullData.install_timestamp = "2";
// This check only works on Windows 10 and above
if (AppConstants.isPlatformAndVersionAtLeast("win", "10")) {
fullExtra.other_msix_inst = "true";
}
await writeJsonUtf16(dataFilePath, fullData);
await runReport(dataFile, "full", {
expectExtra: fullExtra,
expectTS: "2",
msixPrefixes: ["Microsoft"],
});
// Missing field
@ -184,4 +202,7 @@ add_task(async function testInstallationTelemetry() {
// Missing file, should return with no exception
await IOUtils.remove(dataFilePath);
await runReport(dataFile, "stub", { setTS: "3", expectTS: "3" });
// bug 1750581 tracks testing this when we're able to run tests in
// an MSIX package environment
});

View File

@ -2964,6 +2964,7 @@ installation:
objects:
- full # if the full installer was run directly
- stub # if the stub installer was used
- msix # if the installation was done through an MSIX package
release_channel_collection: opt-out
record_in_processes: ["main"]
products: ["firefox"]
@ -2973,11 +2974,13 @@ installation:
build_id: The build ID of the application installed by the installer (not necessarily the current version)
admin_user: Whether the installer is running from an elevated admin user
install_existed: Whether there was already an install in this location
other_inst: Whether there was already any non-MSIX install on this system
other_msix_inst: Whether there was already any MSIX install on this system
profdir_existed: Whether the top-level profile directory existed
silent: '(optional, present if object is "full") Whether this was a silent install'
from_msi: '(optional, present if object is "full") Whether this was an MSI install'
default_path: '(optional, present if object is "full") Whether the default path was used'
bug_numbers: [1660198, 1725295]
bug_numbers: [1660198, 1725295, 1743465]
notification_emails:
- application-update-telemetry-alerts@mozilla.com
- rtestard@mozilla.com