summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbd <bdunahu@operationnull.com>2025-09-08 14:50:40 -0400
committerbd <bdunahu@operationnull.com>2025-09-08 14:50:40 -0400
commit98924f4ac0a07cd9d4bb74322364347493e73159 (patch)
tree8a36ba97ab02ae3176e85e59aa5c50323b9f4263
parentba1c72cedb56512f52c48ee947a2b11fa8a90c4d (diff)
Improve reporting format, separate samples based on tident
-rw-r--r--nemesis/experiment.py32
-rwxr-xr-xnemesis/nemesis.py34
2 files changed, 52 insertions, 14 deletions
diff --git a/nemesis/experiment.py b/nemesis/experiment.py
index 9d179ea..353d013 100644
--- a/nemesis/experiment.py
+++ b/nemesis/experiment.py
@@ -16,9 +16,13 @@ class Experiment:
# event loops participating in this this experiment
loops = []
# a key-value pair where keys represent a handle and values
- # represent number of times sampled.
+ # represent the period of time waiting
_samples = None
+ # the amount of time required for a handle to be included in
+ # a report
+ _wait_time_threshold = 0.001
+
def __init__(self, coro, speedup):
self._coro = coro
self._speedup = speedup
@@ -35,11 +39,21 @@ class Experiment:
def get_results(self):
ret = f"Results for {self._coro} at {self._speedup} times speedup:\n"
if len(self._samples) > 0:
- ret += (f" {'HANDLE':<30} {'LOOP':<10} {'SEC':<10}")
+ ret += (f" {'NAME':<15} {'FUNC/CORO':<45} {'TIDENT':<16} {'SEC':<10}")
+ ret += (f"\n {'---':<15} {'---':<45} {'---':<16} {'---':<10}")
+ tot = 0
for key in self._sort_samples(self._samples):
- ret += f"\n {self._get_sample_str(key)}"
+ name = self._trim_to_last_x_chars(key[0], 15)
+ ctxt = self._trim_to_last_x_chars(key[1], 45)
+ tid = key[2]
+ value = round(self._samples[key] / self._speedup, 4)
+ tot += value
+ if value >= self._wait_time_threshold:
+ ret += f"\n {name:<15} {ctxt:<45} {tid:<16} {value:<10}"
+ ret += f"\n {'':<79}---"
+ ret += f"\n {'':<79}{round(tot, 4)}"
else:
- ret += " No samples were gathered. (This is odd!)"
+ ret += " No samples were gathered. (This is odd!)"
return ret
def get_loops(self):
@@ -47,7 +61,8 @@ class Experiment:
def add_handles(self, handles, loop, time):
for h in handles:
- self._samples[h.__str__()] += time
+ h.append(loop._thread_id)
+ self._samples[tuple(h)] += time
def _set_loops(self):
self._loops = self._get_event_loops()
@@ -81,12 +96,11 @@ class Experiment:
frame = frame.f_back
return None
- def _get_sample_str(self, key):
- value = self._samples[key] / self._speedup
- return f"{key:29} {value:10}"
-
def _sort_samples(self, 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)}
+
+ def _trim_to_last_x_chars(self, string, x):
+ return string[-x:] if len(string) >= x else string
diff --git a/nemesis/nemesis.py b/nemesis/nemesis.py
index 69d0c2b..c20d68d 100755
--- a/nemesis/nemesis.py
+++ b/nemesis/nemesis.py
@@ -25,7 +25,6 @@ Commentary:
Code:
'''
-from asyncio.base_events import _format_handle
from collections import defaultdict
from experiment import Experiment
import argparse
@@ -128,12 +127,21 @@ class Nemesis(object):
def _get_waiting_handles(loop):
handles = []
for handle in loop._ready:
- if (fmt_handle := _format_handle(handle)) is not None \
- and fmt_handle not in handles:
- # no duplicates
- handles.append(fmt_handle)
+ # no duplicates
+ handle_info = Nemesis._parse_handle(handle)
+ if handle_info not in handles:
+ handles.append(handle_info)
return handles
+ def _parse_handle(handle):
+ cb = handle._callback
+ if isinstance(getattr(cb, '__self__', None), asyncio.tasks.Task):
+ task = cb.__self__
+ coro = task.get_coro()
+ return [task.get_name(), Nemesis.get_coro_name(coro)]
+ else:
+ return [str(type(handle).__name__), cb.__name__]
+
def _get_current_coro(loop):
tid = loop._thread_id
assert tid, f"{loop} is not running, yet we attempted to sample it!"
@@ -173,6 +181,22 @@ class Nemesis(object):
return False
return True
+ def get_coro_name(coro):
+ '''
+ Stolen from _format_coroutine in cpython/Lib/asyncio/coroutines.py
+ '''
+ # Coroutines compiled with Cython sometimes don't have
+ # proper __qualname__ or __name__. While that is a bug
+ # in Cython, asyncio shouldn't crash with an AttributeError
+ # in its __repr__ functions.
+ if hasattr(coro, '__qualname__') and coro.__qualname__:
+ coro_name = coro.__qualname__
+ elif hasattr(coro, '__name__') and coro.__name__:
+ coro_name = coro.__name__
+ else:
+ # Stop masking Cython bugs, expose them in a friendly way.
+ coro_name = f'<{type(coro).__name__} without __name__>'
+ return f'{coro_name}()'
the_globals = {
'__name__': '__main__',