From e213186295a1abe629d9542dadbd3118f8e0dd33 Mon Sep 17 00:00:00 2001 From: Markus Zolliker Date: Mon, 13 May 2024 08:41:03 +0200 Subject: [PATCH] nicer output of server state - more outstanding output of frappy server state - frappy() does now alse an update if needed - frappy.show() to show state only --- commands.py | 2 +- devices.py | 277 ++++++++++++++++++++++++++--------------------- setups/frappy.py | 20 ++-- 3 files changed, 165 insertions(+), 134 deletions(-) diff --git a/commands.py b/commands.py index b635278..14bf4bb 100644 --- a/commands.py +++ b/commands.py @@ -33,7 +33,7 @@ SERVICES = FrappyManager.services def set_se_list(): fc = get_frappy_config() if fc: - fc.set_envlist() + fc.set_envalias() @usercommand diff --git a/devices.py b/devices.py index 03cb569..b87265c 100644 --- a/devices.py +++ b/devices.py @@ -138,8 +138,7 @@ class FrappyConfig(Device): meanings = [n for n, p in parameters.items() if p.type is anytype] _update_setup = 'on_frappy' - _previous_shown = None - _previous_state = None + _back_to_normal = None _initial_info = None _shutdown_event = None _restarting = False @@ -163,23 +162,27 @@ class FrappyConfig(Device): def detect_changes(self): before_check = before_change = prev_shown = None cnt = 0 - normal_state = None + self._back_to_normal = None while not self._shutdown_event.wait(1): - busy = bool(session.experiment.scripts or session.daemon_device._controller.queue.scripts) + busy = session.daemon_device._controller.status >= 0 if busy and cnt < 10: # check only every 10 sec when busy cnt += 1 continue - if not busy and normal_state == 'back_to_normal': - session.log.warning(all_info(self._current_cfgs, 'servers match configuration: ')) - normal_state = None + if not busy and self._back_to_normal: + session.log.info(' %s', 75*'_') + session.log.info(' ') + session.log.info(all_info(self._current_cfgs, ' servers match configuration: ')) + session.log.info(' %s', 75*'_') + self._back_to_normal = None cnt = 0 - need_change, changes, fm = self.to_consider() + need_change, changes, fm = to_consider = self.to_consider() if fm.state == before_change: continue if not need_change: - if normal_state == 'changed': - normal_state = 'back_to_normal' + if self._back_to_normal == 0: + # we had a change, but are back to normal state + self._back_to_normal = True continue if fm.state != before_check: # must be twice the same @@ -187,37 +190,10 @@ class FrappyConfig(Device): continue if busy or fm.state == prev_shown: continue - normal_state = 'changed' - self.show_changes(changes) + self._back_to_normal = 0 # indicates that we had a change + self.show_state(True, to_consider) prev_shown = fm.state - def show_changes(self, changes): - if changes is None: - need_change, changes, fm = self.to_consider() - session.log.info(' ') - session.log.warning('sample environment configuration should be changed:') - session.log.info(' ') - kwargs = {} - for service, cfg in changes.items(): - kwargs[service] = str(cfg) - prev = self._target_cfgs.get(service) - if cfg: - if isinstance(cfg, Keep): - kwargs.pop(service, None) - session.log.info('%s: keep %s', service, cfg) - else: - if prev: - session.log.info('%s: change from %s to %s', service, prev, cfg) - else: - session.log.info('%s: start as %s', service, cfg) - elif cfg == '': - session.log.info('%s: remove %s', service, prev) - if self._rebuild_env: - session.log.info('%s', self._rebuild_env) - alternative = f" or {all_info(kwargs, '')}" if kwargs else '' - session.log.info(' ') - session.log.info('use frappy.update()%s to activate', alternative) - def to_consider(self, cfgs=None): """return info about a proposed changes @@ -263,7 +239,7 @@ class FrappyConfig(Device): if cfg and not isinstance(cfg, Keep): need_change = cfg if not need_change and all(isinstance(v, Keep) for v in changes.values()): - self.set_envlist(checkonly=True) + self._rebuild_env = self.check_envalias() if self._rebuild_env: need_change = True else: @@ -352,14 +328,17 @@ class FrappyConfig(Device): for service, secnode in secnodes.items(): if services.get(service): secnode._secnode.connect() - self.set_envlist() + self.set_envalias() for secnode in remove_cfg: secnode.disable() finally: self._restarting = False return all_cfg - def __call__(self, *args, main=None, stick=None, addons=None, force=False): + def show(self): + return self(update=False) + + def __call__(self, *args, main=None, stick=None, addons=None, force=False, update=True): """(re)start frappy server(s) with given configs and load setup if needed - without argument: list running frappy servers, restart failed frappy servers @@ -370,6 +349,7 @@ class FrappyConfig(Device): - frappy('restart') # restart all frappy servers - frappy('reconnect') # reconnect to running frappy servers """ + self._back_to_normal = None # reset 'back to normal' machanism stickarg = stick _, changes, fm = to_consider = self.to_consider() @@ -404,43 +384,62 @@ class FrappyConfig(Device): raise TypeError('got multiple values for addons') addons = ','.join(alist) elif main is None and stick is None and addons is None: # bare frappy() command - self.show_config(None, True, to_consider=to_consider) + if update: + self.update() + else: + self.show_state(False, to_consider) return if confirmed and confirmed != main and main not in (None, 'restart') and not force: session.log.warning('%r is plugged to the cryostat control rack', confirmed) cmd = all_info({'main': main, 'stick': stickarg, 'addons': addons}, '')[:-1] + ', force=True)' session.log.warning(f'if you are sure, use: %s', cmd) raise TypeError('refuse to override plugged device') - self.show_config(self.start_services(main, stick, addons)) + session.log.info(all_info(self.start_services(main, stick, addons))) - def show_config(self, allcfg, show_server_state=False, to_consider=None): - need_change, changes, fm = to_consider or self.to_consider(allcfg) - if show_server_state == 'auto': - show_server_state = fm.state != self._previous_shown and need_change - if show_server_state: - rows = [['server', 'frappy', 'sea', '']] - for key, remark in fm.remarks.items(): - rows.append([key if key in ('main', 'stick') else 'addons', - fm.frappy_cfgs.get(key, ''), fm.sea_cfgs.get(key, ''), remark]) + def show_state(self, changes_only=False, consider_result=None): + need_changes, changes, fm = consider_result or self.to_consider() + if need_changes: + session.log.warning('sample environment configuration should be changed:') + elif changes_only: + return + else: + session.log.info('status of sample environment services:') + session.log.info(' %s', 75*'_') + session.log.info(' ') + kwargs = {} + rows = [['service', 'sea', 'frappy', 'nicos', 'proposed'], [''] * 5] + for service in SERVICES: + cfg = changes.get(service) + if not (cfg is None or isinstance(cfg, Keep)): + kwargs[service] = str(cfg) + frappy_cfg = fm.frappy_cfgs.get(service, '-') + prev = self._target_cfgs.get(service, '-') + rows.append([service, fm.sea_cfgs.get(service, '-'), frappy_cfg, + prev, '-' if cfg is None else cfg]) + try: wid = [max(len(v) for v in column) for column in zip(*rows)] - # insert title underlines - rows.insert(1, ['-' * w for w in wid[:-1]] + ['']) - for row in rows: - session.log.info('%s', ' '.join(v.ljust(w) for w, v in zip(wid, row))) - session.log.info('') - self._previous_state = self._previous_shown = fm.state - session.log.info(all_info(self._current_cfgs)) - - if need_change: - self.show_changes(changes) + except Exception as e: + print(e) + raise + for row in rows: + if not kwargs: + row.pop() + session.log.info(' %s', ' '.join(v.ljust(w) for w, v in zip(wid, row))) + if self._rebuild_env: + session.log.info(' %s', self._rebuild_env) + alternative = f" or {all_info(kwargs, '')}" if kwargs else '' + session.log.info(' ') + session.log.info(' use frappy()%s to configure sample environment', alternative) + session.log.info(' %s', 75*'_') def update(self, main=None, stick=None, addons=None): + self._back_to_normal = None if main is None and stick is None and addons is None: changes = self.to_consider()[1] else: changes = {k: Keep(v) for k, v in zip(SERVICES, (main, stick, addons)) if v is not None} - self.show_config(self.start_services(**changes)) + session.log.info(all_info(self.start_services(**changes))) def get_init_info(self, service): """check whether a connect of this service is required""" @@ -457,9 +456,6 @@ class FrappyConfig(Device): cfgs[serv] = cfg self._initial_info = {s: (cfgs.get(s), running.get(s)) for s in fm.services} fm.get_server_state(config.instrument, cfgs) - if not fm.error: - # do not show server state on startup - self._previous_state = self._previous_shown = fm.state return self._initial_info[service] def remove_aliases(self): @@ -488,14 +484,11 @@ class FrappyConfig(Device): result[aliasname] = aliasdev return result - def set_envlist(self, checkonly=False): + def needed_envalias(self): """create aliases and envlist for SECoP devices depending on their meaning """ - previous_aliases = self.get_se_aliases() - # self.remove_aliases() - nodedevs = filter(None, [session.devices.get(devname) for devname in self.nodes]) sample_devices = {} for nodedev in nodedevs: @@ -512,8 +505,11 @@ class FrappyConfig(Device): meaning_name, importance = meaning sample_devices.setdefault(meaning_name, []).append((importance, devname)) + previous_aliases = self.get_se_aliases() newenv = {} # to be added to envlist (dict [devname] of aliasname) - to_remove = set() # items to be removed from previous envlist, if present + to_remove = set() # items to be removed or replaced, if present + needed_aliases = {} + predef_aliases = [] for meaning in self.meanings: info = getattr(self, meaning) aliasnames = info.get('alias') @@ -529,8 +525,7 @@ class FrappyConfig(Device): if aliases: if len(aliases) > 1: raise TypeError(f'do know to which of {aliases} {meaning} to assign to') - alias_config = session.alias_config.setdefault(aliases[0], []) - alias_config.extend(list(aliascfg.items())) + predef_aliases.append((aliases[0], list(aliascfg.items()))) elif not aliasnames: session.log.warn("neither 'predefined_alias' nor 'alias' configured. skip %s", meaning) continue @@ -542,37 +537,11 @@ class FrappyConfig(Device): dev = session.devices.get(devname) if dev is None or info.get('drivable_only', False) and not isinstance(dev, Moveable): continue - if checkonly: - if aliasnames and devname not in newenv: - newenv[devname] = aliasnames[0] - for aliasname in aliasnames: - adev = previous_aliases.pop(aliasname, None) - if not adev: - self._rebuild_env = f'need alias {aliasname} for {devname}' - return - if adev.alias != devname: - self._rebuild_env = f'alias {aliasname} must point to {devname}' - return - continue + # determine aliases for aliasname in aliasnames: - devcfg = ('nicos.core.DeviceAlias', {}) - session.configured_devices[aliasname] = devcfg - session.dynamic_devices[aliasname] = 'frappy' # assign to frappy setup - aliasdev = previous_aliases.pop(aliasname, None) - if aliasdev: - if aliasdev.alias != devname: - session.log.debug('change alias %r -> %r', aliasname, devname) - else: - session.log.debug('create alias %r -> %r', aliasname, devname) - session.cache.put(aliasname, 'visibility', dev.visibility) - session.cache.put(aliasname, 'loglevel', dev.loglevel) - session.cache.put(aliasname, 'description', dev.description) - aliasdev = session.createDevice(aliasname, recreate=True, explicit=True) - aliasdev.alias = devname - # make sure device panel is updated - dev = aliasdev._obj - dev._cache.put(dev, 'status', dev.status()) - dev._cache.put(dev, 'value', dev.read()) + if aliasname not in needed_aliases: + needed_aliases[aliasname] = devname + # determine envlist if aliasnames: # only the first item of aliasnames is added to the envlist aliasname = aliasnames[0] @@ -586,32 +555,92 @@ class FrappyConfig(Device): else: to_remove.update(aliasnames) - if checkonly: - if previous_aliases: - self._rebuild_env = f'need to remove aliases {", ".join(previous_aliases)}' - return - else: - for aliasname in previous_aliases: - session.destroyDevice(aliasname) - session.configured_devices.pop(aliasname, None) - session.dynamic_devices.pop(aliasname, None) + # determine aliases to be changed + for aliasname, dev in previous_aliases.items(): + target = needed_aliases.get(aliasname) + if target: + if dev.alias == target: + needed_aliases.pop(aliasname) + else: + needed_aliases[aliasname] = None + + # build new env list + prevenv = session.experiment.envlist + addedenv = [v for v in newenv.values() if v not in to_remove] + to_remove = to_remove.difference(addedenv) + envlist = [k for k in prevenv if k not in to_remove] + addedenv + if set(envlist) == set(prevenv): + envlist = None + + predef_changes = [] + for aliasname, cfg in predef_aliases: + if cfg not in session.alias_config.get(aliasname, []): + predef_changes.append(cfg) + + return envlist, needed_aliases, predef_changes + + def check_envalias(self): + envlist, new_aliases, predef_aliases = self.needed_envalias() + if envlist: + return f"envlist should be {', '.join(envlist)}" + anew = [k for k, v in new_aliases.items() if v is not None] + removed = set(new_aliases).difference(anew) + anew.extend([k for k, _ in predef_aliases]) + if anew: + return f"aliases {', '.join(anew)} should change" + if removed: + return f"aliases {', '.join(anew)} should be removed" + return None + + def set_envalias(self): + """create aliases and envlist for SECoP devices + + depending on their meaning (and name) + """ + envlist, new_aliases, predef_aliases = self.needed_envalias() + if new_aliases or predef_aliases: + for aliasname, devname in new_aliases.items(): + if devname is None: + session.destroyDevice(aliasname) + session.configured_devices.pop(aliasname, None) + session.dynamic_devices.pop(aliasname, None) + else: + dev = session.devices.get(devname) + devcfg = ('nicos.core.DeviceAlias', {}) + session.configured_devices[aliasname] = devcfg + session.dynamic_devices[aliasname] = 'frappy' # assign to frappy setup + adev = session.devices.get(aliasname) + if adev: + session.log.debug('change alias %r -> %r', aliasname, devname) + else: + session.log.debug('create alias %r -> %r', aliasname, devname) + session.cache.put(aliasname, 'visibility', dev.visibility) + session.cache.put(aliasname, 'loglevel', dev.loglevel) + session.cache.put(aliasname, 'description', dev.description) + adev = session.createDevice(aliasname, recreate=True, explicit=True) + adev.alias = devname + # make sure device panel is updated + try: + session.cache.put(devname, 'status', dev.status()) + session.cache.put(devname, 'value', dev.read()) + except Exception: + pass + for aliasname, cfg in predef_aliases: + alias_config = session.alias_config.setdefault(aliasname, []) + if cfg not in alias_config: + alias_config.extend(cfg) applyAliasConfig() # for other aliases - envlist = [k for k in session.experiment.envlist if k not in to_remove] + list(newenv.values()) - if checkonly: - if set(envlist) != set(session.experiment.envlist): - self._rebuild_env = f'need to update envlist to {envlist}' - else: - self._rebuild_env = None - return - if envlist != session.experiment.envlist: - removed = set(session.experiment.envlist).difference(envlist) + if envlist is not None: + prev = session.experiment.envlist + removed = set(prev).difference(envlist) session.experiment.setEnvironment(envlist) if removed: session.log.info('removed %s from environment', ', '.join(removed)) - if newenv: - session.log.info('added %s to environment', ', '.join(newenv.values())) + added = set(envlist).difference(prev) + if added: + session.log.info('added %s to environment', ', '.join(added)) class FrappyNode(SecNodeDevice, Moveable): diff --git a/setups/frappy.py b/setups/frappy.py index a8345c8..37c85c7 100644 --- a/setups/frappy.py +++ b/setups/frappy.py @@ -51,15 +51,17 @@ devices = dict( ) startupcode = ''' -printinfo("=======================================================================================") -printinfo("Welcome to the NICOS frappy secnode setup!") +printinfo(" ___________________________________________________________________________________________") printinfo(" ") -printinfo("Usage:") -printinfo(" frappy('
') # change main SE configuration (e.g. cryostat)") -printinfo(" frappy('
', '') # change main and stick cfg") -printinfo(" frappy(stick='') # remove stick") -printinfo(" frappy('') # remove main SE apparatus") -printinfo(" frappy() # show the current SE configuration") -printinfo("=======================================================================================") +printinfo(" Welcome to the NICOS frappy secnode setup!") +printinfo(" ") +printinfo(" Usage:") +printinfo(" frappy('
') # change main SE configuration (e.g. cryostat)") +printinfo(" frappy('
', '') # change main and stick cfg") +printinfo(" frappy(stick='') # remove stick") +printinfo(" frappy('') # remove main SE apparatus") +printinfo(" frappy.read() # show the current SE configuration") +printinfo(" frappy() # show and update SE configuration form server state") +printinfo(" ___________________________________________________________________________________________") set_se_list() '''