paulscherrerinstitute / pcaspy

Portable Channel Access Server in Python
BSD 3-Clause "New" or "Revised" License
32 stars 24 forks source link

what does server.process(delay) do? #82

Closed vstadnytskyi-FDA closed 2 years ago

vstadnytskyi-FDA commented 2 years ago

Hi All,

I have found a docstring for server.process(delay)


        """
        Process server transactions.
        :param float delay: Processing time in second
        This method should be called so frequent so that the incoming channel access
        requests are answered in time. Normally called in the loop::
            server = SimpleServer()
            ...
            while True:
                server.process(0.1)
        """

but I still do not understand what it does exactly. For example, I have a server that reads my laptop's CPU MEMORY BATTERY etc. This is an example of ca server I made.

from pcaspy import SimpleServer, Driver
import random

prefix = 'simple_daq:'
pvdb = {
    'CPU' : {
        'value': 0.0,
        'prec' : 1,
        'scan' : 1,
        'count': 1,
        'unit': '%'
    },
    'MEMORY' : {
        'value': 0.0,
        'prec' : 1,
        'scan' : 1,
        'count': 1,
        'unit': 'GB'
    },
    'BATTERY' : {
        'value': 0.0,
        'prec' : 1,
        'scan' : 1,
        'count': 1,
        'unit': '%'
       },
    'TIME' : {
        'type' : 'str',
        'value': 'time',
        'scan' : 1,
            },
    'dt' : {
        'value': 2.0,
        'prec' : 1,
        'scan' : 1,
        'count': 1,
        'unit': 's' 
    },
}

class myDriver(Driver):
    def __init__(self):
        super(myDriver, self).__init__()

    def read(self, reason):
        from time import ctime, time
        import psutil
        if reason == 'TIME':
            value = ctime(time())
        elif reason == 'CPU':
            value = psutil.cpu_percent()
        elif reason == 'BATTERY':
            if psutil.sensors_battery() is not None:
                value = psutil.sensors_battery().percent
            else:
                value = np.nan
        elif reason == 'MEMORY':
            value = psutil.virtual_memory().used / (1024**3)
        else:
            value = self.getParam(reason)
        return value

if __name__ == '__main__':
    from time import sleep,ctime, time
    server = SimpleServer()
    server.createPV(prefix, pvdb)
    driver = myDriver()
    while True:
        server.process(driver.getParam('dt'))
        print(ctime(time()))

It works well. I have added print statement into while True loop to see how often it is called. I noticed that it is called many many time instead of every 2 seconds.

Wed May 25 18:35:41 2022
Wed May 25 18:35:41 2022
Wed May 25 18:35:41 2022
Wed May 25 18:35:41 2022
Wed May 25 18:35:43 2022
Wed May 25 18:35:43 2022
Wed May 25 18:35:43 2022
Wed May 25 18:35:43 2022
Wed May 25 18:35:43 2022
Wed May 25 18:35:43 2022
Wed May 25 18:35:45 2022

I had a GUI and a terminal client connected. When I disconnected them the rate dropped down to 2 second. So it seems to me that server.process processes one request at a time. And I assume that the read() function in MyDriver is called every time server.process is called. Is it correct? Is there some documentation on what is actually happening.

I prefer driver instance or device instance to push update whenever is needed instead of server pulling updates on a timer. for example, if a PV on a server changes I want all clients to get an updated ASAP

xiaoqiangwang commented 2 years ago

The SimpleServer.process spends as much time as delay in wait for client transactions. The actual time varies.

In the following modification, the driver polls the CPU/MEMORY status and pushes the updates to the monitoring clients in a thread.

import threading
import time

import psutil

from pcaspy import SimpleServer, Driver

prefix = 'simple_daq:'
pvdb = {
    'CPU' : {
        'value': 0.0,
        'prec' : 1,
        'unit': '%'
    },
    'MEMORY' : {
        'value': 0.0,
        'prec' : 1,
        'unit': 'GB'
    },
    'BATTERY' : {
        'value': 0.0,
        'prec' : 1,
        'unit': '%'
       },
    'TIME' : {
        'type' : 'str',
        'value': 'time',
    },
    'dt' : {
        'value': 2.0,
        'prec' : 1,
        'unit': 's'
    },
}

class myDriver(Driver):
    def __init__(self):
        super(myDriver, self).__init__()
        threading.Thread(target=self.poll, daemon=True).start()

    def poll(self):
        while True:
            self.setParam('TIME', time.ctime())
            self.setParam('CPU', psutil.cpu_percent())
            if psutil.sensors_battery() is not None:
                self.setParam('BATTERY', psutil.sensors_battery().percent)
            self.setParam('MEMORY', psutil.virtual_memory().used / (1024**3))

            self.updatePVs()

            time.sleep(self.getParam('dt'))

if __name__ == '__main__':
    server = SimpleServer()
    server.createPV(prefix, pvdb)
    driver = myDriver()
    while True:
        server.process(driver.getParam('dt'))
        print(time.ctime())
vstadnytskyi-FDA commented 2 years ago

Thank you this is very useful!

The SimpleServer.process spends as much time as delay in wait for client transactions.

What is appropriate time for delay. I see you use 0.1 s, why not use 0.01s or 1 second. How should go about picking dt?

xiaoqiangwang commented 2 years ago

0.1 is quite arbitrarily chosen. Also because it is called in a tight loop, the server actually is always waiting for requests from the clients and driver.

dt is the how frequent the driver updates the values. It is not coupled with the processing period.

vstadnytskyi-FDA commented 2 years ago

The fastest update I can get it around 1 second. Is it something in my code that takes time or there is limitation on the server side? I want to update around 0.3 seconds, then it does not feel like there is a gap between updates.

server: https://github.com/vstadnytskyi/caproto-sandbox/blob/master/caproto_sandbox/transition/pcaspy/simple_daq_push_server.py gui: https://github.com/vstadnytskyi/caproto-sandbox/blob/master/caproto_sandbox/simple_daq/gui.py

even with poll function running in a thread I also get 1second per update. I cannot go faster

vstadnytskyi-FDA commented 2 years ago

The fastest update I can get it around 1 second. Is it something in my code that takes time or there is limitation on the server side? I want to update around 0.3 seconds, then it does not feel like there is a gap between updates.

server: https://github.com/vstadnytskyi/caproto-sandbox/blob/master/caproto_sandbox/transition/pcaspy/simple_daq_push_server.py gui: https://github.com/vstadnytskyi/caproto-sandbox/blob/master/caproto_sandbox/simple_daq/gui.py

even with poll function running in a thread I also get 1second per update. I cannot go faster

I have figured it! I need to change SCAN to small value for each PV. what is the purpose of SCAN? is it the fastest update allowed?

xiaoqiangwang commented 2 years ago

scan fields controls how often the read method is called. Any value is allowed, there is no checking. But the system has to be able to cope with it.

On the other hand, the modified program structure is my comment is preferred, because it uses only one thread.