Skip to content

Commit

Permalink
on Trace_Call, added trace_ignore_contains feature, added support for…
Browse files Browse the repository at this point in the history
… starting and stopping trace in separate threads

couple refactorings
fixed tests
  • Loading branch information
DinisCruz committed Aug 20, 2024
1 parent f4ec771 commit 2647ad6
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 14 deletions.
44 changes: 44 additions & 0 deletions osbot_utils/helpers/trace/Trace_Call.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import linecache
import sys
import threading
from functools import wraps

from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
Expand All @@ -8,6 +9,8 @@
from osbot_utils.helpers.trace.Trace_Call__Print_Lines import Trace_Call__Print_Lines
from osbot_utils.helpers.trace.Trace_Call__Print_Traces import Trace_Call__Print_Traces
from osbot_utils.helpers.trace.Trace_Call__View_Model import Trace_Call__View_Model
from osbot_utils.testing.Stdout import Stdout
from osbot_utils.utils.Str import ansi_to_text


def trace_calls(title = None , print_traces = True , show_locals = False, source_code = False ,
Expand Down Expand Up @@ -59,6 +62,7 @@ def __init__(self, **kwargs):
self.config.trace_capture_contains = self.config.trace_capture_contains or [] # and None will be quite common since we can use [] on method's params
self.config.print_max_string_length = self.config.print_max_string_length or PRINT_MAX_STRING_LENGTH
self.stack = self.trace_call_handler.stack
self.trace_on_thread__data = {}
#self.prev_trace_function = None # Stores the previous trace function


Expand Down Expand Up @@ -94,6 +98,13 @@ def print(self):
#self.print_lines()
return view_model

def print_to_str(self):
with Stdout() as stdout:
self.print()
trace_data = ansi_to_text(stdout.value())
return trace_data


def print_lines(self):
print()
view_model = self.view_data()
Expand All @@ -106,13 +117,46 @@ def start(self):
self.started = True # set this here so that it does show in the trace
sys.settrace(self.trace_call_handler.trace_calls) # Set the new trace function

def start__on_thread(self, root_node=None):
if sys.gettrace() is None:
current_thread = threading.current_thread()
thread_sys_trace = sys.gettrace()
thread_name = current_thread.name
thread_id = current_thread.native_id
thread_nodes = []
thread_data = dict(thread_name = thread_name ,
thread_id = thread_id ,
thread_nodes = thread_nodes ,
thread_sys_trace = thread_sys_trace)
title = f"Thread: {thread_name} ({thread_id})"
thread_node__for_title = self.trace_call_handler.stack.add_node(title=title) # Add node with name of Thread

if root_node: # Add node with name of node
thread_node__for_root = self.trace_call_handler.stack.add_node(root_node)
thread_nodes.append(thread_node__for_root)
thread_nodes.append(thread_node__for_title)
sys.settrace(self.trace_call_handler.trace_calls)
self.trace_on_thread__data[thread_id] = thread_data


def stop(self):
if self.started:
sys.settrace(self.prev_trace_function) # Restore the previous trace function
self.stack.empty_stack()
self.started = False

def stop__on_thread(self):
current_thread = threading.current_thread()
thread_id = current_thread.native_id
thread_data = self.trace_on_thread__data.get(thread_id)
if thread_data: # if there trace_call set up in the current thread
thread_sys_trace = thread_data.get('thread_sys_trace')
thread_nodes = thread_data.get('thread_nodes')
for thread_node in thread_nodes: # remove extra nodes added during start__on_thread
self.trace_call_handler.stack.pop(thread_node)
sys.settrace(thread_sys_trace) # restore previous sys.trace value
del self.trace_on_thread__data[thread_id]

def stats(self):
return self.trace_call_handler.stats

Expand Down
1 change: 1 addition & 0 deletions osbot_utils/helpers/trace/Trace_Call__Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class Trace_Call__Config(Kwargs_To_Self):
trace_capture_contains : list
trace_enabled : bool = True
trace_ignore_start_with : list
trace_ignore_contains : list
trace_show_internals : bool
trace_up_to_depth : int
with_duration_bigger_than : float
Expand Down
23 changes: 18 additions & 5 deletions osbot_utils/helpers/trace/Trace_Call__Handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,19 @@
'__default__value__' ,
'__setattr__' ,
'<module>']
GLOBAL_MODULES_TO_IGNORE = ['osbot_utils.helpers.trace.Trace_Call' , # todo: map out and document why exactly these modules are ignore (and what is the side effect)
'osbot_utils.helpers.CPrint' , # also see if this should be done here or at the print/view stage
'osbot_utils.helpers.Print_Table' ,
'osbot_utils.decorators.methods.cache_on_self' ,
'codecs']
GLOBAL_MODULES_TO_IGNORE = ['osbot_utils.helpers.trace.Trace_Call' , # todo: map out and document why exactly these modules are ignore (and what is the side effect)
'osbot_utils.helpers.trace.Trace_Call__Config' ,
'osbot_utils.helpers.trace.Trace_Call__View_Model' ,
'osbot_utils.helpers.trace.Trace_Call__Print_Traces' ,
'osbot_utils.helpers.trace.Trace_Call__Stack' ,
'osbot_utils.base_classes.Type_Safe' ,
'osbot_utils.helpers.CPrint' , # also see if this should be done here or at the print/view stage
'osbot_utils.helpers.Print_Table' ,
'osbot_utils.decorators.methods.cache_on_self' ,
'codecs' ]

#GLOBAL_MODULES_TO_IGNORE = []
#GLOBAL_FUNCTIONS_TO_IGNORE = []

class Trace_Call__Handler(Kwargs_To_Self):
config : Trace_Call__Config
Expand Down Expand Up @@ -171,6 +179,11 @@ def should_capture(self, frame):
if module.startswith(item) or func_name.startswith(item):
capture = False
break

for item in self.config.trace_ignore_contains: # Check if the module should be ignored
if item in module or item in func_name:
capture = False
break
return capture

def stack_json__parse_node(self, stack_node: Trace_Call__Stack_Node):
Expand Down
14 changes: 10 additions & 4 deletions osbot_utils/testing/Stderr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
from contextlib import redirect_stderr


class Stderr:
class Stderr: # todo: refactor with Stdout whose code is 90% the same as this one. Add class to capture both at the same time
def __init__(self):
self.output = io.StringIO()
self.redirect_stderr = redirect_stderr(self.output)

def __enter__(self):
self.redirect_stderr.__enter__()
self.start()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.redirect_stderr.__exit__(exc_type, exc_val, exc_tb)
def __exit__(self, *args, **kwargs):
self.stop(*args, **kwargs)

def start(self):
self.redirect_stderr.__enter__()

def stop(self, exc_type=None, exc_inst=None, exc_tb=None):
self.redirect_stderr.__exit__(exc_type, exc_inst, exc_tb)

def value(self):
return self.output.getvalue()
Expand Down
12 changes: 9 additions & 3 deletions osbot_utils/testing/Stdout.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,17 @@ def __init__(self):
self.redirect_stdout = redirect_stdout(self.output)

def __enter__(self):
self.redirect_stdout.__enter__()
self.start()
return self

def __exit__(self, exc_type, exc_val, exc_tb):
self.redirect_stdout.__exit__(exc_type, exc_val, exc_tb)
def __exit__(self, *args, **kwargs):
self.stop(*args, **kwargs)

def start(self):
self.redirect_stdout.__enter__()

def stop(self, exc_type=None, exc_inst=None, exc_tb=None):
self.redirect_stdout.__exit__(exc_type, exc_inst, exc_tb)

def value(self):
return self.output.getvalue()
Expand Down
3 changes: 3 additions & 0 deletions osbot_utils/utils/Env.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ def find_dotenv_file(start_path=None, env_file_to_find='.env'):
def in_github_action():
return os.getenv('GITHUB_ACTIONS') == 'true'

def in_pytest_with_coverage():
return os.getenv('COVERAGE_RUN') == 'true'

def in_python_debugger():
if sys.gettrace() is not None: # Check for a trace function
return True
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/helpers/trace/test_Trace_Call.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def test___init__(self):
'started' : False ,
'trace_call_handler' : self.trace_call.trace_call_handler ,
'trace_call_view_model' : self.trace_call.trace_call_view_model ,
'trace_call_print_traces': self.trace_call.trace_call_print_traces}
'trace_call_print_traces': self.trace_call.trace_call_print_traces,
'trace_on_thread__data' : {} }
assert type(self.trace_call.trace_call_handler ) is Trace_Call__Handler
assert type(self.trace_call.trace_call_view_model) is Trace_Call__View_Model

Expand Down
1 change: 1 addition & 0 deletions tests/unit/helpers/trace/test_Trace_Call__Config.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def test__kwargs__(self):
'trace_capture_start_with' : [] ,
'trace_capture_contains' : [] ,
'trace_enabled' : True ,
'trace_ignore_contains' : [] ,
'trace_ignore_start_with' : [] ,
'trace_show_internals' : False ,
'trace_up_to_depth' : 0 ,
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/helpers/trace/test_Trace_Files.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ def test___default_kwargs__(self):
'stack' : trace_files.stack ,
'trace_call_handler' : trace_files.trace_call_handler ,
'trace_call_print_traces' : trace_files.trace_call_print_traces,
'trace_call_view_model' : trace_files.trace_call_view_model }
'trace_call_view_model' : trace_files.trace_call_view_model ,
'trace_on_thread__data' : {} }

trace_files.stack.add_node(DEFAULT_ROOT_NODE_NODE_TITLE)
assert len(trace_files.stack) == 1
Expand Down

0 comments on commit 2647ad6

Please sign in to comment.