22import sys
33import os
44import json
5- import argparse
65import runpy
76import csv
8- from .tracer import Tracer
9- from .compare import compare_traces
7+ from .tracer import Tracer , TracerData
8+ from .compare import compare_traces , ComparisonData
9+ from typing import List , Dict , Any , Optional
10+ from re import Pattern
11+ from argparse import ArgumentParser , Namespace
12+ from pathlib import Path
13+ from dataclasses import asdict
1014
1115
12- def main ():
13- parser = argparse . ArgumentParser (
16+ def main () -> int :
17+ parser : ArgumentParser = ArgumentParser (
1418 description = "OracleTrace - Lightweight execution tracer for Python projects"
1519 )
1620 parser .add_argument ("target" , help = "Python script to trace" )
@@ -39,21 +43,21 @@ def main():
3943 default = 5.0 ,
4044 help = "Regression threshold percentage used with --fail-on-regression." ,
4145 )
42- args = parser .parse_args ()
46+ args : Namespace = parser .parse_args ()
4347
44- target = args .target
48+ target : str = args .target
4549
4650 if not os .path .exists (target ):
4751 print (f"Target not found: { target } " )
4852 return 1
4953
5054 target = os .path .abspath (target )
51- root = os .getcwd ()
52- target_dir = os .path .dirname (target )
55+ root : str = os .getcwd ()
56+ target_dir : str = os .path .dirname (target )
5357 # Setup paths so imports work correctly in the target script
5458 sys .path .insert (0 , target_dir )
55- ignored_args = [] if args .ignore is None else args .ignore
56- ignore_patterns = []
59+ ignored_args : List [ str ] = [] if args .ignore is None else args .ignore
60+ ignore_patterns : List [ Pattern ] = []
5761
5862 for pattern in ignored_args :
5963 try :
@@ -63,19 +67,19 @@ def main():
6367 return 1
6468
6569 # Start tracing, run the script, then stop
66- tracer = Tracer (root , ignore_patterns = ignore_patterns )
70+ tracer : Tracer = Tracer (root , ignore_patterns = ignore_patterns )
6771 tracer .start ()
6872 try :
6973 runpy .run_path (target , run_name = "__main__" )
7074 finally :
7175 tracer .stop ()
7276
73- data = tracer .get_trace_data ()
77+ data : TracerData = tracer .get_trace_data ()
7478
7579 # Save json
7680 if args .json :
7781 with open (args .json , "w" , encoding = "utf-8" ) as f :
78- json .dump (data , f , indent = 4 )
82+ json .dump (asdict ( data ) , f , indent = 4 )
7983
8084 # Display the analysis
8185 if args .top :
@@ -86,17 +90,17 @@ def main():
8690 # Export as csv
8791 if args .csv :
8892 with open (args .csv , "w" , newline = "" , encoding = "utf-8" ) as f :
89- writer = csv .DictWriter (f , fieldnames = ["function" , "total_time" , "calls" , "avg_time" ])
93+ writer : csv . DictWriter = csv .DictWriter (f , fieldnames = ["function" , "total_time" , "calls" , "avg_time" ])
9094 writer .writeheader ()
91- for fn in data [ " functions" ] :
95+ for fn in data . functions :
9296 writer .writerow ({
93- "function" : fn [ " name" ] ,
94- "total_time" : fn [ " total_time" ] ,
95- "calls" : fn [ " call_count" ] ,
96- "avg_time" : fn [ " avg_time" ] ,
97+ "function" : fn . name ,
98+ "total_time" : fn . total_time ,
99+ "calls" : fn . call_count ,
100+ "avg_time" : fn . avg_time ,
97101 })
98102
99- comparison_result = None
103+ comparison_result : Optional [ ComparisonData ] = None
100104
101105 # Compare jsons
102106 if args .compare :
@@ -105,11 +109,11 @@ def main():
105109 return 1
106110
107111 with open (args .compare , "r" , encoding = "utf-8" ) as f :
108- old_data = json .load (f )
112+ old_data : TracerData = TracerData . from_dict ( json .load (f ) )
109113
110114 comparison_result = compare_traces (old_data , data , threshold = args .threshold )
111115
112- if args .fail_on_regression and comparison_result [ " has_regression" ] :
116+ if args .fail_on_regression and comparison_result . has_regression :
113117 print (
114118 f"Build failed: performance regression above { args .threshold :.2f} % detected."
115119 )
0 commit comments