From 49fcc33a6bfa27644b5b4a8e19a1b23992b98ac4 Mon Sep 17 00:00:00 2001 From: timotheyca Date: Fri, 24 Jun 2022 00:26:43 +0300 Subject: [PATCH] trace/plot update --- plot.py | 21 +++- rainbowadn/collection/linear/array.py | 2 +- rainbowadn/testing/instrument/__init__.py | 4 + rainbowadn/testing/instrument/concurrency.py | 31 +++++ rainbowadn/testing/instrument/counter.py | 14 +++ rainbowadn/testing/instrument/entryexit.py | 21 ++++ .../testing/instrument/instrumentation.py | 53 +++++++++ rainbowadn/testing/instrumentation.py | 107 ------------------ rainbowadn/testing/test_all.py | 20 ++-- trace.py | 53 ++++++--- 10 files changed, 190 insertions(+), 136 deletions(-) create mode 100644 rainbowadn/testing/instrument/__init__.py create mode 100644 rainbowadn/testing/instrument/concurrency.py create mode 100644 rainbowadn/testing/instrument/counter.py create mode 100644 rainbowadn/testing/instrument/entryexit.py create mode 100644 rainbowadn/testing/instrument/instrumentation.py delete mode 100644 rainbowadn/testing/instrumentation.py diff --git a/plot.py b/plot.py index f256671..aabba7c 100644 --- a/plot.py +++ b/plot.py @@ -1,4 +1,5 @@ import json +from pathlib import Path import matplotlib.pyplot as plt import numpy as np @@ -11,15 +12,24 @@ def plottable(log: list[tuple[float, int]]): return np.array([[], []]) -def plot(): +def plot(fn: str): plt.rcParams['figure.figsize'] = [16, 9] plt.style.use("dark_background") - plt.subplots_adjust(left=0.03, right=0.99, top=0.99, bottom=0.05) + plt.subplots_adjust(left=0.05, right=0.99, top=0.95, bottom=0.05) + plt.title(fn) + plt.xlabel('time (s)') + plt.ylabel('concurrency (1)') - with open('trace/latest.json') as file: + with open(fn) as file: jsonified = json.load(file) if (log := jsonified.get('DelayedResolver:sleep:concurrency')) is not None: plt.plot(*plottable(log)) + if (log := jsonified.get('ActiveBinaryTree:add:concurrency')) is not None: + plt.plot(*plottable(log)) + if (log := jsonified.get('ActiveBinaryTree:contains:concurrency')) is not None: + plt.plot(*plottable(log)) + if (log := jsonified.get('Stack:list:concurrency')) is not None: + plt.plot(*plottable(log)) if (log := jsonified.get('ActiveBinaryTree:add:entry')) is not None: plt.scatter(*plottable(log), c='tomato', zorder=100, s=.5) if (log := jsonified.get('ActiveBinaryTree:add:exit')) is not None: @@ -28,4 +38,7 @@ def plot(): if __name__ == '__main__': - plot() + plot('trace/latest.json') + for fp in list(Path('trace').glob('*.json')): + if fp != Path('trace/latest.json'): + plot(str(fp)) diff --git a/rainbowadn/collection/linear/array.py b/rainbowadn/collection/linear/array.py index 010ab56..74b3b4e 100644 --- a/rainbowadn/collection/linear/array.py +++ b/rainbowadn/collection/linear/array.py @@ -32,7 +32,7 @@ class Array(RecursiveMentionable, Generic[ElementType]): formatted = f'(' formatted += ''.join( f'{tabulate(tab + 1)}{hash_point_str}' - for hash_point_str in gather( + for hash_point_str in await gather( *( hash_point_format(hash_point, tab + 1) for hash_point in self.array ) diff --git a/rainbowadn/testing/instrument/__init__.py b/rainbowadn/testing/instrument/__init__.py new file mode 100644 index 0000000..26319ff --- /dev/null +++ b/rainbowadn/testing/instrument/__init__.py @@ -0,0 +1,4 @@ +from .concurrency import Concurrency +from .counter import Counter +from .entryexit import EntryExit +from .instrumentation import Instrumentation diff --git a/rainbowadn/testing/instrument/concurrency.py b/rainbowadn/testing/instrument/concurrency.py new file mode 100644 index 0000000..c48d7f9 --- /dev/null +++ b/rainbowadn/testing/instrument/concurrency.py @@ -0,0 +1,31 @@ +import time + +from .instrumentation import Instrumentation + +__all__ = ('Concurrency',) + + +class Concurrency(Instrumentation): + start = time.time() + + def __init__(self, target, methodname: str): + assert isinstance(methodname, str) + super().__init__(target, methodname) + self.concurrency = 0 + self.log: list[tuple[float, int]] = [] + + def time(self) -> float: + return time.time() - self.start + + def point(self) -> tuple[float, int]: + return self.time(), self.concurrency + + async def instrument(self, method, *args, **kwargs): + self.log.append(self.point()) + self.concurrency += 1 + self.log.append(self.point()) + result = await method(*args, **kwargs) + self.log.append(self.point()) + self.concurrency -= 1 + self.log.append(self.point()) + return result diff --git a/rainbowadn/testing/instrument/counter.py b/rainbowadn/testing/instrument/counter.py new file mode 100644 index 0000000..1748ad5 --- /dev/null +++ b/rainbowadn/testing/instrument/counter.py @@ -0,0 +1,14 @@ +from .instrumentation import Instrumentation + +__all__ = ('Counter',) + + +class Counter(Instrumentation): + def __init__(self, target, methodname: str): + assert isinstance(methodname, str) + super().__init__(target, methodname) + self.counter = 0 + + def instrument(self, method, *args, **kwargs): + self.counter += 1 + return method(*args, **kwargs) diff --git a/rainbowadn/testing/instrument/entryexit.py b/rainbowadn/testing/instrument/entryexit.py new file mode 100644 index 0000000..31313b1 --- /dev/null +++ b/rainbowadn/testing/instrument/entryexit.py @@ -0,0 +1,21 @@ +from typing import Callable + +from .instrumentation import Instrumentation + +__all__ = ('EntryExit',) + + +class EntryExit(Instrumentation): + def __init__(self, target, methodname: str, point: Callable[[], tuple[float, int]]): + assert callable(point) + assert isinstance(methodname, str) + super().__init__(target, methodname) + self.point = point + self.entry_log: list[tuple[float, int]] = [] + self.exit_log: list[tuple[float, int]] = [] + + async def instrument(self, method, *args, **kwargs): + self.entry_log.append(self.point()) + result = await method(*args, **kwargs) + self.exit_log.append(self.point()) + return result diff --git a/rainbowadn/testing/instrument/instrumentation.py b/rainbowadn/testing/instrument/instrumentation.py new file mode 100644 index 0000000..0135159 --- /dev/null +++ b/rainbowadn/testing/instrument/instrumentation.py @@ -0,0 +1,53 @@ +import functools +from contextlib import ExitStack +from typing import Callable, Generic, TypeVar + +__all__ = ('Instrumentation',) + +IType = TypeVar('IType') + + +class Instrumentation(Generic[IType]): + deinstrumentation = {} + method: Callable + wrap: Callable + + def __init__(self, target, methodname: str): + assert isinstance(methodname, str) + assert callable(getattr(target, methodname)) + self.target = target + self.methodname = methodname + + def instrument(self, method, *args, **kwargs): + raise NotImplementedError + + def __enter__(self: IType) -> IType: + assert not hasattr(self, 'method') + self.method = getattr(self.target, self.methodname) + assert callable(self.method) + + @functools.wraps(self.method) + def wrap(*args, **kwargs): + return self.instrument(self.method, *args, **kwargs) + + self.wrap = wrap + + setattr(self.target, self.methodname, self.wrap) + + return self + + def schedule_deinstrumentation(self): + self.deinstrumentation[self.wrap] = self.method + del self.wrap + del self.method + + def deinstrument(self): + while (method := getattr(self.target, self.methodname)) in self.deinstrumentation: + setattr(self.target, self.methodname, self.deinstrumentation.pop(method)) + + def __exit__(self, exc_type, exc_val, exc_tb): + self.schedule_deinstrumentation() + self.deinstrument() + + def enter(self: IType, stack: ExitStack) -> IType: + return stack.enter_context(self) diff --git a/rainbowadn/testing/instrumentation.py b/rainbowadn/testing/instrumentation.py deleted file mode 100644 index 9df42bf..0000000 --- a/rainbowadn/testing/instrumentation.py +++ /dev/null @@ -1,107 +0,0 @@ -import functools -import time -from contextlib import ExitStack -from typing import Callable, Generic, TypeVar - -__all__ = ('Instrumentation', 'Counter', 'Concurrency', 'EntryExit',) - -IType = TypeVar('IType') - - -class Instrumentation(Generic[IType]): - deinstrumentation = {} - method: Callable - wrap: Callable - - def __init__(self, target, methodname: str): - assert isinstance(methodname, str) - assert callable(getattr(target, methodname)) - self.target = target - self.methodname = methodname - - def instrument(self, method, *args, **kwargs): - raise NotImplementedError - - def __enter__(self: IType) -> IType: - assert not hasattr(self, 'method') - self.method = getattr(self.target, self.methodname) - assert callable(self.method) - - @functools.wraps(self.method) - def wrap(*args, **kwargs): - return self.instrument(self.method, *args, **kwargs) - - self.wrap = wrap - - setattr(self.target, self.methodname, self.wrap) - - return self - - def schedule_deinstrumentation(self): - self.deinstrumentation[self.wrap] = self.method - del self.wrap - del self.method - - def deinstrument(self): - while (method := getattr(self.target, self.methodname)) in self.deinstrumentation: - setattr(self.target, self.methodname, self.deinstrumentation.pop(method)) - - def __exit__(self, exc_type, exc_val, exc_tb): - self.schedule_deinstrumentation() - self.deinstrument() - - def enter(self: IType, stack: ExitStack) -> IType: - return stack.enter_context(self) - - -class Counter(Instrumentation): - def __init__(self, target, methodname: str): - assert isinstance(methodname, str) - super().__init__(target, methodname) - self.counter = 0 - - def instrument(self, method, *args, **kwargs): - self.counter += 1 - return method(*args, **kwargs) - - -class Concurrency(Instrumentation): - start = time.time() - - def __init__(self, target, methodname: str): - assert isinstance(methodname, str) - super().__init__(target, methodname) - self.concurrency = 0 - self.log: list[tuple[float, int]] = [] - - def time(self) -> float: - return time.time() - self.start - - def point(self) -> tuple[float, int]: - return self.time(), self.concurrency - - async def instrument(self, method, *args, **kwargs): - self.log.append(self.point()) - self.concurrency += 1 - self.log.append(self.point()) - result = await method(*args, **kwargs) - self.log.append(self.point()) - self.concurrency -= 1 - self.log.append(self.point()) - return result - - -class EntryExit(Instrumentation): - def __init__(self, target, methodname: str, point: Callable[[], tuple[float, int]]): - assert callable(point) - assert isinstance(methodname, str) - super().__init__(target, methodname) - self.point = point - self.entry_log: list[tuple[float, int]] = [] - self.exit_log: list[tuple[float, int]] = [] - - async def instrument(self, method, *args, **kwargs): - self.entry_log.append(self.point()) - result = await method(*args, **kwargs) - self.exit_log.append(self.point()) - return result diff --git a/rainbowadn/testing/test_all.py b/rainbowadn/testing/test_all.py index f532d55..e2bac9a 100644 --- a/rainbowadn/testing/test_all.py +++ b/rainbowadn/testing/test_all.py @@ -16,7 +16,7 @@ from rainbowadn.encryption import * from rainbowadn.nullability import * from rainbowadn.v13 import * from rainbowadn.wrisbt import * -from .instrumentation import * +from .instrument import * from .resolvers import * @@ -83,10 +83,10 @@ class TestAll(unittest.IsolatedAsyncioTestCase): stoptime = now return delta - n = 2500 + n = 5000 keysize = 7 with self.subTest('create empty'): - btree: WrisbtRoot = WrisbtRoot.empty(WrisbtParametres(1, keysize)) + btree: WrisbtRoot = WrisbtRoot.empty(WrisbtParametres(5, keysize)) measure('init') with self.subTest('add keys', n=n): for _ in range(n): @@ -158,7 +158,7 @@ class TestAll(unittest.IsolatedAsyncioTestCase): async def test_encryption(self): set_gather_linear() - instrumentation = Counter(Encrypted, 'encrypt') + encrypt_ctr = Counter(Encrypted, 'encrypt') with self.subTest('setup'): key = b'a' * 32 with self.subTest('create empty'): @@ -171,9 +171,9 @@ class TestAll(unittest.IsolatedAsyncioTestCase): print(await tree.reference.str(0)) with self.subTest('encrypt'): target = tree.reference - with instrumentation: + with encrypt_ctr: target = (await Encrypted.encrypt(target, key)).decrypted - print(instrumentation.counter) + print(encrypt_ctr.counter) tree = tree.create(target) print(await tree.reference.str(0)) with self.subTest('alter'): @@ -182,13 +182,13 @@ class TestAll(unittest.IsolatedAsyncioTestCase): print(await tree.reference.str(0)) with self.subTest('encrypt and migrate'): target = tree.reference - with instrumentation: + with encrypt_ctr: eeed = await Encrypted.encrypt(target, key) - print(instrumentation.counter) + print(encrypt_ctr.counter) print(await (await self.dr().migrate_resolved(eeed)).decrypted.str(0)) with self.subTest('re-encrypt'): new_key = b'b' * 32 target = eeed.decrypted - with instrumentation: + with encrypt_ctr: await Encrypted.encrypt(target, new_key) - print(instrumentation.counter) + print(encrypt_ctr.counter) diff --git a/trace.py b/trace.py index 483f776..1564fbe 100644 --- a/trace.py +++ b/trace.py @@ -10,10 +10,11 @@ from nacl.signing import SigningKey from plot import plot from rainbowadn.chain import * +from rainbowadn.collection.linear import * from rainbowadn.collection.trees.binary import * from rainbowadn.core import * from rainbowadn.nullability import * -from rainbowadn.testing.instrumentation import * +from rainbowadn.testing.instrument import * from rainbowadn.testing.resolvers import * from rainbowadn.v13 import * @@ -33,11 +34,11 @@ def target_str(target) -> str: return name -def jsonify(instrumentation: Instrumentation) -> dict: - prefix = f'{target_str(instrumentation.target)}:{instrumentation.methodname}' - match instrumentation: - case Counter(counter=counter): - return {f'{prefix}:counter': counter} +def jsonify(dumped: Instrumentation) -> dict: + prefix = f'{target_str(dumped.target)}:{dumped.methodname}' + match dumped: + case Counter(counter=ctr): + return {f'{prefix}:counter': ctr} case Concurrency(log=log): return {f'{prefix}:concurrency': log} case EntryExit(entry_log=entry_log, exit_log=exit_log): @@ -49,9 +50,7 @@ def jsonify(instrumentation: Instrumentation) -> dict: return {} -async def main(): - set_gather_linear() - bank: BankChain = BankChain.empty(ReductionChainMetaFactory().loose()) +async def mock(bank: BankChain) -> BankChain: key_0 = SigningKey.generate() transaction_0 = Transaction.make( [], @@ -69,6 +68,24 @@ async def main(): ), ] ) + return bank + + +def get_instrumentations() -> list[Instrumentation]: + sleep_cc = Concurrency(DelayedResolver, 'sleep') + return [ + sleep_cc, + EntryExit(ActiveBinaryTree, 'add', sleep_cc.point), + Concurrency(ActiveBinaryTree, 'add'), + Concurrency(ActiveBinaryTree, 'contains'), + Concurrency(Stack, 'list'), + ] + + +async def main(): + set_gather_linear() + bank: BankChain = BankChain.empty(ReductionChainMetaFactory().loose()) + # bank = await mock(bank) for _ in range(16): bank = await bank.adds( [ @@ -87,19 +104,27 @@ async def main(): ) print('saved') set_gather_asyncio() - with ExitStack() as stack: - sleep_cc = Concurrency(DelayedResolver, 'sleep').enter(stack) - avl_ex = EntryExit(ActiveBinaryTree, 'add', sleep_cc.point).enter(stack) + with ExitStack() as estack: + instrumentations = get_instrumentations() + for stacked in instrumentations: + stacked.enter(estack) assert_true(await bank.verify()) print(Instrumentation.deinstrumentation) + print('traced') fn = f'trace/{int(time.time())}-{os.urandom(2).hex()}.json' + jsonified = {} + for dumped in instrumentations: + jsonified |= jsonify(dumped) with open(fn, 'w') as file: json.dump( - jsonify(sleep_cc) | jsonify(avl_ex), + jsonified, file ) + print('dumped') shutil.copy(fn, f'trace/latest.json') - plot() + print('copied') + plot(fn) + print('plotted') if __name__ == '__main__':