Bug 1522934: Add telemetry data to record what happened during profile selection at startup. r=froydnj, datareview=chutten
☠☠ backed out by b4d8ca47f938 ☠ ☠
authorDave Townsend <dtownsend@oxymoronical.com>
Fri, 25 Jan 2019 14:05:39 -0800
changeset 456184 ebcd8225434ae82b837d632b5ef44bcc9dd5c5b0
parent 456183 e69cac07b209ad4ef4229815ffd8138ed64c348e
child 456185 05200c5388b4f7adc4414268727458515d7b9ed9
push id35474
push useropoprus@mozilla.com
push dateThu, 31 Jan 2019 09:37:52 +0000
treeherdermozilla-central@9ee54a21a22a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfroydnj
bugs1522934
milestone67.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1522934: Add telemetry data to record what happened during profile selection at startup. r=froydnj, datareview=chutten Set a telemetry scalar depending on the path taken during profile selection at startup. Differential Revision: https://phabricator.services.mozilla.com/D17696
toolkit/components/telemetry/Scalars.yaml
toolkit/profile/nsToolkitProfileService.cpp
toolkit/profile/nsToolkitProfileService.h
toolkit/profile/xpcshell/head.js
toolkit/profile/xpcshell/test_create_default.js
toolkit/profile/xpcshell/test_new_default.js
toolkit/profile/xpcshell/test_profile_reset.js
toolkit/profile/xpcshell/test_select_default.js
toolkit/profile/xpcshell/test_select_environment.js
toolkit/profile/xpcshell/test_select_environment_named.js
toolkit/profile/xpcshell/test_select_named.js
toolkit/profile/xpcshell/test_single_profile_selected.js
toolkit/profile/xpcshell/test_single_profile_unselected.js
toolkit/profile/xpcshell/test_snap.js
toolkit/profile/xpcshell/test_snap_empty.js
toolkit/profile/xpcshell/test_steal_inuse.js
toolkit/profile/xpcshell/test_update_selected_dedicated.js
toolkit/profile/xpcshell/test_update_unknown_dedicated.js
toolkit/profile/xpcshell/test_update_unselected_dedicated.js
toolkit/profile/xpcshell/test_use_dedicated.js
toolkit/xre/nsAppRunner.cpp
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -2351,16 +2351,56 @@ encoding:
     kind: boolean
     notification_emails:
       - hsivonen@mozilla.com
     release_channel_collection: opt-out
     record_in_processes:
       - main
       - content
 
+startup:
+  profile_selection_reason:
+    bug_numbers:
+      - 1522934
+    description: >
+      How the profile was selected during startup. One of the following reasons:
+        unknown:
+          Generally should not happen, set as a default in case no other reason
+          occured.
+        profile-manager:
+          The profile was selected by the profile manager.
+        profile-reset:
+          The profile was selected for reset, normally this would mean a restart.
+        restart:
+          The user restarted the application, the same profile as previous will
+          be used.
+        argument-profile
+          The profile was selected by the --profile command line argument.
+        argument-p
+          The profile was selected by the -p command line argument.
+        firstrun-claimed-default
+          A first run of a dedicated profiles build chose the old default
+          profile to be the default for this install.
+        firstrun-skipped-default:
+          A first run of a dedicated profiles build skipped over the old default
+          profile and created a new profile.
+        firstrun-created-default:
+          A first run of the application created a new profile to use.
+        default:
+          The default profile was selected as normal.
+    expires: "72"
+    keyed: false
+    kind: string
+    notification_emails:
+      - dtownsend@mozilla.com
+      - rtestard@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
 # The following section is for probes testing the Telemetry system. They will not be
 # submitted in pings and are only used for testing.
 telemetry.test:
   unsigned_int_kind:
     bug_numbers:
       - 1276190
     description: >
       This is a test uint type with a really long description, maybe spanning even multiple
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -40,16 +40,17 @@
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsNativeCharsetUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Sprintf.h"
 #include "nsPrintfCString.h"
 #include "mozilla/UniquePtr.h"
 #include "nsIToolkitShellService.h"
