55from rich .tree import Tree
66from rich .table import Table
77from rich import print
8-
9-
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
1012class 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 ):
13+ def __init__ (self , root_dir : str , ignore_patterns : Optional [ List [ Pattern ]] = None ) -> None :
14+ self ._root_path : str = os .path .abspath (root_dir )
15+ self ._call_stack : List [ Tuple [ int , str , float ]] = []
16+ self ._func_calls : DefaultDict [ str , int ] = defaultdict (int )
17+ self ._func_time : DefaultDict [ str , float ] = defaultdict (float )
18+ self ._call_map : DefaultDict [ str , DefaultDict [ str , int ]] = defaultdict (lambda : defaultdict (int ))
19+ self ._original_profile_func : Optional [ Callable [[ FrameType , str , Any ], object ]] = None
20+ self ._enabled : bool = False
21+ self ._start_time : float = 0.0
22+ self ._total_time : float = 0.0
23+ self ._ignore_patterns : Optional [ List [ Pattern ]] = ignore_patterns
24+
25+
26+ def start (self ) -> None :
2527 # Start Tracer
2628 self ._enabled = True
2729 self ._start_time = time .perf_counter ()
2830 self ._original_profile_func = sys .getprofile ()
2931 sys .setprofile (self ._trace )
3032
31- def stop (self ):
33+ def stop (self ) -> None :
3234 # Stops Tracer
3335 self ._enabled = False
3436 self ._total_time = time .perf_counter () - self ._start_time
3537 sys .setprofile (self ._original_profile_func )
3638
37- def _is_ignored (self , filename ) :
39+ def _is_ignored (self , filename : str ) -> bool :
3840 # Return true if the filename should be ignored
3941 if not self ._ignore_patterns :
4042 return False
@@ -45,46 +47,46 @@ def _is_ignored(self, filename):
4547
4648 return False
4749
48- def _is_user_code (self , filename ) :
50+ def _is_user_code (self , filename : str ) -> bool :
4951 # Filter out files not in the project root
50- if not filename .startswith (self ._root_path ):
52+ if not filename .startswith (str ( self ._root_path ) ):
5153 return False
5254 # Filter out third-party libraries
5355 if "site-packages" in filename or "dist-packages" in filename :
5456 return False
5557 return True
5658
57- def _get_key (self , frame ) :
58- co_filename = frame .f_code .co_filename
59+ def _get_key (self , frame : FrameType ) -> Optional [ str ] :
60+ co_filename : str = frame .f_code .co_filename
5961 # Ignore internal python frames (e.g. <string>)
6062 if co_filename .startswith ("<" ):
6163 return None
62- filename = os .path .abspath (co_filename )
64+ filename : str = os .path .abspath (co_filename )
6365 # Check if the file belongs to the user's project
6466 if not self ._is_user_code (filename ):
6567 return None
6668 # 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 } "
69+ rel_path : str = os .path .relpath (filename , self ._root_path )
70+ qualname : str = getattr (frame .f_code , "co_qualname" , frame .f_code .co_name )
71+ key : str = f"{ rel_path } :{ qualname } "
7072
7173 # Check if the file should be ignored based on inputted ignoring pattern
7274 if self ._is_ignored (key ):
7375 return None
7476
7577 return key
7678
77- def _trace (self , frame , event , arg ) :
79+ def _trace (self , frame : FrameType , event : str , _ : Any ) -> None :
7880 try :
7981 if not self ._enabled :
8082 return
8183
8284 if event == "call" :
83- key = self ._get_key (frame )
85+ key : Optional [ str ] = self ._get_key (frame )
8486 if not key :
8587 return
8688
87- caller = self ._call_stack [- 1 ][1 ] if self ._call_stack else "<module>"
89+ caller : str = self ._call_stack [- 1 ][1 ] if self ._call_stack else "<module>"
8890 self ._call_map [caller ][key ] += 1
8991 self ._func_calls [key ] += 1
9092 self ._call_stack .append ((id (frame ), key , time .perf_counter ()))
@@ -99,8 +101,8 @@ def _trace(self, frame, event, arg):
99101 self ._func_time [key ] += time .perf_counter () - start
100102 else :
101103 # Stack unwinding (handle exceptions or missed returns)
102- fid = id (frame )
103- found = False
104+ fid : int = id (frame )
105+ found : bool = False
104106 # Search for the frame in the stack from top to bottom
105107 for i in range (len (self ._call_stack ) - 1 , - 1 , - 1 ):
106108 if self ._call_stack [i ][0 ] == fid :
@@ -117,15 +119,15 @@ def _trace(self, frame, event, arg):
117119 except Exception as e :
118120 print (f"[bold red]Error in oracletrace tracer: { e } [/]" , file = sys .stderr )
119121
120- def show_results (self , _top ) :
122+ def show_results (self , _top : Optional [ int ]) -> None :
121123 if not self ._func_calls :
122124 print ("[yellow]No calls traced.[/]" )
123125 return
124126
125127 # Summary table
126128 print ("[bold green]Summary:[/]" )
127129 print (f"[bold cyan]Total execution time: { self ._total_time :.4f} s[/]" )
128- table = Table (title = "Top functions by Total Time" )
130+ table : Table = Table (title = "Top functions by Total Time" )
129131 table .add_column ("Function" , justify = "left" , style = "cyan" , no_wrap = True )
130132 table .add_column ("Total Time (s)" , justify = "right" , style = "magenta" )
131133 table .add_column ("Calls" , justify = "right" , style = "green" )
@@ -145,11 +147,11 @@ def show_results(self, _top):
145147
146148 print ("\n [bold green]Logic Flow:[/]" )
147149
148- tree = Tree ("[bold yellow]<module>[/]" )
150+ tree : Tree = Tree ("[bold yellow]<module>[/]" )
149151
150152 # Recursively build the execution tree
151- def add_nodes (parent_node , parent_key , current_path ) :
152- children = self ._call_map .get (parent_key , {} )
153+ def add_nodes (parent_node : Tree , parent_key : str , current_path : set [ str ]) -> None :
154+ children : DefaultDict [ str , int ] = self ._call_map .get (parent_key , defaultdict ( int ) )
153155 # Sort children by total execution time
154156 sorted_children = sorted (
155157 children .items (),
@@ -171,8 +173,8 @@ def add_nodes(parent_node, parent_key, current_path):
171173 add_nodes (tree , "<module>" , {"<module>" })
172174 print (tree )
173175
174- def get_trace_data (self ):
175- functions = []
176+ def get_trace_data (self ) -> Dict [ str , Any ] :
177+ functions : List [ Dict [ str , Any ]] = []
176178
177179 for key , total_time in self ._func_time .items ():
178180 calls = self ._func_calls [key ]
@@ -198,10 +200,10 @@ def get_trace_data(self):
198200 }
199201
200202
201- _tracer_instance = None
203+ _tracer_instance : Optional [ Tracer ] = None
202204
203205
204- def start_trace (root_dir ) :
206+ def start_trace (root_dir : str ) -> None :
205207 # Starts tracer instance
206208 global _tracer_instance
207209 if _tracer_instance is not None :
@@ -211,7 +213,7 @@ def start_trace(root_dir):
211213 _tracer_instance .start ()
212214
213215
214- def stop_trace ():
216+ def stop_trace () -> Optional [ Dict [ str , Any ]] :
215217 # Stops tracer instance
216218 global _tracer_instance
217219 if _tracer_instance :
@@ -224,10 +226,10 @@ def stop_trace():
224226 return None
225227
226228
227- def show_results () :
229+ def show_results (top : Optional [ int ]) -> None :
228230 # Show results from global tracer instance
229231 global _tracer_instance
230232 if _tracer_instance :
231- _tracer_instance .show_results ()
233+ _tracer_instance .show_results (top )
232234 else :
233235 print ("[yellow]Tracer was not started.[/]" )
0 commit comments