Fixed issues with telemetry.

This commit is contained in:
2026-09-08 22:46:20 +02:00
parent 9b0abf6223
commit 1a120bbf86
7 changed files with 300 additions and 52 deletions

View File

@@ -34,6 +34,8 @@ DEFAULT_TELEMETRY_ENDPOINT = "https://pristontale.eu/api/launcher/telemetry"
TELEMETRY_ENDPOINT_VARIABLE = "PTLINUX_TELEMETRY_ENDPOINT"
TELEMETRY_KEY_VARIABLE = "PTLINUX_TELEMETRY_KEY"
TELEMETRY_DEBUG_VARIABLE = "PTLINUX_TELEMETRY_DEBUG"
TELEMETRY_PROCESS_ROLE_VARIABLE = "PTLINUX_TELEMETRY_PROCESS_ROLE"
TELEMETRY_OPERATION_PROCESS_ROLE = "operation"
MAX_REQUEST_BYTES = 64 * 1024
MAX_MESSAGE_BYTES = 10 * 1024
@@ -263,9 +265,14 @@ class TelemetryService:
self._session_metadata = dict(session_metadata or {})
self._monotonic = monotonic
self._started_at = monotonic()
self._reports_launcher_lifecycle = (
self._environment.get(TELEMETRY_PROCESS_ROLE_VARIABLE, "").strip().casefold()
!= TELEMETRY_OPERATION_PROCESS_ROLE
)
self._enabled = bool(enabled)
self._active = False
self._accepting = False
self._closing = False
self._ready = False
self._first_run = False
self._installation_id: str | None = None
@@ -322,6 +329,7 @@ class TelemetryService:
self._base_payload = self._collect_common_payload(first_seen)
self._stop.clear()
self._accepting = True
self._closing = False
self._active = True
worker = threading.Thread(
target=self._worker_loop,
@@ -338,15 +346,16 @@ class TelemetryService:
self._debug_log(f"initialization failed: {type(exc).__name__}")
return
self.send_event(
"launcher_started",
metadata={"cold_start": True, **self._session_metadata},
)
if self._first_run:
if self._reports_launcher_lifecycle:
self.send_event(
"first_run_started",
metadata={"install_type": self._install_type()},
"launcher_started",
metadata={"cold_start": True, **self._session_metadata},
)
if self._first_run:
self.send_event(
"first_run_started",
metadata={"install_type": self._install_type()},
)
self._start_environment_collection()
def set_enabled(self, enabled: bool) -> None:
@@ -368,6 +377,8 @@ class TelemetryService:
if self._ready or not self._active:
return
self._ready = True
if not self._reports_launcher_lifecycle:
return
duration_ms = round((self._monotonic() - self._started_at) * 1000)
self.send_event(
"launcher_ready",
@@ -674,11 +685,12 @@ class TelemetryService:
self._send_repeated_error(state)
def shutdown(self, timeout_seconds: float = SHUTDOWN_WAIT_SECONDS) -> None:
"""Queue the closing event and give the daemon worker a bounded flush."""
"""Finish this process role and give the daemon worker a bounded flush."""
with self._state_lock:
if not self._active:
if not self._active or self._closing:
return
self._closing = True
deadline = time.monotonic() + max(0.0, timeout_seconds)
environment_worker = self._environment_worker
if isinstance(environment_worker, threading.Thread) and (
@@ -692,17 +704,18 @@ class TelemetryService:
)
self._flush_repeated_errors()
runtime_seconds = max(0, round(self._monotonic() - self._started_at))
if self._first_run and not self._ready:
if self._reports_launcher_lifecycle:
if self._first_run and not self._ready:
self.send_event(
"first_run_failed",
severity="error",
message="Launcher did not reach the ready state",
metadata={"duration_ms": runtime_seconds * 1000},
)
self.send_event(
"first_run_failed",
severity="error",
message="Launcher did not reach the ready state",
metadata={"duration_ms": runtime_seconds * 1000},
"launcher_closed",
metadata={"runtime_seconds": runtime_seconds, **self._session_metadata},
)
self.send_event(
"launcher_closed",
metadata={"runtime_seconds": runtime_seconds, **self._session_metadata},
)
with self._state_lock:
self._accepting = False
self._stop.set()
@@ -719,6 +732,7 @@ class TelemetryService:
with self._state_lock:
self._enabled = False
self._accepting = False
self._closing = True
self._stop.set()
self._repeated_errors.clear()
while True:
@@ -795,6 +809,11 @@ class TelemetryService:
self._base_payload[key] = {**existing, **value}
else:
self._base_payload[key] = value
if not self._reports_launcher_lifecycle:
# Dashboard operation subprocesses enrich their own events with the
# detected environment, while the dashboard remains the sole source
# of launcher lifecycle and automatic compatibility events.
return
runtime = sanitized.get("runtime")
dependencies = sanitized.get("dependencies")
@@ -816,14 +835,38 @@ class TelemetryService:
context={"operation": "launcher", "stage": "environment_detection"},
)
missing_required = dependency_details.get("missing_required")
if dependency_details.get("healthy") is False:
generic_dependency_failures = (
[
str(value)
for value in missing_required
if isinstance(value, str) and value
]
if isinstance(missing_required, list)
else []
)
if (
dependency_details.get("container_ready") is False
and "container_host" not in generic_dependency_failures
):
generic_dependency_failures.append("container_host")
if (
dependency_details.get("healthy") is False
and generic_dependency_failures
):
self.send_event(
"dependency_check_failed",
severity="warning",
message="One or more launcher dependencies are unavailable",
metadata={
"dependencies": (
missing_required if isinstance(missing_required, list) else []
"dependencies": generic_dependency_failures,
"required_shared_libraries_present": dependency_details.get(
"required_shared_libraries_present"
),
"vulkan_64bit_available": dependency_details.get(
"vulkan_64bit_available"
),
"vulkan_32bit_available": dependency_details.get(
"vulkan_32bit_available"
),
},
context={"operation": "launcher", "stage": "dependency_check"},
@@ -846,7 +889,17 @@ class TelemetryService:
self.send_event(
"vulkan_32bit_check_failed",
severity="warning",
metadata={"dependency": "vulkan_32bit", "check": "32bit_vulkan"},
metadata={
"dependency": "vulkan_32bit",
"check": "32bit_vulkan",
"loader_available": runtime_details.get(
"vulkan_32bit_loader_available"
),
"icd_available": runtime_details.get(
"vulkan_32bit_icd_available"
),
"detection": runtime_details.get("vulkan_32bit_detection"),
},
context={"operation": "launcher", "stage": "vulkan_check"},
)