browser_startup_mainthreadio.js (23418B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 /* This test records I/O syscalls done on the main thread during startup. 5 * 6 * To run this test similar to try server, you need to run: 7 * ./mach package 8 * ./mach test --appname=dist <path to test> 9 * 10 * If you made changes that cause this test to fail, it's likely because you 11 * are touching more files or directories during startup. 12 * Most code has no reason to use main thread I/O. 13 * If for some reason accessing the file system on the main thread is currently 14 * unavoidable, consider defering the I/O as long as you can, ideally after 15 * the end of startup. 16 * If your code isn't strictly required to show the first browser window, 17 * it shouldn't be loaded before we are done with first paint. 18 * Finally, if your code isn't really needed during startup, it should not be 19 * loaded before we have started handling user events. 20 */ 21 22 "use strict"; 23 24 /* Set this to true only for debugging purpose; it makes the output noisy. */ 25 const kDumpAllStacks = false; 26 27 // Shortcuts for conditions. 28 const LINUX = AppConstants.platform == "linux"; 29 const WIN = AppConstants.platform == "win"; 30 const MAC = AppConstants.platform == "macosx"; 31 32 const kSharedFontList = SpecialPowers.getBoolPref("gfx.e10s.font-list.shared"); 33 34 /* This is an object mapping string phases of startup to lists of known cases 35 * of IO happening on the main thread. Ideally, IO should not be on the main 36 * thread, and should happen as late as possible (see above). 37 * 38 * Paths in the entries in these lists can: 39 * - be a full path, eg. "/etc/mime.types" 40 * - have a prefix which will be resolved using Services.dirsvc 41 * eg. "GreD:omni.ja" 42 * It's possible to have only a prefix, in thise case the directory will 43 * still be resolved, eg. "UAppData:" 44 * - use * at the begining and/or end as a wildcard 45 * The folder separator is '/' even for Windows paths, where it'll be 46 * automatically converted to '\'. 47 * 48 * Specifying 'ignoreIfUnused: true' will make the test ignore unused entries; 49 * without this the test is strict and will fail if the described IO does not 50 * happen. 51 * 52 * Each entry specifies the maximum number of times an operation is expected to 53 * occur. 54 * The operations currently reported by the I/O interposer are: 55 * create/open: only supported on Windows currently. The test currently 56 * ignores these markers to have a shorter initial list of IO operations. 57 * Adding Unix support is bug 1533779. 58 * stat: supported on all platforms when checking the last modified date or 59 * file size. Supported only on Windows when checking if a file exists; 60 * fixing this inconsistency is bug 1536109. 61 * read: supported on all platforms, but unix platforms will only report read 62 * calls going through NSPR. 63 * write: supported on all platforms, but Linux will only report write calls 64 * going through NSPR. 65 * close: supported only on Unix, and only for close calls going through NSPR. 66 * Adding Windows support is bug 1524574. 67 * fsync: supported only on Windows. 68 * 69 * If an entry specifies more than one operation, if at least one of them is 70 * encountered, the test won't report a failure for the entry if other 71 * operations are not encountered. This helps when listing cases where the 72 * reported operations aren't the same on all platforms due to the I/O 73 * interposer inconsistencies across platforms documented above. 74 */ 75 const startupPhases = { 76 // Anything done before or during app-startup must have a compelling reason 77 // to run before we have even selected the user profile. 78 "before profile selection": [ 79 { 80 // bug 1541200 81 path: "UAppData:Crash Reports/InstallTime20*", 82 condition: AppConstants.MOZ_CRASHREPORTER, 83 stat: 1, // only caught on Windows. 84 read: 1, 85 write: 2, 86 close: 1, 87 }, 88 { 89 // bug 1541200 90 path: "UAppData:Crash Reports/LastCrash", 91 condition: WIN && AppConstants.MOZ_CRASHREPORTER, 92 stat: 1, // only caught on Windows. 93 read: 1, 94 }, 95 { 96 // bug 1541200 97 path: "UAppData:Crash Reports/LastCrash", 98 condition: !WIN && AppConstants.MOZ_CRASHREPORTER, 99 ignoreIfUnused: true, // only if we ever crashed on this machine 100 read: 1, 101 close: 1, 102 }, 103 { 104 // At least the read seems unavoidable for a regular startup. 105 path: "UAppData:profiles.ini", 106 ignoreIfUnused: true, 107 condition: MAC, 108 stat: 1, 109 read: 1, 110 close: 1, 111 }, 112 { 113 // At least the read seems unavoidable for a regular startup. 114 path: "UAppData:profiles.ini", 115 condition: WIN, 116 ignoreIfUnused: true, // only if a real profile exists on the system. 117 read: 1, 118 stat: 1, 119 }, 120 { 121 path: "ProfLD:.startup-incomplete", 122 condition: !WIN, // Visible on Windows with an open marker 123 close: 1, 124 }, 125 { 126 // bug 1541491 to stop using this file, bug 1541494 to write correctly. 127 path: "ProfLD:compatibility.ini", 128 write: 18, 129 close: 1, 130 }, 131 { 132 path: "GreD:omni.ja", 133 condition: !WIN, // Visible on Windows with an open marker 134 stat: 1, 135 }, 136 { 137 // bug 1376994 138 path: "XCurProcD:omni.ja", 139 condition: !WIN, // Visible on Windows with an open marker 140 stat: 1, 141 }, 142 { 143 path: "ProfD:parent.lock", 144 condition: WIN, 145 stat: 1, 146 }, 147 { 148 // bug 1541603 149 path: "ProfD:minidumps", 150 condition: WIN, 151 stat: 1, 152 }, 153 { 154 // bug 1543746 155 path: "XCurProcD:defaults/preferences", 156 condition: WIN, 157 stat: 1, 158 }, 159 { 160 // bug 1544034 161 path: "ProfLDS:startupCache/scriptCache-child-current.bin", 162 condition: WIN, 163 stat: 1, 164 }, 165 { 166 // bug 1544034 167 path: "ProfLDS:startupCache/scriptCache-child.bin", 168 condition: WIN, 169 stat: 1, 170 }, 171 { 172 // bug 1544034 173 path: "ProfLDS:startupCache/scriptCache-current.bin", 174 condition: WIN, 175 stat: 1, 176 }, 177 { 178 // bug 1544034 179 path: "ProfLDS:startupCache/scriptCache.bin", 180 condition: WIN, 181 stat: 1, 182 }, 183 { 184 // bug 1541601 185 path: "PrfDef:channel-prefs.js", 186 condition: !MAC, 187 stat: 1, 188 read: 1, 189 close: 1, 190 }, 191 { 192 // At least the read seems unavoidable 193 path: "PrefD:prefs.js", 194 stat: 1, 195 read: 1, 196 close: 1, 197 }, 198 { 199 // bug 1543752 200 path: "PrefD:user.js", 201 stat: 1, 202 read: 1, 203 close: 1, 204 }, 205 { 206 // This is the startup lock used to restrict only one Firefox startup at a time. 207 path: `TmpD:firefox-${AppConstants.MOZ_UPDATE_CHANNEL}/parent.lock`, 208 condition: WIN, 209 stat: 1, 210 }, 211 ], 212 213 "before opening first browser window": [ 214 { 215 // bug 1541226 216 path: "ProfD:", 217 condition: WIN, 218 ignoreIfUnused: true, // Sometimes happens in the next phase 219 stat: 1, 220 }, 221 { 222 // bug 1534745 223 path: "ProfD:cookies.sqlite-journal", 224 condition: !LINUX, 225 ignoreIfUnused: true, // Sometimes happens in the next phase 226 stat: 3, 227 write: 4, 228 }, 229 { 230 // bug 1534745 231 path: "ProfD:cookies.sqlite", 232 condition: !LINUX, 233 ignoreIfUnused: true, // Sometimes happens in the next phase 234 stat: 2, 235 read: 3, 236 write: 1, 237 }, 238 { 239 // bug 1534745 240 path: "ProfD:cookies.sqlite-wal", 241 ignoreIfUnused: true, // Sometimes happens in the next phase 242 condition: WIN, 243 stat: 2, 244 }, 245 { 246 // Seems done by OS X and outside of our control. 247 path: "*.savedState/restorecount.plist", 248 condition: MAC, 249 ignoreIfUnused: true, 250 write: 1, 251 }, 252 { 253 // Side-effect of bug 1412090, via sandboxing (but the real 254 // problem there is main-thread CPU use; see bug 1439412) 255 path: "*ld.so.conf*", 256 condition: LINUX && !AppConstants.MOZ_CODE_COVERAGE && !kSharedFontList, 257 read: 22, 258 close: 11, 259 }, 260 { 261 // bug 1541246 262 path: "ProfD:extensions", 263 ignoreIfUnused: true, // bug 1649590 264 condition: WIN, 265 stat: 1, 266 }, 267 { 268 // bug 1541246 269 path: "UAppData:", 270 ignoreIfUnused: true, // sometimes before opening first browser window, 271 // sometimes before first paint 272 condition: WIN, 273 stat: 1, 274 }, 275 { 276 // bug 1833104 has context - this is artifact-only so doesn't affect 277 // any real users, will just show up for developer builds and 278 // artifact trypushes so we include it here. 279 path: "GreD:jogfile.json", 280 condition: 281 WIN && Services.prefs.getBoolPref("telemetry.fog.artifact_build"), 282 stat: 1, 283 }, 284 ], 285 286 // We reach this phase right after showing the first browser window. 287 // This means that any I/O at this point delayed first paint. 288 "before first paint": [ 289 { 290 // We only hit this for new profiles. 291 path: "XREAppDist:distribution.ini", 292 // check we're not msix to disambiguate from the next entry... 293 condition: WIN && !Services.sysinfo.getProperty("hasWinPackageId"), 294 stat: 1, 295 }, 296 { 297 // On MSIX, we actually read this file - bug 1833341. 298 path: "XREAppDist:distribution.ini", 299 condition: WIN && Services.sysinfo.getProperty("hasWinPackageId"), 300 stat: 1, 301 read: 1, 302 }, 303 { 304 // bug 1545139 305 path: "*Fonts/StaticCache.dat", 306 condition: WIN, 307 ignoreIfUnused: true, // Only on Win7 308 read: 1, 309 }, 310 { 311 // Bug 1626738 312 path: "SysD:spool/drivers/color/*", 313 condition: WIN, 314 read: 1, 315 }, 316 { 317 // Sandbox policy construction 318 path: "*ld.so.conf*", 319 condition: LINUX && !AppConstants.MOZ_CODE_COVERAGE, 320 read: 22, 321 close: 11, 322 }, 323 { 324 // bug 1541246 325 path: "UAppData:", 326 ignoreIfUnused: true, // sometimes before opening first browser window, 327 // sometimes before first paint 328 condition: WIN, 329 stat: 1, 330 }, 331 { 332 // Not in packaged builds; useful for artifact builds. 333 path: "GreD:ScalarArtifactDefinitions.json", 334 condition: WIN && !AppConstants.MOZILLA_OFFICIAL, 335 stat: 1, 336 }, 337 { 338 // Not in packaged builds; useful for artifact builds. 339 path: "GreD:EventArtifactDefinitions.json", 340 condition: WIN && !AppConstants.MOZILLA_OFFICIAL, 341 stat: 1, 342 }, 343 { 344 // bug 1541226 345 path: "ProfD:", 346 condition: WIN, 347 ignoreIfUnused: true, // Usually happens in the previous phase 348 stat: 1, 349 }, 350 { 351 // bug 1534745 352 path: "ProfD:cookies.sqlite-journal", 353 condition: WIN, 354 ignoreIfUnused: true, // Usually happens in the previous phase 355 stat: 3, 356 write: 4, 357 }, 358 { 359 // bug 1534745 360 path: "ProfD:cookies.sqlite", 361 condition: WIN, 362 ignoreIfUnused: true, // Usually happens in the previous phase 363 stat: 2, 364 read: 3, 365 write: 1, 366 }, 367 { 368 // bug 1534745 369 path: "ProfD:cookies.sqlite-wal", 370 condition: WIN, 371 ignoreIfUnused: true, // Usually happens in the previous phase 372 stat: 2, 373 }, 374 ], 375 376 // We are at this phase once we are ready to handle user events. 377 // Any IO at this phase or before gets in the way of the user 378 // interacting with the first browser window. 379 "before handling user events": [ 380 { 381 path: "GreD:update.test", 382 ignoreIfUnused: true, 383 condition: LINUX, 384 close: 1, 385 }, 386 { 387 // Bug 1660582 - access while running on windows10 hardware. 388 path: "ProfD:wmfvpxvideo.guard", 389 condition: WIN, 390 ignoreIfUnused: true, 391 stat: 1, 392 close: 1, 393 }, 394 { 395 // Bug 1649590 396 path: "ProfD:extensions", 397 ignoreIfUnused: true, 398 condition: WIN, 399 stat: 1, 400 }, 401 ], 402 403 // Things that are expected to be completely out of the startup path 404 // and loaded lazily when used for the first time by the user should 405 // be listed here. 406 "before becoming idle": [ 407 { 408 // bug 1370516 - NSS should be initialized off main thread. 409 path: `ProfD:cert9.db`, 410 condition: WIN, 411 read: 5, 412 stat: AppConstants.NIGHTLY_BUILD ? 5 : 4, 413 }, 414 { 415 // bug 1370516 - NSS should be initialized off main thread. 416 path: `ProfD:cert9.db-journal`, 417 condition: WIN, 418 stat: 3, 419 }, 420 { 421 // bug 1370516 - NSS should be initialized off main thread. 422 path: `ProfD:cert9.db-wal`, 423 condition: WIN, 424 stat: 3, 425 }, 426 { 427 // bug 1370516 - NSS should be initialized off main thread. 428 path: "ProfD:pkcs11.txt", 429 condition: WIN, 430 read: 2, 431 }, 432 { 433 // bug 1370516 - NSS should be initialized off main thread. 434 path: `ProfD:key4.db`, 435 condition: WIN, 436 read: 10, 437 stat: AppConstants.NIGHTLY_BUILD ? 5 : 4, 438 }, 439 { 440 // bug 1370516 - NSS should be initialized off main thread. 441 path: `ProfD:key4.db-journal`, 442 condition: WIN, 443 stat: 7, 444 }, 445 { 446 // bug 1370516 - NSS should be initialized off main thread. 447 path: `ProfD:key4.db-wal`, 448 condition: WIN, 449 stat: 7, 450 }, 451 { 452 // bug 1391590 453 path: "ProfD:places.sqlite-journal", 454 ignoreIfUnused: true, 455 fsync: 1, 456 stat: 4, 457 read: 1, 458 write: 2, 459 }, 460 { 461 // bug 1391590 462 path: "ProfD:places.sqlite-wal", 463 ignoreIfUnused: true, 464 stat: 4, 465 fsync: 3, 466 read: 51, 467 write: 178, 468 }, 469 { 470 // bug 1391590 471 path: "ProfD:places.sqlite-shm", 472 condition: WIN, 473 ignoreIfUnused: true, 474 stat: 1, 475 }, 476 { 477 // bug 1391590 478 path: "ProfD:places.sqlite", 479 ignoreIfUnused: true, 480 fsync: 2, 481 read: 4, 482 stat: 3, 483 write: 1324, 484 }, 485 { 486 // bug 1391590 487 path: "ProfD:favicons.sqlite-journal", 488 ignoreIfUnused: true, 489 fsync: 2, 490 stat: 7, 491 read: 2, 492 write: 7, 493 }, 494 { 495 // bug 1391590 496 path: "ProfD:favicons.sqlite-wal", 497 ignoreIfUnused: true, 498 fsync: 2, 499 stat: 7, 500 read: 7, 501 write: 15, 502 }, 503 { 504 // bug 1391590 505 path: "ProfD:favicons.sqlite-shm", 506 condition: WIN, 507 ignoreIfUnused: true, 508 stat: 2, 509 }, 510 { 511 // bug 1391590 512 path: "ProfD:favicons.sqlite", 513 ignoreIfUnused: true, 514 fsync: 3, 515 read: 8, 516 stat: 4, 517 write: 1300, 518 }, 519 { 520 path: "ProfD:", 521 condition: WIN, 522 ignoreIfUnused: true, 523 stat: 3, 524 }, 525 ], 526 }; 527 528 for (let name of ["d3d11layers", "glcontext", "wmfvpxvideo"]) { 529 startupPhases["before first paint"].push({ 530 path: `ProfD:${name}.guard`, 531 ignoreIfUnused: true, 532 stat: 1, 533 }); 534 } 535 536 function expandPathWithDirServiceKey(path) { 537 if (path.includes(":")) { 538 let [prefix, suffix] = path.split(":"); 539 let [key, property] = prefix.split("."); 540 let dir = Services.dirsvc.get(key, Ci.nsIFile); 541 if (property) { 542 dir = dir[property]; 543 } 544 545 // Resolve symLinks. 546 let dirPath = dir.path; 547 while (dir && !dir.isSymlink()) { 548 dir = dir.parent; 549 } 550 if (dir) { 551 dirPath = dirPath.replace(dir.path, dir.target); 552 } 553 554 path = dirPath; 555 556 if (suffix) { 557 path += "/" + suffix; 558 } 559 } 560 if (AppConstants.platform == "win") { 561 path = path.replace(/\//g, "\\"); 562 } 563 return path; 564 } 565 566 function getStackFromProfile(profile, stack) { 567 const stackPrefixCol = profile.stackTable.schema.prefix; 568 const stackFrameCol = profile.stackTable.schema.frame; 569 const frameLocationCol = profile.frameTable.schema.location; 570 571 let result = []; 572 while (stack) { 573 let sp = profile.stackTable.data[stack]; 574 let frame = profile.frameTable.data[sp[stackFrameCol]]; 575 stack = sp[stackPrefixCol]; 576 frame = profile.stringTable[frame[frameLocationCol]]; 577 if (frame != "js::RunScript" && !frame.startsWith("next (self-hosted:")) { 578 result.push(frame); 579 } 580 } 581 return result; 582 } 583 584 function pathMatches(path, filename) { 585 path = path.toLowerCase(); 586 return ( 587 path == filename || // Full match 588 // Wildcard on both sides of the path 589 (path.startsWith("*") && 590 path.endsWith("*") && 591 filename.includes(path.slice(1, -1))) || 592 // Wildcard suffix 593 (path.endsWith("*") && filename.startsWith(path.slice(0, -1))) || 594 // Wildcard prefix 595 (path.startsWith("*") && filename.endsWith(path.slice(1))) 596 ); 597 } 598 599 add_task(async function () { 600 if ( 601 !AppConstants.NIGHTLY_BUILD && 602 !AppConstants.MOZ_DEV_EDITION && 603 !AppConstants.DEBUG 604 ) { 605 ok( 606 !("@mozilla.org/test/startuprecorder;1" in Cc), 607 "the startup recorder component shouldn't exist in this non-nightly/non-devedition/" + 608 "non-debug build." 609 ); 610 return; 611 } 612 613 TestUtils.assertPackagedBuild(); 614 615 let startupRecorder = 616 Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject; 617 await startupRecorder.done; 618 619 // Check for main thread I/O markers in the startup profile. 620 let profile = startupRecorder.data.profile.threads[0]; 621 622 let phases = {}; 623 { 624 const nameCol = profile.markers.schema.name; 625 const dataCol = profile.markers.schema.data; 626 627 let markersForCurrentPhase = []; 628 let foundIOMarkers = false; 629 630 for (let m of profile.markers.data) { 631 let markerName = profile.stringTable[m[nameCol]]; 632 if (markerName.startsWith("startupRecorder:")) { 633 phases[markerName.split("startupRecorder:")[1]] = 634 markersForCurrentPhase; 635 markersForCurrentPhase = []; 636 continue; 637 } 638 639 if (markerName != "FileIO") { 640 continue; 641 } 642 643 let markerData = m[dataCol]; 644 if (markerData.source == "sqlite-mainthread") { 645 continue; 646 } 647 648 let samples = markerData.stack.samples; 649 let stack = samples.data[0][samples.schema.stack]; 650 markersForCurrentPhase.push({ 651 operation: markerData.operation, 652 filename: markerData.filename, 653 source: markerData.source, 654 stackId: stack, 655 }); 656 foundIOMarkers = true; 657 } 658 659 // The I/O interposer is disabled if !RELEASE_OR_BETA, so we expect to have 660 // no I/O marker in that case, but it's good to keep the test running to check 661 // that we are still able to produce startup profiles. 662 is( 663 foundIOMarkers, 664 !AppConstants.RELEASE_OR_BETA, 665 "The IO interposer should be enabled in builds that are not RELEASE_OR_BETA" 666 ); 667 if (!foundIOMarkers) { 668 // If a profile unexpectedly contains no I/O marker, it's better to return 669 // early to avoid having a lot of of confusing "no main thread IO when we 670 // expected some" failures. 671 return; 672 } 673 } 674 675 for (let phase in startupPhases) { 676 startupPhases[phase] = startupPhases[phase].filter( 677 entry => !("condition" in entry) || entry.condition 678 ); 679 startupPhases[phase].forEach(entry => { 680 entry.listedPath = entry.path; 681 entry.path = expandPathWithDirServiceKey(entry.path); 682 }); 683 } 684 685 let tmpPath = expandPathWithDirServiceKey("TmpD:").toLowerCase(); 686 let shouldPass = true; 687 for (let phase in phases) { 688 let knownIOList = startupPhases[phase]; 689 info( 690 `known main thread IO paths during ${phase}:\n` + 691 knownIOList 692 .map(e => { 693 let operations = Object.keys(e) 694 .filter(k => k != "path") 695 .map(k => `${k}: ${e[k]}`); 696 return ` ${e.path} - ${operations.join(", ")}`; 697 }) 698 .join("\n") 699 ); 700 701 let markers = phases[phase]; 702 for (let marker of markers) { 703 if (marker.operation == "create/open") { 704 // TODO: handle these I/O markers once they are supported on 705 // non-Windows platforms. 706 continue; 707 } 708 709 if (!marker.filename) { 710 // We are still missing the filename on some mainthreadio markers, 711 // these markers are currently useless for the purpose of this test. 712 continue; 713 } 714 715 // Convert to lower case before comparing because the OS X test machines 716 // have the 'Firefox' folder in 'Library/Application Support' created 717 // as 'firefox' for some reason. 718 let filename = marker.filename.toLowerCase(); 719 720 if (!WIN && filename == "/dev/urandom") { 721 continue; 722 } 723 724 // /dev/shm is always tmpfs (a memory filesystem); this isn't 725 // really I/O any more than mmap/munmap are. 726 if (LINUX && filename.startsWith("/dev/shm/")) { 727 continue; 728 } 729 730 // "Files" from memfd_create() are similar to tmpfs but never 731 // exist in the filesystem; however, they have names which are 732 // exposed in procfs, and the I/O interposer observes when 733 // they're close()d. 734 if (LINUX && filename.startsWith("/memfd:")) { 735 continue; 736 } 737 738 // Shared memory uses temporary files on MacOS <= 10.11 to avoid 739 // a kernel security bug that will never be patched (see 740 // https://crbug.com/project-zero/1671 for details). This can 741 // be removed when we no longer support those OS versions. 742 if (MAC && filename.startsWith(tmpPath + "/org.mozilla.ipc.")) { 743 continue; 744 } 745 746 let expected = false; 747 for (let entry of knownIOList) { 748 if (pathMatches(entry.path, filename)) { 749 entry[marker.operation] = (entry[marker.operation] || 0) - 1; 750 entry._used = true; 751 expected = true; 752 break; 753 } 754 } 755 if (!expected) { 756 record( 757 false, 758 `unexpected ${marker.operation} on ${marker.filename} ${phase}`, 759 undefined, 760 " " + getStackFromProfile(profile, marker.stackId).join("\n ") 761 ); 762 shouldPass = false; 763 } 764 info(`(${marker.source}) ${marker.operation} - ${marker.filename}`); 765 if (kDumpAllStacks) { 766 info( 767 getStackFromProfile(profile, marker.stackId) 768 .map(f => " " + f) 769 .join("\n") 770 ); 771 } 772 } 773 774 for (let entry of knownIOList) { 775 for (let op in entry) { 776 if ( 777 [ 778 "listedPath", 779 "path", 780 "condition", 781 "ignoreIfUnused", 782 "_used", 783 ].includes(op) 784 ) { 785 continue; 786 } 787 let message = `${op} on ${entry.path} `; 788 if (entry[op] == 0) { 789 message += "as many times as expected"; 790 } else if (entry[op] > 0) { 791 message += `allowed ${entry[op]} more times`; 792 } else { 793 message += `${entry[op] * -1} more times than expected`; 794 } 795 Assert.greaterOrEqual(entry[op], 0, `${message} ${phase}`); 796 } 797 if (!("_used" in entry) && !entry.ignoreIfUnused) { 798 ok( 799 false, 800 `no main thread IO when we expected some during ${phase}: ${entry.path} (${entry.listedPath})` 801 ); 802 shouldPass = false; 803 } 804 } 805 } 806 807 if (shouldPass) { 808 ok(shouldPass, "No unexpected main thread I/O during startup"); 809 } else { 810 const filename = "profile_startup_mainthreadio.json"; 811 let path = Services.env.get("MOZ_UPLOAD_DIR"); 812 let profilePath = PathUtils.join(path, filename); 813 await IOUtils.writeJSON(profilePath, startupRecorder.data.profile); 814 ok( 815 false, 816 "Unexpected main thread I/O behavior during startup; open the " + 817 `${filename} artifact in the Firefox Profiler to see what happened` 818 ); 819 } 820 });