forked from mirrors/gecko-dev
		
	 820d29897f
			
		
	
	
		820d29897f
		
	
	
	
	
		
			
			Depends on D178063 Differential Revision: https://phabricator.services.mozilla.com/D178064
		
			
				
	
	
		
			888 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			888 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* Any copyright is dedicated to the Public Domain.
 | |
|    http://creativecommons.org/publicdomain/zero/1.0/ */
 | |
| 
 | |
| /* This test records I/O syscalls done on the main thread during startup.
 | |
|  *
 | |
|  * To run this test similar to try server, you need to run:
 | |
|  *   ./mach package
 | |
|  *   ./mach test --appname=dist <path to test>
 | |
|  *
 | |
|  * If you made changes that cause this test to fail, it's likely because you
 | |
|  * are touching more files or directories during startup.
 | |
|  * Most code has no reason to use main thread I/O.
 | |
|  * If for some reason accessing the file system on the main thread is currently
 | |
|  * unavoidable, consider defering the I/O as long as you can, ideally after
 | |
|  * the end of startup.
 | |
|  * If your code isn't strictly required to show the first browser window,
 | |
|  * it shouldn't be loaded before we are done with first paint.
 | |
|  * Finally, if your code isn't really needed during startup, it should not be
 | |
|  * loaded before we have started handling user events.
 | |
|  */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| /* Set this to true only for debugging purpose; it makes the output noisy. */
 | |
| const kDumpAllStacks = false;
 | |
| 
 | |
| // Shortcuts for conditions.
 | |
| const LINUX = AppConstants.platform == "linux";
 | |
| const WIN = AppConstants.platform == "win";
 | |
| const MAC = AppConstants.platform == "macosx";
 | |
| 
 | |
| const kSharedFontList = SpecialPowers.getBoolPref("gfx.e10s.font-list.shared");
 | |
| 
 | |
| /* This is an object mapping string phases of startup to lists of known cases
 | |
|  * of IO happening on the main thread. Ideally, IO should not be on the main
 | |
|  * thread, and should happen as late as possible (see above).
 | |
|  *
 | |
|  * Paths in the entries in these lists can:
 | |
|  *  - be a full path, eg. "/etc/mime.types"
 | |
|  *  - have a prefix which will be resolved using Services.dirsvc
 | |
|  *    eg. "GreD:omni.ja"
 | |
|  *    It's possible to have only a prefix, in thise case the directory will
 | |
|  *    still be resolved, eg. "UAppData:"
 | |
|  *  - use * at the begining and/or end as a wildcard
 | |
|  * The folder separator is '/' even for Windows paths, where it'll be
 | |
|  * automatically converted to '\'.
 | |
|  *
 | |
|  * Specifying 'ignoreIfUnused: true' will make the test ignore unused entries;
 | |
|  * without this the test is strict and will fail if the described IO does not
 | |
|  * happen.
 | |
|  *
 | |
|  * Each entry specifies the maximum number of times an operation is expected to
 | |
|  * occur.
 | |
|  * The operations currently reported by the I/O interposer are:
 | |
|  *   create/open: only supported on Windows currently. The test currently
 | |
|  *     ignores these markers to have a shorter initial list of IO operations.
 | |
|  *     Adding Unix support is bug 1533779.
 | |
|  *   stat: supported on all platforms when checking the last modified date or
 | |
|  *     file size. Supported only on Windows when checking if a file exists;
 | |
|  *     fixing this inconsistency is bug 1536109.
 | |
|  *   read: supported on all platforms, but unix platforms will only report read
 | |
|  *     calls going through NSPR.
 | |
|  *   write: supported on all platforms, but Linux will only report write calls
 | |
|  *     going through NSPR.
 | |
|  *   close: supported only on Unix, and only for close calls going through NSPR.
 | |
|  *     Adding Windows support is bug 1524574.
 | |
|  *   fsync: supported only on Windows.
 | |
|  *
 | |
|  * If an entry specifies more than one operation, if at least one of them is
 | |
|  * encountered, the test won't report a failure for the entry if other
 | |
|  * operations are not encountered. This helps when listing cases where the
 | |
|  * reported operations aren't the same on all platforms due to the I/O
 | |
|  * interposer inconsistencies across platforms documented above.
 | |
|  */
 | |
