Add System Health Monitor: RAM/CPU/locks tracking with emergency kill controls + Visual Builder plan

This commit is contained in:
cawcenter
2025-12-16 11:06:40 -05:00
parent 11af92b0d0
commit 0f4330b7e1
6 changed files with 967 additions and 3 deletions

View File

@@ -0,0 +1,49 @@
// EMERGENCY API: Kill stuck database locks
// USE WITH CAUTION - Terminates blocking queries
import type { APIRoute } from 'astro';
import { killStuckLocks, getBlockingQueries } from '@/lib/shim/health';
export const POST: APIRoute = async ({ request }) => {
try {
// STRICT token validation - this is destructive
const authHeader = request.headers.get('Authorization');
const token = authHeader?.replace('Bearer ', '');
const godToken = import.meta.env.GOD_MODE_TOKEN;
if (!godToken || token !== godToken) {
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
status: 401,
headers: { 'Content-Type': 'application/json' }
});
}
// Get list of what will be killed before killing
const blocking = await getBlockingQueries();
// Execute kill
const killedCount = await killStuckLocks();
console.warn(`[EMERGENCY] Killed ${killedCount} stuck locks`, { blocking });
return new Response(JSON.stringify({
success: true,
killedCount,
blockedQueries: blocking.length,
message: `Terminated ${killedCount} blocking queries`
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error: any) {
console.error('[EMERGENCY] Kill locks failed:', error);
return new Response(JSON.stringify({
error: 'Kill locks failed',
message: error.message
}), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
};

View File

@@ -1,8 +1,9 @@
// API Route: GET /api/shim/health
// Returns connection pool stats and database health
// Returns connection pool stats, database health, and system metrics (RAM/CPU/locks)
import type { APIRoute } from 'astro';
import { getPoolStats, getDatabaseStats, getVacuumCandidates } from '@/lib/shim/pool';
import { getSystemHealth } from '@/lib/shim/health';
export const GET: APIRoute = async ({ request }) => {
try {
@@ -18,21 +19,35 @@ export const GET: APIRoute = async ({ request }) => {
});
}
// Get health stats
// Get all health stats
const poolStats = getPoolStats();
const dbStats = await getDatabaseStats();
const vacuumCandidates = await getVacuumCandidates();
const systemHealth = await getSystemHealth();
const needsVacuum = vacuumCandidates.length > 0 && vacuumCandidates[0].deadPercent > 20;
// Overall status (most critical wins)
const overallStatus =
systemHealth.status === 'critical' || poolStats.status === 'critical'
? 'critical'
: systemHealth.status === 'warning' || poolStats.status === 'warning'
? 'warning'
: 'healthy';
return new Response(JSON.stringify({
timestamp: new Date().toISOString(),
status: overallStatus,
system: systemHealth,
pool: poolStats,
database: dbStats,
vacuum: {
recommended: needsVacuum,
candidates: vacuumCandidates
},
status: poolStats.status
alerts: [
...systemHealth.alerts,
...(poolStats.status !== 'healthy' ? [poolStats.message] : [])
]
}), {
status: 200,
headers: { 'Content-Type': 'application/json' }