+#include "mozilla/Telemetry.h"
 
 using namespace mozilla;
 
 #define DEV_EDITION_NAME "dev-edition-default"
 #define DEFAULT_NAME "default"
 
 nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
                                    nsIFile* aLocalDir, nsToolkitProfile* aPrev)
@@ -297,25 +298,35 @@ nsToolkitProfileService::nsToolkitProfil
       mStartWithLast(true),
       mIsFirstRun(true),
       mUseDevEditionProfile(false),
 #ifdef MOZ_DEDICATED_PROFILES
       mUseDedicatedProfile(!IsSnapEnvironment()),
 #else
       mUseDedicatedProfile(false),
 #endif
-      mCreatedAlternateProfile(false) {
+      mCreatedAlternateProfile(false),
+      mStartupReason(NS_LITERAL_STRING("unknown")) {
 #ifdef MOZ_DEV_EDITION
   mUseDevEditionProfile = true;
 #endif
   gService = this;
 }
 
 nsToolkitProfileService::~nsToolkitProfileService() { gService = nullptr; }
 
+void nsToolkitProfileService::RecordStartupTelemetry() {
+  if (!mStartupProfileSelected) {
+    return;
+  }
+
+  ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_SELECTION_REASON,
+            mStartupReason);
+}
+
 // Tests whether the passed profile was last used by this install.
 bool nsToolkitProfileService::IsProfileForCurrentInstall(
     nsIToolkitProfile* aProfile) {
   nsCOMPtr<nsIFile> profileDir;
   nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
   NS_ENSURE_SUCCESS(rv, false);
 
   nsCOMPtr<nsIFile> compatFile;
@@ -799,16 +810,22 @@ nsToolkitProfileService::SelectStartupPr
     allocated[i].reset(ToNewCString(aArgv[i]));
     argv[i] = allocated[i].get();
   }
   argv[argc] = nullptr;
 
   nsresult rv = SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir,
                                      aLocalDir, aProfile, aDidCreate);
 
+  // Since we were called outside of the normal startup path record the
+  // telemetry now.
+  if (NS_SUCCEEDED(rv)) {
+    RecordStartupTelemetry();
+  }
+
   return rv;
 }
 
 /**
  * Selects or creates a profile to use based on the profiles database, any
  * environment variables and any command line arguments. Will not create
  * a profile if aIsResetting is true. The profile is selected based on this
  * order of preference:
@@ -843,16 +860,24 @@ nsresult nsToolkitProfileService::Select
   // app initiated restart).
   nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
   if (lf) {
     nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
     if (!localDir) {
       localDir = lf;
     }
 
+    if (EnvHasValue("XRE_RESTARTED_BY_PROFILE_MANAGER")) {
+      mStartupReason = NS_LITERAL_STRING("profile-manager");
+    } else if (aIsResetting) {
+      mStartupReason = NS_LITERAL_STRING("profile-reset");
+    } else {
+      mStartupReason = NS_LITERAL_STRING("restart");
+    }
+
     // Clear out flags that we handled (or should have handled!) last startup.
     const char* dummy;
     CheckArg(*aArgc, aArgv, "p", &dummy);
     CheckArg(*aArgc, aArgv, "profile", &dummy);
     CheckArg(*aArgc, aArgv, "profilemanager");
 
     GetProfileByDir(lf, localDir, getter_AddRefs(mCurrent));
     lf.forget(aRootDir);
@@ -888,16 +913,18 @@ nsresult nsToolkitProfileService::Select
       if (!isDir) {
         PR_fprintf(
             PR_STDERR,
             "Error: argument --profile requires a path to a directory\n");
         return NS_ERROR_FAILURE;
       }
     }
 
+    mStartupReason = NS_LITERAL_STRING("argument-profile");
+
     // If a profile path is specified directly on the command line, then
     // assume that the temp directory is the same as the given directory.
     GetProfileByDir(lf, lf, getter_AddRefs(mCurrent));
     NS_ADDREF(*aRootDir = lf);
     lf.forget(aLocalDir);
     NS_IF_ADDREF(*aProfile = mCurrent);
     return NS_OK;
   }
@@ -963,16 +990,18 @@ nsresult nsToolkitProfileService::Select
       PR_fprintf(
           PR_STDERR,
           "Error: argument -p is invalid when argument --osint is specified\n");
       return NS_ERROR_FAILURE;
     }
 
     rv = GetProfileByName(nsDependentCString(arg), getter_AddRefs(mCurrent));
     if (NS_SUCCEEDED(rv)) {
+      mStartupReason = NS_LITERAL_STRING("argument-p");
+
       mCurrent->GetRootDir(aRootDir);
       mCurrent->GetLocalDir(aLocalDir);
 
       NS_ADDREF(*aProfile = mCurrent);
       return NS_OK;
     }
 
     return NS_ERROR_SHOW_PROFILE_MANAGER;
@@ -1006,16 +1035,18 @@ nsresult nsToolkitProfileService::Select
 
       // Find what would have been the default profile for old installs.
       nsCOMPtr<nsIToolkitProfile> profile = mNormalDefault;
       if (mUseDevEditionProfile) {
         profile = mDevEditionDefault;
       }
 
       if (profile && MaybeMakeDefaultDedicatedProfile(profile)) {
+        mStartupReason = NS_LITERAL_STRING("firstrun-claimed-default");
+
         mCurrent = profile;
         profile->GetRootDir(aRootDir);
         profile->GetLocalDir(aLocalDir);
         profile.forget(aProfile);
         return NS_OK;
       }
 
       // We're going to create a new profile for this install. If there was a
@@ -1052,16 +1083,22 @@ nsresult nsToolkitProfileService::Select
       if ((mUseDedicatedProfile || mUseDevEditionProfile) && mFirst &&
           !mFirst->mNext) {
         CreateProfile(nullptr, NS_LITERAL_CSTRING(DEFAULT_NAME),
                       getter_AddRefs(mNormalDefault));
       }
 
       Flush();
 
+      if (mCreatedAlternateProfile) {
+        mStartupReason = NS_LITERAL_STRING("firstrun-skipped-default");
+      } else {
+        mStartupReason = NS_LITERAL_STRING("firstrun-created-default");
+      }
+
       // Use the new profile.
       mCurrent->GetRootDir(aRootDir);
       mCurrent->GetLocalDir(aLocalDir);
       NS_ADDREF(*aProfile = mCurrent);
 
       *aDidCreate = true;
       return NS_OK;
     }
@@ -1075,16 +1112,18 @@ nsresult nsToolkitProfileService::Select
   GetDefaultProfile(getter_AddRefs(mCurrent));
 
   // None of the profiles was marked as default (generally only happens if the
   // user modifies profiles.ini manually). Let the user choose.
   if (!mCurrent) {
     return NS_ERROR_SHOW_PROFILE_MANAGER;
   }
 
+  mStartupReason = NS_LITERAL_STRING("default");
+
   // Use the selected profile.
   mCurrent->GetRootDir(aRootDir);
   mCurrent->GetLocalDir(aLocalDir);
   NS_ADDREF(*aProfile = mCurrent);
 
   return NS_OK;
 }
 
--- a/toolkit/profile/nsToolkitProfileService.h
+++ b/toolkit/profile/nsToolkitProfileService.h
@@ -74,16 +74,17 @@ class nsToolkitProfileService final : pu
   NS_DECL_ISUPPORTS
   NS_DECL_NSITOOLKITPROFILESERVICE
 
   nsresult SelectStartupProfile(int* aArgc, char* aArgv[], bool aIsResetting,
                                 nsIFile** aRootDir, nsIFile** aLocalDir,
                                 nsIToolkitProfile** aProfile, bool* aDidCreate);
   nsresult CreateResetProfile(nsIToolkitProfile** aNewProfile);
   nsresult ApplyResetProfile(nsIToolkitProfile* aOldProfile);
+  void RecordStartupTelemetry();
 
  private:
   friend class nsToolkitProfile;
   friend class nsToolkitProfileFactory;
   friend nsresult NS_NewToolkitProfileService(nsIToolkitProfileService**);
 
   nsToolkitProfileService();
   ~nsToolkitProfileService();
@@ -136,16 +137,17 @@ class nsToolkitProfileService final : pu
   bool mIsFirstRun;
   // True if the default profile is the separate dev-edition-profile.
   bool mUseDevEditionProfile;
   // True if this install should use a dedicated default profile.
   const bool mUseDedicatedProfile;
   // True if during startup no dedicated profile was already selected, an old
   // default profile existed but was rejected so a new profile was created.
   bool mCreatedAlternateProfile;
+  nsString mStartupReason;
 
   static nsToolkitProfileService* gService;
 
   class ProfileEnumerator final : public nsSimpleEnumerator {
    public:
     NS_DECL_NSISIMPLEENUMERATOR
 
     const nsID& DefaultInterface() override {
--- a/toolkit/profile/xpcshell/head.js
+++ b/toolkit/profile/xpcshell/head.js
@@ -2,16 +2,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 const { FileUtils } = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 const { AppConstants } = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+const { TelemetryTestUtils } = ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm");
 
 const NS_ERROR_START_PROFILE_MANAGER = 0x805800c9;
 
 let gProfD = do_get_profile();
 let gDataHome = gProfD.clone();
 gDataHome.append("data");
 gDataHome.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
 let gDataHomeLocal = gProfD.clone();
@@ -112,16 +113,17 @@ function selectStartupProfile(args = [],
     didCreate,
   };
 }
 
 function testStartsProfileManager(args = [], isResetting = false) {
   try {
     selectStartupProfile(args, isResetting);
     Assert.ok(false, "Should have started the profile manager");
+    checkStartupReason();
   } catch (e) {
     Assert.equal(e.result, NS_ERROR_START_PROFILE_MANAGER, "Should have started the profile manager");
   }
 }
 
 function safeGet(ini, section, key) {
   try {
     return ini.getString(section, key);
@@ -356,8 +358,24 @@ function checkProfileService(profileData
 /**
  * Asynchronously reads an nsIFile from disk.
  */
 async function readFile(file) {
   let decoder = new TextDecoder();
   let data = await OS.File.read(file.path);
   return decoder.decode(data);
 }
+
+function checkStartupReason(expected = undefined) {
+  const tId = "startup.profile_selection_reason";
+  let scalars = TelemetryTestUtils.getParentProcessScalars(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTOUT);
+
+  if (expected === undefined) {
+    Assert.ok(!(tId in scalars), "Startup telemetry should not have been recorded.");
+    return;
+  }
+
+  if (tId in scalars) {
+    Assert.equal(scalars[tId], expected, "Should have seen the right startup reason.");
+  } else {
+    Assert.ok(false, "Startup telemetry should have been recorded.");
+  }
+}
--- a/toolkit/profile/xpcshell/test_create_default.js
+++ b/toolkit/profile/xpcshell/test_create_default.js
@@ -1,16 +1,18 @@
 /*
  * Tests that from an empty database a default profile is created.
  */
 
 add_task(async () => {
   let service = getProfileService();
   let { profile, didCreate } = selectStartupProfile();
 
+  checkStartupReason("firstrun-created-default");
+
   let profileData = readProfilesIni();
   let installData = readInstallsIni();
   checkProfileService(profileData, installData);
 
   Assert.ok(didCreate, "Should have created a new profile.");
   Assert.equal(profile, service.defaultProfile, "Should now be the default profile.");
   Assert.equal(profile.name, DEDICATED_NAME, "Should have created a new profile with the right name.");
   Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
--- a/toolkit/profile/xpcshell/test_new_default.js
+++ b/toolkit/profile/xpcshell/test_new_default.js
@@ -22,16 +22,17 @@ add_task(async () => {
     }, {
       name: "dev-edition-default",
       path: devDefaultProfile.leafName,
     }],
   });
 
   let service = getProfileService();
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-claimed-default");
 
   let hash = xreDirProvider.getInstallHash();
   let profileData = readProfilesIni();
   let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 3, "Should have the right number of profiles.");
 
