Skip to content

Instantly share code, notes, and snippets.

@gabedonnan
Last active September 3, 2025 07:08
Show Gist options
  • Select an option

  • Save gabedonnan/e01bb0077fc370cf19cfecda1a7fe55f to your computer and use it in GitHub Desktop.

Select an option

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.
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)
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