diff options
| author | bd <bdunahu@operationnull.com> | 2025-11-30 23:22:01 -0500 |
|---|---|---|
| committer | bd <bdunahu@operationnull.com> | 2025-11-30 23:22:01 -0500 |
| commit | 8041035d3ce04e72ab0dadef325c03447dc6d87f (patch) | |
| tree | e23bc1be9b268ff672f17e76229f113a49423db3 | |
| parent | 444554a579fe57e3bd470821d77c3cf4af60c56f (diff) | |
nemesis: split nemesis.py into utils.py, main.py.
nemesis/nemesis.py: remove main function, parsing, utility files
[new file]: nemesis/utils.py
[new file]: main.py
Update copyright information
| -rw-r--r-- | main.py | 99 | ||||
| -rw-r--r-- | nemesis/causal_event_loop.py | 5 | ||||
| -rw-r--r-- | nemesis/html_gen.py | 20 | ||||
| -rwxr-xr-x | nemesis/nemesis.py | 118 | ||||
| -rw-r--r-- | nemesis/utils.py | 34 |
5 files changed, 164 insertions, 112 deletions
@@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +''' + _/ _/ _/ + _/_/ _/ _/_/ _/_/_/ _/_/ _/_/ _/_/_/ _/_/_/ + _/ _/ _/ _/_/_/_/ _/ _/ _/ _/_/_/_/ _/_/ _/ _/_/ + _/ _/_/ _/ _/ _/ _/ _/ _/_/ _/ _/_/ + _/ _/ _/_/_/ _/ _/ _/ _/_/_/ _/_/_/ _/ _/_/_/ + +Copyright: + + Copyright © 2025 bdunahu <bdunahu@operationnull.com> + + 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 sys +import traceback +from nemesis.nemesis import Nemesis +from nemesis.utils import validate_html, validate_dir + +def parse_args(): + 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('-e', '--experiment-duration', + help='The performance experiment duration. Defaults to 3 seconds.', + metavar='', + type=float, + default=3) + parser.add_argument('-f', '--filename', + help='The filename to write results to. Must name an HTML file.', + metavar='', + type=validate_html, + default="results.html") + parser.add_argument('--include-paths', + help='Specify the path(s) containing files to profile. If a file is in this path, it is a candidate for optimization.', + nargs="+", + type=validate_dir, + required=True) + parser.add_argument('--exclude-paths', + help='Specify the path(s) containing files to exclude profile. Takes priority over --include-paths.', + nargs="*", + type=validate_dir, + required=False, + default=[]) + parser.add_argument('prog', + type=str, + nargs='*', + help='Path to the python script and its arguments.') + return parser.parse_args() + +the_globals = { + '__name__': '__main__', + '__doc__': None, + '__package__': None, + '__loader__': globals()['__loader__'], + '__spec__': None, + '__annotations__': {}, + '__builtins__': globals()['__builtins__'], + '__file__': None, + '__cached__': None, +} + +if __name__ == "__main__": + args = 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.experiment_duration, + args.include_paths, + args.exclude_paths, + args.filename, + args.prog[0], + args.interval).start() + exec(code, the_globals) + Nemesis.stop() + except Exception: + traceback.print_exc() diff --git a/nemesis/causal_event_loop.py b/nemesis/causal_event_loop.py index 9b774d0..f9cffb4 100644 --- a/nemesis/causal_event_loop.py +++ b/nemesis/causal_event_loop.py @@ -1,5 +1,7 @@ ''' -Copyright 2025 bdunahu +Copyright: + + Copyright © 2025 bdunahu <bdunahu@operationnull.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,7 +16,6 @@ Copyright 2025 bdunahu limitations under the License. Commentary: - Code: ''' from typing import TYPE_CHECKING, Any, Callable, Self diff --git a/nemesis/html_gen.py b/nemesis/html_gen.py index 85d2cae..2a417fc 100644 --- a/nemesis/html_gen.py +++ b/nemesis/html_gen.py @@ -1,3 +1,23 @@ +''' +Copyright: + + Copyright © 2025 bdunahu <bdunahu@operationnull.com> + + 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: +''' from typing import TYPE_CHECKING if TYPE_CHECKING: import collections diff --git a/nemesis/nemesis.py b/nemesis/nemesis.py index 9d4014c..812cd2d 100755 --- a/nemesis/nemesis.py +++ b/nemesis/nemesis.py @@ -1,13 +1,7 @@ -#!/usr/bin/env python3 ''' - _/ _/ _/ - _/_/ _/ _/_/ _/_/_/ _/_/ _/_/ _/_/_/ _/_/_/ - _/ _/ _/ _/_/_/_/ _/ _/ _/ _/_/_/_/ _/_/ _/ _/_/ - _/ _/_/ _/ _/ _/ _/ _/ _/_/ _/ _/_/ - _/ _/ _/_/_/ _/ _/ _/ _/_/_/ _/_/_/ _/ _/_/_/ +Copyright: - -Copyright 2025 bdunahu + Copyright © 2025 bdunahu <bdunahu@operationnull.com> Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -24,22 +18,20 @@ Copyright 2025 bdunahu Commentary: Code: ''' -from typing import Any, Self -from causal_event_loop import CausalEventLoop +from nemesis.causal_event_loop import CausalEventLoop from collections import defaultdict -from html_gen import plot_results -import argparse +from nemesis.html_gen import plot_results +from pathlib import Path +from typing import Any, Self import asyncio -import os import inspect +import os import random import signal import sys import threading import time -import traceback import types -from pathlib import Path CO_COROUTINE = inspect.CO_COROUTINE @@ -114,21 +106,6 @@ class Nemesis(object): Nemesis.signal_interval) @staticmethod - def print_results(): - for coro_name, x_values in Nemesis.results.items(): - print(f'Results for {coro_name:}') - for speedup, experiments in x_values.items(): - print(f' {speedup * 100}% speedup:') - for experiment in experiments: - num_callbacks = len(experiment) - if num_callbacks > 0: - total_wait = sum([cb[1] for cb in experiment]) - latency = total_wait / num_callbacks - print(f' latency: {latency}') - print(f' callbacks processed: {num_callbacks}') - print(f'') - - @staticmethod def stop() -> None: signal.setitimer(signal.ITIMER_REAL, 0) plot_results(Nemesis.results, Nemesis.filename, Nemesis.prog) @@ -144,7 +121,7 @@ class Nemesis(object): loops = Nemesis._get_event_loops() for loop in loops: if not isinstance(loop, CausalEventLoop): - raise RuntimeError(f"Nemesis requires a custom event loop to insert slowdowns. You must start the event loop with `asyncio.run(your_coro(), loop_factory=causal_loop_factory)'.") + raise RuntimeError(f"Nemesis requires a custom event loop to insert slowdowns. You must start the event loop with `asyncio.run(your_coro(), loop_factory=causal_loop_factory)'. Received: {type(loop)}") loop.set_speedup(speedup) Nemesis.experiment_data = Experiment(loops) @@ -313,82 +290,3 @@ class Nemesis(object): # 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__', - '__doc__': None, - '__package__': None, - '__loader__': globals()['__loader__'], - '__spec__': None, - '__annotations__': {}, - '__builtins__': globals()['__builtins__'], - '__file__': None, - '__cached__': None, -} - -def validate_dir(path_str: str) -> Path: - p = Path(path_str).expanduser().resolve() - if not p.exists(): - raise ValueError(f"Profile path does not exist: {p}") - if not p.is_dir(): - raise ValueError(f"Can't profile a non-dir: {p}") - return p - -def validate_html(filename: str) -> str: - if not filename.lower().endswith('.html'): - raise argparse.ArgumentTypeError(f"Did not name an HTML file: '{filename}'") - return filename - -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('-e', '--experiment-duration', - help='The performance experiment duration. Defaults to 3 seconds.', - metavar='', - type=float, - default=3) - parser.add_argument('-f', '--filename', - help='The filename to write results to. Must name an HTML file.', - metavar='', - type=validate_html, - default="results.html") - parser.add_argument('--include-paths', - help='Specify the path(s) containing files to profile. If a file is in this path, it is a candidate for optimization.', - nargs="+", - type=validate_dir, - required=True) - parser.add_argument('--exclude-paths', - help='Specify the path(s) containing files to exclude profile. Takes priority over --include-paths.', - nargs="*", - type=validate_dir, - required=False, - default=[]) - 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.experiment_duration, - args.include_paths, - args.exclude_paths, - args.filename, - args.prog[0], - args.interval).start() - exec(code, the_globals) - Nemesis.stop() - except Exception: - traceback.print_exc() diff --git a/nemesis/utils.py b/nemesis/utils.py new file mode 100644 index 0000000..0cee784 --- /dev/null +++ b/nemesis/utils.py @@ -0,0 +1,34 @@ +''' +Copyright: + + Copyright © 2025 bdunahu <bdunahu@operationnull.com> + + 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: +''' +from pathlib import Path + +def validate_dir(path_str: str) -> Path: + p = Path(path_str).expanduser().resolve() + if not p.exists(): + raise ValueError(f"Profile path does not exist: {p}") + if not p.is_dir(): + raise ValueError(f"Can't profile a non-dir: {p}") + return p + +def validate_html(filename: str) -> str: + if not filename.lower().endswith('.html'): + raise argparse.ArgumentTypeError(f"Did not name an HTML file: '{filename}'") + return filename |
