profileserver.py (9334B)
1 #!/usr/bin/python 2 # 3 # This Source Code Form is subject to the terms of the Mozilla Public 4 # License, v. 2.0. If a copy of the MPL was not distributed with this 5 # file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7 import glob 8 import os 9 import subprocess 10 import sys 11 12 import mozcrash 13 from mozbuild.base import BinaryNotFoundException, MozbuildObject 14 from mozfile import TemporaryDirectory, json 15 from mozhttpd import MozHttpd 16 from mozprofile import FirefoxProfile, Preferences 17 from mozprofile.permissions import ServerLocations 18 from mozrunner import CLI, FirefoxRunner 19 20 PORT = 8888 21 22 PATH_MAPPINGS = { 23 "/webkit/PerformanceTests": "third_party/webkit/PerformanceTests", 24 # It is tempting to map to `testing/talos/talos/tests` instead, to avoid 25 # writing `tests/` in every path, but we can't do that because some files 26 # refer to scripts located in `../..`. 27 "/talos": "testing/talos/talos", 28 } 29 30 31 def get_crashreports(directory, name=None): 32 rc = 0 33 upload_path = os.environ.get("UPLOAD_PATH") 34 if not upload_path: 35 upload_path = os.environ.get("UPLOAD_DIR") 36 if upload_path: 37 # For automation, log the minidumps with stackwalk and get them moved to 38 # the artifacts directory. 39 fetches_dir = os.environ.get("MOZ_FETCHES_DIR") 40 if not fetches_dir: 41 raise Exception( 42 "Unable to process minidump in automation because " 43 "$MOZ_FETCHES_DIR is not set in the environment" 44 ) 45 stackwalk_binary = os.path.join( 46 fetches_dir, "minidump-stackwalk", "minidump-stackwalk" 47 ) 48 if sys.platform == "win32": 49 stackwalk_binary += ".exe" 50 minidump_path = os.path.join(directory, "minidumps") 51 rc = mozcrash.check_for_crashes( 52 minidump_path, 53 symbols_path=fetches_dir, 54 stackwalk_binary=stackwalk_binary, 55 dump_save_path=upload_path, 56 test_name=name, 57 ) 58 return rc 59 60 61 if __name__ == "__main__": 62 cli = CLI() 63 debug_args, interactive = cli.debugger_arguments() 64 runner_args = cli.runner_args() 65 66 build = MozbuildObject.from_environment() 67 68 binary = runner_args.get("binary") 69 if not binary: 70 try: 71 binary = build.get_binary_path(where="staged-package") 72 except BinaryNotFoundException as e: 73 print(f"{e}\n\n{e.help()}\n") 74 sys.exit(1) 75 binary = os.path.normpath(os.path.abspath(binary)) 76 77 path_mappings = { 78 k: os.path.join(build.topsrcdir, v) for k, v in PATH_MAPPINGS.items() 79 } 80 httpd = MozHttpd( 81 port=PORT, 82 docroot=os.path.join(build.topsrcdir, "build", "pgo"), 83 path_mappings=path_mappings, 84 ) 85 httpd.start(block=False) 86 87 sp3_httpd = MozHttpd( 88 port=8000, 89 docroot=os.path.join( 90 build.topsrcdir, "third_party", "webkit", "PerformanceTests", "Speedometer3" 91 ), 92 path_mappings=path_mappings, 93 ) 94 sp3_httpd.start(block=False) 95 print("started SP3 server on port 8000") 96 locations = ServerLocations() 97 locations.add_host(host="127.0.0.1", port=PORT, options="primary,privileged") 98 99 old_profraw_files = glob.glob("*.profraw") 100 for f in old_profraw_files: 101 os.remove(f) 102 103 with TemporaryDirectory() as profilePath: 104 # TODO: refactor this into mozprofile 105 profile_data_dir = os.path.join(build.topsrcdir, "testing", "profiles") 106 with open(os.path.join(profile_data_dir, "profiles.json")) as fh: 107 base_profiles = json.load(fh)["profileserver"] 108 109 prefpaths = [ 110 os.path.join(profile_data_dir, profile, "user.js") 111 for profile in base_profiles 112 ] 113 114 prefs = {} 115 for path in prefpaths: 116 prefs.update(Preferences.read_prefs(path)) 117 118 interpolation = {"server": "%s:%d" % httpd.httpd.server_address} 119 sp3_interpolation = {"server": "%s:%d" % sp3_httpd.httpd.server_address} 120 for k, v in prefs.items(): 121 if isinstance(v, str): 122 v = v.format(**interpolation) 123 prefs[k] = Preferences.cast(v) 124 125 profile = FirefoxProfile( 126 profile=profilePath, 127 preferences=prefs, 128 addons=[ 129 os.path.join( 130 build.topsrcdir, "tools", "quitter", "quitter@mozilla.org.xpi" 131 ) 132 ], 133 locations=locations, 134 ) 135 136 env = os.environ.copy() 137 env["MOZ_CRASHREPORTER_NO_REPORT"] = "1" 138 env["MOZ_CRASHREPORTER_SHUTDOWN"] = "1" 139 env["XPCOM_DEBUG_BREAK"] = "warn" 140 141 # Ensure different pids write to different files 142 # Use absolute path to ensure that Sandbox computes the correct permissions 143 env["LLVM_PROFILE_FILE"] = os.path.join( 144 os.getcwd(), "default_%p_random_%m.profraw" 145 ) 146 147 # Write to an output file if we're running in automation 148 process_args = {"universal_newlines": True} 149 if "UPLOAD_PATH" in env: 150 process_args["logfile"] = os.path.join( 151 env["UPLOAD_PATH"], "profile-run-1.log" 152 ) 153 154 # Run Firefox a first time to initialize its profile 155 runner = FirefoxRunner( 156 profile=profile, 157 binary=binary, 158 cmdargs=["data:text/html,<script>Quitter.quit()</script>"], 159 env=env, 160 process_args=process_args, 161 ) 162 runner.start() 163 ret = runner.wait() 164 if ret: 165 print("Firefox exited with code %d during profile initialization" % ret) 166 logfile = process_args.get("logfile") 167 if logfile: 168 print("Firefox output (%s):" % logfile) 169 with open(logfile) as f: 170 print(f.read()) 171 sp3_httpd.stop() 172 httpd.stop() 173 get_crashreports(profilePath, name="Profile initialization") 174 sys.exit(ret) 175 176 jarlog = os.getenv("JARLOG_FILE") 177 if jarlog: 178 env["MOZ_JAR_LOG_FILE"] = os.path.abspath(jarlog) 179 print("jarlog: %s" % env["MOZ_JAR_LOG_FILE"]) 180 if os.path.exists(jarlog): 181 os.remove(jarlog) 182 183 if "UPLOAD_PATH" in env: 184 process_args["logfile"] = os.path.join( 185 env["UPLOAD_PATH"], "profile-run-2.log" 186 ) 187 cmdargs = ["http://localhost:%d/index.html" % PORT] 188 runner = FirefoxRunner( 189 profile=profile, 190 binary=binary, 191 cmdargs=cmdargs, 192 env=env, 193 process_args=process_args, 194 ) 195 runner.start(debug_args=debug_args, interactive=interactive) 196 ret = runner.wait() 197 sp3_httpd.stop() 198 httpd.stop() 199 if ret: 200 print("Firefox exited with code %d during profiling" % ret) 201 logfile = process_args.get("logfile") 202 if logfile: 203 print("Firefox output (%s):" % logfile) 204 with open(logfile) as f: 205 print(f.read()) 206 get_crashreports(profilePath, name="Profiling run") 207 sys.exit(ret) 208 209 if "UPLOAD_PATH" in env: 210 should_err = False 211 print("Verify log for LLVM Profile Error") 212 for n in range(1, 2): 213 log = os.path.join(env["UPLOAD_PATH"], f"profile-run-{n}.log") 214 with open(log) as f: 215 for line in f.readlines(): 216 if "LLVM Profile Error" in line: 217 print(f"Error [{log}]: '{line.strip()}'") 218 should_err = True 219 220 if should_err: 221 print("Found some LLVM Profile Error in logs, see above.") 222 sys.exit(1) 223 224 # Try to move the crash reports to the artifacts even if Firefox appears 225 # to exit successfully, in case there's a crash that doesn't set the 226 # return code to non-zero for some reason. 227 if get_crashreports(profilePath, name="Firefox exited successfully?") != 0: 228 print("Firefox exited successfully, but produced a crashreport") 229 sys.exit(1) 230 231 llvm_profdata = env.get("LLVM_PROFDATA") 232 if llvm_profdata: 233 profraw_files = glob.glob("*.profraw") 234 if not profraw_files: 235 print( 236 "Could not find profraw files in the current directory: %s" 237 % os.getcwd() 238 ) 239 sys.exit(1) 240 241 merged_profdata = "merged.profdata" 242 merge_cmd = [ 243 llvm_profdata, 244 "merge", 245 "-o", 246 merged_profdata, 247 ] + profraw_files 248 rc = subprocess.call(merge_cmd) 249 if rc != 0: 250 print("INFRA-ERROR: Failed to merge profile data. Corrupt profile?") 251 # exit with TBPL_RETRY 252 sys.exit(4) 253 254 # llvm-profdata may fail while still exiting without an error. 255 if not os.path.isfile(merged_profdata): 256 print(merged_profdata, "was not created", file=sys.stderr) 257 sys.exit(1) 258 259 if os.path.getsize(merged_profdata) == 0: 260 print(merged_profdata, "was created but it is empty", file=sys.stderr) 261 sys.exit(1)