--- a/toolkit/profile/xpcshell/test_profile_reset.js
+++ b/toolkit/profile/xpcshell/test_profile_reset.js
@@ -1,15 +1,17 @@
 /*
  * Tests that from an empty database profile reset doesn't create a new profile.
  */
 
 add_task(async () => {
   let service = getProfileService();
 
   let { profile, didCreate } = selectStartupProfile([], true);
+  // Profile reset will normally end up in a restart.
+  checkStartupReason("unknown");
   checkProfileService();
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
   Assert.ok(!profile, "Should not be a returned profile.");
   Assert.equal(service.profileCount, 0, "Still should be no profiles.");
   Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
 });
--- a/toolkit/profile/xpcshell/test_select_default.js
+++ b/toolkit/profile/xpcshell/test_select_default.js
@@ -41,16 +41,17 @@ add_task(async () => {
       default: true,
     });
   }
 
   writeProfilesIni(profileData);
   writeInstallsIni(installData);
 
   let { profile, didCreate } = selectStartupProfile();
+  checkStartupReason("default");
 
   let service = getProfileService();
   checkProfileService(profileData);
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
   Assert.equal(profile, service.defaultProfile, "Should have returned the default profile.");
   Assert.equal(profile.name, "default", "Should have selected the right profile");
   Assert.ok(!service.createdAlternateProfile, "Should not have created an alternate profile.");
--- a/toolkit/profile/xpcshell/test_select_environment.js
+++ b/toolkit/profile/xpcshell/test_select_environment.js
@@ -26,14 +26,15 @@ add_task(async () => {
   checkProfileService(profileData);
 
   let env = Cc["@mozilla.org/process/environment;1"].
             getService(Ci.nsIEnvironment);
   env.set("XRE_PROFILE_PATH", dir.path);
   env.set("XRE_PROFILE_LOCAL_PATH", dir.path);
 
   let { rootDir, localDir, profile, didCreate } = selectStartupProfile();
+  checkStartupReason("restart");
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
   Assert.ok(rootDir.equals(dir), "Should have selected the right root dir.");
   Assert.ok(localDir.equals(dir), "Should have selected the right local dir.");
   Assert.ok(!profile, "No named profile matches this.");
 });
--- a/toolkit/profile/xpcshell/test_select_environment_named.js
+++ b/toolkit/profile/xpcshell/test_select_environment_named.js
@@ -28,15 +28,16 @@ add_task(async () => {
   checkProfileService(profileData);
 
   let env = Cc["@mozilla.org/process/environment;1"].
             getService(Ci.nsIEnvironment);
   env.set("XRE_PROFILE_PATH", root.path);
   env.set("XRE_PROFILE_LOCAL_PATH", local.path);
 
   let { rootDir, localDir, profile, didCreate } = selectStartupProfile(["-P", "Profile3"]);
+  checkStartupReason("restart");
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
   Assert.ok(rootDir.equals(root), "Should have selected the right root dir.");
   Assert.ok(localDir.equals(local), "Should have selected the right local dir.");
   Assert.ok(profile, "A named profile matches this.");
   Assert.equal(profile.name, "Profile1", "The right profile was matched.");
 });
--- a/toolkit/profile/xpcshell/test_select_named.js
+++ b/toolkit/profile/xpcshell/test_select_named.js
@@ -20,12 +20,13 @@ add_task(async () => {
     }],
   };
 
   writeProfilesIni(profileData);
 
   checkProfileService(profileData);
 
   let { profile, didCreate } = selectStartupProfile(["-P", "Profile1"]);
