added timing
This commit is contained in:
@@ -27,11 +27,10 @@ from python_app.gui.controllers.app_window_ui_mixin import AppWindowUiMixin
|
||||
from python_app.gui.preprocess_dialog import PreprocessDialog
|
||||
from python_app.models.dataset_model import ResultCollection, SweepCollection
|
||||
from python_app.models.gui_profile_model import GuiProfileModel
|
||||
from python_app.models.run_config_model import RunConfigModel
|
||||
from python_app.orchestration.config_writer import ConfigWriter
|
||||
from python_app.orchestration.gui_session_state import GuiSessionState, GuiSessionStateStore
|
||||
from python_app.orchestration.live_processing_config import ProcessingLiveConfigWriter
|
||||
from python_app.orchestration.locator_runtime import LocatorTcpService
|
||||
from python_app.orchestration.pipeline_metrics import PipelineMetrics
|
||||
from python_app.orchestration.preprocess_assets import VISIBLE_PREPROCESS_ASSET_KEYS, preprocess_asset_model
|
||||
from python_app.orchestration.process_supervisor import ProcessSupervisor
|
||||
from python_app.orchestration.shm_reader import ShmRingReader
|
||||
@@ -83,7 +82,22 @@ class AppWindow(
|
||||
self._supervisor = ProcessSupervisor(self._project_root)
|
||||
self._live_config_writer = ProcessingLiveConfigWriter(runtime_dir / "processing_live.json")
|
||||
self._gui_session_state_store = GuiSessionStateStore(runtime_dir / "gui_session_state.json")
|
||||
self._locator_service: LocatorTcpService | None = None
|
||||
# `log_sink` is attached after the runtime log widget exists.
|
||||
self._pipeline_metrics = PipelineMetrics(
|
||||
report_every=self._resolve_metrics_report_every()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _resolve_metrics_report_every() -> int:
|
||||
"""Read the metrics flush threshold from env, falling back to 50."""
|
||||
raw = os.environ.get("RADAR_SYSTEM_METRICS_REPORT_EVERY", "").strip()
|
||||
if not raw:
|
||||
return 50
|
||||
try:
|
||||
value = int(raw)
|
||||
except ValueError:
|
||||
return 50
|
||||
return value if value >= 1 else 50
|
||||
|
||||
def _init_config_profile_state(self) -> None:
|
||||
"""Resolve startup profile path, load active profile, and queue fallback notices."""
|
||||
@@ -112,7 +126,6 @@ class AppWindow(
|
||||
"INFO",
|
||||
f"Loaded legacy run config without GUI defaults: {active_profile_path}",
|
||||
)
|
||||
self._locator_service = self._build_locator_service(self._defaults_config)
|
||||
self._remember_active_profile_path(active_profile_path, startup=True)
|
||||
|
||||
def _init_reader_handles(self) -> None:
|
||||
@@ -217,60 +230,13 @@ class AppWindow(
|
||||
self._log(f"Active config profile: {self._active_profile_path}")
|
||||
self._refresh_preprocess_summary_labels()
|
||||
self._apply_initial_radar_limits()
|
||||
self._start_locator_service()
|
||||
self._on_processing_mode_changed(self._processing_mode.currentText())
|
||||
self._write_live_processing_config()
|
||||
# Defer the log sink wiring until the runtime log widget exists.
|
||||
self._pipeline_metrics.set_log_sink(self._log)
|
||||
self._timer.start()
|
||||
self._maybe_auto_start_pipeline()
|
||||
|
||||
def _start_locator_service(self) -> None:
|
||||
"""Start embedded locator TCP service without failing the GUI."""
|
||||
try:
|
||||
if self._locator_service is None:
|
||||
self._locator_service = self._build_locator_service(self._defaults_config)
|
||||
self._locator_service.start()
|
||||
self._locator_service.publish_empty()
|
||||
self._log(
|
||||
f"Locator TCP server listening on "
|
||||
f"{self._locator_service.host}:{self._locator_service.port}"
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._log_exception("Failed to start locator TCP server", exc, level="WARN")
|
||||
|
||||
def _build_locator_service(self, config: RunConfigModel) -> LocatorTcpService:
|
||||
"""Create locator service instance from stable run config."""
|
||||
locator_server = config.runtime.locator_server
|
||||
return LocatorTcpService(
|
||||
host=str(locator_server.host),
|
||||
port=int(locator_server.port),
|
||||
device_id=int(locator_server.device_id),
|
||||
protocol_version=int(locator_server.protocol_version),
|
||||
max_payload_bytes=int(locator_server.max_payload_bytes),
|
||||
client_queue_size=int(locator_server.client_queue_size),
|
||||
logger_name=str(locator_server.logger_name),
|
||||
)
|
||||
|
||||
def _reload_locator_service_from_config(self) -> None:
|
||||
"""Rebuild locator service using current stable config and restart if needed."""
|
||||
previous_service = self._locator_service
|
||||
was_running = previous_service is not None and previous_service.is_running()
|
||||
if previous_service is not None:
|
||||
previous_service.stop()
|
||||
|
||||
self._locator_service = self._build_locator_service(self._defaults_config)
|
||||
if not was_running:
|
||||
return
|
||||
|
||||
try:
|
||||
self._locator_service.start()
|
||||
self._locator_service.publish_empty()
|
||||
self._log(
|
||||
"Locator TCP server reloaded from config: "
|
||||
f"{self._locator_service.host}:{self._locator_service.port}"
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._log_exception("Failed to reload locator TCP server from config", exc, level="WARN")
|
||||
|
||||
def _resolve_startup_profile_path(self) -> Path:
|
||||
"""Resolve active profile path from session-state or root fallback path."""
|
||||
env_profile_path = os.environ.get("RADAR_SYSTEM_PROFILE", "").strip()
|
||||
@@ -300,12 +266,36 @@ class AppWindow(
|
||||
return profile_path
|
||||
|
||||
def _maybe_auto_start_pipeline(self) -> None:
|
||||
"""Schedule pipeline start when requested by launcher environment."""
|
||||
auto_start = os.environ.get("RADAR_SYSTEM_AUTO_START", "").strip().lower()
|
||||
if auto_start not in {"1", "true", "yes", "on"}:
|
||||
"""Schedule pipeline start when requested by launcher environment.
|
||||
|
||||
With `RADAR_SYSTEM_AUTO_APPLY_RADAR=1` the launcher also reproduces the
|
||||
"Apply Radar" click before "Start". This is the headless deployment
|
||||
recipe: the GUI configures the device exactly as a human operator
|
||||
would, then starts the capture pipeline.
|
||||
"""
|
||||
auto_start = self._is_truthy_env("RADAR_SYSTEM_AUTO_START")
|
||||
if not auto_start:
|
||||
return
|
||||
self._log("Auto-start requested by launcher.")
|
||||
QTimer.singleShot(500, self._start_run)
|
||||
if self._is_truthy_env("RADAR_SYSTEM_AUTO_APPLY_RADAR"):
|
||||
QTimer.singleShot(500, self._auto_apply_radar_then_start)
|
||||
else:
|
||||
QTimer.singleShot(500, self._start_run)
|
||||
|
||||
def _auto_apply_radar_then_start(self) -> None:
|
||||
"""Apply current radar settings then start the pipeline (headless boot)."""
|
||||
try:
|
||||
self._apply_radar_settings()
|
||||
except Exception as exc: # noqa: BLE001
|
||||
self._log_exception("Auto apply-radar failed", exc, level="WARN")
|
||||
# Hand control back to the event loop so widget updates from
|
||||
# _apply_radar_settings can flush before _start_run takes over.
|
||||
QTimer.singleShot(100, self._start_run)
|
||||
|
||||
@staticmethod
|
||||
def _is_truthy_env(name: str) -> bool:
|
||||
"""Return True when an environment variable is set to a truthy literal."""
|
||||
return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
|
||||
|
||||
def _normalize_profile_path(self, path: Path) -> Path:
|
||||
"""Return normalized absolute profile path."""
|
||||
@@ -494,6 +484,8 @@ class AppWindow(
|
||||
def _show_error(self, message: str, *, details: str | None = None) -> None:
|
||||
"""Log and present an error in a modal dialog with optional detail text."""
|
||||
self._log_error(message, details=details)
|
||||
if self._is_truthy_env("RADAR_SYSTEM_HEADLESS"):
|
||||
return
|
||||
dialog = QMessageBox(self)
|
||||
dialog.setIcon(QMessageBox.Icon.Critical)
|
||||
dialog.setWindowTitle("Error")
|
||||
@@ -505,6 +497,8 @@ class AppWindow(
|
||||
def _show_exception(self, context: str, exc: Exception) -> None:
|
||||
"""Log full exception details and show modal dialog with expandable traceback."""
|
||||
message, details = self._log_exception(context, exc, level="ERROR")
|
||||
if self._is_truthy_env("RADAR_SYSTEM_HEADLESS"):
|
||||
return
|
||||
dialog = QMessageBox(self)
|
||||
dialog.setIcon(QMessageBox.Icon.Critical)
|
||||
dialog.setWindowTitle("Error")
|
||||
@@ -520,9 +514,6 @@ class AppWindow(
|
||||
self._abort_capture_sequence(resume_pipeline=False)
|
||||
# 2) Stop all managed processes/readers.
|
||||
self._stop_all_processes()
|
||||
# 3) Stop embedded locator service.
|
||||
if self._locator_service is not None:
|
||||
self._locator_service.stop()
|
||||
# 3) Close auxiliary dialog windows.
|
||||
if self._preprocess_dialog is not None:
|
||||
self._preprocess_dialog.close()
|
||||
|
||||
Reference in New Issue
Block a user