| const startupPhases = {
 | |
|   // Anything done before or during app-startup must have a compelling reason
 | |
|   // to run before we have even selected the user profile.
 | |
|   "before profile selection": [
 | |
|     {
 | |
|       // bug 1541200
 | |
|       path: "UAppData:Crash Reports/InstallTime20*",
 | |
|       condition: AppConstants.MOZ_CRASHREPORTER,
 | |
|       stat: 1, // only caught on Windows.
 | |
|       read: 1,
 | |
|       write: 2,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541200
 | |
|       path: "UAppData:Crash Reports/LastCrash",
 | |
|       condition: WIN && AppConstants.MOZ_CRASHREPORTER,
 | |
|       stat: 1, // only caught on Windows.
 | |
|       read: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541200
 | |
|       path: "UAppData:Crash Reports/LastCrash",
 | |
|       condition: !WIN && AppConstants.MOZ_CRASHREPORTER,
 | |
|       ignoreIfUnused: true, // only if we ever crashed on this machine
 | |
|       read: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // At least the read seems unavoidable for a regular startup.
 | |
|       path: "UAppData:profiles.ini",
 | |
|       ignoreIfUnused: true,
 | |
|       condition: MAC,
 | |
|       stat: 1,
 | |
|       read: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // At least the read seems unavoidable for a regular startup.
 | |
|       path: "UAppData:profiles.ini",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true, // only if a real profile exists on the system.
 | |
|       read: 1,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541226, bug 1363586, bug 1541593
 | |
|       path: "ProfD:",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       path: "ProfLD:.startup-incomplete",
 | |
|       condition: !WIN, // Visible on Windows with an open marker
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541491 to stop using this file, bug 1541494 to write correctly.
 | |
|       path: "ProfLD:compatibility.ini",
 | |
|       write: 18,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       path: "GreD:omni.ja",
 | |
|       condition: !WIN, // Visible on Windows with an open marker
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1376994
 | |
|       path: "XCurProcD:omni.ja",
 | |
|       condition: !WIN, // Visible on Windows with an open marker
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       path: "ProfD:parent.lock",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541603
 | |
|       path: "ProfD:minidumps",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1543746
 | |
|       path: "XCurProcD:defaults/preferences",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1544034
 | |
|       path: "ProfLDS:startupCache/scriptCache-child-current.bin",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1544034
 | |
|       path: "ProfLDS:startupCache/scriptCache-child.bin",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1544034
 | |
|       path: "ProfLDS:startupCache/scriptCache-current.bin",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1544034
 | |
|       path: "ProfLDS:startupCache/scriptCache.bin",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541601
 | |
|       path: "PrfDef:channel-prefs.js",
 | |
|       stat: 1,
 | |
|       read: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // At least the read seems unavoidable
 | |
|       path: "PrefD:prefs.js",
 | |
|       stat: 1,
 | |
|       read: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1543752
 | |
|       path: "PrefD:user.js",
 | |
|       stat: 1,
 | |
|       read: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|   ],
 | |
| 
 | |
|   "before opening first browser window": [
 | |
|     {
 | |
|       // bug 1541226
 | |
|       path: "ProfD:",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true, // Sometimes happens in the next phase
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1534745
 | |
|       path: "ProfD:cookies.sqlite-journal",
 | |
|       condition: !LINUX,
 | |
|       ignoreIfUnused: true, // Sometimes happens in the next phase
 | |
|       stat: 3,
 | |
|       write: 4,
 | |
|     },
 | |
|     {
 | |
|       // bug 1534745
 | |
|       path: "ProfD:cookies.sqlite",
 | |
|       condition: !LINUX,
 | |
|       ignoreIfUnused: true, // Sometimes happens in the next phase
 | |
|       stat: 2,
 | |
|       read: 3,
 | |
|       write: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1534745
 | |
|       path: "ProfD:cookies.sqlite-wal",
 | |
|       ignoreIfUnused: true, // Sometimes happens in the next phase
 | |
|       condition: WIN,
 | |
|       stat: 2,
 | |
|     },
 | |
|     {
 | |
|       // Seems done by OS X and outside of our control.
 | |
|       path: "*.savedState/restorecount.plist",
 | |
|       condition: MAC,
 | |
|       ignoreIfUnused: true,
 | |
|       write: 1,
 | |
|     },
 | |
|     {
 | |
|       // Side-effect of bug 1412090, via sandboxing (but the real
 | |
|       // problem there is main-thread CPU use; see bug 1439412)
 | |
|       path: "*ld.so.conf*",
 | |
|       condition: LINUX && !AppConstants.MOZ_CODE_COVERAGE && !kSharedFontList,
 | |
|       read: 22,
 | |
|       close: 11,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541246
 | |
|       path: "ProfD:extensions",
 | |
|       ignoreIfUnused: true, // bug 1649590
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541246
 | |
|       path: "UAppData:",
 | |
|       ignoreIfUnused: true, // sometimes before opening first browser window,
 | |
|       // sometimes before first paint
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1833104 has context - this is artifact-only so doesn't affect
 | |
|       // any real users, will just show up for developer builds and
 | |
|       // artifact trypushes so we include it here.
 | |
|       path: "GreD:jogfile.json",
 | |
|       condition:
 | |
|         WIN && Services.prefs.getBoolPref("telemetry.fog.artifact_build"),
 | |
|       stat: 1,
 | |
|     },
 | |
|   ],
 | |
| 
 | |
|   // We reach this phase right after showing the first browser window.
 | |
|   // This means that any I/O at this point delayed first paint.
 | |
|   "before first paint": [
 | |
|     {
 | |
|       // bug 1545119
 | |
|       path: "OldUpdRootD:",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1446012
 | |
|       path: "UpdRootD:updates/0/update.status",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       path: "XREAppFeat:formautofill@mozilla.org.xpi",
 | |
|       condition: !WIN,
 | |
|       stat: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       path: "XREAppFeat:webcompat@mozilla.org.xpi",
 | |
|       condition: LINUX,
 | |
|       ignoreIfUnused: true, // Sometimes happens in the previous phase
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // We only hit this for new profiles.
 | |
|       path: "XREAppDist:distribution.ini",
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1545139
 | |
|       path: "*Fonts/StaticCache.dat",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true, // Only on Win7
 | |
|       read: 1,
 | |
|     },
 | |
|     {
 | |
|       // Bug 1626738
 | |
|       path: "SysD:spool/drivers/color/*",
 | |
|       condition: WIN,
 | |
|       read: 1,
 | |
|     },
 | |
|     {
 | |
|       // Sandbox policy construction
 | |
|       path: "*ld.so.conf*",
 | |
|       condition: LINUX && !AppConstants.MOZ_CODE_COVERAGE,
 | |
|       read: 22,
 | |
|       close: 11,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541246
 | |
|       path: "UAppData:",
 | |
|       ignoreIfUnused: true, // sometimes before opening first browser window,
 | |
|       // sometimes before first paint
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // Not in packaged builds; useful for artifact builds.
 | |
|       path: "GreD:ScalarArtifactDefinitions.json",
 | |
|       condition: WIN && !AppConstants.MOZILLA_OFFICIAL,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // Not in packaged builds; useful for artifact builds.
 | |
|       path: "GreD:EventArtifactDefinitions.json",
 | |
|       condition: WIN && !AppConstants.MOZILLA_OFFICIAL,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1541226
 | |
|       path: "ProfD:",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true, // Usually happens in the previous phase
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1534745
 | |
|       path: "ProfD:cookies.sqlite-journal",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true, // Usually happens in the previous phase
 | |
|       stat: 3,
 | |
|       write: 4,
 | |
|     },
 | |
|     {
 | |
|       // bug 1534745
 | |
|       path: "ProfD:cookies.sqlite",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true, // Usually happens in the previous phase
 | |
|       stat: 2,
 | |
|       read: 3,
 | |
|       write: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1534745
 | |
|       path: "ProfD:cookies.sqlite-wal",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true, // Usually happens in the previous phase
 | |
|       stat: 2,
 | |
|     },
 | |
|   ],
 | |
| 
 | |
|   // We are at this phase once we are ready to handle user events.
 | |
|   // Any IO at this phase or before gets in the way of the user
 | |
|   // interacting with the first browser window.
 | |
|   "before handling user events": [
 | |
|     {
 | |
|       path: "GreD:update.test",
 | |
|       ignoreIfUnused: true,
 | |
|       condition: LINUX,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       path: "XREAppFeat:webcompat-reporter@mozilla.org.xpi",
 | |
|       condition: !WIN,
 | |
|       ignoreIfUnused: true,
 | |
|       stat: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // Bug 1660582 - access while running on windows10 hardware.
 | |
|       path: "ProfD:wmfvpxvideo.guard",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true,
 | |
|       stat: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // Bug 1649590
 | |
|       path: "ProfD:extensions",
 | |
|       ignoreIfUnused: true,
 | |
|       condition: WIN,
 | |
|       stat: 1,
 | |
|     },
 | |
|   ],
 | |
| 
 | |
|   // Things that are expected to be completely out of the startup path
 | |
|   // and loaded lazily when used for the first time by the user should
 | |
|   // be listed here.
 | |
|   "before becoming idle": [
 | |
|     {
 | |
|       // bug 1370516 - NSS should be initialized off main thread.
 | |
|       path: `ProfD:cert9.db`,
 | |
|       condition: WIN,
 | |
|       read: 5,
 | |
|       stat: AppConstants.NIGHTLY_BUILD ? 5 : 4,
 | |
|     },
 | |
|     {
 | |
|       // bug 1370516 - NSS should be initialized off main thread.
 | |
|       path: `ProfD:cert9.db-journal`,
 | |
|       condition: WIN,
 | |
|       stat: 3,
 | |
|     },
 | |
|     {
 | |
|       // bug 1370516 - NSS should be initialized off main thread.
 | |
|       path: `ProfD:cert9.db-wal`,
 | |
|       condition: WIN,
 | |
|       stat: 3,
 | |
|     },
 | |
|     {
 | |
|       // bug 1370516 - NSS should be initialized off main thread.
 | |
|       path: "ProfD:pkcs11.txt",
 | |
|       condition: WIN,
 | |
|       read: 2,
 | |
|     },
 | |
|     {
 | |
|       // bug 1370516 - NSS should be initialized off main thread.
 | |
|       path: `ProfD:key4.db`,
 | |
|       condition: WIN,
 | |
|       read: 10,
 | |
|       stat: AppConstants.NIGHTLY_BUILD ? 5 : 4,
 | |
|     },
 | |
|     {
 | |
|       // bug 1370516 - NSS should be initialized off main thread.
 | |
|       path: `ProfD:key4.db-journal`,
 | |
|       condition: WIN,
 | |
|       stat: 7,
 | |
|     },
 | |
|     {
 | |
|       // bug 1370516 - NSS should be initialized off main thread.
 | |
|       path: `ProfD:key4.db-wal`,
 | |
|       condition: WIN,
 | |
|       stat: 7,
 | |
|     },
 | |
|     {
 | |
|       path: "XREAppFeat:screenshots@mozilla.org.xpi",
 | |
|       ignoreIfUnused: true,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       path: "XREAppFeat:webcompat-reporter@mozilla.org.xpi",
 | |
|       ignoreIfUnused: true,
 | |
|       stat: 1,
 | |
|       close: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1833110, utility process instantiation due to JS ORB validator.
 | |
|       path: "*ld.so.conf*",
 | |
|       condition:
 | |
|         LINUX &&
 | |
|         !AppConstants.MOZ_CODE_COVERAGE &&
 | |
|         Services.prefs.getBoolPref(
 | |
|           "browser.opaqueResponseBlocking.javascriptValidator"
 | |
|         ),
 | |
|       read: 14,
 | |
|       /* Whether this happens before or after idle is racy: */
 | |
|       ignoreIfUnused: true,
 | |
|       close: 7,
 | |
|     },
 | |
|     {
 | |
|       // bug 1391590
 | |
|       path: "ProfD:places.sqlite-journal",
 | |
|       ignoreIfUnused: true,
 | |
|       fsync: 1,
 | |
|       stat: 4,
 | |
|       read: 1,
 | |
|       write: 2,
 | |
|     },
 | |
|     {
 | |
|       // bug 1391590
 | |
|       path: "ProfD:places.sqlite-wal",
 | |
|       ignoreIfUnused: true,
 | |
|       stat: 4,
 | |
|       fsync: 3,
 | |
|       read: 51,
 | |
|       write: 178,
 | |
|     },
 | |
|     {
 | |
|       // bug 1391590
 | |
|       path: "ProfD:places.sqlite-shm",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true,
 | |
|       stat: 1,
 | |
|     },
 | |
|     {
 | |
|       // bug 1391590
 | |
|       path: "ProfD:places.sqlite",
 | |
|       ignoreIfUnused: true,
 | |
|       fsync: 2,
 | |
|       read: 4,
 | |
|       stat: 3,
 | |
|       write: 1324,
 | |
|     },
 | |
|     {
 | |
|       // bug 1391590
 | |
|       path: "ProfD:favicons.sqlite-journal",
 | |
|       ignoreIfUnused: true,
 | |
|       fsync: 2,
 | |
|       stat: 7,
 | |
|       read: 2,
 | |
|       write: 7,
 | |
|     },
 | |
|     {
 | |
|       // bug 1391590
 | |
|       path: "ProfD:favicons.sqlite-wal",
 | |
|       ignoreIfUnused: true,
 | |
|       fsync: 2,
 | |
|       stat: 7,
 | |
|       read: 7,
 | |
|       write: 15,
 | |
|     },
 | |
|     {
 | |
|       // bug 1391590
 | |
|       path: "ProfD:favicons.sqlite-shm",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true,
 | |
|       stat: 2,
 | |
|     },
 | |
|     {
 | |
|       // bug 1391590
 | |
|       path: "ProfD:favicons.sqlite",
 | |
|       ignoreIfUnused: true,
 | |
|       fsync: 3,
 | |
|       read: 8,
 | |
|       stat: 4,
 | |
|       write: 1300,
 | |
|     },
 | |
|     {
 | |
|       path: "ProfD:",
 | |
|       condition: WIN,
 | |
|       ignoreIfUnused: true,
 | |
|       stat: 3,
 | |
|     },
 | |
|   ],
 | |
| };
 | |
| 
 | |
| for (let name of ["d3d11layers", "glcontext", "wmfvpxvideo"]) {
 | |
|   startupPhases["before first paint"].push({
 | |
|     path: `ProfD:${name}.guard`,
 | |
|     ignoreIfUnused: true,
 | |
|     stat: 1,
 | |
|   });
 | |
| }
 | |
| 
 | |
| function expandPathWithDirServiceKey(path) {
 | |
|   if (path.includes(":")) {
 | |
|     let [prefix, suffix] = path.split(":");
 | |
|     let [key, property] = prefix.split(".");
 | |
|     let dir = Services.dirsvc.get(key, Ci.nsIFile);
 | |
|     if (property) {
 | |
|       dir = dir[property];
 | |
|     }
 | |
| 
 | |
|     // Resolve symLinks.
 | |
|     let dirPath = dir.path;
 | |
|     while (dir && !dir.isSymlink()) {
 | |
|       dir = dir.parent;
 | |
|     }
 | |
|     if (dir) {
 | |
|       dirPath = dirPath.replace(dir.path, dir.target);
 | |
|     }
 | |
| 
 | |
|     path = dirPath;
 | |
| 
 | |
|     if (suffix) {
 | |
|       path += "/" + suffix;
 | |
|     }
 | |
|   }
 | |
|   if (AppConstants.platform == "win") {
 | |
|     path = path.replace(/\//g, "\\");
 | |
|   }
 | |
|   return path;
 | |
| }
 | |
| 
 | |
| function getStackFromProfile(profile, stack) {
 | |
|   const stackPrefixCol = profile.stackTable.schema.prefix;
 | |
|   const stackFrameCol = profile.stackTable.schema.frame;
 | |
|   const frameLocationCol = profile.frameTable.schema.location;
 | |
| 
 | |
|   let result = [];
 | |
|   while (stack) {
 | |
|     let sp = profile.stackTable.data[stack];
 | |
|     let frame = profile.frameTable.data[sp[stackFrameCol]];
 | |
|     stack = sp[stackPrefixCol];
 | |
|     frame = profile.stringTable[frame[frameLocationCol]];
 | |
|     if (frame != "js::RunScript" && !frame.startsWith("next (self-hosted:")) {
 | |
|       result.push(frame);
 | |
|     }
 | |
|   }
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| function pathMatches(path, filename) {
 | |
|   path = path.toLowerCase();
 | |
|   return (
 | |
|     path == filename || // Full match
 | |
|     // Wildcard on both sides of the path
 | |
|     (path.startsWith("*") &&
 | |
|       path.endsWith("*") &&
 | |
|       filename.includes(path.slice(1, -1))) ||
 | |
|     // Wildcard suffix
 | |
|     (path.endsWith("*") && filename.startsWith(path.slice(0, -1))) ||
 | |
|     // Wildcard prefix
 | |
|     (path.startsWith("*") && filename.endsWith(path.slice(1)))
 | |
|   );
 | |
| }
 | |
| 
 | |
| add_task(async function() {
 | |
|   if (
 | |
|     !AppConstants.NIGHTLY_BUILD &&
 | |
|     !AppConstants.MOZ_DEV_EDITION &&
 | |
|     !AppConstants.DEBUG
 | |
|   ) {
 | |
|     ok(
 | |
|       !("@mozilla.org/test/startuprecorder;1" in Cc),
 | |
|       "the startup recorder component shouldn't exist in this non-nightly/non-devedition/" +
 | |
|         "non-debug build."
 | |
|     );
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   TestUtils.assertPackagedBuild();
 | |
| 
 | |
|   let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService()
 | |
|     .wrappedJSObject;
 | |
|   await startupRecorder.done;
 | |
| 
 | |
|   // Add system add-ons to the list of known IO dynamically.
 | |
|   // They should go in the omni.ja file (bug 1357205).
 | |
|   {
 | |
|     let addons = await AddonManager.getAddonsByTypes(["extension"]);
 | |
|     for (let addon of addons) {
 | |
|       if (addon.isSystem) {
 | |
|         startupPhases["before opening first browser window"].push({
 | |
|           path: `XREAppFeat:${addon.id}.xpi`,
 | |
|           stat: 3,
 | |
|           close: 2,
 | |
|         });
 | |
|         startupPhases["before handling user events"].push({
 | |
|           path: `XREAppFeat:${addon.id}.xpi`,
 | |
|           condition: WIN,
 | |
|           stat: 2,
 | |
|         });
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Check for main thread I/O markers in the startup profile.
 | |
|   let profile = startupRecorder.data.profile.threads[0];
 | |
| 
 | |
|   let phases = {};
 | |
|   {
 | |
|     const nameCol = profile.markers.schema.name;
 | |
|     const dataCol = profile.markers.schema.data;
 | |
| 
 | |
|     let markersForCurrentPhase = [];
 | |
|     let foundIOMarkers = false;
 | |
| 
 | |
|     for (let m of profile.markers.data) {
 | |
|       let markerName = profile.stringTable[m[nameCol]];
 | |
|       if (markerName.startsWith("startupRecorder:")) {
 | |
|         phases[
 | |
|           markerName.split("startupRecorder:")[1]
 | |
|         ] = markersForCurrentPhase;
 | |
|         markersForCurrentPhase = [];
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       if (markerName != "FileIO") {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let markerData = m[dataCol];
 | |
|       if (markerData.source == "sqlite-mainthread") {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let samples = markerData.stack.samples;
 | |
|       let stack = samples.data[0][samples.schema.stack];
 | |
|       markersForCurrentPhase.push({
 | |
|         operation: markerData.operation,
 | |
|         filename: markerData.filename,
 | |
|         source: markerData.source,
 | |
|         stackId: stack,
 | |
|       });
 | |
|       foundIOMarkers = true;
 | |
|     }
 | |
| 
 | |
|     // The I/O interposer is disabled if !RELEASE_OR_BETA, so we expect to have
 | |
|     // no I/O marker in that case, but it's good to keep the test running to check
 | |
|     // that we are still able to produce startup profiles.
 | |
|     is(
 | |
|       foundIOMarkers,
 | |
|       !AppConstants.RELEASE_OR_BETA,
 | |
|       "The IO interposer should be enabled in builds that are not RELEASE_OR_BETA"
 | |
|     );
 | |
|     if (!foundIOMarkers) {
 | |
|       // If a profile unexpectedly contains no I/O marker, it's better to return
 | |
|       // early to avoid having a lot of of confusing "no main thread IO when we
 | |
|       // expected some" failures.
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   for (let phase in startupPhases) {
 | |
|     startupPhases[phase] = startupPhases[phase].filter(
 | |
|       entry => !("condition" in entry) || entry.condition
 | |
|     );
 | |
|     startupPhases[phase].forEach(entry => {
 | |
|       entry.listedPath = entry.path;
 | |
|       entry.path = expandPathWithDirServiceKey(entry.path);
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   let tmpPath = expandPathWithDirServiceKey("TmpD:").toLowerCase();
 | |
|   let shouldPass = true;
 | |
|   for (let phase in phases) {
 | |
|     let knownIOList = startupPhases[phase];
 | |
|     info(
 | |
|       `known main thread IO paths during ${phase}:\n` +
 | |
|         knownIOList
 | |
|           .map(e => {
 | |
|             let operations = Object.keys(e)
 | |
|               .filter(k => k != "path")
 | |
|               .map(k => `${k}: ${e[k]}`);
 | |
|             return `  ${e.path} - ${operations.join(", ")}`;
 | |
|           })
 | |
|           .join("\n")
 | |
|     );
 | |
| 
 | |
|     let markers = phases[phase];
 | |
|     for (let marker of markers) {
 | |
|       if (marker.operation == "create/open") {
 | |
|         // TODO: handle these I/O markers once they are supported on
 | |
|         // non-Windows platforms.
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       if (!marker.filename) {
 | |
|         // We are still missing the filename on some mainthreadio markers,
 | |
|         // these markers are currently useless for the purpose of this test.
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // Convert to lower case before comparing because the OS X test machines
 | |
|       // have the 'Firefox' folder in 'Library/Application Support' created
 | |
|       // as 'firefox' for some reason.
 | |
|       let filename = marker.filename.toLowerCase();
 | |
| 
 | |
|       if (!WIN && filename == "/dev/urandom") {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // /dev/shm is always tmpfs (a memory filesystem); this isn't
 | |
|       // really I/O any more than mmap/munmap are.
 | |
|       if (LINUX && filename.startsWith("/dev/shm/")) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // "Files" from memfd_create() are similar to tmpfs but never
 | |
|       // exist in the filesystem; however, they have names which are
 | |
|       // exposed in procfs, and the I/O interposer observes when
 | |
|       // they're close()d.
 | |
|       if (LINUX && filename.startsWith("/memfd:")) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       // Shared memory uses temporary files on MacOS <= 10.11 to avoid
 | |
|       // a kernel security bug that will never be patched (see
 | |
|       // https://crbug.com/project-zero/1671 for details).  This can
 | |
|       // be removed when we no longer support those OS versions.
 | |
|       if (MAC && filename.startsWith(tmpPath + "/org.mozilla.ipc.")) {
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       let expected = false;
 | |
|       for (let entry of knownIOList) {
 | |
|         if (pathMatches(entry.path, filename)) {
 | |
|           entry[marker.operation] = (entry[marker.operation] || 0) - 1;
 | |
|           entry._used = true;
 | |
|           expected = true;
 | |
|           break;
 | |
|         }
 | |
|       }
 | |
|       if (!expected) {
 | |
|         record(
 | |
|           false,
 | |
|           `unexpected ${marker.operation} on ${marker.filename} ${phase}`,
 | |
|           undefined,
 | |
|           "  " + getStackFromProfile(profile, marker.stackId).join("\n  ")
 | |
|         );
 | |
|         shouldPass = false;
 | |
|       }
 | |
|       info(`(${marker.source}) ${marker.operation} - ${marker.filename}`);
 | |
|       if (kDumpAllStacks) {
 | |
|         info(
 | |
|           getStackFromProfile(profile, marker.stackId)
 | |
|             .map(f => "  " + f)
 | |
|             .join("\n")
 | |
|         );
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     for (let entry of knownIOList) {
 | |
|       for (let op in entry) {
 | |
|         if (
 | |
|           [
 | |
|             "listedPath",
 | |
|             "path",
 | |
|             "condition",
 | |
|             "ignoreIfUnused",
 | |
|             "_used",
 | |
|           ].includes(op)
 | |
|         ) {
 | |
|           continue;
 | |
|         }
 | |
|         let message = `${op} on ${entry.path} `;
 | |
|         if (entry[op] == 0) {
 | |
|           message += "as many times as expected";
 | |
|         } else if (entry[op] > 0) {
 | |
|           message += `allowed ${entry[op]} more times`;
 | |
|         } else {
 | |
|           message += `${entry[op] * -1} more times than expected`;
 | |
|         }
 | |
|         ok(entry[op] >= 0, `${message} ${phase}`);
 | |
|       }
 | |
|       if (!("_used" in entry) && !entry.ignoreIfUnused) {
 | |
|         ok(
 | |
|           false,
 | |
|           `no main thread IO when we expected some during ${phase}: ${entry.path} (${entry.listedPath})`
 | |
|         );
 | |
|         shouldPass = false;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (shouldPass) {
 | |
|     ok(shouldPass, "No unexpected main thread I/O during startup");
 | |
|   } else {
 | |
|     const filename = "profile_startup_mainthreadio.json";
 | |
|     let path = Services.env.get("MOZ_UPLOAD_DIR");
 | |
|     let profilePath = PathUtils.join(path, filename);
 | |
|     await IOUtils.writeJSON(profilePath, startupRecorder.data.profile);
 | |
|     ok(
 | |
|       false,
 | |
|       "Unexpected main thread I/O behavior during startup; open the " +
 | |
|         `${filename} artifact in the Firefox Profiler to see what happened`
 | |
|     );
 | |
|   }
 | |
| });
 |