health.ts•5.49 kB
/**
* Health check implementation
*
* Provides comprehensive health status for the MCP server
*/
import type { IChromeService } from '../core/interfaces/chrome-service.interface.js'
import type { ILogger } from '../core/interfaces/logger.interface.js'
export interface HealthStatus {
status: 'healthy' | 'degraded' | 'unhealthy'
timestamp: string
uptime: number
checks: {
chrome: {
connected: boolean
version?: string
targets?: number
}
memory: {
heapUsed: number
heapTotal: number
external: number
rss: number
}
resources: {
available: boolean
count?: number
}
tools: {
available: boolean
count?: number
}
}
errors?: string[]
}
export class HealthChecker {
private startTime: number = Date.now()
private chromeService: IChromeService
private resourceCount: number = 0
private toolCount: number = 0
private logger: ILogger
constructor(chromeService: IChromeService, logger?: ILogger) {
this.chromeService = chromeService
this.logger = logger || console as any
}
/**
* Update counts from providers
*/
updateCounts(resources: number, tools: number) {
this.resourceCount = resources
this.toolCount = tools
}
/**
* Perform health check
*/
async check(): Promise<HealthStatus> {
const errors: string[] = []
const memoryUsage = process.memoryUsage()
// Check Chrome connection
const chromeCheck = await this.checkChrome()
if (!chromeCheck.connected) {
errors.push('Chrome DevTools Protocol not connected')
}
// Check memory usage
const memoryCheck = this.checkMemory(memoryUsage)
if (memoryCheck.warning) {
errors.push(memoryCheck.warning)
}
// Determine overall status
let status: HealthStatus['status'] = 'healthy'
if (errors.length > 0) {
status = chromeCheck.connected ? 'degraded' : 'unhealthy'
}
return {
status,
timestamp: new Date().toISOString(),
uptime: Date.now() - this.startTime,
checks: {
chrome: chromeCheck,
memory: {
heapUsed: memoryUsage.heapUsed,
heapTotal: memoryUsage.heapTotal,
external: memoryUsage.external,
rss: memoryUsage.rss,
},
resources: {
available: this.resourceCount > 0,
count: this.resourceCount,
},
tools: {
available: this.toolCount > 0,
count: this.toolCount,
},
},
errors: errors.length > 0 ? errors : undefined,
}
}
/**
* Check Chrome connection
*/
private async checkChrome(): Promise<HealthStatus['checks']['chrome']> {
try {
const isConnected = this.chromeService.isConnected()
if (!isConnected) {
return { connected: false }
}
// Get Chrome client to check version and targets
const client = this.chromeService.getCurrentClient()
if (!client) {
return { connected: false }
}
// Try to get version info
interface BrowserVersion {
product?: string;
userAgent?: string;
jsVersion?: string;
}
interface TargetInfo {
targetId: string;
type: string;
title: string;
url: string;
}
interface GetTargetsResult {
targetInfos?: TargetInfo[];
}
const version = await client.send<BrowserVersion>('Browser.getVersion')
const targets = await client.send<GetTargetsResult>('Target.getTargets')
return {
connected: true,
version: version?.product || 'unknown',
targets: targets?.targetInfos?.length || 0,
}
} catch (error) {
this.logger.error({ error }, 'Chrome health check failed')
return { connected: false }
}
}
/**
* Check memory usage
*/
private checkMemory(usage: NodeJS.MemoryUsage): { warning?: string } {
const heapUsedMB = usage.heapUsed / 1024 / 1024
const heapTotalMB = usage.heapTotal / 1024 / 1024
const heapPercentage = (usage.heapUsed / usage.heapTotal) * 100
// Warn if heap usage is over 80%
if (heapPercentage > 80) {
return {
warning: `High memory usage: ${heapUsedMB.toFixed(2)}MB / ${heapTotalMB.toFixed(2)}MB (${heapPercentage.toFixed(1)}%)`,
}
}
// Warn if total heap is over 500MB
if (heapUsedMB > 500) {
return {
warning: `High absolute memory usage: ${heapUsedMB.toFixed(2)}MB`,
}
}
return {}
}
/**
* Get simple health status
*/
async getSimpleStatus(): Promise<{ healthy: boolean; message: string }> {
const health = await this.check()
return {
healthy: health.status === 'healthy',
message: health.status === 'healthy'
? 'All systems operational'
: health.errors?.join(', ') || 'System degraded',
}
}
/**
* Get metrics for monitoring
*/
getMetrics() {
const memoryUsage = process.memoryUsage()
const uptime = Date.now() - this.startTime
return {
uptime_seconds: Math.floor(uptime / 1000),
memory_heap_used_bytes: memoryUsage.heapUsed,
memory_heap_total_bytes: memoryUsage.heapTotal,
memory_external_bytes: memoryUsage.external,
memory_rss_bytes: memoryUsage.rss,
chrome_connected: this.chromeService.isConnected() ? 1 : 0,
resources_count: this.resourceCount,
tools_count: this.toolCount,
}
}
}