From 810ac1fcfa75905890c0893d694d60f247a994ac Mon Sep 17 00:00:00 2001 From: MacRimi Date: Mon, 6 Oct 2025 17:25:08 +0200 Subject: [PATCH] Update AppImage --- AppImage/components/hardware-monitor.tsx | 114 +++++++ AppImage/components/hardware.tsx | 297 ++++++++++++------ AppImage/scripts/flask_server.py | 105 ++++++- AppImage/scripts/hardware_monitor.py | 369 +++++++++++++++++++++++ 4 files changed, 797 insertions(+), 88 deletions(-) create mode 100644 AppImage/components/hardware-monitor.tsx create mode 100644 AppImage/scripts/hardware_monitor.py diff --git a/AppImage/components/hardware-monitor.tsx b/AppImage/components/hardware-monitor.tsx new file mode 100644 index 0000000..ca3a049 --- /dev/null +++ b/AppImage/components/hardware-monitor.tsx @@ -0,0 +1,114 @@ +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card" +import { Cpu } from "@/components/icons/cpu" // Added import for Cpu +import type { PCIDevice } from "@/types/pcidevice" // Added import for PCIDevice +import { Progress } from "@/components/ui/progress" + +function GPUCard({ device }: { device: PCIDevice }) { + const hasMonitoring = device.gpu_temperature !== undefined || device.gpu_utilization !== undefined + + return ( + + + + + {device.device} + + {device.vendor} + + +
+
+
Slot
+
{device.slot}
+
+ {device.driver && ( +
+
Driver
+
{device.driver}
+
+ )} + {device.gpu_driver_version && ( +
+
Driver Version
+
{device.gpu_driver_version}
+
+ )} + {device.gpu_memory && ( +
+
Memory
+
{device.gpu_memory}
+
+ )} + {device.gpu_compute_capability && ( +
+
Compute Capability
+
{device.gpu_compute_capability}
+
+ )} +
+ + {hasMonitoring && ( +
+

Real-time Monitoring

+ + {device.gpu_temperature !== undefined && ( +
+
+ Temperature + {device.gpu_temperature}°C +
+ +
+ )} + + {device.gpu_utilization !== undefined && ( +
+
+ GPU Utilization + {device.gpu_utilization}% +
+ +
+ )} + + {device.gpu_memory_used && device.gpu_memory_total && ( +
+
+ Memory Usage + + {device.gpu_memory_used} / {device.gpu_memory_total} + +
+ +
+ )} + + {device.gpu_power_draw && ( +
+ Power Draw + {device.gpu_power_draw} +
+ )} + + {device.gpu_clock_speed && ( +
+ GPU Clock + {device.gpu_clock_speed} +
+ )} + + {device.gpu_memory_clock && ( +
+ Memory Clock + {device.gpu_memory_clock} +
+ )} +
+ )} +
+
+ ) +} diff --git a/AppImage/components/hardware.tsx b/AppImage/components/hardware.tsx index 10d3afe..4cc1e79 100644 --- a/AppImage/components/hardware.tsx +++ b/AppImage/components/hardware.tsx @@ -15,6 +15,8 @@ import { PowerIcon, Battery, Cpu, + MemoryStick, + Cpu as Gpu, } from "lucide-react" import useSWR from "swr" import { useState } from "react" @@ -46,7 +48,7 @@ export default function Hardware() { return (
- {/* CPU & Motherboard Info */} + {/* System Information - CPU & Motherboard */} {(hardwareData?.cpu || hardwareData?.motherboard) && (
@@ -57,43 +59,43 @@ export default function Hardware() {
{/* CPU Info */} {hardwareData?.cpu && Object.keys(hardwareData.cpu).length > 0 && ( -
-

Processor

-
+
+
+ +

CPU

+
+
{hardwareData.cpu.model && ( -
- Model - {hardwareData.cpu.model} +
+ Model + {hardwareData.cpu.model}
)} - {hardwareData.cpu.sockets && ( -
- Sockets - {hardwareData.cpu.sockets} -
- )} - {hardwareData.cpu.cores_per_socket && ( -
- Cores per Socket - {hardwareData.cpu.cores_per_socket} + {hardwareData.cpu.cores_per_socket && hardwareData.cpu.sockets && ( +
+ Cores + + {hardwareData.cpu.sockets} × {hardwareData.cpu.cores_per_socket} ={" "} + {hardwareData.cpu.sockets * hardwareData.cpu.cores_per_socket} cores +
)} {hardwareData.cpu.total_threads && ( -
- Total Threads - {hardwareData.cpu.total_threads} +
+ Threads + {hardwareData.cpu.total_threads}
)} - {hardwareData.cpu.current_mhz && ( -
- Current Speed - {hardwareData.cpu.current_mhz.toFixed(0)} MHz + {hardwareData.cpu.l3_cache && ( +
+ L3 Cache + {hardwareData.cpu.l3_cache}
)} - {hardwareData.cpu.max_mhz && ( -
- Max Speed - {hardwareData.cpu.max_mhz.toFixed(0)} MHz + {hardwareData.cpu.virtualization && ( +
+ Virtualization + {hardwareData.cpu.virtualization}
)}
@@ -102,51 +104,41 @@ export default function Hardware() { {/* Motherboard Info */} {hardwareData?.motherboard && Object.keys(hardwareData.motherboard).length > 0 && ( -
-

Motherboard

-
+
+
+ +

Motherboard

+
+
{hardwareData.motherboard.manufacturer && ( -
- Manufacturer - {hardwareData.motherboard.manufacturer} +
+ Manufacturer + {hardwareData.motherboard.manufacturer}
)} {hardwareData.motherboard.model && ( -
- Model - {hardwareData.motherboard.model} +
+ Model + {hardwareData.motherboard.model}
)} - {hardwareData.motherboard.version && ( -
- Version - {hardwareData.motherboard.version} + {hardwareData.motherboard.bios?.vendor && ( +
+ BIOS + {hardwareData.motherboard.bios.vendor}
)} - {hardwareData.motherboard.bios && ( - <> -
- BIOS -
- {hardwareData.motherboard.bios.vendor && ( -
- Vendor - {hardwareData.motherboard.bios.vendor} -
- )} - {hardwareData.motherboard.bios.version && ( -
- Version - {hardwareData.motherboard.bios.version} -
- )} - {hardwareData.motherboard.bios.date && ( -
- Date - {hardwareData.motherboard.bios.date} -
- )} - + {hardwareData.motherboard.bios?.version && ( +
+ Version + {hardwareData.motherboard.bios.version} +
+ )} + {hardwareData.motherboard.bios?.date && ( +
+ Date + {hardwareData.motherboard.bios.date} +
)}
@@ -155,7 +147,54 @@ export default function Hardware() { )} - {/* Thermal Monitoring */} + {/* Memory Modules */} + {hardwareData?.memory_modules && hardwareData.memory_modules.length > 0 && ( + +
+ +

Memory Modules

+ + {hardwareData.memory_modules.length} installed + +
+ +
+ {hardwareData.memory_modules.map((module, index) => ( +
+
{module.slot}
+
+ {module.size && ( +
+ Size + {module.size} +
+ )} + {module.type && ( +
+ Type + {module.type} +
+ )} + {module.speed && ( +
+ Speed + {module.speed} +
+ )} + {module.manufacturer && ( +
+ Manufacturer + {module.manufacturer} +
+ )} +
+
+ ))} +
+
+ )} + + {/* Thermal Monitoring - Restored blue progress bars */} {hardwareData?.temperatures && hardwareData.temperatures.length > 0 && (
@@ -182,7 +221,12 @@ export default function Hardware() { {temp.current.toFixed(1)}°C
- +
+
+
{temp.adapter && {temp.adapter}}
) @@ -191,6 +235,89 @@ export default function Hardware() {
)} + {/* GPU Information - New dedicated GPU section */} + {hardwareData?.gpus && hardwareData.gpus.length > 0 && ( + +
+ +

Graphics Cards

+ + {hardwareData.gpus.length} GPU{hardwareData.gpus.length > 1 ? "s" : ""} + +
+ +
+ {hardwareData.gpus.map((gpu, index) => ( +
+
+ {gpu.name} + {gpu.vendor} +
+ +
+ {gpu.memory_total && ( +
+ Memory + + {gpu.memory_used} / {gpu.memory_total} + +
+ )} + + {gpu.temperature !== undefined && gpu.temperature > 0 && ( +
+
+ Temperature + {gpu.temperature}°C +
+
+
+
+
+ )} + + {gpu.utilization !== undefined && ( +
+
+ Utilization + {gpu.utilization}% +
+
+
+
+
+ )} + + {gpu.power_draw && gpu.power_draw !== "N/A" && ( +
+ Power Draw + {gpu.power_draw} +
+ )} + + {gpu.driver_version && ( +
+ Driver + {gpu.driver_version} +
+ )} + + {gpu.type && ( +
+ Type + {gpu.type} +
+ )} +
+
+ ))} +
+ + )} + {/* PCI Devices */} {hardwareData?.pci_devices && hardwareData.pci_devices.length > 0 && ( @@ -277,7 +404,7 @@ export default function Hardware() { )} - {/* Power Consumption */} + {/* Power Consumption - Only show if data exists */} {hardwareData?.power_meter && (
@@ -302,7 +429,7 @@ export default function Hardware() { )} - {/* Fans */} + {/* Fans - Only show if data exists */} {hardwareData?.fans && hardwareData.fans.length > 0 && (
@@ -326,7 +453,9 @@ export default function Hardware() { {fan.speed.toFixed(0)} {fan.unit}
- +
+
+
) })} @@ -334,7 +463,7 @@ export default function Hardware() {
)} - {/* Power Supplies */} + {/* Power Supplies - Only show if data exists */} {hardwareData?.power_supplies && hardwareData.power_supplies.length > 0 && (
@@ -362,8 +491,8 @@ export default function Hardware() { )} - {/* UPS - Solo mostrar si hay datos */} - {hardwareData?.ups && Object.keys(hardwareData.ups).length > 0 && ( + {/* UPS - Only show if data exists and has content */} + {hardwareData?.ups && Object.keys(hardwareData.ups).length > 0 && hardwareData.ups.model && (
@@ -373,7 +502,7 @@ export default function Hardware() {
- {hardwareData.ups.model || "UPS"} + {hardwareData.ups.model} {hardwareData.ups.status} @@ -439,11 +568,11 @@ export default function Hardware() { .filter((d) => d.type.toLowerCase().includes("network")) .map((device, index) => (
-
- {device.device} - Ethernet +
+ {device.device} + Ethernet
-

{device.vendor}

+

{device.vendor}

))}
@@ -453,7 +582,7 @@ export default function Hardware() { )} - {/* Storage Summary */} + {/* Storage Summary - Larger badges with blue color */} {hardwareData?.storage_devices && hardwareData.storage_devices.length > 0 && (
@@ -467,14 +596,12 @@ export default function Hardware() {
{hardwareData.storage_devices.map((device, index) => (
-
+
{device.name} - - {device.type} - + {device.type}
- {device.size &&

{device.size}

} - {device.model &&

{device.model}

} + {device.size &&

{device.size}

} + {device.model &&

{device.model}

}
))}
diff --git a/AppImage/scripts/flask_server.py b/AppImage/scripts/flask_server.py index a9ee901..d89e4d4 100644 --- a/AppImage/scripts/flask_server.py +++ b/AppImage/scripts/flask_server.py @@ -1511,6 +1511,95 @@ def get_temperature_info(): 'power_meter': power_meter } +def get_gpu_info(): + """Get GPU information from nvidia-smi, intel_gpu_top, and radeontop""" + gpus = [] + + # Try NVIDIA GPUs + try: + result = subprocess.run( + ['nvidia-smi', '--query-gpu=index,name,memory.total,memory.used,memory.free,temperature.gpu,power.draw,utilization.gpu,driver_version', + '--format=csv,noheader,nounits'], + capture_output=True, text=True, timeout=5 + ) + if result.returncode == 0: + for line in result.stdout.strip().split('\n'): + if line: + parts = [p.strip() for p in line.split(',')] + if len(parts) >= 9: + gpus.append({ + 'index': int(parts[0]), + 'name': parts[1], + 'vendor': 'NVIDIA', + 'memory_total': f"{parts[2]} MB", + 'memory_used': f"{parts[3]} MB", + 'memory_free': f"{parts[4]} MB", + 'temperature': int(float(parts[5])) if parts[5] != '[N/A]' else 0, + 'power_draw': f"{parts[6]} W" if parts[6] != '[N/A]' else 'N/A', + 'utilization': int(float(parts[7])) if parts[7] != '[N/A]' else 0, + 'driver_version': parts[8] + }) + print(f"[v0] Found {len(gpus)} NVIDIA GPU(s)") + except FileNotFoundError: + print("[v0] nvidia-smi not found") + except Exception as e: + print(f"[v0] Error getting NVIDIA GPU info: {e}") + + # Try Intel GPUs + try: + result = subprocess.run(['intel_gpu_top', '-l'], capture_output=True, text=True, timeout=2) + if result.returncode == 0: + # Parse intel_gpu_top output + # This is a simplified version - intel_gpu_top output is complex + gpu_found = False + for line in result.stdout.split('\n'): + if 'intel' in line.lower() or 'gpu' in line.lower(): + gpu_found = True + break + + if gpu_found: + # Get more info from lspci + result_lspci = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5) + if result_lspci.returncode == 0: + for line in result_lspci.stdout.split('\n'): + if 'Intel' in line and any(keyword in line for keyword in ['VGA', 'Display', 'Graphics']): + parts = line.split(':', 2) + if len(parts) >= 3: + gpus.append({ + 'name': parts[2].strip(), + 'vendor': 'Intel', + 'type': 'Integrated' + }) + print(f"[v0] Found Intel GPU: {parts[2].strip()}") + except FileNotFoundError: + print("[v0] intel_gpu_top not found") + except Exception as e: + print(f"[v0] Error getting Intel GPU info: {e}") + + # Try AMD GPUs + try: + result = subprocess.run(['radeontop', '-d', '-', '-l', '1'], capture_output=True, text=True, timeout=3) + if result.returncode == 0: + # Get AMD GPU info from lspci + result_lspci = subprocess.run(['lspci'], capture_output=True, text=True, timeout=5) + if result_lspci.returncode == 0: + for line in result_lspci.stdout.split('\n'): + if any(keyword in line for keyword in ['AMD', 'ATI', 'Radeon']) and any(keyword in line for keyword in ['VGA', 'Display', 'Graphics']): + parts = line.split(':', 2) + if len(parts) >= 3: + gpus.append({ + 'name': parts[2].strip(), + 'vendor': 'AMD', + 'type': 'Discrete' + }) + print(f"[v0] Found AMD GPU: {parts[2].strip()}") + except FileNotFoundError: + print("[v0] radeontop not found") + except Exception as e: + print(f"[v0] Error getting AMD GPU info: {e}") + + return gpus + def get_hardware_info(): """Get comprehensive hardware information""" try: @@ -1522,6 +1611,7 @@ def get_hardware_info(): 'storage_devices': [], 'network_cards': [], 'graphics_cards': [], + 'gpus': [], # Added dedicated GPU array 'pci_devices': [], 'sensors': { 'temperatures': [], @@ -1665,7 +1755,7 @@ def get_hardware_info(): hardware_data['graphics_cards'].append({ 'name': parts[0].strip(), 'memory': parts[1].strip(), - 'temperature': int(parts[2].strip()) if parts[2].strip().isdigit() else 0, + 'temperature': int(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' }) @@ -1675,7 +1765,7 @@ def get_hardware_info(): if result.returncode == 0: for line in result.stdout.split('\n'): # Match VGA, 3D, Display controllers, and specific GPU keywords - if any(keyword in line for keyword in ['VGA compatible controller', '3D controller', 'Display controller', 'Graphics', 'GPU']): + if any(keyword in line for keyword in ['VGA compatible controller', '3D controller', 'Display controller', 'Graphics controller']): parts = line.split(':', 2) if len(parts) >= 3: gpu_name = parts[2].strip() @@ -1694,6 +1784,9 @@ def get_hardware_info(): for existing_gpu in hardware_data['graphics_cards']: if gpu_name in existing_gpu['name'] or existing_gpu['name'] in gpu_name: already_exists = True + # Update vendor if it was previously unknown + if existing_gpu['vendor'] == 'Unknown': + existing_gpu['vendor'] = vendor break if not already_exists: @@ -1888,6 +1981,10 @@ def get_hardware_info(): if ups_info: hardware_data['ups'] = ups_info + gpus = get_gpu_info() + if gpus: + hardware_data['gpus'] = gpus + return hardware_data except Exception as e: @@ -2051,7 +2148,8 @@ def api_hardware(): 'fans': hardware_info.get('ipmi_fans', []), 'power_supplies': hardware_info.get('ipmi_power', {}).get('power_supplies', []), 'power_meter': hardware_info.get('power_meter'), - 'ups': hardware_info.get('ups') if hardware_info.get('ups') else None + 'ups': hardware_info.get('ups') if hardware_info.get('ups') else None, + 'gpus': hardware_info.get('gpus', []) # Include GPU data } print(f"[v0] /api/hardware returning data") @@ -2061,6 +2159,7 @@ def api_hardware(): print(f"[v0] - Power supplies: {len(formatted_data['power_supplies'])} PSUs") print(f"[v0] - Power meter: {'Yes' if formatted_data['power_meter'] else 'No'}") print(f"[v0] - UPS: {'Yes' if formatted_data['ups'] else 'No'}") + print(f"[v0] - GPUs: {len(formatted_data['gpus'])} found") return jsonify(formatted_data) diff --git a/AppImage/scripts/hardware_monitor.py b/AppImage/scripts/hardware_monitor.py new file mode 100644 index 0000000..2f77284 --- /dev/null +++ b/AppImage/scripts/hardware_monitor.py @@ -0,0 +1,369 @@ +#!/usr/bin/env python3 +import json +import subprocess +import re +import os +from typing import Dict, List, Any, Optional + +def run_command(cmd: List[str]) -> str: + """Run a command and return its output.""" + try: + result = subprocess.run(cmd, capture_output=True, text=True, timeout=5) + return result.stdout + except Exception: + return "" + +def get_nvidia_gpu_info() -> List[Dict[str, Any]]: + """Get detailed NVIDIA GPU information using nvidia-smi.""" + gpus = [] + + # Check if nvidia-smi is available + if not os.path.exists('/usr/bin/nvidia-smi'): + return gpus + + try: + # Query all GPU metrics at once + query_fields = [ + 'index', + 'name', + 'driver_version', + 'memory.total', + 'memory.used', + 'memory.free', + 'temperature.gpu', + 'utilization.gpu', + 'utilization.memory', + 'power.draw', + 'power.limit', + 'clocks.current.graphics', + 'clocks.current.memory', + 'pcie.link.gen.current', + 'pcie.link.width.current' + ] + + cmd = ['nvidia-smi', '--query-gpu=' + ','.join(query_fields), '--format=csv,noheader,nounits'] + output = run_command(cmd) + + if not output: + return gpus + + for line in output.strip().split('\n'): + if not line: + continue + + values = [v.strip() for v in line.split(',')] + if len(values) < len(query_fields): + continue + + gpu_info = { + 'index': values[0], + 'name': values[1], + 'driver_version': values[2], + 'memory_total': f"{values[3]} MiB", + 'memory_used': f"{values[4]} MiB", + 'memory_free': f"{values[5]} MiB", + 'temperature': values[6], + 'utilization_gpu': values[7], + 'utilization_memory': values[8], + 'power_draw': f"{values[9]} W", + 'power_limit': f"{values[10]} W", + 'clock_graphics': f"{values[11]} MHz", + 'clock_memory': f"{values[12]} MHz", + 'pcie_gen': values[13], + 'pcie_width': f"x{values[14]}" + } + + # Get CUDA version if available + cuda_output = run_command(['nvidia-smi', '--query-gpu=compute_cap', '--format=csv,noheader', '-i', values[0]]) + if cuda_output: + gpu_info['compute_capability'] = cuda_output.strip() + + gpus.append(gpu_info) + + except Exception as e: + print(f"Error getting NVIDIA GPU info: {e}", file=sys.stderr) + + return gpus + +def get_amd_gpu_info() -> List[Dict[str, Any]]: + """Get AMD GPU information using rocm-smi.""" + gpus = [] + + # Check if rocm-smi is available + if not os.path.exists('/opt/rocm/bin/rocm-smi'): + return gpus + + try: + # Get basic GPU info + output = run_command(['/opt/rocm/bin/rocm-smi', '--showid', '--showtemp', '--showuse', '--showmeminfo', 'vram']) + + if not output: + return gpus + + # Parse rocm-smi output (format varies, this is a basic parser) + current_gpu = None + for line in output.split('\n'): + if 'GPU[' in line: + if current_gpu: + gpus.append(current_gpu) + current_gpu = {'index': line.split('[')[1].split(']')[0]} + elif current_gpu: + if 'Temperature' in line: + temp_match = re.search(r'(\d+\.?\d*)', line) + if temp_match: + current_gpu['temperature'] = temp_match.group(1) + elif 'GPU use' in line: + use_match = re.search(r'(\d+)%', line) + if use_match: + current_gpu['utilization_gpu'] = use_match.group(1) + elif 'VRAM' in line: + mem_match = re.search(r'(\d+)MB / (\d+)MB', line) + if mem_match: + current_gpu['memory_used'] = f"{mem_match.group(1)} MiB" + current_gpu['memory_total'] = f"{mem_match.group(2)} MiB" + + if current_gpu: + gpus.append(current_gpu) + + except Exception as e: + print(f"Error getting AMD GPU info: {e}", file=sys.stderr) + + return gpus + +def get_temperatures() -> List[Dict[str, Any]]: + """Get temperature readings from sensors.""" + temps = [] + output = run_command(['sensors', '-A', '-u']) + + current_adapter = None + current_sensor = None + + for line in output.split('\n'): + line = line.strip() + if not line: + continue + + if line.endswith(':') and not line.startswith(' '): + current_adapter = line[:-1] + elif '_input:' in line and current_adapter: + parts = line.split(':') + if len(parts) == 2: + sensor_name = parts[0].replace('_input', '').replace('_', ' ').title() + try: + temp_value = float(parts[1].strip()) + temps.append({ + 'name': sensor_name, + 'current': round(temp_value, 1), + 'adapter': current_adapter + }) + except ValueError: + pass + + return temps + +def get_fans() -> List[Dict[str, Any]]: + """Get fan speed readings.""" + fans = [] + output = run_command(['sensors', '-A', '-u']) + + current_adapter = None + + for line in output.split('\n'): + line = line.strip() + if not line: + continue + + if line.endswith(':') and not line.startswith(' '): + current_adapter = line[:-1] + elif 'fan' in line.lower() and '_input:' in line and current_adapter: + parts = line.split(':') + if len(parts) == 2: + fan_name = parts[0].replace('_input', '').replace('_', ' ').title() + try: + speed = float(parts[1].strip()) + fans.append({ + 'name': fan_name, + 'speed': int(speed), + 'unit': 'RPM' + }) + except ValueError: + pass + + return fans + +def get_network_cards() -> List[Dict[str, Any]]: + """Get network interface information.""" + cards = [] + output = run_command(['ip', '-o', 'link', 'show']) + + for line in output.split('\n'): + if not line or 'lo:' in line: + continue + + parts = line.split() + if len(parts) >= 2: + name = parts[1].rstrip(':') + state = 'UP' if 'UP' in line else 'DOWN' + + # Get interface type + iface_type = 'Unknown' + if 'ether' in line: + iface_type = 'Ethernet' + elif 'wlan' in name or 'wifi' in name: + iface_type = 'WiFi' + + # Try to get speed + speed = None + speed_output = run_command(['ethtool', name]) + speed_match = re.search(r'Speed: (\d+\w+)', speed_output) + if speed_match: + speed = speed_match.group(1) + + cards.append({ + 'name': name, + 'type': iface_type, + 'status': state, + 'speed': speed + }) + + return cards + +def get_storage_devices() -> List[Dict[str, Any]]: + """Get storage device information.""" + devices = [] + output = run_command(['lsblk', '-d', '-o', 'NAME,TYPE,SIZE,MODEL', '-n']) + + for line in output.split('\n'): + if not line: + continue + + parts = line.split(None, 3) + if len(parts) >= 3: + name = parts[0] + dev_type = parts[1] + size = parts[2] + model = parts[3] if len(parts) > 3 else 'Unknown' + + if dev_type in ['disk', 'nvme']: + devices.append({ + 'name': name, + 'type': dev_type, + 'size': size, + 'model': model.strip() + }) + + return devices + +def get_pci_devices() -> List[Dict[str, Any]]: + """Get PCI device information including GPUs.""" + devices = [] + output = run_command(['lspci', '-vmm']) + + current_device = {} + + for line in output.split('\n'): + line = line.strip() + + if not line: + if current_device: + devices.append(current_device) + current_device = {} + continue + + if ':' in line: + key, value = line.split(':', 1) + key = key.strip().lower().replace(' ', '_') + value = value.strip() + current_device[key] = value + + if current_device: + devices.append(current_device) + + # Enhance GPU devices with monitoring data + nvidia_gpus = get_nvidia_gpu_info() + amd_gpus = get_amd_gpu_info() + + nvidia_idx = 0 + amd_idx = 0 + + for device in devices: + # Check if it's a GPU + device_class = device.get('class', '').lower() + vendor = device.get('vendor', '').lower() + + if 'vga' in device_class or 'display' in device_class or '3d' in device_class: + device['type'] = 'GPU' + + # Add NVIDIA GPU monitoring data + if 'nvidia' in vendor and nvidia_idx < len(nvidia_gpus): + gpu_data = nvidia_gpus[nvidia_idx] + device['gpu_memory'] = gpu_data.get('memory_total') + device['gpu_driver_version'] = gpu_data.get('driver_version') + device['gpu_compute_capability'] = gpu_data.get('compute_capability') + device['gpu_power_draw'] = gpu_data.get('power_draw') + device['gpu_temperature'] = float(gpu_data.get('temperature', 0)) + device['gpu_utilization'] = float(gpu_data.get('utilization_gpu', 0)) + device['gpu_memory_used'] = gpu_data.get('memory_used') + device['gpu_memory_total'] = gpu_data.get('memory_total') + device['gpu_clock_speed'] = gpu_data.get('clock_graphics') + device['gpu_memory_clock'] = gpu_data.get('clock_memory') + nvidia_idx += 1 + + # Add AMD GPU monitoring data + elif 'amd' in vendor and amd_idx < len(amd_gpus): + gpu_data = amd_gpus[amd_idx] + device['gpu_temperature'] = float(gpu_data.get('temperature', 0)) + device['gpu_utilization'] = float(gpu_data.get('utilization_gpu', 0)) + device['gpu_memory_used'] = gpu_data.get('memory_used') + device['gpu_memory_total'] = gpu_data.get('memory_total') + amd_idx += 1 + elif 'network' in device_class or 'ethernet' in device_class: + device['type'] = 'Network' + elif 'storage' in device_class or 'sata' in device_class or 'nvme' in device_class: + device['type'] = 'Storage' + else: + device['type'] = 'Other' + + return devices + +def get_power_info() -> Optional[Dict[str, Any]]: + """Get power consumption information if available.""" + # Try to get system power from RAPL (Running Average Power Limit) + rapl_path = '/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj' + + if os.path.exists(rapl_path): + try: + with open(rapl_path, 'r') as f: + energy_uj = int(f.read().strip()) + + # This is cumulative energy, would need to track over time for watts + # For now, just indicate power monitoring is available + return { + 'name': 'System Power', + 'watts': 0, # Would need time-based calculation + 'adapter': 'RAPL' + } + except Exception: + pass + + return None + +def main(): + """Main function to gather all hardware information.""" + data = { + 'temperatures': get_temperatures(), + 'fans': get_fans(), + 'network_cards': get_network_cards(), + 'storage_devices': get_storage_devices(), + 'pci_devices': get_pci_devices(), + } + + power_info = get_power_info() + if power_info: + data['power_meter'] = power_info + + print(json.dumps(data, indent=2)) + +if __name__ == '__main__': + import sys + main()