diff --git a/Ccs/monitor.py b/Ccs/monitor.py index 61ebcb5f4f1100fbdd2960a43565e6c38b4bc9d4..c24a88a3b7348406830b4df2052dddc02a698a19 100644 --- a/Ccs/monitor.py +++ b/Ccs/monitor.py @@ -1,7 +1,6 @@ import json import threading import time -import configparser import sys import DBus_Basic import dbus @@ -9,20 +8,24 @@ import dbus.service from dbus.mainloop.glib import DBusGMainLoop import gi import confignator - gi.require_version('Gtk', '3.0') from gi.repository import Gtk, Gdk, GLib, GdkPixbuf from database.tm_db import DbTelemetryPool, DbTelemetry, scoped_session_maker +from sqlalchemy.sql.expression import func import ccs_function_lib as cfl +INTERVAL = 1. +MAX_AGE = 20. + + class ParameterMonitor(Gtk.Window): limit_colors = {0: "green", 1: "orange", 2: "red"} alarm_colors = {'red': Gdk.RGBA(1, 0, 0, 1), 'orange': Gdk.RGBA(1, 0.647059, 0, 1), 'green': Gdk.RGBA(0.913725, 0.913725, 0.913725, 1.)} parameter_types = {"S": "s", "N": ".3G"} - def __init__(self, given_cfg=None, pool_name=None, parameter_set='default', interval=1.0, max_age=20., user_limits=None): + def __init__(self, given_cfg=None, pool_name=None, parameter_set=None, interval=INTERVAL, max_age=MAX_AGE, user_limits=None): Gtk.Window.__init__(self, title="Parameter Monitor (Pool: {:})".format(pool_name)) self.set_border_width(10) @@ -63,29 +66,31 @@ class ParameterMonitor(Gtk.Window): self.add(hbox) self.evt_cnt = self.create_event_counter() + self.evt_check_enabled = True + self.evt_check_tocnt = 0 self.parameter_set = parameter_set self.parameters = {} + self.monitored_pkts = None # Is now done in the __name__ == __main__ part #if parameter_set is not None: # self.set_parameter_view(parameter_set) - hbox.pack_start(self.evt_cnt, 0, 0, 0) - hbox.pack_start(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL), 1, 1, 0) + hbox.pack_start(Gtk.Separator(orientation=Gtk.Orientation.VERTICAL), 0, 1, 0) hbox.pack_start(self.grid, 1, 1, 0) # Add Univie Button univie_box = self.create_univie_box() - hbox.pack_start(univie_box, 1, 1, 0) + hbox.pack_start(univie_box, 0, 1, 0) hbox.set_spacing(20) # Is now done in the __name__ == __main__ part #if pool_name is not None: # self.set_pool(pool_name) - #self.update_parameter_view(interval=interval, max_age=max_age) + #self.update_parameter_view(interval=interval, max_age=max_age) self.connect('destroy', self.destroy_monitor) @@ -111,7 +116,6 @@ class ParameterMonitor(Gtk.Window): except: return - def set_pool(self, pool_name): self.pool_name = pool_name self.set_title("Parameter Monitor (Pool: {:})".format(pool_name)) @@ -176,16 +180,16 @@ class ParameterMonitor(Gtk.Window): # Popover creates the popup menu over the button and lets one use multiple buttons for the same one self.popover = Gtk.Popover() # Add the different Starting Options - vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) for name in self.cfg['ccs-dbus_names']: start_button = Gtk.Button.new_with_label("Start " + name.capitalize() + ' ') start_button.connect("clicked", cfl.on_open_univie_clicked) - vbox.pack_start(start_button, False, True, 10) + vbox.pack_start(start_button, False, True, 0) # Add the manage connections option conn_button = Gtk.Button.new_with_label('Communication') conn_button.connect("clicked", self.on_communication_dialog) - vbox.pack_start(conn_button, False, True, 10) + vbox.pack_start(conn_button, False, True, 0) # Add the option to see the Credits about_button = Gtk.Button.new_with_label('About') @@ -238,6 +242,8 @@ class ParameterMonitor(Gtk.Window): for ncol, col in enumerate(parameters): for nrow, parameter in enumerate(col): box = Gtk.HBox() + parameter, *pktid = eval(parameter) + box.pktid = tuple(pktid) dbres = dbcon.execute('SELECT pcf.pcf_name,pcf.pcf_descr,pcf.pcf_categ,pcf.pcf_unit,ocf.ocf_nbool,\ ocp.ocp_lvalu,ocp.ocp_hvalu from pcf left join ocf on pcf.pcf_name=ocf.ocf_name\ left join ocp on ocf_name=ocp_name where pcf.pcf_name="{}"'.format(parameter)) @@ -255,7 +261,6 @@ class ParameterMonitor(Gtk.Window): except IndexError: self.logger.error('Parameter {} does not exist - cannot add!'.format(parameter)) continue - # override with user defined limits if self.pdescr.get(parameter) in self.user_limits: try: @@ -299,11 +304,14 @@ class ParameterMonitor(Gtk.Window): for parameter in parameter_grid: self.parameters[parameter.param_id] = {'field': parameter.get_children()[1], 'value': None, - 'format': parameter.format, 'alarm': None} + 'format': parameter.format, 'alarm': None, 'pktid': parameter.pktid} + + self.monitored_pkts = {self.parameters[k]['pktid']: {'pkttime': 0, 'reftime': time.time(), 'data': None} for k in self.parameters} + self.grid.show_all() return - def update_parameter_view(self, interval=1.0, max_age=20.): + def update_parameter_view(self, interval=INTERVAL, max_age=MAX_AGE): self.interval = interval self.max_age = max_age self.updating = True @@ -313,45 +321,60 @@ class ParameterMonitor(Gtk.Window): thread.start() def update_parameters(self): + while not self.parameters: + time.sleep(self.interval) + while self.updating: + start = time.time() self.update_parameters_worker() - # GLib.idle_add() - time.sleep(self.interval) - return + dt = time.time() - start + # print(dt) + time.sleep(self.interval - min(self.interval, dt)) def update_parameters_worker(self): - # tmlist = self.poolmgr.datapool[self.pool_name]['pckts'].values() rows = cfl.get_pool_rows(self.pool_name) - reftime = cfl.get_last_pckt_time(self.pool_name, False) - self.check_evts(rows) + if self.evt_check_enabled: + ctime = time.time() + self.check_evts(rows) + cdt = time.time() - ctime + # disable check_evts if it causes too much delay + if cdt > 0.7 * self.interval: + self.evt_check_tocnt += 1 + if self.evt_check_tocnt > 5: + self.disable_evt_cnt() + + for pktid in self.monitored_pkts: + pktinfo = self.get_last_pkt_with_id(rows, pktid) + + if pktinfo is None: + continue + + pkttime, pkt = pktinfo + if pkttime != self.monitored_pkts[pktid]['pkttime']: + self.monitored_pkts[pktid]['reftime'] = time.time() + self.monitored_pkts[pktid]['pkttime'] = pkttime + + tm = cfl.Tmdata(pkt)[0] + self.monitored_pkts[pktid]['data'] = {par[4][1][0]: par[0] for par in tm} + + checktime = time.time() for pname in self.parameters: - # pname = parameter.param_id - # value_field = parameter.get_children()[1] - # value_field = self.parameters[pname]['field'] - value = cfl.get_param_values(rows, None, pname, last=1)[0] - if len(value) == 0: - # buf = self.parameters[pname]['field'].get_buffer() + pktid = self.parameters[pname]['pktid'] + if self.monitored_pkts[pktid]['data'] is None: self.parameters[pname]['value'] = None self.parameters[pname]['alarm'] = "blue" - - # def updt_buf(): - # buf.delete(*buf.get_bounds()) - # buf.insert_markup(buf.get_start_iter(), - # '<span size="large" foreground="{}" weight="bold">{}</span>'.format( - # 'blue', '--'), -1) - - # GLib.idle_add(updt_buf) continue else: try: - [value_time], [value] = value - except ValueError: - value_time, value = value[0] + value = self.monitored_pkts[pktid]['data'][pname] + except Exception as err: + self.logger.warning('Could not update value of {} [{}]!'.format(pname, str(err))) + continue - if reftime - value_time > self.max_age: + if checktime - self.monitored_pkts[pktid]['reftime'] > self.max_age: limit_color = 'grey' else: if self.pdescr.get(pname) in self.user_limits: @@ -360,8 +383,6 @@ class ParameterMonitor(Gtk.Window): user_limit = None limit_color = self.limit_colors[cfl.Tm_limits_check(pname, value, user_limit)] - # buf = self.parameters[pname]['field'].get_buffer() - self.parameters[pname]['value'] = value self.parameters[pname]['alarm'] = limit_color @@ -381,24 +402,38 @@ class ParameterMonitor(Gtk.Window): GLib.idle_add(updt_buf) - def updt_bg_color(): - alarms = [self.parameters[x]['alarm'] for x in self.parameters.keys()] - if alarms.count('red'): - self.override_background_color(Gtk.StateType.NORMAL, self.alarm_colors['red']) - if not self.presented: - self.present() - self.presented = True - elif alarms.count('orange'): - self.override_background_color(Gtk.StateType.NORMAL, self.alarm_colors['orange']) - if not self.presented: - self.present() - self.presented = True - else: - self.override_background_color(Gtk.StateType.NORMAL, self.alarm_colors['green']) - self.presented = False + # def updt_bg_color(): + # alarms = [self.parameters[x]['alarm'] for x in self.parameters.keys()] + # if alarms.count('red'): + # self.override_background_color(Gtk.StateType.NORMAL, self.alarm_colors['red']) + # if not self.presented: + # self.present() + # self.presented = True + # elif alarms.count('orange'): + # self.override_background_color(Gtk.StateType.NORMAL, self.alarm_colors['orange']) + # if not self.presented: + # self.present() + # self.presented = True + # else: + # self.override_background_color(Gtk.StateType.NORMAL, self.alarm_colors['green']) + # self.presented = False # GLib.idle_add(updt_bg_color) - return + # return + + def get_last_pkt_with_id(self, rows, pktid): + spid, st, sst, apid, pi1, pi1off, pi1wid = pktid + if pi1off != -1: + rows = rows.filter(DbTelemetry.stc == st, DbTelemetry.sst == sst, DbTelemetry.apid == apid, + func.mid(DbTelemetry.data, pi1off - cfl.TM_HEADER_LEN + 1, pi1wid // 8) == pi1.to_bytes( + pi1wid // 8, 'big')).order_by(DbTelemetry.idx.desc()).first() + else: + rows = rows.filter(DbTelemetry.stc == st, DbTelemetry.sst == sst, DbTelemetry.apid == apid).order_by(DbTelemetry.idx.desc()).first() + + if rows is None: + return + + return float(rows.timestamp[:-1]), rows.raw def pckt_counter(self, rows, st, sst): npckts = rows.filter(DbTelemetry.stc == st, DbTelemetry.sst == sst).count() @@ -421,7 +456,6 @@ class ParameterMonitor(Gtk.Window): GLib.idle_add(updt_buf, buf, evt) - def updt_bg_color(): if self.events['Error HIGH'][1] > self.evt_reset_values['Error HIGH']: self.override_background_color(Gtk.StateType.NORMAL, self.alarm_colors['red']) @@ -436,14 +470,30 @@ class ParameterMonitor(Gtk.Window): for panel in panels: buf = panel[1].get_buffer() value = int(buf.get_text(*buf.get_bounds(), True)) - # GLib.idle_add(buf.insert_markup, buf.get_start_iter(), - # '<span size="large" foreground="black" weight="bold">{}</span>'.format(value), -1) # reset alarm treshold for evts self.evt_reset_values[panel[0].get_text()] = value - # GLib.idle_add(self.override_background_color, Gtk.StateType.NORMAL, self.alarm_colors['green']) return - def add_evt_cnt(self, widget = None): + def disable_evt_cnt(self, widget=None): + self.evt_check_enabled = False + + def updt_buf(cbuf, cevt): + cbuf.delete(*cbuf.get_bounds()) + cbuf.insert_markup(cbuf.get_start_iter(), '<span size="large" foreground="{}" weight="bold">{}</span>'.format( + 'grey', self.events[cevt][1]), -1) + + for evt in self.evt_cnt.get_children()[:-2]: + evt.set_sensitive(False) + buf = evt.get_children()[1] + + GLib.idle_add(updt_buf, buf.get_buffer(), evt.get_children()[0].get_text()) + + rbutton = self.evt_cnt.get_children()[-2] + rbutton.set_label('Count EVTs') + rbutton.set_tooltip_text('Event counting has been disabled because of heavy load, probably because of a too large pool.\nClick to force count update.') + self.logger.warning('Counting events takes too long - disabling.') + + def add_evt_cnt(self, widget=None): self.monitor_setup() def set_update_interval(self, widget=None, interval=1.0): @@ -478,6 +528,7 @@ class ParameterMonitor(Gtk.Window): self.cfg.save_to_file() self.setup_grid(parameters) + # self.set_pool(self.pool_name) dialog.destroy() else: @@ -687,7 +738,7 @@ class MonitorSetupDialog(Gtk.Dialog): self.label.set_model(self.create_label_model()) self.label.connect('changed', self.check_label) - self.load_button = Gtk.Button('Load') + self.load_button = Gtk.Button(label='Load') self.load_button.set_tooltip_text('Load Parameter Set') self.load_button.connect('clicked', self.load_set) @@ -715,10 +766,10 @@ class MonitorSetupDialog(Gtk.Dialog): def create_param_view(self): - self.treeview = Gtk.TreeView(self.create_parameter_model()) + self.treeview = Gtk.TreeView(model=self.create_parameter_model()) self.treeview.append_column(Gtk.TreeViewColumn("Parameters", Gtk.CellRendererText(), text=0)) - hidden_column = Gtk.TreeViewColumn("PCF_NAME", Gtk.CellRendererText(), text=1) + hidden_column = Gtk.TreeViewColumn("ID", Gtk.CellRendererText(), text=1) hidden_column.set_visible(False) self.treeview.append_column(hidden_column) @@ -734,10 +785,10 @@ class MonitorSetupDialog(Gtk.Dialog): def create_slot(self, group=None): parameter_list = Gtk.ListStore(str, str) - treeview = Gtk.TreeView(parameter_list) + treeview = Gtk.TreeView(model=parameter_list) treeview.append_column(Gtk.TreeViewColumn("Parameters", Gtk.CellRendererText(), text=0)) - hidden_column = Gtk.TreeViewColumn("PCF_NAME", Gtk.CellRendererText(), text=1) + hidden_column = Gtk.TreeViewColumn("ID", Gtk.CellRendererText(), text=1) hidden_column.set_visible(False) treeview.append_column(hidden_column) treeview.set_headers_visible(False) @@ -745,9 +796,10 @@ class MonitorSetupDialog(Gtk.Dialog): # add parameters if modifying existing configuration if group is not None: for item in group: - descr, name = self.name_to_descr(item) + pname, *param_id = eval(item) + descr, name = self.name_to_descr(pname) if descr is not None: - parameter_list.append([descr, name]) + parameter_list.append([descr, item]) sw = Gtk.ScrolledWindow() sw.set_size_request(100, 200) @@ -783,16 +835,30 @@ class MonitorSetupDialog(Gtk.Dialog): parameter_model = Gtk.TreeStore(str, str) dbcon = self.session_factory_idb - #dbres = dbcon.execute('SELECT pid_descr,pid_spid from pid where pid_type=3 and pid_stype=25') - dbres = dbcon.execute('SELECT pid_descr,pid_spid from pid order by pid_type,pid_pi1_val') + dbres = dbcon.execute('SELECT pid_descr,pid_spid,pid_type from pid order by pid_type,pid_stype,pid_pi1_val') hks = dbres.fetchall() + + topleveliters = {} for hk in hks: - it = parameter_model.append(None, [hk[0], None]) - dbres = dbcon.execute('SELECT pcf.pcf_descr, pcf.pcf_name from pcf left join plf on\ - pcf.pcf_name=plf.plf_name left join pid on plf.plf_spid=pid.pid_spid where pid.pid_spid={}'.format(hk[1])) + + if not hk[2] in topleveliters: + serv = parameter_model.append(None, ['Service ' + str(hk[2]), None]) + topleveliters[hk[2]] = serv + + it = parameter_model.append(topleveliters[hk[2]], [hk[0], None]) + + dbres = dbcon.execute('SELECT pcf.pcf_descr, pcf.pcf_name, pid.pid_spid, pid.pid_type, pid.pid_stype, \ + pid.pid_apid, pid.pid_pi1_val, pic.pic_pi1_off, pic.pic_pi1_wid from pcf left join plf on\ + pcf.pcf_name=plf.plf_name left join pid on plf.plf_spid=pid.pid_spid left join pic\ + on pid.pid_type=pic.pic_type and pid.pid_stype=pic.pic_stype\ + and pid.pid_apid=pic.pic_apid where pid.pid_spid={}'.format(hk[1])) + params = dbres.fetchall() - [parameter_model.append(it, [par[0], par[1]]) for par in params] + + [parameter_model.append(it, [par[0], str(par[1:])]) for par in params] + dbcon.close() + self.useriter = parameter_model.append(None, ['User defined', None]) for userpar in self.monitor.cfg['ccs-plot_parameters']: parameter_model.append(self.useriter, [userpar, None]) @@ -806,8 +872,11 @@ class MonitorSetupDialog(Gtk.Dialog): return param = par_model[par_iter] + + if param[1] is None: + return + listmodel.append([*param]) - return def remove_parameter(self, widget, listview): model, modeliter = listview.get_selection().get_selected() @@ -816,49 +885,42 @@ class MonitorSetupDialog(Gtk.Dialog): return model.remove(modeliter) - return def check_label(self, widget): if widget.get_active_text(): - self.ok_button.set_sensitive(True) + self.ok_button.set_sensitive(True) else: - self.ok_button.set_sensitive(False) - #if widget.get_text_length(): - # self.ok_button.set_sensitive(True) - #else: - # self.ok_button.set_sensitive(False) + self.ok_button.set_sensitive(False) def load_set(self, widget): entry = self.label.get_active_text() if entry in self.monitor.cfg['ccs-monitor_parameter_sets'].keys(): for slot in self.slots: slot[3].clear() - #param_set = self.monitor.cfg['ccs-monitor_parameter_sets', entry] + param_set = json.loads(self.monitor.cfg['ccs-monitor_parameter_sets'][entry]) dbcon = self.session_factory_idb i = -1 for slots in param_set: - i +=1 - if len(slots) > 1: - dbres = dbcon.execute('SELECT pcf.pcf_descr, pcf.pcf_name FROM pcf where pcf.pcf_name in {}'.format(tuple(slots))) + i += 1 + pnames = {eval(par)[0]: par for par in slots} + if len(pnames) > 1: + dbres = dbcon.execute('SELECT pcf.pcf_descr, pcf.pcf_name FROM pcf where pcf.pcf_name in {}'.format(tuple(pnames))) params = dbres.fetchall() - elif len(slots) == 1: - dbres = dbcon.execute('SELECT pcf.pcf_descr, pcf.pcf_name FROM pcf where pcf.pcf_name ="{}"'.format(slots[0])) + elif len(pnames) == 1: + dbres = dbcon.execute('SELECT pcf.pcf_descr, pcf.pcf_name FROM pcf where pcf.pcf_name="{}"'.format(pnames[0])) params = dbres.fetchall() else: continue for par in params: - self.slots[i][3].append([*par]) + self.slots[i][3].append([par[0], pnames[par[1]]]) dbcon.close() else: - print('Inserted Set Name could not be found') - self.logger.info('Given Set name could not be found in Config File') - - + self.logger.info('Given Set name could not be found in config File') if __name__ == "__main__": @@ -872,6 +934,7 @@ if __name__ == "__main__": cfg = confignator.get_config(file_path=confignator.get_option('config-files', 'ccs')) win = ParameterMonitor() + # Important to tell Dbus that Gtk loop can be used before the first dbus command DBusGMainLoop(set_as_default=True) Bus_Name = cfg.get('ccs-dbus_names', 'monitor') @@ -887,9 +950,9 @@ if __name__ == "__main__": elif len(sys.argv) == 3: win.pool_name = sys.argv[1] - win.set_pool(sys.argv[1]) win.parameter_set = sys.argv[2] win.set_parameter_view(sys.argv[2]) + win.set_pool(sys.argv[1]) else: win.check_for_pools()