from motor.common.alarm.record import Record
from motor.common.logger import get_logger
from motor.common.utils.singleton import ThreadSafeSingleton
from motor.config.controller import ControllerConfig
from motor.controller.observability.alarm.alarm_store import AlarmStore
from motor.controller.api_client.coordinator_api_client import CoordinatorApiClient
logger = get_logger(__name__)
class Observability(ThreadSafeSingleton):
"""
Observability module for Motor Controller: manages alarms.
Metrics are served by the Coordinator; this module provides an internal
get_metrics() for Controller components that need Coordinator metrics data.
"""
def __init__(self, config: ControllerConfig | None = None) -> None:
super().__init__()
if hasattr(self, "_initialized"):
return
self._initialized = True
if config is None:
config = ControllerConfig()
self.config = config
self.alarm_store = AlarmStore()
def start(self) -> None:
logger.info("Starting observability...")
def stop(self) -> None:
logger.info("Stopping observability...")
def add_alarm(self, record: Record) -> bool:
return self.add_alarms([record])
def add_alarms(self, records: list[Record]) -> bool:
try:
is_succeed = self.alarm_store.add_alarms(records)
if is_succeed:
logger.debug("Alarms added successfully via observability, count=%s", len(records))
else:
logger.warning("Alarms add failed via observability, count=%s", len(records))
return is_succeed
except Exception as e:
logger.error("Failed to add alarms via observability: %s", e)
return False
def get_alarms(self, source_id: str = None) -> list[list[dict]]:
try:
return self.alarm_store.get_alarms(source_id)
except Exception as e:
logger.error("Failed to get alarms via observability: %s", e)
return []
def get_metrics(self, metrics_type: str = "full", role: str | None = None) -> str:
"""
Get metrics from Coordinator (internal API, not exposed via HTTP).
Controller components that need metrics data should call this method.
:param metrics_type: "full" (default), "instance", or "role"
:param role: when metrics_type is "role", filter to a specific role
:returns: Prometheus text
"""
try:
result = CoordinatorApiClient.get_metrics(metrics_type=metrics_type, role=role)
if result is None:
return ""
return result
except Exception as e:
logger.error("Failed to get metrics via observability: %s", e)
return ""