diff --git a/docker/jupyter-lab/requirements.txt b/docker/jupyter-lab/requirements.txt index e052016d4347c1e7115d61121a461919da6a6bd3..c5c2a40c26443ebb2f29cc2d5c1e8d153d743b39 100644 --- a/docker/jupyter-lab/requirements.txt +++ b/docker/jupyter-lab/requirements.txt @@ -30,6 +30,7 @@ matplotlib jupyterplot # useful LOFAR software +lofar-lotus # Apache2 pabeam@git+https://git.astron.nl/mevius/pabeam # Apache2 # user packages diff --git a/integration_tests/default/devices/test_observation_client.py b/integration_tests/default/devices/test_observation_client.py index 00b584a8dda9c7c0bbe5fd047c03a680393cd880..bb918f191a76096ebb40da57d74bfc02d367e389 100644 --- a/integration_tests/default/devices/test_observation_client.py +++ b/integration_tests/default/devices/test_observation_client.py @@ -82,4 +82,5 @@ class TestObservation(base.IntegrationTestCase): with grpc.insecure_channel("rpc.service.consul:50051") as channel: stub = ObservationStub(channel) stub.StopObservation(StopObservationRequest(observation_id=obs_id)) + self.assertFalse(obs.is_observation_running(obs_id)) diff --git a/tangostationcontrol/metrics/_metrics.py b/tangostationcontrol/metrics/_metrics.py index 7cfd27703429edb4f62dfa8395246ea82ee63990..52f0628cc8e2dde6b66ee14763780c0187d49f01 100644 --- a/tangostationcontrol/metrics/_metrics.py +++ b/tangostationcontrol/metrics/_metrics.py @@ -4,13 +4,15 @@ from tango import CmdArgType from tango import Attribute from tango import DevFailed from tango import DevState -from prometheus_client import Metric, Gauge, Info, Enum +from prometheus_client import Gauge, Info, Enum from asyncio import iscoroutinefunction from enum import IntEnum from typing import List, Dict, Callable, Union import functools import logging +from lofar_lotus.metrics import Metric + __all__ = [ "wrap_method", "device_labels", @@ -105,7 +107,7 @@ def metric_name(attribute_name: str) -> str: return f"ds_{metric_name}".lower() -class AttributeMetric: +class AttributeMetric(Metric): """Manage a Prometheus Metric object for Tango devices.""" def __init__( @@ -117,90 +119,19 @@ class AttributeMetric: metric_class_init_kwargs: Dict[str, object] | None = None, dynamic_labels: List[str] | None = None, ): - self.name = metric_name(name) - self.description = description - self.metric_class = metric_class - - self.static_label_keys = list(static_labels.keys()) - self.static_label_values = list(static_labels.values()) - - self.dynamic_label_keys = dynamic_labels or [] - - self.metric_class_init_kwargs = metric_class_init_kwargs or {} - - if self.name not in METRICS: - METRICS[self.name] = self.make_metric() - - self.metric = METRICS[self.name] - assert self.metric.__class__ == metric_class, ( - f"Metric {self.name} was previously provided as {self.metric.__class__} but is now needed as {metric_class}" - ) - - def __str__(self): - return f"{self.__class__.__name__}(name={self.name}, metric_class={self.metric_class}, static_labels={self.static_label_keys}, dynamic_labels={self.dynamic_label_keys})" - - def clear(self): - """Remove all cached metrics.""" - - self.metric.clear() - - def label_keys(self) -> List[str]: - """Return the list of labels that we will use.""" - - return self.static_label_keys + self.dynamic_label_keys - - def make_metric(self) -> Metric: - """Construct a metric that collects samples for this attribute.""" - return self.metric_class( - self.name, - self.description, - labelnames=self.label_keys(), - **self.metric_class_init_kwargs, + super().__init__( + metric_name(name), + description, + static_labels, + metric_class, + metric_class_init_kwargs, + dynamic_labels, ) - def get_metric(self, dynamic_label_values: List = None) -> Metric: - """Return the metric that uses the default labels.""" - return self.metric.labels( - *self.static_label_values, *(dynamic_label_values or []) - ) - - def set_value(self, value: object): - """A new value for the attribute is known. Feed it to the metric.""" - - # set it, this class will take care of the default labels - self._set_value(value, self.static_label_values) - - def _set_value(self, value: object, labels: List[str]): - if self.metric_class == Enum: - self._metric_enum_value(value, labels) - elif self.metric_class == Info: - self._metric_info_value(value, labels) - else: - self._metric_set_value(value, labels) - - def _metric_set_value(self, value: object, labels: List[str]): - if value is None: - raise ValueError(f"Invalid value for metric: {value}") - - self.metric.labels(*labels).set(value) - - def _metric_info_value(self, value: Dict[str, str], labels: List[str]): - if value is None or None in value.values(): - raise ValueError(f"Invalid value for metric: {value}") - - self.metric.labels(*labels).info(value) - - def _metric_enum_value(self, value: str | IntEnum, labels: List[str]): - if value is None: - raise ValueError(f"Invalid value for metric: {value}") - - self.metric.labels(*labels).state( - value.name if isinstance(value, (DevState, IntEnum)) else value - ) + def _metric_enum_value(self, value: str | IntEnum | DevState, labels: List[str]): + value = value.name if isinstance(value, DevState) else value - def collect(self) -> List[Metric]: - """Return all collected samples.""" - return self.metric.collect() + super()._metric_enum_value(value, labels) class ScalarAttributeMetric(AttributeMetric):