// useMetrics Composable // Real-time system and agent metrics management import { ref, computed, onMounted, onUnmounted, type Ref } from 'vue' import { useWebSocket } from './useWebSocket' import type { SystemMetrics, AgentMetrics, ResourceUsage } from '@/types' interface MetricsComposable { // System Metrics systemMetrics: Ref systemLoading: Ref systemError: Ref // Agent Metrics agentMetrics: Ref> agentLoading: Ref agentError: Ref // Computed systemHealthScore: Ref topPerformingAgents: Ref systemAlerts: Ref // Actions refreshSystemMetrics: () => Promise refreshAgentMetrics: (agentId?: string) => Promise exportMetrics: (format: 'json' | 'csv') => Promise clearErrors: () => void // Utils getMetricTrend: (current: number, previous: number) => 'up' | 'down' | 'stable' formatMetricValue: (value: number, type: string) => string } export function useMetrics(): MetricsComposable { // State const systemMetrics = ref(null) const systemLoading = ref(false) const systemError = ref(null) const agentMetrics = ref>({}) const agentLoading = ref(false) const agentError = ref(null) // WebSocket for real-time updates const { connected, on, off } = useWebSocket() // API client async function apiCall(endpoint: string) { const response = await fetch(`/api/v1${endpoint}`) if (!response.ok) { const errorData = await response.json().catch(() => ({})) throw new Error(errorData.message || `HTTP ${response.status}`) } return response.json() } // Computed const systemHealthScore = computed(() => { if (!systemMetrics.value) return 0 const metrics = systemMetrics.value let score = 100 // Deduct points for high resource usage if (metrics.memoryUsage.percentage > 80) score -= 20 if (metrics.cpuUsage.percentage > 80) score -= 20 // Deduct points for high error rate if (metrics.errorRate > 5) score -= 15 if (metrics.errorRate > 10) score -= 25 // Deduct points for network issues if (metrics.networkLatency > 500) score -= 10 if (metrics.networkLatency > 1000) score -= 20 // Deduct points for inactive agents const activePercentage = (metrics.activeAgents / metrics.totalAgents) * 100 if (activePercentage < 80) score -= 10 if (activePercentage < 50) score -= 20 return Math.max(0, score) }) const topPerformingAgents = computed(() => { return Object.values(agentMetrics.value) .sort((a, b) => { // Sort by success rate first, then by response time if (a.successRate !== b.successRate) { return b.successRate - a.successRate } return a.avgResponseTime - b.avgResponseTime }) .slice(0, 5) // Top 5 performers }) const systemAlerts = computed(() => { const alerts: string[] = [] if (!systemMetrics.value) return alerts const metrics = systemMetrics.value if (metrics.memoryUsage.percentage > 90) { alerts.push('Critical: Memory usage above 90%') } else if (metrics.memoryUsage.percentage > 80) { alerts.push('Warning: High memory usage') } if (metrics.cpuUsage.percentage > 90) { alerts.push('Critical: CPU usage above 90%') } else if (metrics.cpuUsage.percentage > 80) { alerts.push('Warning: High CPU usage') } if (metrics.errorRate > 10) { alerts.push('Critical: High error rate detected') } else if (metrics.errorRate > 5) { alerts.push('Warning: Elevated error rate') } if (metrics.networkLatency > 1000) { alerts.push('Critical: Network latency above 1s') } else if (metrics.networkLatency > 500) { alerts.push('Warning: High network latency') } const inactiveAgents = metrics.totalAgents - metrics.activeAgents if (inactiveAgents > metrics.totalAgents * 0.5) { alerts.push('Warning: More than 50% of agents are inactive') } return alerts }) // Actions async function refreshSystemMetrics(): Promise { systemLoading.value = true systemError.value = null try { const response = await apiCall('/system/metrics') systemMetrics.value = response.data } catch (err) { systemError.value = err instanceof Error ? err.message : 'Failed to fetch system metrics' throw err } finally { systemLoading.value = false } } async function refreshAgentMetrics(agentId?: string): Promise { agentLoading.value = true agentError.value = null try { const endpoint = agentId ? `/agents/${agentId}/metrics` : '/agents/metrics' const response = await apiCall(endpoint) if (agentId) { agentMetrics.value[agentId] = response.data } else { agentMetrics.value = response.data } } catch (err) { agentError.value = err instanceof Error ? err.message : 'Failed to fetch agent metrics' throw err } finally { agentLoading.value = false } } async function exportMetrics(format: 'json' | 'csv'): Promise { try { const response = await fetch(`/api/v1/metrics/export?format=${format}`) if (!response.ok) { throw new Error('Failed to export metrics') } const blob = await response.blob() const url = URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = `saap-metrics-${new Date().toISOString().split('T')[0]}.${format}` document.body.appendChild(link) link.click() document.body.removeChild(link) URL.revokeObjectURL(url) } catch (err) { const message = err instanceof Error ? err.message : 'Failed to export metrics' systemError.value = message throw new Error(message) } } function clearErrors(): void { systemError.value = null agentError.value = null } // Utils function getMetricTrend(current: number, previous: number): 'up' | 'down' | 'stable' { const threshold = 0.05 // 5% change threshold const change = Math.abs(current - previous) / previous if (change < threshold) return 'stable' return current > previous ? 'up' : 'down' } function formatMetricValue(value: number, type: string): string { switch (type) { case 'percentage': return `${Math.round(value)}%` case 'bytes': return formatBytes(value) case 'duration': return formatDuration(value) case 'rate': return `${value.toFixed(2)}/min` case 'latency': return `${value}ms` case 'count': return value.toLocaleString() default: return value.toString() } } function formatBytes(bytes: number): string { if (bytes >= 1024 ** 3) { return `${(bytes / (1024 ** 3)).toFixed(1)}GB` } if (bytes >= 1024 ** 2) { return `${(bytes / (1024 ** 2)).toFixed(1)}MB` } if (bytes >= 1024) { return `${(bytes / 1024).toFixed(1)}KB` } return `${bytes}B` } function formatDuration(ms: number): string { if (ms < 1000) { return `${ms}ms` } if (ms < 60000) { return `${(ms / 1000).toFixed(1)}s` } return `${Math.round(ms / 60000)}m` } // WebSocket event handlers function handleMetricsUpdated(event: any) { if (event.data.type === 'system') { systemMetrics.value = event.data.metrics } else if (event.data.type === 'agent') { agentMetrics.value[event.data.agentId] = event.data.metrics } } // Lifecycle onMounted(() => { // Subscribe to WebSocket events on('metrics_updated', handleMetricsUpdated) // Initial data fetch if (connected.value) { Promise.all([ refreshSystemMetrics().catch(() => {}), refreshAgentMetrics().catch(() => {}) ]) } // Set up periodic refresh (fallback if WebSocket fails) const refreshInterval = setInterval(() => { if (!connected.value) { refreshSystemMetrics().catch(() => {}) refreshAgentMetrics().catch(() => {}) } }, 30000) // 30 seconds onUnmounted(() => { clearInterval(refreshInterval) }) }) onUnmounted(() => { // Unsubscribe from WebSocket events off('metrics_updated', handleMetricsUpdated) }) return { // System Metrics systemMetrics, systemLoading, systemError, // Agent Metrics agentMetrics, agentLoading, agentError, // Computed systemHealthScore, topPerformingAgents, systemAlerts, // Actions refreshSystemMetrics, refreshAgentMetrics, exportMetrics, clearErrors, // Utils getMetricTrend, formatMetricValue } } // Helper function to create mock metrics for development export function createMockSystemMetrics(): SystemMetrics { return { totalAgents: 8, activeAgents: 6, totalMessages: 1247, messagesPerMinute: 15.3, avgResponseTime: 1250, systemUptime: Date.now() - (3 * 24 * 60 * 60 * 1000), // 3 days ago memoryUsage: { current: 8.5 * 1024 ** 3, // 8.5 GB maximum: 16 * 1024 ** 3, // 16 GB percentage: 53, trend: 'stable' }, cpuUsage: { current: 45, maximum: 100, percentage: 45, trend: 'increasing' }, networkLatency: 125, errorRate: 2.1 } } export function createMockAgentMetrics(agentId: string): AgentMetrics { return { agentId, messagesProcessed: Math.floor(Math.random() * 500) + 50, successRate: Math.random() * 20 + 80, // 80-100% avgResponseTime: Math.random() * 2000 + 500, // 0.5-2.5s errorCount: Math.floor(Math.random() * 10), uptime: Date.now() - (Math.random() * 24 * 60 * 60 * 1000), // Up to 24h ago memoryUsage: Math.random() * 512 * 1024 * 1024, // Up to 512MB cpuUsage: Math.random() * 50, // 0-50% lastActivity: new Date(Date.now() - Math.random() * 60 * 60 * 1000).toISOString() // Up to 1h ago } }