Commit 40cf4675 authored by Dominik Luntzer's avatar Dominik Luntzer

Add caching feature to the public API

Introduce a new optional parameter to the constructor of the server. If
a cache file path is defined, the address space will be loaded from the
cache or the cache file will be created if it dose not exist yet.
parent 299edf06
......@@ -85,7 +85,9 @@ Server: what is not implemented
* better securty model with users and password
* adding some missing modify methods
Server: Running on a Raspberry Pi
Setting up the standard address-space from scratch is the most time-consuming step of the startup process which may lead to long startup times on less powerful devices like a Raspberry Pi. By passing a path to a cache-file to the server constructor, a shelve holding the address space will be created during the first startup. All following startups will make use of the cache-file which leads to significantly better startup performance (~3.5 vs 125 seconds on a Raspbery Pi Model B).
# Development
......
......@@ -5,6 +5,7 @@ Internal server implementing opcu-ua interface. can be used on server side or to
from datetime import datetime
from copy import copy, deepcopy
from datetime import timedelta
from os import path
import logging
from threading import Lock
from enum import Enum
......@@ -43,7 +44,7 @@ class ServerDesc(object):
class InternalServer(object):
def __init__(self):
def __init__(self, cacheFile = None):
self.logger = logging.getLogger(__name__)
self.endpoints = []
self._channel_id_counter = 5
......@@ -56,14 +57,19 @@ class InternalServer(object):
self.view_service = ViewService(self.aspace)
self.method_service = MethodService(self.aspace)
self.node_mgt_service = NodeManagementService(self.aspace)
# import address space from code generated from xml
standard_address_space.fill_address_space(self.node_mgt_service)
# import address space from save db to disc
#standard_address_space.fill_address_space_from_disk(self.aspace)
# import address space directly from xml, this has preformance impact so disabled
#importer = xmlimporter.XmlImporter(self.node_mgt_service)
#importer.import_xml("/home/olivier/python-opcua/schemas/Opc.Ua.NodeSet2.xml")
if cacheFile and path.isfile(cacheFile):
# import address space from shelve
self.aspace.load(cacheFile)
else:
# import address space from code generated from xml
standard_address_space.fill_address_space(self.node_mgt_service)
# import address space directly from xml, this has preformance impact so disabled
#importer = xmlimporter.XmlImporter(self.node_mgt_service)
#importer.import_xml("/path/to/python-opcua/schemas/Opc.Ua.NodeSet2.xml")
if cacheFile:
self.aspace.dump(cacheFile)
self.loop = utils.ThreadLoop()
self.asyncio_transports = []
......
......@@ -44,6 +44,14 @@ class Server(object):
If you need more flexibility you call directly the Ua Service methods
on the iserver or iserver.isesssion object members.
During startup the standard address space will be constructed, which may be
time-consuming when running a server on a less powerful device (e.g. a
Raspberry Pi). In order to improve startup performance, a optional path to a
cache file can be passed to the server constructor.
If the parameter is defined, the address space will be loaded from the
cache file or the file will be created if it does not exist yet.
As a result the first startup will be even slower due to the cache file
generation but all further startups will be significantly faster.
:ivar application_uri:
:vartype application_uri: uri
......@@ -60,7 +68,7 @@ class Server(object):
"""
def __init__(self):
def __init__(self, cacheFile = None):
self.logger = logging.getLogger(__name__)
self.endpoint = urlparse("opc.tcp://0.0.0.0:4840/freeopcua/server/")
self.application_uri = "urn:freeopcua:python:server"
......@@ -68,7 +76,7 @@ class Server(object):
self.name = "FreeOpcUa Python Server"
self.application_type = ua.ApplicationType.ClientAndServer
self.default_timeout = 3600000
self.iserver = InternalServer()
self.iserver = InternalServer(cacheFile)
self.bserver = None
self._discovery_clients = {}
self._discovery_period = 60
......
......@@ -22,9 +22,3 @@ def fill_address_space(nodeservice):
create_standard_address_space_Part10(nodeservice)
create_standard_address_space_Part11(nodeservice)
create_standard_address_space_Part13(nodeservice)
def fill_address_space_from_disk(aspace):
dirname = os.path.dirname(opcua.__file__)
path = os.path.join(dirname, "binary_address_space.pickle")
aspace.load(path)
......@@ -14,7 +14,7 @@ except ImportError:
from tests_cmd_lines import TestCmdLines
from tests_server import TestServer
from tests_server import TestServer, TestServerCaching
from tests_client import TestClient
from tests_unit import TestUnit
from tests_history import TestHistory
......
import unittest
from tests_common import CommonTests, add_server_methods, MySubHandler
import os
import shelve
import time
from datetime import timedelta, datetime
from tempfile import NamedTemporaryFile
import opcua
from opcua import Server
......@@ -380,3 +383,24 @@ def check_custom_event_type(test, ev):
test.assertEqual(ev.get_child("2:PropertyNum").get_data_value().Value.VariantType, ua.VariantType.Float)
test.assertTrue(ev.get_child("2:PropertyString") in properties)
test.assertEqual(ev.get_child("2:PropertyString").get_data_value().Value.VariantType, ua.VariantType.String)
class TestServerCaching(unittest.TestCase):
def runTest(self):
tmpfile = NamedTemporaryFile()
path = tmpfile.name
tmpfile.close()
#create cache file
server = Server(cacheFile = path)
#modify cache content
id = ua.NodeId(ua.ObjectIds.Server_ServerStatus_SecondsTillShutdown)
s = shelve.open(path, "w", writeback = True)
s[id.to_string()].attributes[ua.AttributeIds.Value].value = ua.DataValue(123)
s.close()
#ensure that we are actually loading from the cache
server = Server(cacheFile = path)
self.assertEqual(server.get_node(id).get_value(), 123)
os.remove(path)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment