Last active
September 3, 2025 07:08
-
-
Save gabedonnan/e01bb0077fc370cf19cfecda1a7fe55f to your computer and use it in GitHub Desktop.
A simple python decorator to track the calls of a decorated function, including generating a tree of it's recursive calls, tracking time taken for execution and memory allocated.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| from tracing import Tracker | |
| @Tracker | |
| def recur_fibo(n): | |
| # Simple recursive fibbonacci program found on stackoverflow for testing | |
| if n <= 1: | |
| return n | |
| else: | |
| return recur_fibo(n-1) + recur_fibo(n-2) | |
| recur_fibo(4) | |
| for trace in recur_fibo.traces: | |
| print(trace) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import os | |
| from time import time | |
| import psutil | |
| class TreeNode: | |
| """ | |
| Stores information about called functions including time taken to call, return values and memory used | |
| Memory usage tracking is a little finicky | |
| """ | |
| def __init__(self, val, func_name): | |
| self.val = val | |
| self.func_name = func_name | |
| self.parent = None | |
| self.children = [] | |
| # For storage of info | |
| self.retval = None | |
| self.memory_used = None | |
| self.time_taken = None | |
| def __str__(self, level=0): | |
| ret = "\t" * level + self.__repr__() + "\n" | |
| for child in self.children: | |
| ret += child.__str__(level + 1) | |
| return ret | |
| def __repr__(self): | |
| return ( | |
| f"{self.func_name}{self.val}->(return={self.retval}, time={self.time_taken}" | |
| f"{' memory=' + str(self.memory_used) if self.memory_used is not None else ''})" | |
| ) | |
| class Tracker: | |
| def __init__(self, func): | |
| self._func = func | |
| self.traces = [] | |
| self.call_count = 0 | |
| self._root = None | |
| self._cur = None | |
| self.process_id = os.getpid() | |
| @staticmethod | |
| def _unpack_args(args: tuple, kwargs: dict): | |
| return args + tuple(val for val in kwargs.values()) | |
| def get_process_memory(self): | |
| return psutil.Process(self.process_id).memory_info().rss | |
| def __call__(self, *args, **kwargs): | |
| # Increment call count | |
| self.call_count += 1 | |
| # Next function call to store is create | |
| node = TreeNode(self._unpack_args(args, kwargs), self._func.__name__) | |
| if self._root is None: | |
| # First call of this function | |
| self._root = node | |
| self._cur = self._root | |
| else: | |
| # First call of this function | |
| self._cur.children.append(node) | |
| node.parent = self._cur | |
| self._cur = self._cur.children[-1] | |
| # Call the function and time it | |
| t0 = time() | |
| m0 = self.get_process_memory() | |
| retval = self._func(*args, **kwargs) | |
| # Load node function call values into node | |
| node.memory_used = self.get_process_memory() - m0 | |
| node.time_taken = time() - t0 | |
| node.retval = retval | |
| # Exit current location | |
| if self._cur.parent is not None: | |
| self._cur = self._cur.parent | |
| else: | |
| self.traces.append(self._cur) | |
| self._root = None | |
| self._cur = None | |
| return retval | |
| def clear_memory(self): | |
| self._root = None | |
| self._cur = None | |
| self.traces = [] | |
| self.call_count = 0 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment