|
| 1 | +"""Compile mypy using mypyc with trace logging enabled, and collect a trace. |
| 2 | +
|
| 3 | +The trace log can be used to analyze low-level performance bottlenecks. |
| 4 | +
|
| 5 | +By default does a self check as the workload. |
| 6 | +
|
| 7 | +This works on all supported platforms, unlike some of our other performance tools. |
| 8 | +""" |
| 9 | + |
| 10 | +from __future__importannotations |
| 11 | + |
| 12 | +importargparse |
| 13 | +importglob |
| 14 | +importos |
| 15 | +importshutil |
| 16 | +importsubprocess |
| 17 | +importsys |
| 18 | +importtime |
| 19 | + |
| 20 | +fromperf_compareimportbuild_mypy,clone |
| 21 | + |
| 22 | +# Generated files, including binaries, go under this directory to avoid overwriting user state. |
| 23 | +TARGET_DIR="mypy.log_trace.tmpdir" |
| 24 | + |
| 25 | + |
| 26 | +defperform_type_check(target_dir:str,code:str|None)->None: |
| 27 | +cache_dir=os.path.join(target_dir,".mypy_cache") |
| 28 | +ifos.path.exists(cache_dir): |
| 29 | +shutil.rmtree(cache_dir) |
| 30 | +args= [] |
| 31 | +ifcodeisNone: |
| 32 | +args.extend(["--config-file","mypy_self_check.ini"]) |
| 33 | +forpatin"mypy/*.py","mypy/*/*.py","mypyc/*.py","mypyc/test/*.py": |
| 34 | +args.extend(glob.glob(pat)) |
| 35 | +else: |
| 36 | +args.extend(["-c",code]) |
| 37 | +check_cmd= ["python","-m","mypy"]+args |
| 38 | +t0=time.time() |
| 39 | +subprocess.run(check_cmd,cwd=target_dir,check=True) |
| 40 | +elapsed=time.time()-t0 |
| 41 | +print(f"{elapsed:.2f}s elapsed") |
| 42 | + |
| 43 | + |
| 44 | +defmain()->None: |
| 45 | +parser=argparse.ArgumentParser( |
| 46 | +description="Compile mypy and collect a trace log while type checking (by default, self check)." |
| 47 | + ) |
| 48 | +parser.add_argument( |
| 49 | +"--multi-file", |
| 50 | +action="store_true", |
| 51 | +help="compile mypy into one C file per module (to reduce RAM use during compilation)", |
| 52 | + ) |
| 53 | +parser.add_argument( |
| 54 | +"--skip-compile",action="store_true",help="use compiled mypy from previous run" |
| 55 | + ) |
| 56 | +parser.add_argument( |
| 57 | +"-c", |
| 58 | +metavar="CODE", |
| 59 | +default=None, |
| 60 | +type=str, |
| 61 | +help="type check Python code fragment instead of mypy self-check", |
| 62 | + ) |
| 63 | +args=parser.parse_args() |
| 64 | +multi_file:bool=args.multi_file |
| 65 | +skip_compile:bool=args.skip_compile |
| 66 | +code:str|None=args.c |
| 67 | + |
| 68 | +target_dir=TARGET_DIR |
| 69 | + |
| 70 | +ifnotskip_compile: |
| 71 | +clone(target_dir,"HEAD") |
| 72 | + |
| 73 | +print(f"Building mypy in{target_dir} with trace logging enabled...") |
| 74 | +build_mypy(target_dir,multi_file,log_trace=True,opt_level="0") |
| 75 | +elifnotos.path.isdir(target_dir): |
| 76 | +sys.exit("error: Can't find compile mypy from previous run -- can't use --skip-compile") |
| 77 | + |
| 78 | +perform_type_check(target_dir,code) |
| 79 | + |
| 80 | +trace_fnam=os.path.join(target_dir,"mypyc_trace.txt") |
| 81 | +print(f"Generated event trace log in{trace_fnam}") |
| 82 | + |
| 83 | + |
| 84 | +if__name__=="__main__": |
| 85 | +main() |