forked from mirrors/gecko-dev
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:
parent
b9f8a703ea
commit
3e28c91b15
3 changed files with 149 additions and 47 deletions
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue