Files
automaker/apps/ui/src/components/debug/cpu-monitor.tsx
Kacper 78d08c2b5b feat: introduce debug panel for performance monitoring
- Added a debug panel to monitor server performance, including memory and CPU metrics.
- Implemented debug services for real-time tracking of processes and performance metrics.
- Created API endpoints for metrics collection and process management.
- Enhanced UI components for displaying metrics and process statuses.
- Updated documentation to include new debug API details.

This feature is intended for development use and can be toggled with the `ENABLE_DEBUG_PANEL` environment variable.
2026-01-05 18:59:09 +01:00

176 lines
4.6 KiB
TypeScript

/**
* CPU Monitor Component
*
* Displays CPU usage percentage with historical chart and event loop lag indicator.
*/
import { useMemo } from 'react';
import { Cpu, Activity, AlertTriangle } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { CPUDataPoint, ServerCPUMetrics } from '@automaker/types';
interface CPUMonitorProps {
history: CPUDataPoint[];
current: ServerCPUMetrics | null;
eventLoopLag?: number;
className?: string;
}
/**
* Simple sparkline chart for CPU data
*/
function CPUSparkline({ data, className }: { data: CPUDataPoint[]; className?: string }) {
const pathD = useMemo(() => {
if (data.length < 2) {
return '';
}
const w = 200;
const h = 40;
const padding = 2;
// CPU percentage is 0-100, but we'll use 0-100 as our range
const points = data.map((d, i) => {
const x = (i / (data.length - 1)) * (w - padding * 2) + padding;
const y = h - padding - (d.percentage / 100) * (h - padding * 2);
return `${x},${y}`;
});
return `M ${points.join(' L ')}`;
}, [data]);
if (data.length < 2) {
return (
<div
className={cn(
'h-10 flex items-center justify-center text-muted-foreground text-xs',
className
)}
>
Collecting data...
</div>
);
}
return (
<svg viewBox="0 0 200 40" className={cn('w-full', className)} preserveAspectRatio="none">
<path
d={pathD}
fill="none"
stroke="currentColor"
strokeWidth="1.5"
className="text-green-500"
/>
</svg>
);
}
/**
* CPU usage gauge
*/
function CPUGauge({ percentage }: { percentage: number }) {
const isHigh = percentage > 60;
const isCritical = percentage > 80;
return (
<div className="relative w-16 h-16">
{/* Background circle */}
<svg className="w-full h-full -rotate-90" viewBox="0 0 36 36">
<circle cx="18" cy="18" r="16" fill="none" strokeWidth="3" className="stroke-muted" />
<circle
cx="18"
cy="18"
r="16"
fill="none"
strokeWidth="3"
strokeDasharray={`${percentage} 100`}
strokeLinecap="round"
className={cn(
'transition-all duration-300',
isCritical ? 'stroke-red-500' : isHigh ? 'stroke-yellow-500' : 'stroke-green-500'
)}
/>
</svg>
{/* Center text */}
<div className="absolute inset-0 flex items-center justify-center">
<span
className={cn(
'text-sm font-mono font-bold',
isCritical ? 'text-red-400' : isHigh ? 'text-yellow-400' : 'text-green-400'
)}
>
{percentage.toFixed(0)}%
</span>
</div>
</div>
);
}
/**
* Event loop lag indicator
*/
function EventLoopLag({ lag }: { lag?: number }) {
if (lag === undefined) {
return null;
}
const isBlocked = lag > 50;
const isSevere = lag > 100;
return (
<div
className={cn(
'flex items-center gap-1.5 text-xs px-2 py-1 rounded',
isSevere && 'bg-red-500/20 text-red-400',
isBlocked && !isSevere && 'bg-yellow-500/20 text-yellow-400',
!isBlocked && 'bg-muted text-muted-foreground'
)}
>
{isSevere ? <AlertTriangle className="w-3 h-3" /> : <Activity className="w-3 h-3" />}
<span>Event Loop: {lag.toFixed(0)}ms</span>
</div>
);
}
export function CPUMonitor({ history, current, eventLoopLag, className }: CPUMonitorProps) {
const percentage = current?.percentage ?? 0;
return (
<div className={cn('space-y-3', className)}>
{/* Header */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Cpu className="w-4 h-4 text-green-500" />
<span className="text-sm font-medium">CPU</span>
</div>
<EventLoopLag lag={eventLoopLag} />
</div>
{/* Main content */}
<div className="flex items-center gap-4">
{/* Gauge */}
<CPUGauge percentage={percentage} />
{/* Sparkline */}
<div className="flex-1 h-10">
<CPUSparkline data={history} />
</div>
</div>
{/* Details */}
{current && (
<div className="grid grid-cols-2 gap-2 text-xs">
<div>
<span className="text-muted-foreground">User: </span>
<span>{(current.user / 1000).toFixed(1)}ms</span>
</div>
<div>
<span className="text-muted-foreground">System: </span>
<span>{(current.system / 1000).toFixed(1)}ms</span>
</div>
</div>
)}
</div>
);
}