55from rich .tree import Tree
66from rich .table import Table
77from rich import print
8+ from typing import List , Optional , Callable , DefaultDict , Any , Tuple , Dict
9+ from re import Pattern
10+ from pathlib import Path
11+ from types import FrameType
812
913
1014class Tracer :
11- def __init__ (self , root_dir , ignore_patterns = None ):
12- self ._root_path = os .path .abspath (root_dir )
13- self ._call_stack = []
14- self ._func_calls = defaultdict (int )
15- self ._func_time = defaultdict (float )
16- self ._call_map = defaultdict (lambda : defaultdict (int ))
17- self ._original_profile_func = None
18- self ._enabled = False
19- self ._start_time = 0.0
20- self ._total_time = 0.0
21- self ._ignore_patterns = ignore_patterns
22-
23-
24- def start (self ):
15+ def __init__ (self , root_dir : str , ignore_patterns : Optional [ List [ Pattern ]] = None ) -> None :
16+ self ._root_path : str = os .path .abspath (root_dir )
17+ self ._call_stack : List [ Tuple [ int , str , float ]] = []
18+ self ._func_calls : DefaultDict [ str , int ] = defaultdict (int )
19+ self ._func_time : DefaultDict [ str , float ] = defaultdict (float )
20+ self ._call_map : DefaultDict [ str , DefaultDict [ str , int ]] = defaultdict (lambda : defaultdict (int ))
21+ self ._original_profile_func : Optional [ Callable [[ FrameType , str , Any ], object ]] = None
22+ self ._enabled : bool = False
23+ self ._start_time : float = 0.0
24+ self ._total_time : float = 0.0
25+ self ._ignore_patterns : Optional [ List [ Pattern ]] = ignore_patterns
26+
27+
28+ def start (self ) -> None :
2529 # Start Tracer
2630 self ._enabled = True
2731 self ._start_time = time .perf_counter ()
2832 self ._original_profile_func = sys .getprofile ()
2933 sys .setprofile (self ._trace )
3034
31- def stop (self ):
35+ def stop (self ) -> None :
3236 # Stops Tracer
3337 self ._enabled = False
3438 self ._total_time = time .perf_counter () - self ._start_time
3539 sys .setprofile (self ._original_profile_func )
3640
37- def _is_ignored (self , filename ) :
41+ def _is_ignored (self , filename : str ) -> bool :
3842 # Return true if the filename should be ignored
3943 if not self ._ignore_patterns :
4044 return False
@@ -45,46 +49,46 @@ def _is_ignored(self, filename):
4549
4650 return False
4751
48- def _is_user_code (self , filename ) :
52+ def _is_user_code (self , filename : str ) -> bool :
4953 # Filter out files not in the project root
50- if not filename .startswith (self ._root_path ):
54+ if not filename .startswith (str ( self ._root_path ) ):
5155 return False
5256 # Filter out third-party libraries
5357 if "site-packages" in filename or "dist-packages" in filename :
5458 return False
5559 return True
5660
57- def _get_key (self , frame ) :
58- co_filename = frame .f_code .co_filename
61+ def _get_key (self , frame : FrameType ) -> Optional [ str ] :
62+ co_filename : str = frame .f_code .co_filename
5963 # Ignore internal python frames (e.g. <string>)
6064 if co_filename .startswith ("<" ):
6165 return None
62- filename = os .path .abspath (co_filename )
66+ filename : str = os .path .abspath (co_filename )
6367 # Check if the file belongs to the user's project
6468 if not self ._is_user_code (filename ):
6569 return None
6670 # Create a relative path key for readability
67- rel_path = os .path .relpath (filename , self ._root_path )
68- qualname = getattr (frame .f_code , "co_qualname" , frame .f_code .co_name )
69- key = f"{ rel_path } :{ qualname } "
71+ rel_path : str = os .path .relpath (filename , self ._root_path )
72+ qualname : str = getattr (frame .f_code , "co_qualname" , frame .f_code .co_name )
73+ key : str = f"{ rel_path } :{ qualname } "
7074
7175 # Check if the file should be ignored based on inputted ignoring pattern
7276 if self ._is_ignored (key ):
7377 return None
7478
7579 return key
7680
77- def _trace (self , frame , event , arg ) :
81+ def _trace (self , frame : FrameType , event : str , _ : Any ) -> None :
7882 try :
7983 if not self ._enabled :
8084 return
8185
8286 if event == "call" :
83- key = self ._get_key (frame )
87+ key : Optional [ str ] = self ._get_key (frame )
8488 if not key :
8589 return
8690
87- caller = self ._call_stack [- 1 ][1 ] if self ._call_stack else "<module>"
91+ caller : str = self ._call_stack [- 1 ][1 ] if self ._call_stack else "<module>"
8892 self ._call_map [caller ][key ] += 1
8993 self ._func_calls [key ] += 1
9094 self ._call_stack .append ((id (frame ), key , time .perf_counter ()))
@@ -99,8 +103,8 @@ def _trace(self, frame, event, arg):
99103 self ._func_time [key ] += time .perf_counter () - start
100104 else :
101105 # Stack unwinding (handle exceptions or missed returns)
102- fid = id (frame )
103- found = False
106+ fid : int = id (frame )
107+ found : bool = False
104108 # Search for the frame in the stack from top to bottom
105109 for i in range (len (self ._call_stack ) - 1 , - 1 , - 1 ):
106110 if self ._call_stack [i ][0 ] == fid :
@@ -117,15 +121,15 @@ def _trace(self, frame, event, arg):
117121 except Exception as e :
118122 print (f"[bold red]Error in oracletrace tracer: { e } [/]" , file = sys .stderr )
119123
120- def show_results (self , _top ) :
124+ def show_results (self , _top : Optional [ int ]) -> None :
121125 if not self ._func_calls :
122126 print ("[yellow]No calls traced.[/]" )
123127 return
124128
125129 # Summary table
126130 print ("[bold green]Summary:[/]" )
127131 print (f"[bold cyan]Total execution time: { self ._total_time :.4f} s[/]" )
128- table = Table (title = "Top functions by Total Time" )
132+ table : Table = Table (title = "Top functions by Total Time" )
129133 table .add_column ("Function" , justify = "left" , style = "cyan" , no_wrap = True )
130134 table .add_column ("Total Time (s)" , justify = "right" , style = "magenta" )
131135 table .add_column ("Calls" , justify = "right" , style = "green" )
@@ -145,11 +149,11 @@ def show_results(self, _top):
145149
146150 print ("\n [bold green]Logic Flow:[/]" )
147151
148- tree = Tree ("[bold yellow]<module>[/]" )
152+ tree : Tree = Tree ("[bold yellow]<module>[/]" )
149153
150154 # Recursively build the execution tree
151- def add_nodes (parent_node , parent_key , current_path ) :
152- children = self ._call_map .get (parent_key , {} )
155+ def add_nodes (parent_node : Tree , parent_key : str , current_path : set [ str ]) -> None :
156+ children : DefaultDict [ str , int ] = self ._call_map .get (parent_key , defaultdict ( int ) )
153157 # Sort children by total execution time
154158 sorted_children = sorted (
155159 children .items (),
@@ -171,8 +175,8 @@ def add_nodes(parent_node, parent_key, current_path):
171175 add_nodes (tree , "<module>" , {"<module>" })
172176 print (tree )
173177
174- def get_trace_data (self ):
175- functions = []
178+ def get_trace_data (self ) -> Dict [ str , Any ] :
179+ functions : List [ Dict [ str , Any ]] = []
176180
177181 for key , total_time in self ._func_time .items ():
178182 calls = self ._func_calls [key ]
@@ -198,10 +202,10 @@ def get_trace_data(self):
198202 }
199203
200204
201- _tracer_instance = None
205+ _tracer_instance : Optional [ Tracer ] = None
202206
203207
204- def start_trace (root_dir ) :
208+ def start_trace (root_dir : str ) -> None :
205209 # Starts tracer instance
206210 global _tracer_instance
207211 if _tracer_instance is not None :
@@ -211,7 +215,7 @@ def start_trace(root_dir):
211215 _tracer_instance .start ()
212216
213217
214- def stop_trace ():
218+ def stop_trace () -> Optional [ Dict [ str , Any ]] :
215219 # Stops tracer instance
216220 global _tracer_instance
217221 if _tracer_instance :
@@ -224,10 +228,10 @@ def stop_trace():
224228 return None
225229
226230
227- def show_results () :
231+ def show_results (top : Optional [ int ]) -> None :
228232 # Show results from global tracer instance
229233 global _tracer_instance
230234 if _tracer_instance :
231- _tracer_instance .show_results ()
235+ _tracer_instance .show_results (top )
232236 else :
233237 print ("[yellow]Tracer was not started.[/]" )
0 commit comments