mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-02-01 08:13:37 +00:00
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.
This commit is contained in:
317
apps/ui/src/hooks/use-debug-metrics.ts
Normal file
317
apps/ui/src/hooks/use-debug-metrics.ts
Normal file
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* Hook for consuming debug metrics from the server
|
||||
*
|
||||
* Provides real-time metrics data including:
|
||||
* - Memory usage (server-side)
|
||||
* - CPU usage (server-side)
|
||||
* - Tracked processes
|
||||
* - Memory leak detection
|
||||
*
|
||||
* Uses polling for metrics data with configurable interval.
|
||||
*/
|
||||
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { apiGet, apiPost } from '@/lib/api-fetch';
|
||||
import { useDebugStore } from '@/store/debug-store';
|
||||
import type {
|
||||
DebugMetricsSnapshot,
|
||||
DebugMetricsResponse,
|
||||
MemoryDataPoint,
|
||||
CPUDataPoint,
|
||||
TrackedProcess,
|
||||
ProcessSummary,
|
||||
MemoryTrend,
|
||||
BrowserMemoryMetrics,
|
||||
} from '@automaker/types';
|
||||
|
||||
/**
|
||||
* Maximum data points to store in history buffers
|
||||
*/
|
||||
const MAX_HISTORY_POINTS = 60;
|
||||
|
||||
/**
|
||||
* Browser memory metrics (from Chrome's performance.memory API)
|
||||
*/
|
||||
interface BrowserMetrics {
|
||||
memory?: BrowserMemoryMetrics;
|
||||
available: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get browser memory metrics (Chrome only)
|
||||
*/
|
||||
function getBrowserMemoryMetrics(): BrowserMetrics {
|
||||
// performance.memory is Chrome-specific
|
||||
const perf = performance as Performance & {
|
||||
memory?: {
|
||||
jsHeapSizeLimit: number;
|
||||
totalJSHeapSize: number;
|
||||
usedJSHeapSize: number;
|
||||
};
|
||||
};
|
||||
|
||||
if (!perf.memory) {
|
||||
return { available: false };
|
||||
}
|
||||
|
||||
return {
|
||||
available: true,
|
||||
memory: {
|
||||
jsHeapSizeLimit: perf.memory.jsHeapSizeLimit,
|
||||
totalJSHeapSize: perf.memory.totalJSHeapSize,
|
||||
usedJSHeapSize: perf.memory.usedJSHeapSize,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug metrics state
|
||||
*/
|
||||
export interface DebugMetricsState {
|
||||
/** Whether metrics collection is active */
|
||||
isActive: boolean;
|
||||
/** Whether data is currently loading */
|
||||
isLoading: boolean;
|
||||
/** Error message if any */
|
||||
error: string | null;
|
||||
/** Latest metrics snapshot from server */
|
||||
latestSnapshot: DebugMetricsSnapshot | null;
|
||||
/** Memory history for charting */
|
||||
memoryHistory: MemoryDataPoint[];
|
||||
/** CPU history for charting */
|
||||
cpuHistory: CPUDataPoint[];
|
||||
/** Tracked processes */
|
||||
processes: TrackedProcess[];
|
||||
/** Process summary */
|
||||
processSummary: ProcessSummary | null;
|
||||
/** Memory trend analysis */
|
||||
memoryTrend: MemoryTrend | null;
|
||||
/** Browser-side memory metrics */
|
||||
browserMetrics: BrowserMetrics;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug metrics actions
|
||||
*/
|
||||
export interface DebugMetricsActions {
|
||||
/** Start metrics collection */
|
||||
start: () => Promise<void>;
|
||||
/** Stop metrics collection */
|
||||
stop: () => Promise<void>;
|
||||
/** Force garbage collection (if available) */
|
||||
forceGC: () => Promise<{ success: boolean; message: string }>;
|
||||
/** Clear history */
|
||||
clearHistory: () => Promise<void>;
|
||||
/** Refresh metrics immediately */
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook for consuming debug metrics
|
||||
*/
|
||||
export function useDebugMetrics(): DebugMetricsState & DebugMetricsActions {
|
||||
const preferences = useDebugStore((state) => state.preferences);
|
||||
const isOpen = useDebugStore((state) => state.isOpen);
|
||||
|
||||
const [state, setState] = useState<DebugMetricsState>({
|
||||
isActive: false,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
latestSnapshot: null,
|
||||
memoryHistory: [],
|
||||
cpuHistory: [],
|
||||
processes: [],
|
||||
processSummary: null,
|
||||
memoryTrend: null,
|
||||
browserMetrics: { available: false },
|
||||
});
|
||||
|
||||
// Use ref to store history to avoid re-renders during updates
|
||||
const memoryHistoryRef = useRef<MemoryDataPoint[]>([]);
|
||||
const cpuHistoryRef = useRef<CPUDataPoint[]>([]);
|
||||
const pollingIntervalRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
/**
|
||||
* Fetch metrics from server
|
||||
*/
|
||||
const fetchMetrics = useCallback(async () => {
|
||||
try {
|
||||
const response = await apiGet<DebugMetricsResponse>('/api/debug/metrics');
|
||||
|
||||
// Get browser metrics
|
||||
const browserMetrics = getBrowserMemoryMetrics();
|
||||
|
||||
if (response.snapshot) {
|
||||
const snapshot = response.snapshot;
|
||||
|
||||
// Add to history buffers
|
||||
if (snapshot.memory.server) {
|
||||
const memoryPoint: MemoryDataPoint = {
|
||||
timestamp: snapshot.timestamp,
|
||||
heapUsed: snapshot.memory.server.heapUsed,
|
||||
heapTotal: snapshot.memory.server.heapTotal,
|
||||
rss: snapshot.memory.server.rss,
|
||||
};
|
||||
|
||||
memoryHistoryRef.current.push(memoryPoint);
|
||||
if (memoryHistoryRef.current.length > MAX_HISTORY_POINTS) {
|
||||
memoryHistoryRef.current.shift();
|
||||
}
|
||||
}
|
||||
|
||||
if (snapshot.cpu.server) {
|
||||
const cpuPoint: CPUDataPoint = {
|
||||
timestamp: snapshot.timestamp,
|
||||
percentage: snapshot.cpu.server.percentage,
|
||||
eventLoopLag: snapshot.cpu.eventLoopLag,
|
||||
};
|
||||
|
||||
cpuHistoryRef.current.push(cpuPoint);
|
||||
if (cpuHistoryRef.current.length > MAX_HISTORY_POINTS) {
|
||||
cpuHistoryRef.current.shift();
|
||||
}
|
||||
}
|
||||
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isActive: response.active,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
latestSnapshot: snapshot,
|
||||
memoryHistory: [...memoryHistoryRef.current],
|
||||
cpuHistory: [...cpuHistoryRef.current],
|
||||
processes: snapshot.processes,
|
||||
processSummary: snapshot.processSummary,
|
||||
memoryTrend: snapshot.memoryTrend || null,
|
||||
browserMetrics,
|
||||
}));
|
||||
} else {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isActive: response.active,
|
||||
isLoading: false,
|
||||
browserMetrics,
|
||||
}));
|
||||
}
|
||||
} catch (error) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isLoading: false,
|
||||
error: error instanceof Error ? error.message : 'Failed to fetch metrics',
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Start metrics collection
|
||||
*/
|
||||
const start = useCallback(async () => {
|
||||
try {
|
||||
await apiPost<DebugMetricsResponse>('/api/debug/metrics/start');
|
||||
await fetchMetrics();
|
||||
} catch (error) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: error instanceof Error ? error.message : 'Failed to start metrics',
|
||||
}));
|
||||
}
|
||||
}, [fetchMetrics]);
|
||||
|
||||
/**
|
||||
* Stop metrics collection
|
||||
*/
|
||||
const stop = useCallback(async () => {
|
||||
try {
|
||||
await apiPost<DebugMetricsResponse>('/api/debug/metrics/stop');
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
isActive: false,
|
||||
}));
|
||||
} catch (error) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: error instanceof Error ? error.message : 'Failed to stop metrics',
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Force garbage collection
|
||||
*/
|
||||
const forceGC = useCallback(async () => {
|
||||
try {
|
||||
const response = await apiPost<{ success: boolean; message: string }>(
|
||||
'/api/debug/metrics/gc'
|
||||
);
|
||||
return response;
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: error instanceof Error ? error.message : 'Failed to trigger GC',
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Clear metrics history
|
||||
*/
|
||||
const clearHistory = useCallback(async () => {
|
||||
try {
|
||||
await apiPost('/api/debug/metrics/clear');
|
||||
memoryHistoryRef.current = [];
|
||||
cpuHistoryRef.current = [];
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
memoryHistory: [],
|
||||
cpuHistory: [],
|
||||
}));
|
||||
} catch (error) {
|
||||
setState((prev) => ({
|
||||
...prev,
|
||||
error: error instanceof Error ? error.message : 'Failed to clear history',
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
/**
|
||||
* Refresh metrics immediately
|
||||
*/
|
||||
const refresh = useCallback(async () => {
|
||||
setState((prev) => ({ ...prev, isLoading: true }));
|
||||
await fetchMetrics();
|
||||
}, [fetchMetrics]);
|
||||
|
||||
// Set up polling when debug panel is open and monitoring is enabled
|
||||
useEffect(() => {
|
||||
if (!isOpen || !preferences.memoryMonitorEnabled) {
|
||||
// Clear polling when panel is closed or monitoring disabled
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Initial fetch
|
||||
fetchMetrics();
|
||||
|
||||
// Set up polling interval
|
||||
pollingIntervalRef.current = setInterval(fetchMetrics, preferences.updateInterval);
|
||||
|
||||
return () => {
|
||||
if (pollingIntervalRef.current) {
|
||||
clearInterval(pollingIntervalRef.current);
|
||||
pollingIntervalRef.current = null;
|
||||
}
|
||||
};
|
||||
}, [isOpen, preferences.memoryMonitorEnabled, preferences.updateInterval, fetchMetrics]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
start,
|
||||
stop,
|
||||
forceGC,
|
||||
clearHistory,
|
||||
refresh,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user