From dda1561fb9a894533d299f1660bc6795b6fa8531 Mon Sep 17 00:00:00 2001 From: Sven Augustin Date: Fri, 12 Feb 2021 14:08:11 +0000 Subject: [PATCH] prototype epics server --- morioc/context.py | 18 +++ morioc/experiments/dummy.py | 37 ++++++ morioc/experiments/dummy2.py | 57 +++++++++ morioc/experiments/dummy3.py | 122 ++++++++++++++++++++ morioc/experiments/example.py | 50 ++++++++ morioc/experiments/ign/morioc-bak1.py | 129 +++++++++++++++++++++ morioc/experiments/ign/run_old1.py | 74 ++++++++++++ morioc/experiments/ign/run_old2.py | 30 +++++ morioc/experiments/mor.py | 103 +++++++++++++++++ morioc/morioc.py | 160 ++++++++++++++++++++++++++ morioc/run.py | 32 ++++++ morioc/utils.py | 5 + 12 files changed, 817 insertions(+) create mode 100644 morioc/context.py create mode 100755 morioc/experiments/dummy.py create mode 100755 morioc/experiments/dummy2.py create mode 100755 morioc/experiments/dummy3.py create mode 100755 morioc/experiments/example.py create mode 100644 morioc/experiments/ign/morioc-bak1.py create mode 100755 morioc/experiments/ign/run_old1.py create mode 100755 morioc/experiments/ign/run_old2.py create mode 100755 morioc/experiments/mor.py create mode 100644 morioc/morioc.py create mode 100755 morioc/run.py create mode 100644 morioc/utils.py diff --git a/morioc/context.py b/morioc/context.py new file mode 100644 index 0000000..669bc3b --- /dev/null +++ b/morioc/context.py @@ -0,0 +1,18 @@ +from abc import ABC, abstractmethod + + +class Context(ABC): + + @abstractmethod + def stop(self): + raise NotImplementedError + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, exc_traceback): + self.stop() + return (exc_type is None) + + + diff --git a/morioc/experiments/dummy.py b/morioc/experiments/dummy.py new file mode 100755 index 0000000..2dec3d0 --- /dev/null +++ b/morioc/experiments/dummy.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +from pcaspy import Driver, SimpleServer + +prefix = 'MTEST:' +pvdb = { + 'RAND' : { + 'prec' : 3, + }, +} + +class myDriver(Driver): + def __init__(self): + super(myDriver, self).__init__() + +if __name__ == '__main__': + server = SimpleServer() + server.createPV(prefix, pvdb) + driver = myDriver() + + # process CA transactions + for i in range(100): + server.process(0.1) + + print(1) + del server + print(2) + + server = SimpleServer() + server.createPV(prefix, pvdb) + driver = myDriver() + + # process CA transactions + for i in range(100): + server.process(0.1) + + diff --git a/morioc/experiments/dummy2.py b/morioc/experiments/dummy2.py new file mode 100755 index 0000000..2c69ac7 --- /dev/null +++ b/morioc/experiments/dummy2.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +from pcaspy import Driver, SimpleServer + + +prefix1 = 'MTEST1:' +pvdb1 = { + 'RAND1' : { + 'prec' : 3, + }, +} + +prefix2 = 'MTEST1:' #!!! +pvdb2 = { + 'RAND2' : { + 'prec' : 3, + }, +} + +prefix3 = 'MTEST3:' +pvdb3 = { + 'RAND3' : { + 'prec' : 3, + }, +} + + +class myDriver(Driver): + def __init__(self): + super(myDriver, self).__init__() + + +if __name__ == '__main__': + server = SimpleServer() + server.createPV(prefix1, pvdb1) + driver = myDriver() + + print(1) + for i in range(300): + server.process(0.1) + + server.createPV(prefix2, pvdb2) + driver = myDriver() + + print(2) + for i in range(300): + server.process(0.1) + + server.createPV(prefix3, pvdb3) + driver = myDriver() + + print(3) + for i in range(300): + server.process(0.1) + + + diff --git a/morioc/experiments/dummy3.py b/morioc/experiments/dummy3.py new file mode 100755 index 0000000..84bd28e --- /dev/null +++ b/morioc/experiments/dummy3.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python + +from time import sleep +import random +from threading import Thread +from pcaspy import Driver, SimpleServer +from pcaspy.driver import manager + + +prefix1 = 'MTEST1:' +pvdb1 = { + 'RAND1' : { + 'prec' : 3, + }, +} + +prefix2 = 'MTEST2:' +pvdb2 = { + 'RAND2' : { + 'prec' : 3, + }, +} + +prefix3 = 'MTEST3:' +pvdb3 = { + 'RAND3' : { + 'prec' : 3, + }, +} + + +class myDriver(Driver): + def __init__(self): + super(myDriver, self).__init__() + + def read(self, reason): + if reason.startswith('RAND'): + value = random.random() + else: + value = self.getParam(reason) + return value + + +class Mor: + + def __init__(self, prefix, pvdb, wait_time=0.1): + self.wait_time = wait_time + self.server = SimpleServer() + self.server.createPV(prefix, pvdb) + self.driver = myDriver() + self.thread = Thread(target=self.run) + self.thread.start() + + def run(self): + self.processing = True + self.running = True + n = 0 + while self.running: + n += 1 + if self.processing: +# print("proc", n) + self.server.process(self.wait_time) + else: +# print("pause proc", n) + sleep(self.wait_time) + + def stop(self): + self.running = False + self._sleep_after_change() + + def stop_proc(self): + self.processing = False + self._sleep_after_change() + + def start_proc(self): + self.processing = True + self._sleep_after_change() + + def _sleep_after_change(self): + wait_time = self.wait_time * 1.1 + sleep(wait_time) + + def createPV(self, prefix, pvdb): +# self.stop_proc() + self.server.createPV(prefix, pvdb) + self.driver = myDriver() +# self.start_proc() + + def deletePV(self, prefix, pvdb): +# self.stop_proc() + for n in pvdb: + del manager.pvs["default"][n] + del manager.pvf[prefix + n] +# self.start_proc() + + + +if __name__ == '__main__': + print("create", 1) + mor = Mor(prefix1, pvdb1) + sleep(15) + + print("create", 2) + mor.createPV(prefix2, pvdb2) + sleep(15) + + print("delete", 2) + mor.deletePV(prefix2, pvdb2) + sleep(15) + + print("create", 2) + mor.createPV(prefix2, pvdb2) + sleep(15) + + print("create", 3) + mor.createPV(prefix3, pvdb3) + sleep(15) + + mor.stop() + + + diff --git a/morioc/experiments/example.py b/morioc/experiments/example.py new file mode 100755 index 0000000..d25e08a --- /dev/null +++ b/morioc/experiments/example.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python + +from pcaspy import Driver +from mor import Mor + +prefix = 'MTEST:' + +pvdb = { + 'RAND' : { + 'prec' : 3, + }, + "NAME" : { + "type": "string" + } +} + +class myDriver(Driver): + def __init__(self, server): + self.server = server + super(myDriver, self).__init__() + + def write(self, reason, value): + print("write", reason, value) + if reason == "NAME": + new_pvdb = { + value: { + "type": "int" + } + } + self.server.createPV(new_pvdb) +# self.server.restart() + return super().write(reason, value) + + def read(self, reason): + value = self.getParam(reason) + print("read", reason, value) + return super().read(reason) + + + + + +if __name__ == '__main__': + server = Mor(prefix, pvdb, myDriver) +# server.createPV(pvdb) + + + + + diff --git a/morioc/experiments/ign/morioc-bak1.py b/morioc/experiments/ign/morioc-bak1.py new file mode 100644 index 0000000..cbfb1d9 --- /dev/null +++ b/morioc/experiments/ign/morioc-bak1.py @@ -0,0 +1,129 @@ +from time import sleep +from datetime import datetime +from threading import Thread +from pcaspy import SimpleServer, Driver +from pcaspy.driver import manager, _ait_d as AIT_D + +from context import Context +from utils import typename + + +INITIAL_PVDB = { + "START_TIME" : { + "type": "string" + } +} + +D_AIT = {v:k for k, v in AIT_D.items()} + + +class DefaultDriver(Driver): #TODO: caget on START_TIME crashes without this subclass?! + pass + + +class MorIOC(Context): + + def __init__(self, prefix, DriverClass=DefaultDriver, wait_time=0.1): + print("init") + prefix = prefix.upper() + prefix = prefix if prefix.endswith(":") else prefix + ":" + + self.prefix = prefix + self.DriverClass = DriverClass + self.wait_time = wait_time + + self.server = SimpleServer() + self.server.createPV(prefix, INITIAL_PVDB) + self.driver = DriverClass() + + now = datetime.now() + self.driver.setParam("START_TIME", str(now)) + self.driver.updatePVs() + + self.thread = Thread(target=self.run, daemon=True) + self.thread.start() + + + def run(self): + self.processing = True + self.running = True + n = 0 + while self.running: + n += 1 + if self.processing: +# print("proc", n) + self.server.process(self.wait_time) + else: +# print("pause proc", n) + sleep(self.wait_time) + + + def stop(self): + self.running = False + self._sleep_after_change() + +# def stop_proc(self): +# self.processing = False +# self._sleep_after_change() + +# def start_proc(self): +# self.processing = True +# self._sleep_after_change() + + def _sleep_after_change(self): + wait_time = self.wait_time * 1.1 + sleep(wait_time) + + + def createPV(self, pvdb): +# self.stop_proc() + current_pvdb = self.driver.pvDB #TODO crash due to keyerror otherwise + self.server.createPV(self.prefix, pvdb) + self.driver = self.DriverClass() + self.driver.pvDB.update(current_pvdb) +# self.start_proc() + + def deletePVs(self, pvdb): +# self.stop_proc() + for n in pvdb: + self.deletePV(n) +# self.start_proc() + + def deletePV(self, name): + del manager.pvs["default"][name] + del manager.pvf[self.prefix + name] + + + def serve(self, **kwargs): + pvdb = {} + for name, value in kwargs.items(): + name = name.upper() + dtype = typename(value) + if not name in manager.pvs["default"]: + print("will create", name) + pvdb[name] = {"type": dtype} + else: + pv = manager.pvs["default"][name] + pv_type = pv.info.type + pv_type = D_AIT[pv_type] + if pv_type != dtype: + print(f"type for {name} changed:", pv_type, "->", dtype) #TODO what should be done if this happens? +# self.deletePV(name) +# pvdb[name] = {"type": dtype} + if pvdb: + print("new PVs", pvdb) + self.createPV(pvdb) + for name, value in kwargs.items(): + name = name.upper() + self.driver.setParam(name, value) + self.driver.updatePVs() + + + def host(self, **kwargs): + pass + + + +#TODO: +# - if pv name already contains :, don't prepend prefix?! + diff --git a/morioc/experiments/ign/run_old1.py b/morioc/experiments/ign/run_old1.py new file mode 100755 index 0000000..3becc13 --- /dev/null +++ b/morioc/experiments/ign/run_old1.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +from time import sleep +from random import random, randint + +from pcaspy import Driver + +from morioc import MorIOC + + +pvdb1 = { + 'RAND1' : { + 'prec' : 3, + }, +} + +pvdb2 = { + 'RAND2' : { + 'prec' : 3, + }, +} + +pvdb3 = { + 'RAND3' : { + 'prec' : 3, + }, +} + + +class myDriver(Driver): + def __init__(self): + super(myDriver, self).__init__() + + def read(self, reason): + if reason.startswith('RAND'): + value = random() +# self.setParam(reason, value) +# self.updatePVs() + else: + value = self.getParam(reason) + return value + + + + +if __name__ == '__main__': + print("init") + mor = MorIOC("mtest", DriverClass=myDriver) + sleep(3) + + print("create", 1) + mor.createPV(pvdb1) + sleep(3) + + print("create", 2) + mor.createPV(pvdb2) + sleep(3) + + print("delete", 2) + mor.deletePVs(pvdb2) + sleep(3) + + print("create", 2) + mor.createPV(pvdb2) + sleep(3) + + print("create", 3) + mor.createPV(pvdb3) + sleep(3) + + mor.stop() + + + diff --git a/morioc/experiments/ign/run_old2.py b/morioc/experiments/ign/run_old2.py new file mode 100755 index 0000000..841021d --- /dev/null +++ b/morioc/experiments/ign/run_old2.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python + +from time import sleep +from random import random, randint + +from morioc import MorIOC + + +if __name__ == "__main__": + mor = MorIOC("mtest") + + for i in range(100): + mor.serve( + rand1 = random(), + rand2 = randint(100, 200), + rand3 = random() + 10 + ) + sleep(0.5) + +# mor.serve( +# rand1 = random(), +# rand3 = randint(100, 200), +# rand2 = random() + 10 +# ) +# sleep(0.5) + + mor.stop() + + + diff --git a/morioc/experiments/mor.py b/morioc/experiments/mor.py new file mode 100755 index 0000000..95f8513 --- /dev/null +++ b/morioc/experiments/mor.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +import gc +from time import sleep +from pcaspy import SimpleServer +from pcaspy.tools import ServerThread +from pcaspy.driver import cas + + +class KillWrapper: + + def __init__(self, Wrapped, *args, **kwargs): + print("init") + self.Wrapped = Wrapped + self.args = args + self.kwargs = kwargs + self.init() + + def init(self): + self.wrapped = self.Wrapped(*self.args, **self.kwargs) + + def kill(self): + print("del") + del self.wrapped + sleep(1) + self.wrapped = None + sleep(1) + print("del'ed") + gc.collect() + sleep(2) + print("gc'ed") + self.init() + + def __getattr__(self, name): + return getattr(self.wrapped, name) + + + +class Mor: + + def __init__(self, prefix, pvdb, Driver): + self.prefix = prefix + self.Driver = Driver + self.server = None + self.driver = None + self.running = False + self.pvdb = pvdb + self.start() + + def restart(self): + print("restart") + self.process = True + + def start(self): + print("start") + self.server = server = KillWrapper(SimpleServer) +# server.setDebugLevel(4) + print(self.pvdb) + server.createPV(self.prefix, self.pvdb) + self.driver = KillWrapper(self.Driver, self) + +# print("thread start") +# self.thread = thread = ServerThread(server) +# thread.start() + + print("wait loop") + self.process = True + self.running = True + while self.running: + if self.process: + server.process(0.1) + + + def stop(self): + print("stop") +# self.thread.stop() +# sleep(1) +# print(self.thread) +# del self.thread +# self.thread = None + + self.process = False + sleep(0.2) +# del self.driver.server +# del self.driver +# self.driver = None + self.driver.kill() + self.server.kill() +# del self.server +# self.server = None +# gc.collect() +# print("sleeping start") +# sleep(2) +# print("sleeping done") + + def createPV(self, new_pvdb): + print("createPV") + self.stop() +# self.pvdb.update(new_pvdb) + print(self.pvdb) + self.restart() + + diff --git a/morioc/morioc.py b/morioc/morioc.py new file mode 100644 index 0000000..b44e08a --- /dev/null +++ b/morioc/morioc.py @@ -0,0 +1,160 @@ +from time import sleep +from datetime import datetime +from threading import Thread +from pcaspy import SimpleServer, Driver +from pcaspy.driver import manager, _ait_d as AIT_D + +from context import Context +from utils import typename + + +INITIAL_PVDB = { #TODO make wait_time caput-able + "START_TIME" : { + "type": "string" + } +} + +D_AIT = {v:k for k, v in AIT_D.items()} + +#PV data type: enum, string, char, float or int +DTYPES = { + str: "string", + float: "float", + int: "int" +} + + + +class DefaultDriver(Driver): #TODO: caget on START_TIME crashes without this subclass?! + pass + + + +class MorIOC(Context): + + def __init__(self, prefix, DriverClass=DefaultDriver, wait_time=0.1): + print("init") + prefix = prefix.upper() + prefix = prefix if prefix.endswith(":") else prefix + ":" + + self.prefix = prefix + self.DriverClass = DriverClass + self.wait_time = wait_time + + self.server = SimpleServer() + self.server.createPV(prefix, INITIAL_PVDB) + self.driver = DriverClass() + + now = datetime.now() + self.driver.setParam("START_TIME", str(now)) + self.driver.updatePVs() + + self.thread = Thread(target=self.run, daemon=True) + self.thread.start() + + + def run(self): + self.running = True + while self.running: + self.server.process(self.wait_time) + + def stop(self): + self.running = False + wait_time = self.wait_time * 1.1 # wait a bit longer for the last process() call to end + sleep(wait_time) + + + + def serve(self, **kwargs): +# host_kwargs = {name: type(value) for name, value in kwargs.items()} + self.host(**kwargs) + self.setParams(**kwargs) + + + def host(self, **kwargs): + pvdb = make_pvdb(kwargs) + if pvdb: + print("new PVs", pvdb) + self.createPV(pvdb) + + + + def createPV(self, pvdb): + current_pvdb = self.driver.pvDB #TODO crash due to keyerror otherwise + self.server.createPV(self.prefix, pvdb) + self.driver = self.DriverClass() + self.driver.pvDB.update(current_pvdb) + + def deletePVs(self, pvdb): + for name in pvdb: + self.deletePV(name) + + def deletePV(self, name): + name = name.upper() + del manager.pvs["default"][name] + del manager.pvf[self.prefix + name] + + def setParams(self, **kwargs): + for name, value in kwargs.items(): + name = name.upper() + self.driver.setParam(name, value) + self.driver.updatePVs() + + def setParam(self, name, value): + name = name.upper() + self.driver.setParam(name, value) + self.driver.updatePVs() + + def get(self, name): + name = name.upper() + return self.driver.getParam(name) + + + + +def make_pvdb(name_to_value): + pvdb = {} + for name, value in name_to_value.items(): + name = name.upper() + + dtype, value = infer_type(value) + + managed_pvs = manager.pvs["default"] + if not name in managed_pvs: + print("will create", name) + pvdb[name] = {"type": dtype} + else: + pv_type = get_managed_pv_type(name) + if pv_type != dtype: + print(f"type for {name} changed:", pv_type, "->", dtype) #TODO what should be done if this happens? +# self.deletePV(name) +# pvdb[name] = {"type": dtype} + return pvdb + + +def infer_type(value): + if isinstance(value, type): + dtype = value + value = dtype() + else: + dtype = type(value) + + dtype = dtype if isinstance(dtype, str) else DTYPES.get(dtype, "string") #TODO default? + + return dtype, value + + +def get_managed_pv_type(name): + managed_pvs = manager.pvs["default"] + pv = managed_pvs[name] + pv_type = pv.info.type + return D_AIT[pv_type] + +def make_info_dict(value): + pass + +#TODO: +# - if pv name already contains ":", don't prepend prefix?! + + + diff --git a/morioc/run.py b/morioc/run.py new file mode 100755 index 0000000..70a9a3a --- /dev/null +++ b/morioc/run.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python + +from time import sleep +from random import random, randint, choice + +from morioc import MorIOC + + +if __name__ == "__main__": + with MorIOC("mtest") as mor: + for i in range(1000): + + mor.serve( + rand1 = random(), + rand2 = randint(100, 200), + rand3 = random() + 10, + rand4 = choice(["test", "bla", "blabla"]) + ) + + mor.host( + bucket1 = int, + bucket2 = "float", + bucket3 = str + ) + + v = mor.get("bucket1") + mor.serve(test = v*2) + + sleep(0.5) + + + diff --git a/morioc/utils.py b/morioc/utils.py new file mode 100644 index 0000000..c3d4936 --- /dev/null +++ b/morioc/utils.py @@ -0,0 +1,5 @@ + +def typename(obj): + return type(obj).__name__ + +