rework of the server side
main change: the same server may be used for several instruments - client classes are 'Interactors' dealing with the parameters etc. - methods from history are added to the clients + improvements on the js client side
This commit is contained in:
69
base.py
69
base.py
@ -1,6 +1,6 @@
|
||||
import sys
|
||||
import time
|
||||
import logging
|
||||
import uuid
|
||||
|
||||
ONEYEAR = 366 * 24 * 3600
|
||||
|
||||
@ -35,16 +35,63 @@ class Logger(object):
|
||||
pass
|
||||
|
||||
|
||||
class Instrument:
|
||||
class HandlerBase:
|
||||
def __init__(self):
|
||||
self.clients = {}
|
||||
self.handlers = {k[2:]: getattr(self, k) for k in dir(type(self)) if k.startswith('w_')}
|
||||
|
||||
def remove(self, client):
|
||||
try:
|
||||
del self.clients[client.id]
|
||||
except KeyError:
|
||||
logging.warning('client already removed %s', client.id)
|
||||
|
||||
def register(self, client):
|
||||
self.clients[client.id] = client
|
||||
return client
|
||||
class Client(HandlerBase):
|
||||
def __init__(self, server, streams, instrument_name, device_name):
|
||||
super().__init__()
|
||||
self.id = uuid.uuid4().hex[0:15]
|
||||
self.nodes = {}
|
||||
self.node_map = {}
|
||||
if streams:
|
||||
for uri in streams:
|
||||
urisplit = uri.rsplit('://')
|
||||
kind = urisplit[0] if len(urisplit) == 2 else 'secop'
|
||||
node = server.interactor_classes[kind](uri, self.node_map)
|
||||
self.nodes[uri] = node
|
||||
self.server = server
|
||||
self.instrument_name = instrument_name
|
||||
self.device_name = device_name # do not know if this is needed
|
||||
self.updates = {}
|
||||
|
||||
def poll(self):
|
||||
updates = sum((n.get_updates() for n in self.nodes.values()), start=[])
|
||||
result = [dict(type='update', updates=updates)] if updates else []
|
||||
graph_updates = self.handlers.get('graphpoll', object)()
|
||||
if graph_updates:
|
||||
result.append(graph_updates)
|
||||
return result
|
||||
|
||||
def w_getblock(self, path):
|
||||
path = path.split(',')[-1] # TODO: why this?
|
||||
if path == "main": # TODO: change to "-main-"?
|
||||
components = []
|
||||
for node in self.nodes.values():
|
||||
node.add_main_components(components)
|
||||
return dict(type='draw', path='main', title='modules', components=components)
|
||||
node = self.node_map[path]
|
||||
return dict(type='draw', path=path, title=path, components=node.get_components(path))
|
||||
|
||||
def w_updateblock(self, path):
|
||||
if path == 'main': # TODO: change to "-main-"?
|
||||
for node in self.nodes.values():
|
||||
node.update_main()
|
||||
else:
|
||||
node = self.node_map[path]
|
||||
node.update_params(path)
|
||||
return dict(type='accept-block')
|
||||
|
||||
def w_console(self): # TODO: check if still used
|
||||
return dict(type='accept-console')
|
||||
|
||||
def w_sendcommand(self, command):
|
||||
for node in self.nodes.values():
|
||||
if node.handle_command(command):
|
||||
break
|
||||
return dict(type='accept-command')
|
||||
|
||||
def info(self):
|
||||
return ["na"]
|
||||
|
@ -17,9 +17,9 @@ function buildUpdateConnection() {
|
||||
|
||||
// Establishes server-sent-event-connection, which is used for all sorts of
|
||||
// updates and exists as long as the client is running.
|
||||
// Executed at programstart (see also SEAWebClientMain.js).
|
||||
// Executed at program start (see also SEAWebClientMain.js).
|
||||
|
||||
var path = "http://" + hostPort + "/update";
|
||||
var path = "http://" + hostPort + "/update?" + window.clientTags;
|
||||
if (debugCommunication) {
|
||||
console.log("%cto server (SSE): " + path,
|
||||
"color:white;background:lightblue");
|
||||
@ -29,8 +29,7 @@ function buildUpdateConnection() {
|
||||
var src = new EventSource(path);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
alertify.prompt(
|
||||
"NETWORK ERROR",
|
||||
alertify.prompt("NETWORK ERROR",
|
||||
"Failed to establish connection to data-server at the given address!"
|
||||
+ "Try to enter HOST and PORT of the data-server manually!",
|
||||
hostPort, function(evt, value) {
|
||||
@ -50,9 +49,7 @@ function buildUpdateConnection() {
|
||||
src.onerror = function(e) {
|
||||
console.log(e);
|
||||
console.log('EVTSRC error')
|
||||
alertify
|
||||
.prompt(
|
||||
"NETWORK ERROR",
|
||||
alertify.prompt("NETWORK ERROR",
|
||||
"Failed to establish connection to data-server at the given address!"
|
||||
+ "Try to enter HOST and PORT of the data-server manually!",
|
||||
hostPort, function(evt, value) {
|
||||
@ -406,10 +403,9 @@ function successHandler(s, message) {
|
||||
begin = timeRange[0] - timeRange[1];
|
||||
select.value = begin;
|
||||
// Server-request for variable-list.*/
|
||||
console.log('TIME', window['clientTags'], timeRange)
|
||||
console.log('TIME', timeRange)
|
||||
reqJSONPOST(0, "http://" + hostPort + "/getvars",
|
||||
"time=" + timeRange[0] + ',' + timeRange[1]
|
||||
+ window['clientTags']
|
||||
+ "&userconfiguration=" + JSON.stringify(getFormattedUserConfigurationFromLocalStorage())
|
||||
+ "&id=" + clientID, successHandler, errorHandler);
|
||||
break;
|
||||
|
@ -490,7 +490,7 @@ let graphs = (function (){
|
||||
if(idx != -1){ //if the clicked block is displayed somewhere, we create a selection window
|
||||
createSelection(idx); // We will create a selection at the gindex
|
||||
}
|
||||
}
|
||||
}
|
||||
createGraph(gindex, block); // we create at the current shown selector (gindex), the graph corresponding to the one clicked (block)
|
||||
})
|
||||
selection.appendChild(bel);
|
||||
@ -547,7 +547,7 @@ let graphs = (function (){
|
||||
+ "&variables=" + varlist
|
||||
+ "&interval=" + resolution
|
||||
+ "&id=" + clientID).getJSON().then(function(data){
|
||||
//console.log('Graph', block, data)
|
||||
// console.log('Graph', block, data);
|
||||
let graph = new Graph(gindex, graph_elm, "Time", block.unit, block.tag, type);
|
||||
graph_array[gindex] = graph;
|
||||
|
||||
@ -559,7 +559,6 @@ let graphs = (function (){
|
||||
for(let e of data.graph[key]){
|
||||
pdata.push({x: e[0]*1000, y: e[1]});
|
||||
}
|
||||
|
||||
addDataset(gindex, key, pdata, dict[key])
|
||||
// if(pdata.length > 0){
|
||||
// addDataset(gindex, key, pdata, dict[key])
|
||||
@ -919,7 +918,6 @@ let graphs = (function (){
|
||||
|
||||
AJAX("http://" + hostPort + "/getvars").postForm(
|
||||
"time=" + msRightTimestampGetVars/1000
|
||||
+ window['clientTags']
|
||||
+ "&userconfiguration=" + JSON.stringify(getFormattedUserConfigurationFromLocalStorage())
|
||||
+ "&id="+ clientID).then(function(data){
|
||||
blocks = data.blocks;
|
||||
@ -998,7 +996,6 @@ let graphs = (function (){
|
||||
AJAX("http://" + hostPort + "/getvars").postForm(
|
||||
"time=" + msRightTimestamp/1000 + "&userconfiguration="
|
||||
+ JSON.stringify(getFormattedUserConfigurationFromLocalStorage())
|
||||
+ window['clientTags']
|
||||
+ "&id="+ clientID).then(function(data){
|
||||
currentMaxTime = msRightTimestamp + 60000;
|
||||
currentMinTime = msLeftTimestamp;
|
||||
@ -1304,7 +1301,6 @@ let graphs = (function (){
|
||||
AJAX("http://" + hostPort + "/getvars").postForm(
|
||||
"time=" + currentMaxTime/1000
|
||||
+ "&userconfiguration=" + JSON.stringify(userConfiguration)
|
||||
+ window['clientTags']
|
||||
+ "&id="+ clientID).then(function(data){
|
||||
blocks = data.blocks;
|
||||
document.getElementById("device").innerHTML = data.device
|
||||
|
@ -91,18 +91,21 @@ new Settings()
|
||||
.treat("hideRightPart", "hr", to_bool, false) //used to completely disable the right part
|
||||
.treat("wideGraphs", "wg", to_bool, false) //used to toggle the size of the graphs part
|
||||
.treat("showAsync", "sa", to_bool, false)
|
||||
.treat("device", "dev", 0, "*")
|
||||
.treat("server", "srv", 0, "*")
|
||||
.treat("device", "dev", 0, "")
|
||||
.treat("server", "srv", 0, "")
|
||||
.treat("instrument", "instrument", 0, "")
|
||||
.treat("timerange", "time", 0, "-1800,0")
|
||||
|
||||
if (window['instrument']) {
|
||||
window['clientTags'] = "&instrument=" + window["instrument"];
|
||||
if (window.instrument) {
|
||||
window.clientTags = "&instrument=" + window.instrument;
|
||||
} else {
|
||||
window['clientTags'] = "&stream=" + window["server"] + "&device=" + window["device"];
|
||||
let args = '';
|
||||
if (window.server) { args += "&stream=" + window.server; }
|
||||
if (window.device) { args += "&device=" + window.device; }
|
||||
window.clientTags = args;
|
||||
}
|
||||
|
||||
console.log('TAGS', window['clientTags']);
|
||||
console.log('TAGS', window.clientTags);
|
||||
|
||||
function loadFirstBlocks() {
|
||||
if (debug_main_daniel) {
|
||||
@ -112,11 +115,19 @@ function loadFirstBlocks() {
|
||||
if (showMain) pushInitCommand("getblock?path=main&", "main")
|
||||
if (showConsole) pushInitCommand("console?", "console")
|
||||
if (nColumns == 1) { // probably mobile phone}
|
||||
if (showGraphics) pushInitCommand("gettime?time=" + window["timerange"] + "&", "graphics")
|
||||
if (showGraphics) pushInitCommand("gettime?time=" + window.timerange + "&", "graphics")
|
||||
if (showOverview) pushInitCommand("getblock?path=_overview&", "overview")
|
||||
var goFS = document.getElementById('header');
|
||||
goFS.addEventListener(
|
||||
'click',
|
||||
function () {
|
||||
document.body.requestFullscreen();
|
||||
},
|
||||
false,
|
||||
);
|
||||
} else {
|
||||
if (showOverview) pushInitCommand("getblock?path=_overview&", "overview")
|
||||
if (showGraphics) pushInitCommand("gettime?time=" + window["timerange"] + "&", "graphics")
|
||||
if (showGraphics) pushInitCommand("gettime?time=" + window.timerange + "&", "graphics")
|
||||
// last is shown first
|
||||
}
|
||||
}
|
||||
@ -169,7 +180,7 @@ window.onload = function() {
|
||||
|
||||
let crossElement = document.getElementById("close-cross");
|
||||
|
||||
if(window["hideRightPart"]){
|
||||
if(window.hideRightPart){
|
||||
document.body.removeChild(crossElement);
|
||||
}else{
|
||||
crossElement.onclick = function(){
|
||||
@ -193,7 +204,7 @@ window.onload = function() {
|
||||
elements[2].style.display = "none"; // hide parameters
|
||||
}
|
||||
}else{ // else it toggles the graphs window's size and triggers the adjustGrid()
|
||||
window["wideGraphs"] = !window['wideGraphs'];
|
||||
window.wideGraphs = ! window.wideGraphs;
|
||||
adjustGrid();
|
||||
}
|
||||
}
|
||||
@ -207,9 +218,8 @@ window.onload = function() {
|
||||
|
||||
// var homeButton = document.getElementById("home-icon");
|
||||
|
||||
// TODO : uncomment this code with the right URL to navigate to when the way to select the instrument will be decided.
|
||||
homeButton.onclick = function () {
|
||||
window.location = "/select";
|
||||
window.location = "/select_experiment";
|
||||
};
|
||||
buildUpdateConnection();
|
||||
// if (location.hash) {
|
||||
|
241
influxgraph.py
241
influxgraph.py
@ -3,20 +3,19 @@ import logging
|
||||
import json
|
||||
import io
|
||||
import uuid
|
||||
from configparser import ConfigParser
|
||||
# from configparser import ConfigParser
|
||||
from math import ceil
|
||||
from sehistory.seinflux import SEHistory
|
||||
from sehistory.seinflux import fmtime
|
||||
from colors import assign_colors_to_curves
|
||||
from chart_config import ChartConfig
|
||||
from base import Instrument, get_abs_time
|
||||
from secop import SecopClient, SecopInstrument
|
||||
from base import get_abs_time, HandlerBase
|
||||
|
||||
|
||||
def split_tags(tags):
|
||||
return {k: v.split(',') for k, v in tags.items()}
|
||||
|
||||
|
||||
class InfluxGraph:
|
||||
class InfluxGraph(HandlerBase):
|
||||
"""Class implementing the logic of the different routes that are called by the client to retrieve graph data with InfluxDB.
|
||||
|
||||
Global constants :
|
||||
@ -43,22 +42,37 @@ class InfluxGraph:
|
||||
ACTUAL = 1
|
||||
LIVE = 2
|
||||
|
||||
def __init__(self, instrument):
|
||||
self.instrument = instrument
|
||||
self.db = instrument.db
|
||||
instrument_name = instrument.title
|
||||
def __init__(self, server, instrument, device_name, tags):
|
||||
"""create instance for retrieving history
|
||||
|
||||
:param db: a database client (SEInflux instance)
|
||||
:param instrument: the name of anm instrument or None
|
||||
:param streams: a stream or comma separated list of streams
|
||||
:param devices: a device name ar a comma separated list of devices
|
||||
:param device_name: (comma separated) device name for labelling
|
||||
typically only one of the 3 last parameters are needed
|
||||
if more are specified, all of them must be fulfilled
|
||||
"""
|
||||
super().__init__() # put methods w_... to handlers
|
||||
self.handlers['graphpoll'] = self.graphpoll
|
||||
self.server = server
|
||||
self.db = server.db
|
||||
# self.influx_data_getter = influx_data_getter
|
||||
self.chart_configs = [ChartConfig("./config/generic.ini")]
|
||||
try:
|
||||
self.chart_configs.append(ChartConfig(f"./config/{instrument_name}.ini"))
|
||||
except KeyError:
|
||||
pass
|
||||
self.instrument = instrument
|
||||
self.device_name = device_name
|
||||
if instrument: # TODO: should it not be better to have inifiles per device?
|
||||
try:
|
||||
self.chart_configs.append(ChartConfig(f"./config/{instrument}.ini"))
|
||||
except KeyError:
|
||||
pass
|
||||
self.livemode = self.HISTORICAL
|
||||
self.last_values = {} # dict <variable> of last known point (<time>, <value>)
|
||||
self.last_time = {} # dict <stream> of last received time
|
||||
self.last_minute = 0
|
||||
self.last_update = 0 # time of last call with a result
|
||||
self.tags = {} # tags for query (determines device and/or server)
|
||||
self.tags = None
|
||||
self.init_tags = tags
|
||||
|
||||
def w_graph(self, variables, time="-1800,0", interval=None):
|
||||
"""Get the curves given by variables in the time range "time"
|
||||
@ -84,14 +98,12 @@ class InfluxGraph:
|
||||
logging.info('LIVE %g %g %d %d', end, now, end >= now, self.livemode)
|
||||
if interval:
|
||||
interval = float(interval)
|
||||
print('CURVES', start - now, end - now, self.tags)
|
||||
result = self.db.curves(start, end, queried_variables, merge='_measurement',
|
||||
interval=interval or None, **self.tags)
|
||||
print('LEN', len(result))
|
||||
self.update_last(result)
|
||||
self.db.complete(result, self.last_time, 'stream')
|
||||
self.last_minute = now // 60
|
||||
return dict(type='graph-draw', graph={k: v for k, v in result.items()})
|
||||
return dict(type='graph-draw', graph={k: result[k] for k in queried_variables if k in result})
|
||||
|
||||
def update_last(self, curve_dict):
|
||||
"""update last values per variable and last time per stream"""
|
||||
@ -118,7 +130,7 @@ class InfluxGraph:
|
||||
return dict(type='time', time=get_abs_time(
|
||||
[float(t) for t in time.split(',')]))
|
||||
|
||||
def w_getvars(self, time, userconfiguration=None, instrument=None, **tags):
|
||||
def w_getvars(self, time, userconfiguration=None, **_):
|
||||
"""Get the curve names available at a given point in time
|
||||
|
||||
with a possible user configuration on the client side.
|
||||
@ -145,22 +157,17 @@ class InfluxGraph:
|
||||
if userconfiguration is not None:
|
||||
userconfiguration = json.loads(userconfiguration)
|
||||
|
||||
self.tags = split_tags(tags)
|
||||
if instrument:
|
||||
self.tags['stream'] = list(self.db.get_streams(instrument))
|
||||
print('TAGS', self.tags)
|
||||
if self.instrument:
|
||||
streams, tags, self.device_name = self.server.lookup_streams(self.instrument, **self.init_tags)
|
||||
self.tags = {**self.init_tags, **tags}
|
||||
blocks = self.get_available_variables(start_time, end_time, self.chart_configs, userconfiguration)
|
||||
device_name = self.tags.get('device', '<unknown>')
|
||||
# initialize self.last_values to keep track of the available variables
|
||||
self.last_values = {var["name"]: [0, None] for block in blocks for var in block["curves"]}
|
||||
assign_colors_to_curves(blocks)
|
||||
result = dict(type='var_list')
|
||||
result['blocks'] = blocks
|
||||
result['device'] = device_name
|
||||
# print('DEVICE', device_name, tags)
|
||||
# for block in blocks:
|
||||
# print(block['tag'], [c['name'] for c in block['curves']])
|
||||
return result
|
||||
return {'type': 'var_list', 'blocks': blocks, 'device': self.device_name}
|
||||
|
||||
def get_available_variables(self, start_time, end_time, chart_configs=None, user_config=None):
|
||||
"""Gets the available variables
|
||||
@ -182,15 +189,16 @@ class InfluxGraph:
|
||||
|
||||
Returns :
|
||||
[{"tag":(str), "unit":(str), "curves":[{"name":(str), "label":(str), "color":(str)}]}] :
|
||||
a list of dictionnaries, each one representing
|
||||
a list of dicts, each one representing
|
||||
a block of curves with their name, their label and their color to display,
|
||||
grouped by their category if given or unit (in tag).
|
||||
"""
|
||||
if start_time == end_time:
|
||||
start_time = end_time - 3600
|
||||
result = self.db.curves(start_time, end_time, _measurement=None, _field='float', **self.tags)
|
||||
result = self.db.curves(start_time, end_time, _measurement=None,
|
||||
merge='_measurement', **self.tags)
|
||||
assert all(c.key_names[0] == '_measurement' for c in result.values())
|
||||
variables = {k[0] for k in result}
|
||||
variables = {k: t.tags.get('unit') for k, t in result.items()}
|
||||
config = {}
|
||||
if chart_configs:
|
||||
for chart_config in chart_configs:
|
||||
@ -215,38 +223,59 @@ class InfluxGraph:
|
||||
cat = '*'
|
||||
elif not cat:
|
||||
return
|
||||
unit = unit or '1'
|
||||
tag = cat.replace('*', unit)
|
||||
grp = groups.get(tag)
|
||||
if grp is None:
|
||||
curves = []
|
||||
groups[tag] = {'tag': cat.replace('*', unit), 'unit': unit, 'curves': curves}
|
||||
crv_dict = {}
|
||||
groups[tag] = {'tag': cat.replace('*', unit), 'unit': unit, 'curves': crv_dict}
|
||||
else:
|
||||
curves = grp['curves']
|
||||
curves.append({'name': name, 'unit': unit, 'label': label or name})
|
||||
crv_dict = grp['curves']
|
||||
crv_dict[name] = {'name': name, 'unit': unit, 'label': label or name}
|
||||
|
||||
# treat variables in config first (in their order!)
|
||||
result = {}
|
||||
for key, cfg in config.items():
|
||||
cat = cfg.pop('cat', None)
|
||||
unit = cfg.get('unit', '1')
|
||||
cfgunit = cfg.pop('unit', '')
|
||||
if '.' in key:
|
||||
if key in variables:
|
||||
add_to_groups(key, cat, **cfg)
|
||||
variables.discard(key)
|
||||
unit = variables.pop(key, object)
|
||||
if unit is not object:
|
||||
add_to_groups(key, cat, cfgunit or unit, **cfg)
|
||||
else:
|
||||
var = f'{key}.value'
|
||||
if var in variables:
|
||||
unit = variables.pop(var, object)
|
||||
if unit is not object:
|
||||
label = cfg.pop('label', None) or key
|
||||
add_to_groups(var, cat, label=label, **cfg)
|
||||
variables.discard(var)
|
||||
add_to_groups(var, cat, cfgunit or unit, label=label, **cfg)
|
||||
var = f'{key}.target'
|
||||
if var in variables:
|
||||
unit = variables.pop(var, object)
|
||||
if unit is not object:
|
||||
cfg.pop('color', None)
|
||||
add_to_groups(var, cat, **cfg)
|
||||
variables.discard(var)
|
||||
for var in variables:
|
||||
add_to_groups(var)
|
||||
return list(groups.values())
|
||||
add_to_groups(var, cat, cfgunit or unit, **cfg)
|
||||
for var, unit in variables.items():
|
||||
add_to_groups(var, unit=unit)
|
||||
# make order a bit more common
|
||||
result = []
|
||||
for key in ['K', 'T', 'W', 'ln/min'] + list(groups):
|
||||
if key in groups:
|
||||
group = groups.pop(key)
|
||||
curve_dict = group['curves']
|
||||
curves = []
|
||||
# get first '.value' parameters and add targets if available
|
||||
ordered_keys = [f'{m}.value' for m in ('tt', 'T', 'ts', 'Ts')]
|
||||
for name in ordered_keys + list(curve_dict):
|
||||
if name.endswith('.value'):
|
||||
try:
|
||||
curves.append(curve_dict.pop(name))
|
||||
curves.append(curve_dict.pop(f'{name[:-6]}.target'))
|
||||
except KeyError:
|
||||
pass # skip not existing or already removed items
|
||||
# add remaining curves
|
||||
curves.extend(curve_dict.values())
|
||||
print(key, curves)
|
||||
group['curves'] = curves
|
||||
result.append(group)
|
||||
return result
|
||||
|
||||
def w_updategraph(self):
|
||||
"""Set the current visualisation mode to LIVE if not in HISTORICAL mode.
|
||||
@ -348,17 +377,18 @@ class InfluxGraph:
|
||||
return dict(type='graph-update', time=last_time, graph=result)
|
||||
return None
|
||||
|
||||
class InfluxInstrument(Instrument):
|
||||
|
||||
def __init__(self, instr_name, inst_config=None):
|
||||
super().__init__()
|
||||
self.db = InfluxDB()
|
||||
# self.influx_data_getter = InfluxDataGetter(self.db, instr_name)
|
||||
self.title = instr_name
|
||||
self.device = self.influx_data_getter.get_device_name(int(current_time()))
|
||||
|
||||
def new_client(self):
|
||||
return self.register(InfluxClient(self))
|
||||
# class InfluxInstrument(HandlerBase):
|
||||
#
|
||||
# def __init__(self, instr_name, inst_config=None):
|
||||
# super().__init__()
|
||||
# self.db = InfluxDB()
|
||||
# # self.influx_data_getter = InfluxDataGetter(self.db, instr_name)
|
||||
# self.title = instr_name
|
||||
# self.device = self.influx_data_getter.get_device_name(int(current_time()))
|
||||
#
|
||||
# def new_client(self):
|
||||
# return self.register(InfluxClient(self))
|
||||
|
||||
|
||||
class InfluxParams:
|
||||
@ -383,53 +413,50 @@ class InfluxParams:
|
||||
return dict(type='accept-command')
|
||||
|
||||
|
||||
class InfluxClient(InfluxParams, InfluxGraph):
|
||||
def __init__(self, instrument):
|
||||
InfluxParams.__init__(self)
|
||||
InfluxGraph.__init__(self, instrument)
|
||||
|
||||
def poll(self):
|
||||
messages = self.queue
|
||||
self.queue = []
|
||||
msg = self.graphpoll()
|
||||
if msg:
|
||||
messages.append(msg)
|
||||
return messages
|
||||
|
||||
|
||||
class SecopInfluxClient(SecopClient, InfluxGraph):
|
||||
def __init__(self, instrument):
|
||||
SecopClient.__init__(self, instrument)
|
||||
InfluxGraph.__init__(self, instrument)
|
||||
|
||||
def poll(self):
|
||||
messages = super().poll()
|
||||
msg = self.graphpoll()
|
||||
if msg:
|
||||
messages.append(msg)
|
||||
return messages
|
||||
|
||||
|
||||
class SecopInfluxInstrument(SecopInstrument):
|
||||
|
||||
def __init__(self, inst_name, instrument_config):
|
||||
super().__init__(inst_name, instrument_config)
|
||||
config = ConfigParser()
|
||||
config.optionxform = str
|
||||
config.read("./config/influx.ini")
|
||||
section = config["INFLUX"]
|
||||
self.db = SEHistory()
|
||||
# self.db = InfluxDBWrapper(uri=section["url"], token=section["token"],
|
||||
# org=section["org"], bucket=section['bucket'])
|
||||
# self.influx_data_getter = InfluxDataGetter(self.db, inst_name)
|
||||
# self.device = self.influx_data_getter.get_device_name(int(current_time()))
|
||||
|
||||
def new_client(self):
|
||||
return self.register(SecopInfluxClient(self))
|
||||
|
||||
def get_streams(self, timestamp=None):
|
||||
return self.db.get_streams(None, timestamp)
|
||||
|
||||
def get_experiments(self, start=None, stop=None):
|
||||
return self.db.get_experiments(start, stop)
|
||||
# class InfluxClient(InfluxParams, InfluxGraph):
|
||||
# def __init__(self, instrument):
|
||||
# InfluxParams.__init__(self)
|
||||
# InfluxGraph.__init__(self, instrument)
|
||||
#
|
||||
# def poll(self):
|
||||
# messages = self.queue
|
||||
# self.queue = []
|
||||
# msg = self.graphpoll()
|
||||
# if msg:
|
||||
# messages.append(msg)
|
||||
# return messages
|
||||
#
|
||||
#
|
||||
# class SecopInfluxClient(SecopClient, InfluxGraph):
|
||||
# def __init__(self, instrument):
|
||||
# SecopClient.__init__(self, instrument)
|
||||
# InfluxGraph.__init__(self, instrument)
|
||||
#
|
||||
# def poll(self):
|
||||
# messages = super().poll()
|
||||
# msg = self.graphpoll()
|
||||
# if msg:
|
||||
# messages.append(msg)
|
||||
# return messages
|
||||
#
|
||||
#
|
||||
# class SecopInfluxInstrument(SecopInstrument):
|
||||
#
|
||||
# def __init__(self, inst_name, instrument_config):
|
||||
# super().__init__(inst_name, instrument_config)
|
||||
# config = ConfigParser()
|
||||
# config.optionxform = str
|
||||
# config.read("./config/influx.ini")
|
||||
# section = config["INFLUX"]
|
||||
# self.db = SEHistory()
|
||||
# # self.db = InfluxDBWrapper(uri=section["url"], token=section["token"],
|
||||
# # org=section["org"], bucket=section['bucket'])
|
||||
# # self.influx_data_getter = InfluxDataGetter(self.db, inst_name)
|
||||
# # self.device = self.influx_data_getter.get_device_name(int(current_time()))
|
||||
#
|
||||
# def get_streams(self, timestamp=None):
|
||||
# return self.db.get_streams(None, timestamp)
|
||||
#
|
||||
# def get_experiments(self, start=None, stop=None):
|
||||
# return self.db.get_experiments(start, stop)
|
||||
|
||||
|
@ -1,9 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env python
|
||||
|
||||
import sys
|
||||
import pathlib
|
||||
sys.path.insert(0, str((pathlib.Path(__file__) / '..').resolve()))
|
||||
import webserver
|
||||
from influxgraph import SecopInfluxInstrument
|
||||
import argparse
|
||||
import socket
|
||||
from webserver import server
|
||||
from base import Client
|
||||
from influxgraph import InfluxGraph
|
||||
from secop import SecopInteractor
|
||||
from sehistory.seinflux import SEHistory
|
||||
|
||||
webserver.instrument = webserver.main(SecopInfluxInstrument)
|
||||
|
||||
def parseArgv(argv):
|
||||
parser = argparse.ArgumentParser(
|
||||
description="start a webserver for history and interaction",
|
||||
)
|
||||
# loggroup = parser.add_mutually_exclusive_group()
|
||||
# loggroup.add_argument("-v", "--verbose",
|
||||
# help="Output lots of diagnostic information",
|
||||
# action='store_true', default=False)
|
||||
# loggroup.add_argument("-q", "--quiet", help="suppress non-error messages",
|
||||
# action='store_true', default=False)
|
||||
parser.add_argument("port",
|
||||
type=str,
|
||||
help="port number to serve\n")
|
||||
# parser.add_argument('-d',
|
||||
# '--daemonize',
|
||||
# action='store_true',
|
||||
# help='Run as daemon',
|
||||
# default=False)
|
||||
parser.add_argument('-i',
|
||||
'--instrument',
|
||||
action='store',
|
||||
help="instrument, if running on an instrument computer\n"
|
||||
"if the value is HOST, take the host name as instrument name",
|
||||
default=None)
|
||||
return parser.parse_args(argv)
|
||||
|
||||
args = parseArgv(sys.argv[1:])
|
||||
|
||||
instrument = socket.gethostname().split('.')[0] if args.instrument == 'HOST' else args.instrument
|
||||
|
||||
server.run(int(args.port), SEHistory(), InfluxGraph, Client, single_instrument=instrument, secop=SecopInteractor)
|
||||
|
165
secop.py
165
secop.py
@ -1,9 +1,8 @@
|
||||
import logging
|
||||
import uuid
|
||||
from base import Instrument, get_abs_time
|
||||
from frappy.client import SecopClient as SecNodeClient
|
||||
from frappy.lib.enum import EnumMember
|
||||
from frappy.datatypes import get_datatype
|
||||
from base import HandlerBase
|
||||
from frappy.client import SecopClient
|
||||
# from frappy.lib.enum import EnumMember
|
||||
# from frappy.datatypes import get_datatype
|
||||
|
||||
|
||||
def convert_par(module, name, par):
|
||||
@ -20,39 +19,38 @@ def convert_par(module, name, par):
|
||||
return result
|
||||
|
||||
|
||||
class SecopClient:
|
||||
class SecopInteractor(SecopClient):
|
||||
prio_par = ["value", "status", "target"]
|
||||
hide_par = ["baseclass", "class", "pollinterval"]
|
||||
skip_par = ["status2"]
|
||||
|
||||
def __init__(self, instrument):
|
||||
self.instrument = instrument
|
||||
self.id = uuid.uuid4().hex[0:15]
|
||||
def __init__(self, uri, node_map):
|
||||
super().__init__(uri)
|
||||
self.module_updates = set()
|
||||
self.param_updates = set()
|
||||
self.updates = {}
|
||||
|
||||
def w_getblock(self, path):
|
||||
path = path.split(',')[-1] # TODO: why this?
|
||||
if path == "main":
|
||||
components = [dict(type='rdlink', name=f'{name}:value', statusname=f'{name}:status', title=name)
|
||||
for node in self.instrument.nodes for name in node.modules]
|
||||
self.param_updates = {'value', 'status'}
|
||||
return dict(type='draw', path='main', title='modules', components=components)
|
||||
def add_main_components(self, components):
|
||||
# todo: treat non Readable classes correctly
|
||||
components.extend(dict(type='rdlink', name=name + ':value', title=name)
|
||||
for name in self.modules)
|
||||
self.param_updates.add('value')
|
||||
self.param_updates.add('status')
|
||||
|
||||
def get_components(self, path):
|
||||
module = self.modules[path]
|
||||
self.module_updates.add(path) # TODO: remove others?
|
||||
node = self.instrument.node_map[path]
|
||||
module = node.modules[path]
|
||||
# logging.info('MP %r', path)
|
||||
parameters = dict(module["parameters"])
|
||||
components = []
|
||||
for name in SecopClient.skip_par:
|
||||
for name in SecopInteractor.skip_par:
|
||||
if name in parameters:
|
||||
parameters.pop(name)
|
||||
for name in SecopClient.prio_par:
|
||||
for name in SecopInteractor.prio_par:
|
||||
if name in parameters:
|
||||
components.append(convert_par(path, name, parameters.pop(name)))
|
||||
components1 = []
|
||||
for name in SecopClient.hide_par:
|
||||
for name in SecopInteractor.hide_par:
|
||||
if name in parameters:
|
||||
components1.append(convert_par(path, name, parameters.pop(name)))
|
||||
for name, p in parameters.items():
|
||||
@ -72,27 +70,22 @@ class SecopClient:
|
||||
# print(item)
|
||||
self.updates[key] = item
|
||||
|
||||
def w_updateblock(self, path):
|
||||
if path == 'main':
|
||||
path = ''
|
||||
for node in self.instrument.nodes:
|
||||
for modname in node.modules:
|
||||
key = modname, 'value'
|
||||
if key in node.cache:
|
||||
self.updateItem(*key, node.cache[key])
|
||||
else:
|
||||
node = self.instrument.node_map[path]
|
||||
for param in node.modules[path]['parameters']:
|
||||
key = path, param
|
||||
if key in node.cache:
|
||||
self.updateItem(*key, node.cache[key])
|
||||
return dict(type='accept-block')
|
||||
def update_main(self):
|
||||
cache = self.cache
|
||||
for modname in self.modules:
|
||||
key = modname, 'value'
|
||||
if key in cache:
|
||||
self.updateItem(*key, cache[key])
|
||||
|
||||
def w_console(self):
|
||||
return dict(type='accept-console')
|
||||
def update_params(self, path):
|
||||
cache = self.cache
|
||||
for param in self.modules[path]['parameters']:
|
||||
key = path, param
|
||||
if key in cache:
|
||||
self.updateItem(*key, cache[key])
|
||||
|
||||
def w_sendcommand(self, command):
|
||||
logging.info('SENDCOMMAND %r', command)
|
||||
def handle_command(self, command):
|
||||
"""handle command if we can, else return False"""
|
||||
if not command.strip():
|
||||
return dict(type='accept-command')
|
||||
if command.startswith('change '):
|
||||
@ -101,62 +94,54 @@ class SecopClient:
|
||||
module, _, parameter = modpar.partition(':')
|
||||
if not parameter:
|
||||
parameter = 'target'
|
||||
node = self.instrument.node_map[module]
|
||||
if module not in self.modules:
|
||||
return False
|
||||
logging.info('SENDCOMMAND %r', command)
|
||||
try:
|
||||
node.setParameterFromString(module, parameter, strvalue)
|
||||
self.setParameterFromString(module, parameter, strvalue)
|
||||
except Exception as e:
|
||||
print(f"{e!r} converting {strvalue} to {node.modules[module]['parameters'][parameter]['datatype']}")
|
||||
return dict(type='accept-command')
|
||||
print(f"{e!r} converting {strvalue} to {self.modules[module]['parameters'][parameter]['datatype']}")
|
||||
return True
|
||||
|
||||
def w_gettime(self, time):
|
||||
"""parse time (using server time)
|
||||
time: comma separated time range (beg,end) values < 1 year are treated as relative to the current time
|
||||
"""
|
||||
time = [float(t) for t in time.split(',')]
|
||||
return dict(type='time', time=get_abs_time(time))
|
||||
|
||||
def poll(self):
|
||||
def get_updates(self):
|
||||
updates, self.updates = self.updates, {}
|
||||
if not updates:
|
||||
return []
|
||||
messages = [dict(type='update', updates=list(updates.values()))]
|
||||
return messages
|
||||
return list(updates.values())
|
||||
|
||||
def info(self):
|
||||
return ["na"]
|
||||
|
||||
|
||||
class SecopInstrument(Instrument):
|
||||
|
||||
def __init__(self, inst_name, instrument_config):
|
||||
super().__init__()
|
||||
self.instrument_config = instrument_config
|
||||
host_ports = instrument_config['hostport']
|
||||
self.logger_dir = instrument_config.get('logger_dir', '')
|
||||
# test_day = instrument_config.get('test_day', None)
|
||||
# self.test_day = [int(x) for x in test_day.split('-')] if test_day else None
|
||||
self.title = inst_name
|
||||
self.device = ''
|
||||
self.nodes = []
|
||||
self.node_map = {}
|
||||
for host_port in host_ports.split(','):
|
||||
node = SecNodeClient(host_port)
|
||||
node.connect()
|
||||
self.nodes.append(node)
|
||||
for name, mod in node.modules.items():
|
||||
self.node_map[name] = node
|
||||
|
||||
def register(self, client):
|
||||
print('OPEN')
|
||||
for node in self.nodes:
|
||||
node.register_callback(None, client.updateItem)
|
||||
return super().register(client)
|
||||
|
||||
def remove(self, client):
|
||||
print('REMOVE')
|
||||
for node in self.nodes:
|
||||
node.unregister_callback(None, client.updateItem)
|
||||
super().remove(client)
|
||||
|
||||
def new_client(self):
|
||||
return self.register(SecopClient(self))
|
||||
# class SecopInstrument(HandlerBase):
|
||||
#
|
||||
# def __init__(self, inst_name, instrument_config):
|
||||
# super().__init__()
|
||||
# self.instrument_config = instrument_config
|
||||
# host_ports = instrument_config['hostport']
|
||||
# self.logger_dir = instrument_config.get('logger_dir', '')
|
||||
# # test_day = instrument_config.get('test_day', None)
|
||||
# # self.test_day = [int(x) for x in test_day.split('-')] if test_day else None
|
||||
# self.title = inst_name
|
||||
# self.device = ''
|
||||
# self.nodes = []
|
||||
# self.node_map = {}
|
||||
# for host_port in host_ports.split(','):
|
||||
# node = SecopClient(host_port)
|
||||
# node.connect()
|
||||
# self.nodes.append(node)
|
||||
# for name, mod in node.modules.items():
|
||||
# self.node_map[name] = node
|
||||
#
|
||||
# def register(self, client):
|
||||
# print('OPEN')
|
||||
# for node in self.nodes:
|
||||
# node.register_callback(None, client.updateItem)
|
||||
# return super().register(client)
|
||||
#
|
||||
# def remove(self, client):
|
||||
# print('REMOVE')
|
||||
# for node in self.nodes:
|
||||
# node.unregister_callback(None, client.updateItem)
|
||||
# super().remove(client)
|
||||
#
|
||||
# def new_client(self):
|
||||
# return self.register(SecopClient(self))
|
||||
|
160
webserver.py
160
webserver.py
@ -43,7 +43,91 @@ def to_json_sse(msg):
|
||||
return 'data: %s\n\n' % txt
|
||||
|
||||
|
||||
instrument = None
|
||||
class Server:
|
||||
"""singleton: created once in this module"""
|
||||
interactor_classes = None
|
||||
client_cls = None
|
||||
history_cls = None
|
||||
history = None
|
||||
single_instrument = None
|
||||
db = None
|
||||
|
||||
def __init__(self):
|
||||
self.instruments = {}
|
||||
self.clients = {}
|
||||
|
||||
def remove(self, client):
|
||||
try:
|
||||
del self.clients[client.id]
|
||||
except KeyError:
|
||||
logging.warning('client already removed %s', client.id)
|
||||
|
||||
def lookup_streams(self, instrument, stream=None, device=None):
|
||||
if self.single_instrument:
|
||||
instrument = self.single_instrument
|
||||
if stream:
|
||||
if isinstance(stream, str):
|
||||
streams = stream.split(',') if stream else []
|
||||
else:
|
||||
streams = stream
|
||||
else:
|
||||
streams = []
|
||||
device_names = devices = device.split(',') if device else []
|
||||
tags = {}
|
||||
if instrument:
|
||||
# tags['instrument'] = instrument
|
||||
stream_dict = self.db.get_streams(instrument, stream=list(streams), device=devices)
|
||||
streams.extend((s for s in stream_dict if s not in streams))
|
||||
if not devices:
|
||||
device_names = list(filter(None, (t.get('device') for t in stream_dict.values())))
|
||||
if streams:
|
||||
tags['stream'] = streams[0] if len(streams) == 1 else streams
|
||||
if devices:
|
||||
tags['device'] = devices[0] if len(devices) == 1 else devices
|
||||
return streams, tags, ','.join(device_names)
|
||||
|
||||
def register_client(self, instrument=None, stream=None, device=None):
|
||||
streams, tags, device_name = self.lookup_streams(instrument, stream, device)
|
||||
client = self.client_cls(self, streams, instrument or '', device_name)
|
||||
history = self.history_cls(self, instrument, device_name, tags)
|
||||
# history.db.debug = True
|
||||
# all relevant methods of the history instance are saved in client.handlers
|
||||
# so there is no reference needed to history anymore
|
||||
client.handlers.update(history.handlers)
|
||||
self.clients[client.id] = client
|
||||
return client
|
||||
|
||||
def run(self, port, db, history_cls, client_cls, single_instrument=None, **interactor_classes):
|
||||
self.single_instrument = single_instrument
|
||||
self.db = db
|
||||
self.history_cls = history_cls
|
||||
self.client_cls = client_cls
|
||||
self.interactor_classes = interactor_classes
|
||||
|
||||
app.debug = True
|
||||
|
||||
logging.basicConfig(filename='webserver.log', filemode='w', level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)s %(message)s')
|
||||
|
||||
# srv = gevent.wsgi.WSGIServer(('', port), app, keyfile='key.key', certfile='key.crt')
|
||||
srv = gevent.pywsgi.WSGIServer(('', port), app, log=logging.getLogger('server'))
|
||||
|
||||
def handle_term(sig, frame):
|
||||
srv.stop()
|
||||
srv.close()
|
||||
|
||||
signal.signal(signal.SIGTERM, handle_term)
|
||||
|
||||
# def handle_pdb(sig, frame):
|
||||
# import pdb
|
||||
# print('PDB')
|
||||
# pdb.Pdb().set_trace(frame)
|
||||
# signal.signal(signal.SIGUSR1, handle_pdb)
|
||||
|
||||
srv.serve_forever()
|
||||
|
||||
|
||||
server = Server()
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
update_rider = circularlog.Rider("upd")
|
||||
@ -51,9 +135,11 @@ pollinterval = 0.2
|
||||
|
||||
|
||||
@app.route('/update')
|
||||
def get_update(path=None):
|
||||
def get_update(_=None):
|
||||
# Client Adress: socket.getfqdn(flask.request.remote_addr)
|
||||
client = instrument.new_client()
|
||||
kwargs = {k: flask.request.values.get(k) for k in ('instrument', 'stream', 'device')}
|
||||
|
||||
client = server.register_client(**kwargs)
|
||||
client.remote_info = circularlog.strtm() + " " + socket.getfqdn(flask.request.remote_addr.split(':')[-1])
|
||||
|
||||
@flask.stream_with_context
|
||||
@ -61,7 +147,8 @@ def get_update(path=None):
|
||||
logging.info('UPDATE %s %s', client.id, socket.getfqdn(flask.request.remote_addr.split(':')[-1]))
|
||||
# msg = dict(type='id', id=client.id, title=instrument.title);
|
||||
# yield to_json_sse(msg)
|
||||
msg = dict(type='id', id=client.id, instrument=instrument.title, device=instrument.device)
|
||||
msg = dict(type='id', id=client.id, instrument=kwargs.get('instrument', '<unknown>'),
|
||||
device=client.device_name)
|
||||
yield to_json_sse(msg)
|
||||
try:
|
||||
lastmsg = time.time()
|
||||
@ -87,11 +174,11 @@ def get_update(path=None):
|
||||
logging.info("except clause %r", repr(e))
|
||||
logging.info('CLOSED %s', client.id)
|
||||
print('CLOSE client')
|
||||
instrument.remove(client)
|
||||
server.remove(client)
|
||||
except Exception as e:
|
||||
logging.info('error')
|
||||
logging.error('%s', traceback.format_exc())
|
||||
instrument.remove(client)
|
||||
server.remove(client)
|
||||
# msg = dict(type='error',error=traceback.format_exc())
|
||||
# yield to_json_sse(msg)
|
||||
|
||||
@ -109,8 +196,8 @@ def dump_circular():
|
||||
@app.route('/clients')
|
||||
def show_clients():
|
||||
result = ""
|
||||
for id in instrument.clients:
|
||||
c = instrument.clients[id]
|
||||
for id in server.clients:
|
||||
c = server.clients[id]
|
||||
result += c.remote_info + " " + "; ".join(c.info()) + "<br>"
|
||||
return result
|
||||
|
||||
@ -123,9 +210,8 @@ def export():
|
||||
logging.info('GET %s %s', path, repr(kwargs))
|
||||
try:
|
||||
id = kwargs.pop('id')
|
||||
print('export')
|
||||
client = instrument.clients[id]
|
||||
bytes = client.w_export(**kwargs)
|
||||
client = server.clients[id]
|
||||
bytes = client.handlers['export'](**kwargs)
|
||||
return flask.send_file(
|
||||
bytes,
|
||||
as_attachment=True,
|
||||
@ -158,8 +244,8 @@ def reply():
|
||||
logging.info('GET %s %r', path, kwargs)
|
||||
try:
|
||||
id = kwargs.pop('id')
|
||||
client = instrument.clients[id]
|
||||
msg = getattr(client, "w_" + path[1:])(**kwargs)
|
||||
client = server.clients[id]
|
||||
msg = client.handlers[path[1:]](**kwargs)
|
||||
except Exception as e:
|
||||
logging.error('%s', traceback.format_exc())
|
||||
circularlog.log()
|
||||
@ -211,6 +297,9 @@ def replace_by_empty(file):
|
||||
|
||||
@app.route('/')
|
||||
def default():
|
||||
if not any(flask.request.values.get(k) for k in ('instrument', 'server', 'device')):
|
||||
if not server.single_instrument:
|
||||
return select_experiment()
|
||||
return general_file('SEAWebClient.html')
|
||||
|
||||
|
||||
@ -224,7 +313,7 @@ th {
|
||||
</style>
|
||||
<tr><th>instrument</th><th colspan=99>devices</th></tr>''']
|
||||
result = {}
|
||||
for stream, tags in instrument.get_streams().items():
|
||||
for stream, tags in server.db.get_streams().items():
|
||||
ins = tags.get('instrument', '0')
|
||||
result.setdefault(ins, []).append((stream, tags.get('device')))
|
||||
bare_streams = result.pop('0', [])
|
||||
@ -240,7 +329,9 @@ th {
|
||||
|
||||
@app.route('/select_experiment')
|
||||
def select_experiment():
|
||||
out = ['''<html><body>
|
||||
out = ['''<html><head>
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<style>
|
||||
th {
|
||||
text-align: left;
|
||||
@ -249,7 +340,8 @@ th {
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
</style><table>
|
||||
</style></head>
|
||||
<body><table>
|
||||
''']
|
||||
showtitle = 0
|
||||
ONEMONTH = 30 * 24 * 3600
|
||||
@ -271,7 +363,7 @@ a {
|
||||
starttime, endtime = now - ONEMONTH, now
|
||||
|
||||
chunk_list = []
|
||||
for key, chunk_dict in instrument.get_experiments(starttime, endtime).items():
|
||||
for key, chunk_dict in server.db.get_experiments(starttime, endtime).items():
|
||||
for (streams, devices), chunks in chunk_dict.items():
|
||||
chunk_list.extend((r[1], r[0], key, devices) for r in chunks)
|
||||
chunk_list.sort(reverse=True)
|
||||
@ -322,37 +414,3 @@ def general_file(file):
|
||||
def hostport_split(hostport):
|
||||
h = hostport.split(':')
|
||||
return (h[0], int(h[1]))
|
||||
|
||||
|
||||
# def handle_pdb(sig, frame):
|
||||
# import pdb
|
||||
# print('PDB')
|
||||
# pdb.Pdb().set_trace(frame)
|
||||
|
||||
|
||||
def main(cls, **config):
|
||||
global instrument
|
||||
if not config:
|
||||
for arg in sys.argv[1:]:
|
||||
key, _, value = arg.partition('=')
|
||||
config[key] = value
|
||||
port = int(config['port'])
|
||||
inst_name = config['instrument']
|
||||
instrument = cls(inst_name, config)
|
||||
|
||||
# signal.signal(signal.SIGUSR1, handle_pdb)
|
||||
|
||||
def handle_term(sig, _):
|
||||
server.stop()
|
||||
server.close()
|
||||
|
||||
signal.signal(signal.SIGTERM, handle_term)
|
||||
|
||||
app.debug = True
|
||||
|
||||
logging.basicConfig(filename='log/%s.log' % inst_name, filemode='w', level=logging.INFO,
|
||||
format='%(asctime)s %(levelname)s %(message)s')
|
||||
|
||||
#server = gevent.wsgi.WSGIServer(('', port), app, keyfile='key.key', certfile='key.crt')
|
||||
server = gevent.pywsgi.WSGIServer(('', port), app, log=logging.getLogger('server'))
|
||||
server.serve_forever()
|
||||
|
Reference in New Issue
Block a user