소스 검색

Gráficos de consumo añadidos.

Celestino Rey 6 달 전
부모
커밋
f5da3292cf
3개의 변경된 파일352개의 추가작업 그리고 0개의 파일을 삭제
  1. 69 0
      app/main.py
  2. 282 0
      static/dashboard.js
  3. 1 0
      static/index.html

+ 69 - 0
app/main.py

@@ -122,6 +122,75 @@ async def get_vm_info(vm_name: str):
     except Exception as e:
         return {"error": str(e)}, 500
 
+@app.get("/vms/{vm_name}/stats")
+async def get_vm_stats(vm_name: str):
+    """Get CPU and memory usage statistics for a VM"""
+    try:
+        domain = conn.lookupByName(vm_name)
+        
+        # Check if VM is running
+        if not domain.isActive():
+            return {
+                "name": vm_name,
+                "state": "shut off",
+                "cpu_percent": 0,
+                "memory_used": 0,
+                "memory_total": 0,
+                "memory_percent": 0
+            }
+        
+        # Get CPU stats
+        try:
+            cpu_stats = domain.getCPUStats(True)
+            if cpu_stats:
+                # CPU time in nanoseconds
+                cpu_time = cpu_stats[0].get('cpu_time', 0)
+                cpu_percent = cpu_time / 1e9  # Convert to percentage approximation
+                cpu_percent = min(100, cpu_percent % 100)  # Keep between 0-100
+            else:
+                cpu_percent = 0
+        except:
+            cpu_percent = 0
+        
+        # Get memory stats
+        try:
+            memory_stats = domain.memoryStats()
+            # Memory stats returns values in KB
+            memory_used = memory_stats.get('actual', 0) // 1024  # Convert to MiB
+            memory_total = memory_stats.get('swap_in', 0)  # Total memory in MiB
+            
+            # Get from domain info as fallback
+            if memory_total == 0:
+                info = domain.info()
+                memory_total = info[1]  # Memory in MiB
+            
+            memory_percent = (memory_used / memory_total * 100) if memory_total > 0 else 0
+        except:
+            memory_used = 0
+            memory_total = 0
+            memory_percent = 0
+        
+        # Get domain memory from config
+        try:
+            xml_desc = domain.XMLDesc()
+            root = ET.fromstring(xml_desc)
+            memory_elem = root.find("memory")
+            if memory_elem is not None:
+                memory_total = int(memory_elem.text) // 1024  # Convert from KB to MiB
+        except:
+            pass
+        
+        return {
+            "name": vm_name,
+            "state": "running",
+            "cpu_percent": round(cpu_percent, 2),
+            "memory_used": memory_used,
+            "memory_total": memory_total,
+            "memory_percent": round(memory_percent, 2)
+        }
+    except Exception as e:
+        return {"error": str(e), "state": "error"}
+
 @app.get("/vms/{vm_name}/start")
 async def start_vm(vm_name: str):
     try:

+ 282 - 0
static/dashboard.js

