summaryrefslogtreecommitdiff
path: root/nemesis/nemesis.py
diff options
context:
space:
mode:
Diffstat (limited to 'nemesis/nemesis.py')
-rwxr-xr-xnemesis/nemesis.py212
1 files changed, 212 insertions, 0 deletions
diff --git a/nemesis/nemesis.py b/nemesis/nemesis.py
new file mode 100755
index 0000000..eaf41f3
--- /dev/null
+++ b/nemesis/nemesis.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+'''
+ _/ _/ _/
+ _/_/ _/ _/_/ _/_/_/ _/_/ _/_/ _/_/_/ _/_/_/
+ _/ _/ _/ _/_/_/_/ _/ _/ _/ _/_/_/_/ _/_/ _/ _/_/
+ _/ _/_/ _/ _/ _/ _/ _/ _/_/ _/ _/_/
+ _/ _/ _/_/_/ _/ _/ _/ _/_/_/ _/_/_/ _/ _/_/_/
+
+
+Copyright 2025 bdunahu
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+Commentary:
+Code:
+'''
+
+import argparse
+import asyncio
+import signal
+import sys
+import traceback
+import time
+import types
+import os
+from experiment import Experiment
+from asyncio.base_events import _format_handle
+
+
+class Nemesis(object):
+
+ # the (ideal) interval between samples
+ signal_interval = 0.0
+ # the timestamp which the last sample was taken
+ last_sample = None
+ # the current experiment being run
+ curr_experiment = None
+ # results from previous experiments, represented as strings
+ results = []
+ # The duration of each performance experiment
+ e_duration = None
+ # The number of seconds remaining in this performance experiment.
+ r_duration = None
+
+ # temp
+ task = None
+ dilation = 1.0
+
+ @staticmethod
+ def __init__(task, speedup, e_duration, w_time, signal_interval=0.01):
+ os.environ["PYTHONASYNCIODEBUG"] = "1"
+ Nemesis.signal_interval = signal_interval
+ Nemesis.e_duration = e_duration
+ Nemesis.r_duration = w_time
+ # temporary
+ Nemesis.task = task
+ Nemesis.speedup = speedup
+
+ @staticmethod
+ def start():
+ Nemesis.last_sample = time.perf_counter()
+ signal.signal(signal.SIGALRM,
+ Nemesis._signal_handler)
+ signal.setitimer(signal.ITIMER_REAL,
+ Nemesis.signal_interval,
+ Nemesis.signal_interval)
+
+ @staticmethod
+ def stop():
+ signal.setitimer(signal.ITIMER_REAL, 0)
+ Nemesis._stop_experiment()
+ for r in Nemesis.results:
+ print()
+ print(r)
+
+ @staticmethod
+ def _start_experiment():
+ Nemesis.r_duration = Nemesis.e_duration
+ Nemesis.curr_experiment = Experiment(Nemesis.task, Nemesis.speedup)
+
+ @staticmethod
+ def _stop_experiment():
+ if Nemesis.curr_experiment is not None:
+ print(f'finished running {Nemesis.curr_experiment.get_task()} with speedup {Nemesis.curr_experiment.get_speedup()}')
+ Nemesis.results.append(Nemesis.curr_experiment.get_results())
+ del Nemesis.curr_experiment
+
+ @staticmethod
+ def _signal_handler(sig, frame):
+ curr_sample = time.perf_counter()
+ passed_time = curr_sample - Nemesis.last_sample
+ Nemesis.last_sample = curr_sample
+ if Nemesis.curr_experiment:
+ loops = Nemesis.curr_experiment.get_loops()
+ # print(loops)
+ for loop in loops:
+ loop._update_ready(True)
+ handles = Nemesis._get_waiting_handles(loop)
+ Nemesis.curr_experiment.add_handles(handles, loop, passed_time)
+
+ Nemesis.r_duration -= passed_time
+ if (Nemesis.r_duration <= 0):
+ Nemesis._stop_experiment()
+ Nemesis._start_experiment()
+
+ 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)
+ return handles
+
+ @staticmethod
+ 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
+ if not filename:
+ return False
+ if '/gnu/store' in filename:
+ return False
+ if '/usr/local/lib/python' in filename:
+ return False
+ if 'site-packages' in filename:
+ return False
+ if 'propcache' in filename:
+ return False
+ if '.pyx' in filename:
+ return False
+ if filename[0] == '<':
+ return False
+ if 'nemesis' in filename:
+ return False
+ return True
+
+
+the_globals = {
+ '__name__': '__main__',
+ '__doc__': None,
+ '__package__': None,
+ '__loader__': globals()['__loader__'],
+ '__spec__': None,
+ '__annotations__': {},
+ '__builtins__': globals()['__builtins__'],
+ '__file__': None,
+ '__cached__': None,
+}
+
+
+if __name__ == "__main__":
+ # parses CLI arguments and facilitates profiler runtime.
+ parser = argparse.ArgumentParser(
+ usage='%(prog)s [args] -- prog'
+ )
+
+ parser.add_argument('-i', '--interval',
+ help='The minimum amount of time inbetween \
+ samples in seconds.',
+ metavar='',
+ type=float,
+ default=0.01)
+ parser.add_argument('-s', '--speedup',
+ help='The amount of virtual speedup.',
+ metavar='',
+ type=float,
+ default=0.5)
+ parser.add_argument('-c', '--task',
+ help='The task to virtually speedup.',
+ metavar='',
+ type=str,
+ required=True)
+ parser.add_argument('-e', '--experiment-duration',
+ help='The performance experiment duration. Defaults to 4 seconds.',
+ metavar='',
+ type=float,
+ default=4)
+ parser.add_argument('-w', '--warmup-time',
+ help='Amount of time to wait until the first performance experiment. Default is the minimum time of 100 milliseconds',
+ metavar='',
+ type=float,
+ default=0.1)
+ parser.add_argument('prog',
+ type=str,
+ nargs='*',
+ help='Path to the python script and its arguments.')
+ args = parser.parse_args()
+
+ sys.argv = args.prog
+ try:
+ with open(args.prog[0], 'r', encoding='utf-8') as fp:
+ code = compile(fp.read(), args.prog[0], "exec")
+ Nemesis(args.task,
+ args.speedup,
+ args.experiment_duration,
+ args.warmup_time,
+ args.interval).start()
+ exec(code, the_globals)
+ Nemesis.stop()
+ except Exception:
+ traceback.print_exc()