summaryrefslogtreecommitdiff
path: root/nemesis/experiment.py
diff options
context:
space:
mode:
authorbd <bdunahu@operationnull.com>2025-09-12 18:19:29 -0400
committerbd <bdunahu@operationnull.com>2025-09-12 18:19:29 -0400
commit006a5441997a0bc42a8a5277ad7c8f4937782002 (patch)
tree7a2588b249ecffa0cfec22b83e99ec4697e64447 /nemesis/experiment.py
parent963fd1ac52c8d762f65d1cf19d766507e666a986 (diff)
Use plotly to visualize experiments, rework experiment/record logic
Diffstat (limited to 'nemesis/experiment.py')
-rw-r--r--nemesis/experiment.py106
1 files changed, 0 insertions, 106 deletions
diff --git a/nemesis/experiment.py b/nemesis/experiment.py
deleted file mode 100644
index 353d013..0000000
--- a/nemesis/experiment.py
+++ /dev/null
@@ -1,106 +0,0 @@
-from collections import defaultdict, namedtuple
-import asyncio
-import sys
-import threading
-from causal_event_loop import CausalEventLoop
-
-class BadLoopTypeException(Exception):
- pass
-
-class Experiment:
-
- # the selected task for this experiment
- _coro = None
- # the selected speedup for this experiment
- _speedup = None
- # event loops participating in this this experiment
- loops = []
- # a key-value pair where keys represent a handle and values
- # 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
- self._samples = defaultdict(lambda: 0)
-
- self._set_loops()
-
- def get_coro(self):
- return self._coro
-
- def get_speedup(self):
- return self._speedup
-
- def get_results(self):
- ret = f"Results for {self._coro} at {self._speedup} times speedup:\n"
- if len(self._samples) > 0:
- 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):
- 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!)"
- return ret
-
- def get_loops(self):
- return [l for l in self._loops if l.is_running()]
-
- def add_handles(self, handles, loop, time):
- for h in handles:
- h.append(loop._thread_id)
- self._samples[tuple(h)] += time
-
- def _set_loops(self):
- self._loops = self._get_event_loops()
- for l in self._loops:
- if not isinstance(l, CausalEventLoop):
- raise BadLoopTypeException("Nemesis requires a custom event loop to insert slowdowns. It does not work on programs which change the event loop policy.")
- l._set_dilation(1.0 / self._speedup)
-
- def _get_event_loops(self):
- '''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 = self._walk_back_until_loop(frame)
- if loop and loop not in loops:
- loops.append(loop)
- return loops
-
- def _walk_back_until_loop(self, 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
- AbstractEventLoop. Return this variable if true.'''
- while frame:
- if frame.f_code.co_name == '_run_once' and 'self' in frame.f_locals:
- loop = frame.f_locals['self']
- if isinstance(loop, asyncio.AbstractEventLoop):
- return loop
- else:
- frame = frame.f_back
- return None
-
- 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