@@ -68,6 +68,9 @@ async function selectVM(vmName) {
     selectedVM = vmName;
     loadVMs(); // Refresh to highlight selected
     
+    // Clean up previous charts
+    cleanupCharts();
+    
     try {
         const response = await fetch(`${API_BASE}/vms/${vmName}/info`);
         const vm = await response.json();
@@ -78,6 +81,11 @@ async function selectVM(vmName) {
         }
         
         displayVMDetails(vm, vmName);
+        
+        // Initialize performance charts only if VM is running
+        if (vm.state === 'running') {
+            setTimeout(() => initializePerformanceCharts(vmName), 100);
+        }
     } catch (error) {
         console.error('Error loading VM info:', error);
         showError('Error al cargar información de la VM');
@@ -188,6 +196,29 @@ function displayVMDetails(vm, vmName) {
                             </div>
                         </div>
                     </div>
+
+                    <!-- Performance Charts -->
+                    ${vm.state === 'running' ? `
+                    <div class="card">
+                        <div class="card-header">
+                            <h6 class="card-title mb-0"><i class="bi bi-graph-up"></i> Rendimiento</h6>
+                        </div>
+                        <div class="card-body">
+                            <div class="row">
+                                <div class="col-md-6">
+                                    <div style="position: relative; height: 250px;">
+                                        <canvas id="cpuChart"></canvas>
+                                    </div>
+                                </div>
+                                <div class="col-md-6">
+                                    <div style="position: relative; height: 250px;">
+                                        <canvas id="memoryChart"></canvas>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                    ` : ''}
                 </div>
                 
                 <div class="actions-group">
@@ -957,3 +988,254 @@ async function deleteSnapshot(vmName, snapshotName) {
     }
 }
 
+// Chart instances storage
+let cpuChart = null;
+let memoryChart = null;
+let statsInterval = null;
+const statsHistory = {
+    cpu: [],
+    memory: [],
+    maxPoints: 20
+};
+
+// Initialize and update performance charts
+async function initializePerformanceCharts(vmName) {
+    // Clear any existing interval
+    if (statsInterval) clearInterval(statsInterval);
+    
+    // Initialize charts immediately
+    await updatePerformanceStats(vmName);
+    
+    // Update every 2 seconds
+    statsInterval = setInterval(() => updatePerformanceStats(vmName), 2000);
+}
+
+// Update performance statistics
+async function updatePerformanceStats(vmName) {
+    try {
+        const response = await fetch(`${API_BASE}/vms/${vmName}/stats`);
+        const data = await response.json();
+        
+        if (data.error || data.state === 'error') {
+            console.log('VM not running or stats not available');
+            return;
+        }
+        
+        const timestamp = new Date().toLocaleTimeString('es-ES', { 
+            hour: '2-digit', 
+            minute: '2-digit',
+            second: '2-digit'
+        });
+        
+        // Update history
+        statsHistory.cpu.push({
+            time: timestamp,
+            value: data.cpu_percent
+        });
+        statsHistory.memory.push({
+            time: timestamp,
+            value: data.memory_percent
+        });
+        
+        // Keep only last maxPoints
+        if (statsHistory.cpu.length > statsHistory.maxPoints) {
+            statsHistory.cpu.shift();
+            statsHistory.memory.shift();
+        }
+        
+        // Update CPU Chart
+        if (cpuChart) {
+            cpuChart.data.labels = statsHistory.cpu.map(s => s.time);
+            cpuChart.data.datasets[0].data = statsHistory.cpu.map(s => s.value);
+            cpuChart.update('none');
+        } else {
+            createCPUChart();
+        }
+        
+        // Update Memory Chart
+        if (memoryChart) {
+            memoryChart.data.labels = statsHistory.memory.map(s => s.time);
+            memoryChart.data.datasets[0].data = statsHistory.memory.map(s => s.value);
+            memoryChart.update('none');
+        } else {
+            createMemoryChart();
+        }
+        
+    } catch (error) {
+        console.error('Error updating stats:', error);
+    }
+}
+
+// Create CPU chart
+function createCPUChart() {
+    const ctx = document.getElementById('cpuChart');
+    if (!ctx) return;
+    
+    // Destroy existing chart if any
+    if (cpuChart) cpuChart.destroy();
+    
+    cpuChart = new Chart(ctx, {
+        type: 'line',
+        data: {
+            labels: statsHistory.cpu.map(s => s.time),
+            datasets: [{
+                label: 'CPU (%)',
+                data: statsHistory.cpu.map(s => s.value),
+                borderColor: '#667eea',
+                backgroundColor: 'rgba(102, 126, 234, 0.1)',
+                tension: 0.4,
+                fill: true,
+                borderWidth: 2,
+                pointRadius: 4,
+                pointBackgroundColor: '#667eea',
+                pointBorderColor: '#fff',
+                pointBorderWidth: 2,
+                pointHoverRadius: 6
+            }],
+        },
+        options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            plugins: {
+                legend: {
+                    display: true,
+                    labels: {
+                        font: { size: 12 },
+                        padding: 15,
+                        color: '#666'
+                    }
+                },
+                tooltip: {
+                    backgroundColor: 'rgba(0,0,0,0.8)',
+                    padding: 12,
+                    titleFont: { size: 12 },
+                    bodyFont: { size: 12 },
+                    callbacks: {
+                        label: function(context) {
+                            return context.dataset.label + ': ' + context.parsed.y.toFixed(2) + '%';
+                        }
+                    }
+                }
+            },
+            scales: {
+                y: {
+                    beginAtZero: true,
+                    max: 100,
+                    ticks: {
+                        stepSize: 25,
+                        callback: function(value) {
+                            return value + '%';
+                        }
+                    },
+                    grid: {
+                        color: 'rgba(0,0,0,0.05)'
+                    }
+                },
+                x: {
+                    grid: {
+                        color: 'rgba(0,0,0,0.05)'
+                    }
+                }
+            }
+        }
+    });
+}
+
+// Create Memory chart
+function createMemoryChart() {
+    const ctx = document.getElementById('memoryChart');
+    if (!ctx) return;
+    
+    // Destroy existing chart if any
+    if (memoryChart) memoryChart.destroy();
+    
+    memoryChart = new Chart(ctx, {
+        type: 'line',
+        data: {
+            labels: statsHistory.memory.map(s => s.time),
+            datasets: [{
+                label: 'Memoria (%)',
+                data: statsHistory.memory.map(s => s.value),
+                borderColor: '#764ba2',
+                backgroundColor: 'rgba(118, 75, 162, 0.1)',
+                tension: 0.4,
+                fill: true,
+                borderWidth: 2,
+                pointRadius: 4,
+                pointBackgroundColor: '#764ba2',
+                pointBorderColor: '#fff',
+                pointBorderWidth: 2,
+                pointHoverRadius: 6
+            }],
+        },
+        options: {
+            responsive: true,
+            maintainAspectRatio: false,
+            plugins: {
+                legend: {
+                    display: true,
+                    labels: {
+                        font: { size: 12 },
+                        padding: 15,
+                        color: '#666'
+                    }
+                },
+                tooltip: {
+                    backgroundColor: 'rgba(0,0,0,0.8)',
+                    padding: 12,
+                    titleFont: { size: 12 },
+                    bodyFont: { size: 12 },
+                    callbacks: {
+                        label: function(context) {
+                            return context.dataset.label + ': ' + context.parsed.y.toFixed(2) + '%';
+                        }
+                    }
+                }
+            },
+            scales: {
+                y: {
+                    beginAtZero: true,
+                    max: 100,
+                    ticks: {
+                        stepSize: 25,
+                        callback: function(value) {
+                            return value + '%';
+                        }
+                    },
+                    grid: {
+                        color: 'rgba(0,0,0,0.05)'
+                    }
+                },
+                x: {
+                    grid: {
+                        color: 'rgba(0,0,0,0.05)'
+                    }
+                }
+            }
+        }
+    });
+}
+
+// Clean up charts when switching VMs
+function cleanupCharts() {
+    if (statsInterval) {
+        clearInterval(statsInterval);
+        statsInterval = null;
+    }
+    
+    if (cpuChart) {
+        cpuChart.destroy();
+        cpuChart = null;
+    }
+    
+    if (memoryChart) {
+        memoryChart.destroy();
+        memoryChart = null;
+    }
+    
+    // Reset history
+    statsHistory.cpu = [];
+    statsHistory.memory = [];
+}
+
+

+ 1 - 0
static/index.html

@@ -6,6 +6,7 @@
     <title>VirtManager Dashboard</title>
     <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
     <link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css" rel="stylesheet">
+    <script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
     <style>
         * {
             margin: 0;