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", PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.jsm", SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.jsm",
Services: "resource://gre/modules/Services.jsm", Services: "resource://gre/modules/Services.jsm",
WindowsInstallsInfo:
"resource://gre/modules/components-utils/WindowsInstallsInfo.jsm",
setTimeout: "resource://gre/modules/Timer.jsm", setTimeout: "resource://gre/modules/Timer.jsm",
setInterval: "resource://gre/modules/Timer.jsm", setInterval: "resource://gre/modules/Timer.jsm",
clearTimeout: "resource://gre/modules/Timer.jsm", clearTimeout: "resource://gre/modules/Timer.jsm",
@ -1196,16 +1198,92 @@ let BrowserUsageTelemetry = {
* if so then send installation telemetry. * if so then send installation telemetry.
* *
* @param {nsIFile} [dataPathOverride] Optional, full data file path, for tests. * @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} * @return {Promise}
* @resolves When the event has been recorded, or if the data file was not found. * @resolves When the event has been recorded, or if the data file was not found.
* @rejects JavaScript exception on any failure. * @rejects JavaScript exception on any failure.
*/ */
async reportInstallationTelemetry(dataPathOverride) { async reportInstallationTelemetry(
dataPathOverride,
msixPackagePrefixes = ["Mozilla.Firefox", "Mozilla.MozillaFirefox"]
) {
if (AppConstants.platform != "win") { if (AppConstants.platform != "win") {
// This is a windows-only feature. // This is a windows-only feature.
return; return;
} }
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) {}
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,
};
}
let extra = {};
if (pfn) {
if (lastInstallTime != null) {
// We've already seen this install
return;
}
// First time seeing this install, record the timestamp.
Services.prefs.setStringPref(TIMESTAMP_PREF, wpm.getInstalledDate());
let install_data = getInstallData();
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; let dataPath = dataPathOverride;
if (!dataPath) { if (!dataPath) {
dataPath = Services.dirsvc.get("GreD", Ci.nsIFile); dataPath = Services.dirsvc.get("GreD", Ci.nsIFile);
@ -1226,9 +1304,6 @@ let BrowserUsageTelemetry = {
const dataString = new TextDecoder("utf-16").decode(dataBytes); const dataString = new TextDecoder("utf-16").decode(dataBytes);
const data = JSON.parse(dataString); const data = JSON.parse(dataString);
const TIMESTAMP_PREF = "app.installation.timestamp";
const lastInstallTime = Services.prefs.getStringPref(TIMESTAMP_PREF, null);
if (lastInstallTime && data.install_timestamp == lastInstallTime) { if (lastInstallTime && data.install_timestamp == lastInstallTime) {
// We've already seen this install // We've already seen this install
return; return;
@ -1236,32 +1311,35 @@ let BrowserUsageTelemetry = {
// First time seeing this install, record the timestamp. // First time seeing this install, record the timestamp.
Services.prefs.setStringPref(TIMESTAMP_PREF, data.install_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, // Installation timestamp is not intended to be sent with telemetry,
// remove it to emphasize this point. // remove it to emphasize this point.
delete data.install_timestamp; delete data.install_timestamp;
// Build the extra event data // Build the extra event data
let extra = { extra.version = data.version;
version: data.version, extra.build_id = data.build_id;
build_id: data.build_id, extra.admin_user = data.admin_user.toString();
admin_user: data.admin_user.toString(), extra.install_existed = data.install_existed.toString();
install_existed: data.install_existed.toString(), extra.profdir_existed = data.profdir_existed.toString();
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") { if (data.installer_type == "full") {
extra.silent = data.silent.toString(); extra.silent = data.silent.toString();
extra.from_msi = data.from_msi.toString(); extra.from_msi = data.from_msi.toString();
extra.default_path = data.default_path.toString(); extra.default_path = data.default_path.toString();
} }
}
// Record the event // Record the event
Services.telemetry.setEventRecordingEnabled("installation", true); Services.telemetry.setEventRecordingEnabled("installation", true);
Services.telemetry.recordEvent( Services.telemetry.recordEvent(
"installation", "installation",
"first_seen", "first_seen",
data.installer_type, installer_type,
null, null,
extra extra
); );

View file

@ -3,6 +3,9 @@
*/ */
"use strict"; "use strict";
const { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm"
);
const { BrowserUsageTelemetry } = ChromeUtils.import( const { BrowserUsageTelemetry } = ChromeUtils.import(
"resource:///modules/BrowserUsageTelemetry.jsm" "resource:///modules/BrowserUsageTelemetry.jsm"
); );
@ -36,7 +39,7 @@ function writeJsonUtf16(fileName, obj) {
async function runReport( async function runReport(
dataFile, dataFile,
installType, installType,
{ clearTS, setTS, assertRejects, expectExtra, expectTS } { clearTS, setTS, assertRejects, expectExtra, expectTS, msixPrefixes }
) { ) {
// Setup timestamp // Setup timestamp
if (clearTS) { if (clearTS) {
@ -55,8 +58,13 @@ async function runReport(
BrowserUsageTelemetry.reportInstallationTelemetry(dataFile), BrowserUsageTelemetry.reportInstallationTelemetry(dataFile),
assertRejects assertRejects
); );
} else { } else if (!msixPrefixes) {
await BrowserUsageTelemetry.reportInstallationTelemetry(dataFile); await BrowserUsageTelemetry.reportInstallationTelemetry(dataFile);
} else {
await BrowserUsageTelemetry.reportInstallationTelemetry(
dataFile,
msixPrefixes
);
} }
// Check events // Check events
@ -105,6 +113,8 @@ add_task(async function testInstallationTelemetry() {
build_id: "123", build_id: "123",
admin_user: "true", admin_user: "true",
install_existed: "false", install_existed: "false",
other_inst: "false",
other_msix_inst: "false",
profdir_existed: "false", profdir_existed: "false",
}; };
@ -145,6 +155,8 @@ add_task(async function testInstallationTelemetry() {
build_id: "123", build_id: "123",
admin_user: "false", admin_user: "false",
install_existed: "true", install_existed: "true",
other_inst: "false",
other_msix_inst: "false",
profdir_existed: "true", profdir_existed: "true",
silent: "false", silent: "false",
from_msi: "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 // Check that it doesn't generate another event when the timestamp is unchanged
await runReport(dataFile, "full", { expectTS: "1" }); 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"; 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 writeJsonUtf16(dataFilePath, fullData);
await runReport(dataFile, "full", { await runReport(dataFile, "full", {
expectExtra: fullExtra, expectExtra: fullExtra,
expectTS: "2", expectTS: "2",
msixPrefixes: ["Microsoft"],
}); });
// Missing field // Missing field
@ -184,4 +202,7 @@ add_task(async function testInstallationTelemetry() {
// Missing file, should return with no exception // Missing file, should return with no exception
await IOUtils.remove(dataFilePath); await IOUtils.remove(dataFilePath);
await runReport(dataFile, "stub", { setTS: "3", expectTS: "3" }); 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: objects:
- full # if the full installer was run directly - full # if the full installer was run directly
- stub # if the stub installer was used - stub # if the stub installer was used
- msix # if the installation was done through an MSIX package
release_channel_collection: opt-out release_channel_collection: opt-out
record_in_processes: ["main"] record_in_processes: ["main"]
products: ["firefox"] products: ["firefox"]
@ -2973,11 +2974,13 @@ installation:
build_id: The build ID of the application installed by the installer (not necessarily the current version) 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 admin_user: Whether the installer is running from an elevated admin user
install_existed: Whether there was already an install in this location 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 profdir_existed: Whether the top-level profile directory existed
silent: '(optional, present if object is "full") Whether this was a silent install' 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' 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' 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: notification_emails:
- application-update-telemetry-alerts@mozilla.com - application-update-telemetry-alerts@mozilla.com
- rtestard@mozilla.com - rtestard@mozilla.com