diff options
Diffstat (limited to 'nemesis/nemesis.py')
-rwxr-xr-x | nemesis/nemesis.py | 212 |
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() |