+  checkStartupReason("argument-p");
 
   Assert.ok(!didCreate, "Should not have created a new profile.");
   Assert.equal(profile.name, "Profile1", "Should have chosen the right profile");
 });
--- a/toolkit/profile/xpcshell/test_single_profile_selected.js
+++ b/toolkit/profile/xpcshell/test_single_profile_selected.js
@@ -13,16 +13,17 @@ add_task(async () => {
     profiles: [{
       name: "default",
       path: defaultProfile.leafName,
       default: false,
     }],
   });
 
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-claimed-default");
 
   let hash = xreDirProvider.getInstallHash();
   let profileData = readProfilesIni();
   let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
--- a/toolkit/profile/xpcshell/test_single_profile_unselected.js
+++ b/toolkit/profile/xpcshell/test_single_profile_unselected.js
@@ -33,16 +33,17 @@ add_task(async () => {
   Assert.equal(profile.path, defaultProfile.leafName, "Should be the original default profile.");
   Assert.ok(!profile.default, "Should not be marked as the old-style default.");
 
   Assert.equal(Object.keys(installData.installs).length, 0, "Should be no defaults for installs yet.");
 
   checkProfileService(profileData, installData);
 
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-skipped-default");
   Assert.ok(didCreate, "Should have created a new profile.");
   Assert.ok(service.createdAlternateProfile, "Should have created an alternate profile.");
   Assert.ok(!selectedProfile.rootDir.equals(defaultProfile), "Should be using the right directory.");
   Assert.equal(selectedProfile.name, DEDICATED_NAME);
 
   profileData = readProfilesIni();
 
   profile = profileData.profiles[0];
--- a/toolkit/profile/xpcshell/test_snap.js
+++ b/toolkit/profile/xpcshell/test_snap.js
@@ -17,16 +17,17 @@ add_task(async () => {
       path: defaultProfile.leafName,
       default: true,
     }],
   });
 
   simulateSnapEnvironment();
 
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("default");
 
   let profileData = readProfilesIni();
   let installsINI = gDataHome.clone();
   installsINI.append("installs.ini");
   Assert.ok(!installsINI.exists(), "Installs database should not have been created.");
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
--- a/toolkit/profile/xpcshell/test_snap_empty.js
+++ b/toolkit/profile/xpcshell/test_snap_empty.js
@@ -2,16 +2,17 @@
  * Tests that from a clean slate snap builds create an appropriate profile.
  */
 
 add_task(async () => {
   simulateSnapEnvironment();
 
   let service = getProfileService();
   let { profile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-created-default");
 
   Assert.ok(didCreate, "Should have created a new profile.");
   Assert.equal(profile.name, PROFILE_DEFAULT, "Should have used the normal name.");
   if (AppConstants.MOZ_DEV_EDITION) {
     Assert.equal(service.profileCount, 2, "Should be two profiles.");
   } else {
     Assert.equal(service.profileCount, 1, "Should be only one profile.");
   }
--- a/toolkit/profile/xpcshell/test_steal_inuse.js
+++ b/toolkit/profile/xpcshell/test_steal_inuse.js
@@ -20,16 +20,17 @@ add_task(async () => {
       otherhash: {
         default: defaultProfile.leafName,
       },
     },
   });
 
   let service = getProfileService();
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-claimed-default");
 
   let hash = xreDirProvider.getInstallHash();
   let profileData = readProfilesIni();
   let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
--- a/toolkit/profile/xpcshell/test_update_selected_dedicated.js
+++ b/toolkit/profile/xpcshell/test_update_selected_dedicated.js
@@ -13,16 +13,17 @@ add_task(async () => {
       name: PROFILE_DEFAULT,
       path: defaultProfile.leafName,
       default: true,
     }],
   });
 
   let service = getProfileService();
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-claimed-default");
 
   let hash = xreDirProvider.getInstallHash();
   let profileData = readProfilesIni();
   let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
