diff options
| -rwxr-xr-x | aergia/aergia.py | 54 | ||||
| -rw-r--r-- | t/test_functionality.py | 6 |
2 files changed, 30 insertions, 30 deletions
diff --git a/aergia/aergia.py b/aergia/aergia.py index edf82ba..d14520a 100755 --- a/aergia/aergia.py +++ b/aergia/aergia.py @@ -26,11 +26,15 @@ Copyright 2025 bdunahu Commentary: Aergia is a sampling based profiler based off of SCALENE - by Emery Berger (https://github.com/plasma-umass/scalene). + by Emery Berger and the UMASS Plasma Lab + (https://github.com/plasma-umass/scalene). It is not particularly informative, but unlike SCALENE or other sampling-based profilers I could find, reports the wall-time each asyncio await call spends idling. + (yappi can profile asyncio, but only report time spent in + each function. Instrumentation-profilers cannot do this + without adding very large overhead). The goal behind Aergia is to eventually have these features, or similar, merged into SCALENE. @@ -102,12 +106,12 @@ class Aergia(object): Debug mode must be on by default to avoid losing samples. Debug mode is required to view the current coroutine being waited on - in `Aergia.get_idle_task_frames'. The TimerHandler object otherwise + in `Aergia._get_idle_task_frames'. The TimerHandler object otherwise does not keep track of a _source_traceback. ''' os.environ["PYTHONASYNCIODEBUG"] = "1" signal.signal(signal.SIGALRM, - Aergia.idle_signal_handler) + Aergia._idle_signal_handler) signal.setitimer(signal.ITIMER_REAL, Aergia.signal_interval, Aergia.signal_interval) @@ -132,7 +136,7 @@ class Aergia(object): '''Pretty-print profiling results.''' if Aergia.total_samples > 0: print("FILE\tFUNC\tPERC\t(ACTUAL -> SECONDS)") - for key in Aergia.sort_samples(Aergia.samples): + for key in Aergia._sort_samples(Aergia.samples): Aergia.print_sample(key) else: print("No samples were gathered. If you *are* using concurrency, " @@ -143,21 +147,21 @@ class Aergia(object): '''Pretty-print a single sample.''' sig_intv = Aergia.signal_interval value = Aergia.samples[key] - print(f"{Aergia.tuple_to_string(key)} :" + print(f"{Aergia._tuple_to_string(key)} :" f"\t\t{value * 100 / Aergia.total_samples:.3f}%" f"\t({value:.3f} ->" f" {value*sig_intv:.6f} seconds)") @staticmethod - def idle_signal_handler(sig, frame): + def _idle_signal_handler(sig, frame): '''Obtains and records which lines are currently being waited on.''' - keys = Aergia.compute_frames_to_record() + keys = Aergia._compute_frames_to_record() for key in keys: - Aergia.samples[Aergia.frame_to_tuple(key)] += 1 + Aergia.samples[Aergia._frame_to_tuple(key)] += 1 Aergia.total_samples += 1 @staticmethod - def compute_frames_to_record(): + def _compute_frames_to_record(): '''Collects all stack frames which are currently being awaited on during a given timestamp, and @@ -167,24 +171,24 @@ class Aergia(object): Luckily, the event loop and asyncio.all_tasks keeps track of what is running for us.''' - loops = Aergia.get_event_loops() - frames = Aergia.get_frames_from_loops(loops) + loops = Aergia._get_event_loops() + frames = Aergia._get_frames_from_loops(loops) return frames @staticmethod - def get_event_loops(): + def _get_event_loops(): '''Returns each thread's event loop, if it exists.''' loops = [] for t in threading.enumerate(): frame = sys._current_frames().get(t.ident) if frame: - loop = Aergia.walk_back_until_loop(frame) + loop = Aergia._walk_back_until_loop(frame) if loop and loop not in loops: loops.append(loop) return loops @staticmethod - def walk_back_until_loop(frame): + def _walk_back_until_loop(frame): '''Walks back the callstack until we are in a method named '_run_once'. If this is ever true, we assume we are in an Asyncio event loop method, and check to see if the 'self' variable is indeed and instance of @@ -200,15 +204,15 @@ class Aergia(object): return None @staticmethod - def get_frames_from_loops(loops): + def _get_frames_from_loops(loops): '''Given LOOPS, returns a flat list of frames.''' return [ frames for loop in loops - for frames in Aergia.get_idle_task_frames(loop) + for frames in Aergia._get_idle_task_frames(loop) ] @staticmethod - def frame_to_tuple(frame): + def _frame_to_tuple(frame): '''Given a frame, constructs a sample key for tallying lines.''' co = frame.f_code func_name = co.co_name @@ -217,20 +221,20 @@ class Aergia(object): return Sample(filename, line_no, func_name) @staticmethod - def tuple_to_string(sample): + def _tuple_to_string(sample): '''Given a namedtuple corresponding to a sample key, pretty-prints a frame as a function/file name and a line number.''' return sample.file + ':' + str(sample.line) + '\t' + sample.func @staticmethod - def sort_samples(sample_dict): + def _sort_samples(sample_dict): '''Returns SAMPLE_DICT in descending order by number of samples.''' return {k: v for k, v in sorted(sample_dict.items(), key=lambda item: item[1], reverse=True)} @staticmethod - def get_idle_task_frames(loop): + def _get_idle_task_frames(loop): '''Given an asyncio event loop, returns the list of idle task frames. A task is considered 'idle' if it is not currently executing.''' idle = [] @@ -240,24 +244,24 @@ class Aergia(object): continue coro = task.get_coro() if coro: - f = Aergia.get_deepest_traceable_frame(coro) + f = Aergia._get_deepest_traceable_frame(coro) if f: idle.append(f) return idle @staticmethod - def get_deepest_traceable_frame(coro): + def _get_deepest_traceable_frame(coro): curr = coro ret = None while curr: frame = getattr(curr, 'cr_frame', None) - if frame and Aergia.should_trace(frame.f_code.co_filename): + if frame and Aergia._should_trace(frame.f_code.co_filename): ret = frame curr = getattr(curr, 'cr_await', None) return ret @staticmethod - def should_trace(filename): + def _should_trace(filename): '''Returns FALSE if filename is uninteresting to the user. Don't depend on this. It's good enough for testing.''' # FIXME Assume GuixSD. Makes filtering easy @@ -272,7 +276,7 @@ class Aergia(object): return True @staticmethod - def gettime(): + def _gettime(): '''returns the wallclock time''' return time.process_time() diff --git a/t/test_functionality.py b/t/test_functionality.py index 0302a90..2a2bbe3 100644 --- a/t/test_functionality.py +++ b/t/test_functionality.py @@ -1,8 +1,4 @@ -try: - import yappi -except ImportError: - print("yappi module not found. Skipping related tests.") - exit(0) +import yappi import utils import asyncio import threading |
