From 405fff91af0ce0394c08fc30226cf4dedf963119 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Tue, 21 Apr 2026 13:36:07 +0000 Subject: [PATCH] refactor(health): share health snapshot computation --- ts/api/server.ts | 47 ++++++++---------------------------------- ts/helpers/health.ts | 49 ++++++++++++++++++++++++++++++++++++++++++++ ts/ui/server.ts | 42 +++++++------------------------------ 3 files changed, 64 insertions(+), 74 deletions(-) create mode 100644 ts/helpers/health.ts diff --git a/ts/api/server.ts b/ts/api/server.ts index 1c147a3..34671c6 100644 --- a/ts/api/server.ts +++ b/ts/api/server.ts @@ -16,6 +16,7 @@ import { ModelRegistry } from '../models/registry.ts'; import { ModelLoader } from '../models/loader.ts'; import { GpuDetector } from '../hardware/gpu-detector.ts'; import { ClusterHandler } from './handlers/cluster.ts'; +import { buildHealthSnapshot } from '../helpers/health.ts'; interface IApiServerOptions { gpuDetector?: GpuDetector; @@ -209,47 +210,15 @@ export class ApiServer { const gpus = await this.gpuDetector.detectGpus(); const models = await this.containerManager.getAllAvailableModels(); - let status: 'ok' | 'degraded' | 'error' = 'ok'; - const reasons = new Set<'unhealthy_container' | 'no_models_available' | 'gpu_detection_failed'>(); - const containerHealth: Record = {}; - const gpuStatus: Record = {}; - - // Check container health - for (const [id, containerStatus] of statuses) { - if (containerStatus.running && containerStatus.health === 'healthy') { - containerHealth[id] = 'healthy'; - } else { - containerHealth[id] = 'unhealthy'; - status = 'degraded'; - reasons.add('unhealthy_container'); - } - } - - // Check GPU status - for (const gpu of gpus) { - gpuStatus[gpu.id] = 'available'; - } - - if (models.size === 0) { - status = 'degraded'; - reasons.add('no_models_available'); - } - - const response: IHealthResponse = { - status, - reasons: Array.from(reasons), + const response: IHealthResponse = buildHealthSnapshot({ + statuses, + modelCount: models.size, + gpus, + startTime: this.startTime, version: VERSION, - uptime: Math.floor((Date.now() - this.startTime) / 1000), - containers: statuses.size, - models: models.size, - gpus: gpus.length, - details: { - containers: containerHealth, - gpus: gpuStatus, - }, - }; + }); - res.writeHead(status === 'ok' ? 200 : 503, { 'Content-Type': 'application/json' }); + res.writeHead(response.status === 'ok' ? 200 : 503, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(response, null, 2)); } catch (error) { res.writeHead(500, { 'Content-Type': 'application/json' }); diff --git a/ts/helpers/health.ts b/ts/helpers/health.ts new file mode 100644 index 0000000..7f9660e --- /dev/null +++ b/ts/helpers/health.ts @@ -0,0 +1,49 @@ +import type { IHealthResponse } from '../interfaces/api.ts'; +import type { IContainerStatus } from '../interfaces/container.ts'; +import type { IGpuInfo } from '../interfaces/gpu.ts'; + +export function buildHealthSnapshot(options: { + statuses: Map; + modelCount: number; + gpus: IGpuInfo[]; + startTime: number; + version: string; +}): IHealthResponse { + let status: 'ok' | 'degraded' | 'error' = 'ok'; + const reasons = new Set<'unhealthy_container' | 'no_models_available' | 'gpu_detection_failed'>(); + const containerHealth: Record = {}; + const gpuStatus: Record = {}; + + for (const [id, containerStatus] of options.statuses) { + if (containerStatus.running && containerStatus.health === 'healthy') { + containerHealth[id] = 'healthy'; + } else { + containerHealth[id] = 'unhealthy'; + status = 'degraded'; + reasons.add('unhealthy_container'); + } + } + + for (const gpu of options.gpus) { + gpuStatus[gpu.id] = 'available'; + } + + if (options.modelCount === 0) { + status = 'degraded'; + reasons.add('no_models_available'); + } + + return { + status, + reasons: Array.from(reasons), + version: options.version, + uptime: Math.floor((Date.now() - options.startTime) / 1000), + containers: options.statuses.size, + models: options.modelCount, + gpus: options.gpus.length, + details: { + containers: containerHealth, + gpus: gpuStatus, + }, + }; +} diff --git a/ts/ui/server.ts b/ts/ui/server.ts index 6cf707c..613e37a 100644 --- a/ts/ui/server.ts +++ b/ts/ui/server.ts @@ -22,6 +22,7 @@ import { VERSION } from '../constants.ts'; import type { ContainerManager } from '../containers/container-manager.ts'; import type { ClusterManager } from '../cluster/cluster-manager.ts'; import { GpuDetector } from '../hardware/gpu-detector.ts'; +import { buildHealthSnapshot } from '../helpers/health.ts'; interface IBundledFile { path: string; @@ -150,42 +151,13 @@ export class UiServer { const models = await this.containerManager.getAllAvailableModels(); const gpus = await this.gpuDetector.detectGpus(); - let status: 'ok' | 'degraded' | 'error' = 'ok'; - const reasons = new Set<'unhealthy_container' | 'no_models_available' | 'gpu_detection_failed'>(); - const containerHealth: Record = {}; - const gpuStatus: Record = {}; - - for (const [id, s] of statuses) { - if (s.running && s.health === 'healthy') { - containerHealth[id] = 'healthy'; - } else { - containerHealth[id] = 'unhealthy'; - status = 'degraded'; - reasons.add('unhealthy_container'); - } - } - for (const gpu of gpus) { - gpuStatus[gpu.id] = 'available'; - } - - if (models.size === 0) { - status = 'degraded'; - reasons.add('no_models_available'); - } - - const health: IHealthResponse = { - status, - reasons: Array.from(reasons), + const health: IHealthResponse = buildHealthSnapshot({ + statuses, + modelCount: models.size, + gpus, + startTime: this.startTime, version: VERSION, - uptime: Math.floor((Date.now() - this.startTime) / 1000), - containers: statuses.size, - models: models.size, - gpus: gpus.length, - details: { - containers: containerHealth, - gpus: gpuStatus, - }, - }; + }); const clusterConfig = this.clusterManager.getConfig();