--- a/toolkit/profile/xpcshell/test_update_unknown_dedicated.js
+++ b/toolkit/profile/xpcshell/test_update_unknown_dedicated.js
@@ -12,16 +12,17 @@ add_task(async () => {
       name: PROFILE_DEFAULT,
       path: defaultProfile.leafName,
       default: true,
     }],
   });
 
   let service = getProfileService();
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-claimed-default");
 
   let profileData = readProfilesIni();
   let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 1, "Should have the right number of profiles.");
 
   let profile = profileData.profiles[0];
--- a/toolkit/profile/xpcshell/test_update_unselected_dedicated.js
+++ b/toolkit/profile/xpcshell/test_update_unselected_dedicated.js
@@ -17,16 +17,17 @@ add_task(async () => {
       name: PROFILE_DEFAULT,
       path: defaultProfile.leafName,
       default: true,
     }],
   });
 
   let service = getProfileService();
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("firstrun-skipped-default");
 
   let profileData = readProfilesIni();
   let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 2, "Should have the right number of profiles.");
 
   // The name ordering is different for dev edition.
--- a/toolkit/profile/xpcshell/test_use_dedicated.js
+++ b/toolkit/profile/xpcshell/test_use_dedicated.js
@@ -32,16 +32,17 @@ add_task(async () => {
       },
       "otherhash": {
         default: "foobar",
       },
     },
   });
 
   let { profile: selectedProfile, didCreate } = selectStartupProfile();
+  checkStartupReason("default");
 
   let profileData = readProfilesIni();
   let installData = readInstallsIni();
 
   Assert.ok(profileData.options.startWithLastProfile, "Should be set to start with the last profile.");
   Assert.equal(profileData.profiles.length, 3, "Should have the right number of profiles.");
 
   let profile = profileData.profiles[0];
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -2028,16 +2028,17 @@ static ReturnAbortOnError ShowProfileMan
       NS_ENSURE_SUCCESS(rv, rv);
 
       lock->Unlock();
     }
   }
 
   SaveFileToEnv("XRE_PROFILE_PATH", profD);
   SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD);
+  SaveToEnv("XRE_RESTARTED_BY_PROFILE_MANAGER=1");
 
   if (offline) {
     SaveToEnv("XRE_START_OFFLINE=1");
   }
   if (gRestartedByOS) {
     // Re-add this argument when actually starting the application.
     char** newArgv =
         (char**)realloc(gRestartArgv, sizeof(char*) * (gRestartArgc + 2));
@@ -4199,16 +4200,17 @@ nsresult XREMain::XRE_mainRun() {
 
   // clear out any environment variables which may have been set
   // during the relaunch process now that we know we won't be relaunching.
   SaveToEnv("XRE_PROFILE_PATH=");
   SaveToEnv("XRE_PROFILE_LOCAL_PATH=");
   SaveToEnv("XRE_START_OFFLINE=");
   SaveToEnv("XUL_APP_FILE=");
   SaveToEnv("XRE_BINARY_PATH=");
+  SaveToEnv("XRE_RESTARTED_BY_PROFILE_MANAGER=");
 
   if (!mShuttingDown) {
     rv = appStartup->CreateHiddenWindow();
     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
 #ifdef XP_WIN
     Preferences::RegisterCallbackAndCall(RegisterApplicationRestartChanged,
                                          PREF_WIN_REGISTER_APPLICATION_RESTART);
@@ -4307,16 +4309,18 @@ nsresult XREMain::XRE_mainRun() {
   CrashReporter::AnnotateCrashReport(
       CrashReporter::Annotation::ContentSandboxCapabilities, flagsString);
 #endif /* MOZ_SANDBOX && XP_LINUX */
 
 #if defined(MOZ_CONTENT_SANDBOX)
   AddSandboxAnnotations();
 #endif /* MOZ_CONTENT_SANDBOX */
 
+  static_cast<nsToolkitProfileService*>(mProfileSvc.get())->RecordStartupTelemetry();
+
   {
     rv = appStartup->Run();
     if (NS_FAILED(rv)) {
       NS_ERROR("failed to run appstartup");
       gLogConsoleErrors = true;
     }
   }