diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index 9ea4d54..c033ee8 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -64,11 +64,19 @@ export default function Hardware() { const [selectedDisk, setSelectedDisk] = useState(null) const [selectedNetwork, setSelectedNetwork] = useState(null) - const { data: realtimeGPUData } = useSWR(selectedGPU ? `/api/gpu/${selectedGPU.slot}/realtime` : null, fetcher, { - refreshInterval: 2000, // Update every 2 seconds + const realtimeURL = selectedGPU ? `/api/gpu/${selectedGPU.slot}/realtime` : null + console.log("[v0] GPU modal opened, selectedGPU:", selectedGPU) + console.log("[v0] Realtime URL:", realtimeURL) + + const { data: realtimeGPUData, error: realtimeError } = useSWR(realtimeURL, fetcher, { + refreshInterval: 2000, revalidateOnFocus: false, }) + console.log("[v0] Realtime GPU data:", realtimeGPUData) + console.log("[v0] Realtime GPU error:", realtimeError) + // + const findPCIDeviceForGPU = (gpu: GPU): PCIDevice | null => { if (!hardwareData?.pci_devices || !gpu.slot) return null diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index 05c4763..2efd8fe 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1630,20 +1630,95 @@ def get_detailed_gpu_info(gpu): except Exception as e: print(f"[v0] Error getting NVIDIA GPU details: {e}") - # Intel GPU - use intel_gpu_top elif vendor == 'INTEL': try: - # intel_gpu_top requires root, so we'll try to get basic info + # Try to run intel_gpu_top with JSON output for 1 second result = subprocess.run( - ['intel_gpu_top', '-l'], - capture_output=True, text=True, timeout=2 + ['timeout', '2', 'intel_gpu_top', '-J'], + capture_output=True, text=True, timeout=3 ) - if result.returncode == 0: - # Parse intel_gpu_top output - # This is a simplified version, actual parsing would be more complex - detailed_info['note'] = 'Intel GPU monitoring requires root access' + + if result.returncode == 0 or result.returncode == 124: # 124 is timeout exit code + output = result.stdout.strip() + if output: + try: + # Parse JSON output + import json + data = json.loads(output) + + # Extract frequency info + if 'frequency' in data: + freq = data['frequency'] + if 'actual' in freq: + detailed_info['clock_graphics'] = f"{freq['actual']} MHz" + + # Extract power info + if 'power' in data: + power = data['power'] + if 'GPU' in power: + detailed_info['power_draw'] = f"{power['GPU']:.2f} W" + + # Extract engine utilization + if 'engines' in data: + engines = data['engines'] + total_busy = 0 + engine_count = 0 + + for engine_name, engine_data in engines.items(): + if 'busy' in engine_data: + total_busy += engine_data['busy'] + engine_count += 1 + + if engine_count > 0: + detailed_info['utilization_gpu'] = int(total_busy / engine_count) + + print(f"[v0] Intel GPU data parsed from JSON: {detailed_info}") + + except json.JSONDecodeError: + print(f"[v0] Failed to parse intel_gpu_top JSON output") + + # Fallback: Try text output parsing + if not detailed_info: + result = subprocess.run( + ['timeout', '2', 'intel_gpu_top', '-s', '1000'], + capture_output=True, text=True, timeout=3 + ) + + if result.returncode == 0 or result.returncode == 124: + output = result.stdout.strip() + print(f"[v0] Intel GPU top output: {output[:200]}") + + # Parse frequency (e.g., "0/ 0 MHz") + freq_match = re.search(r'(\d+)/\s*(\d+)\s*MHz', output) + if freq_match: + actual_freq = freq_match.group(1) + if int(actual_freq) > 0: + detailed_info['clock_graphics'] = f"{actual_freq} MHz" + + # Parse power (e.g., "0.00/ 7.23 W") + power_match = re.search(r'([\d.]+)/\s*([\d.]+)\s*W', output) + if power_match: + actual_power = float(power_match.group(1)) + max_power = float(power_match.group(2)) + if actual_power > 0 or max_power > 0: + detailed_info['power_draw'] = f"{actual_power:.2f} W" + detailed_info['power_limit'] = f"{max_power:.2f} W" + + # Parse engine utilization (e.g., "Render/3D 0.00%") + engine_lines = re.findall(r'(Render/3D|Blitter|Video|VideoEnhance)\s+([\d.]+)%', output) + if engine_lines: + total_util = sum(float(util) for _, util in engine_lines) + avg_util = total_util / len(engine_lines) + if avg_util > 0: + detailed_info['utilization_gpu'] = int(avg_util) + + print(f"[v0] Intel GPU data parsed from text: {detailed_info}") + except Exception as e: print(f"[v0] Error getting Intel GPU details: {e}") + import traceback + traceback.print_exc() + # # AMD GPU - use radeontop elif vendor == 'AMD': @@ -1682,172 +1757,6 @@ def get_pci_device_info(pci_slot): print(f"[v0] Error getting PCI device info for {pci_slot}: {e}") return pci_info -def get_gpu_info(): - """Get GPU information from lspci and enrich with temperature/fan data from sensors""" - gpus = [] - - try: - result = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - for line in result.stdout.split('\n'): - # Match VGA, 3D, Display controllers - if any(keyword in line for keyword in ['VGA compatible controller', '3D controller', 'Display controller']): - parts = line.split(':', 2) - if len(parts) >= 3: - slot = parts[0].strip() - gpu_name = parts[2].strip() - - # Determine vendor - vendor = 'Unknown' - if 'NVIDIA' in gpu_name or 'nVidia' in gpu_name: - vendor = 'NVIDIA' - elif 'AMD' in gpu_name or 'ATI' in gpu_name or 'Radeon' in gpu_name: - vendor = 'AMD' - elif 'Intel' in gpu_name: - vendor = 'Intel' - - gpu = { - 'slot': slot, - 'name': gpu_name, - 'vendor': vendor, - 'type': 'Discrete' if vendor in ['NVIDIA', 'AMD'] else 'Integrated' - } - - pci_info = get_pci_device_info(slot) - if pci_info: - gpu['pci_class'] = pci_info.get('class', '') - gpu['pci_driver'] = pci_info.get('driver', '') - gpu['pci_kernel_module'] = pci_info.get('kernel_module', '') - - detailed_info = get_detailed_gpu_info(gpu) - gpu.update(detailed_info) - - gpus.append(gpu) - print(f"[v0] Found GPU: {gpu_name} ({vendor}) at slot {slot}") - except Exception as e: - print(f"[v0] Error detecting GPUs from lspci: {e}") - - try: - result = subprocess.run(['sensors'], capture_output=True, text=True, timeout=5) - if result.returncode == 0: - current_adapter = None - - for line in result.stdout.split('\n'): - line = line.strip() - if not line: - continue - - # Detect adapter line - if line.startswith('Adapter:'): - current_adapter = line.replace('Adapter:', '').strip() - continue - - # Look for GPU-related sensors (nouveau, amdgpu, radeon, i915, etc.) - if ':' in line and not line.startswith(' '): - parts = line.split(':', 1) - sensor_name = parts[0].strip() - value_part = parts[1].strip() - - # Check if this is a GPU sensor - gpu_sensor_keywords = ['nouveau', 'amdgpu', 'radeon', 'i915'] - is_gpu_sensor = any(keyword in current_adapter.lower() if current_adapter else False for keyword in gpu_sensor_keywords) - - if is_gpu_sensor: - # Try to match this sensor to a GPU - for gpu in gpus: - # Match nouveau to NVIDIA, amdgpu/radeon to AMD, i915 to Intel - if (('nouveau' in current_adapter.lower() and gpu['vendor'] == 'NVIDIA') or - (('amdgpu' in current_adapter.lower() or 'radeon' in current_adapter.lower()) and gpu['vendor'] == 'AMD') or - ('i915' in current_adapter.lower() and gpu['vendor'] == 'Intel')): - - # Parse temperature (only if not already set by nvidia-smi) - if '°C' in value_part or 'C' in value_part: - if 'temperature' not in gpu or gpu['temperature'] is None: - temp_match = re.search(r'([+-]?[\d.]+)\s*°?C', value_part) - if temp_match: - gpu['temperature'] = float(temp_match.group(1)) - print(f"[v0] GPU {gpu['name']}: Temperature = {gpu['temperature']}°C") - - # Parse fan speed - elif 'RPM' in value_part: - rpm_match = re.search(r'([\d.]+)\s*RPM', value_part) - if rpm_match: - gpu['fan_speed'] = int(float(rpm_match.group(1))) - gpu['fan_unit'] = 'RPM' - print(f"[v0] GPU {gpu['name']}: Fan = {gpu['fan_speed']} RPM") - except Exception as e: - print(f"[v0] Error enriching GPU data from sensors: {e}") - - return gpus - -def get_disk_hardware_info(disk_name): - """Get detailed hardware information for a disk""" - disk_info = {} - - try: - # Get disk type (HDD, SSD, NVMe) - result = subprocess.run(['lsblk', '-d', '-n', '-o', 'NAME,ROTA,TYPE', f'/dev/{disk_name}'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0: - parts = result.stdout.strip().split() - if len(parts) >= 2: - rota = parts[1] - disk_info['type'] = 'HDD' if rota == '1' else 'SSD' - if disk_name.startswith('nvme'): - disk_info['type'] = 'NVMe SSD' - - # Get driver/kernel module - try: - # For NVMe - if disk_name.startswith('nvme'): - disk_info['driver'] = 'nvme' - disk_info['interface'] = 'PCIe/NVMe' - # For SATA/SAS - else: - result = subprocess.run(['udevadm', 'info', '--query=property', f'/dev/{disk_name}'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0: - for line in result.stdout.split('\n'): - if 'ID_BUS=' in line: - bus = line.split('=')[1].strip() - disk_info['interface'] = bus.upper() - if 'ID_MODEL=' in line: - model = line.split('=')[1].strip() - disk_info['model'] = model - if 'ID_SERIAL_SHORT=' in line: - serial = line.split('=')[1].strip() - disk_info['serial'] = serial - except Exception as e: - print(f"[v0] Error getting disk driver info: {e}") - - # Get SMART data - try: - result = subprocess.run(['smartctl', '-i', f'/dev/{disk_name}'], - capture_output=True, text=True, timeout=5) - if result.returncode == 0: - for line in result.stdout.split('\n'): - if 'Model Family:' in line: - disk_info['family'] = line.split(':', 1)[1].strip() - elif 'Device Model:' in line or 'Model Number:' in line: - disk_info['model'] = line.split(':', 1)[1].strip() - elif 'Serial Number:' in line: - disk_info['serial'] = line.split(':', 1)[1].strip() - elif 'Firmware Version:' in line: - disk_info['firmware'] = line.split(':', 1)[1].strip() - elif 'Rotation Rate:' in line: - disk_info['rotation_rate'] = line.split(':', 1)[1].strip() - elif 'Form Factor:' in line: - disk_info['form_factor'] = line.split(':', 1)[1].strip() - elif 'SATA Version is:' in line: - disk_info['sata_version'] = line.split(':', 1)[1].strip() - except Exception as e: - print(f"[v0] Error getting SMART info: {e}") - - except Exception as e: - print(f"[v0] Error getting disk hardware info: {e}") - - return disk_info - def get_network_hardware_info(pci_slot): """Get detailed hardware information for a network interface""" net_info = {} @@ -2075,7 +1984,7 @@ def get_hardware_info(): hardware_data['graphics_cards'].append({ 'name': parts[0].strip(), 'memory': parts[1].strip(), - 'temperature': int(parts[2].strip().split(' ')[0]) if parts[2].strip() != 'N/A' and 'C' in parts[2] else 0, + 'temperature': int(float(parts[2].strip().split(' ')[0])) if parts[2].strip() != 'N/A' and 'C' in parts[2] else 0, 'power_draw': parts[3].strip(), 'vendor': 'NVIDIA' }) @@ -2692,3 +2601,4 @@ if __name__ == '__main__': print("API endpoints available at: /api/system, /api/storage, /api/network, /api/vms, /api/logs, /api/health, /api/hardware") app.run(host='0.0.0.0', port=8008, debug=False) +8, debug=False)