mirror of
https://github.com/MacRimi/ProxMenux.git
synced 2025-12-14 08:06:22 +00:00
Update AppImage
This commit is contained in:
114
AppImage/components/hardware-monitor.tsx
Normal file
114
AppImage/components/hardware-monitor.tsx
Normal file
@@ -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 (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Cpu className="h-5 w-5" />
|
||||||
|
{device.device}
|
||||||
|
</CardTitle>
|
||||||
|
<CardDescription>{device.vendor}</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid grid-cols-2 gap-4 text-sm">
|
||||||
|
<div>
|
||||||
|
<div className="text-muted-foreground">Slot</div>
|
||||||
|
<div className="font-medium">{device.slot}</div>
|
||||||
|
</div>
|
||||||
|
{device.driver && (
|
||||||
|
<div>
|
||||||
|
<div className="text-muted-foreground">Driver</div>
|
||||||
|
<div className="font-medium">{device.driver}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{device.gpu_driver_version && (
|
||||||
|
<div>
|
||||||
|
<div className="text-muted-foreground">Driver Version</div>
|
||||||
|
<div className="font-medium">{device.gpu_driver_version}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{device.gpu_memory && (
|
||||||
|
<div>
|
||||||
|
<div className="text-muted-foreground">Memory</div>
|
||||||
|
<div className="font-medium">{device.gpu_memory}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{device.gpu_compute_capability && (
|
||||||
|
<div>
|
||||||
|
<div className="text-muted-foreground">Compute Capability</div>
|
||||||
|
<div className="font-medium">{device.gpu_compute_capability}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{hasMonitoring && (
|
||||||
|
<div className="space-y-3 pt-4 border-t">
|
||||||
|
<h4 className="text-sm font-semibold">Real-time Monitoring</h4>
|
||||||
|
|
||||||
|
{device.gpu_temperature !== undefined && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Temperature</span>
|
||||||
|
<span className="font-medium">{device.gpu_temperature}°C</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={(device.gpu_temperature / 100) * 100} className="h-2" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{device.gpu_utilization !== undefined && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">GPU Utilization</span>
|
||||||
|
<span className="font-medium">{device.gpu_utilization}%</span>
|
||||||
|
</div>
|
||||||
|
<Progress value={device.gpu_utilization} className="h-2" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{device.gpu_memory_used && device.gpu_memory_total && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Memory Usage</span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{device.gpu_memory_used} / {device.gpu_memory_total}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Progress
|
||||||
|
value={(Number.parseInt(device.gpu_memory_used) / Number.parseInt(device.gpu_memory_total)) * 100}
|
||||||
|
className="h-2"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{device.gpu_power_draw && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Power Draw</span>
|
||||||
|
<span className="font-medium">{device.gpu_power_draw}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{device.gpu_clock_speed && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">GPU Clock</span>
|
||||||
|
<span className="font-medium">{device.gpu_clock_speed}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{device.gpu_memory_clock && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Memory Clock</span>
|
||||||
|
<span className="font-medium">{device.gpu_memory_clock}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -15,6 +15,8 @@ import {
|
|||||||
PowerIcon,
|
PowerIcon,
|
||||||
Battery,
|
Battery,
|
||||||
Cpu,
|
Cpu,
|
||||||
|
MemoryStick,
|
||||||
|
Cpu as Gpu,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import useSWR from "swr"
|
import useSWR from "swr"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
@@ -46,7 +48,7 @@ export default function Hardware() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 p-6">
|
<div className="space-y-6 p-6">
|
||||||
{/* CPU & Motherboard Info */}
|
{/* System Information - CPU & Motherboard */}
|
||||||
{(hardwareData?.cpu || hardwareData?.motherboard) && (
|
{(hardwareData?.cpu || hardwareData?.motherboard) && (
|
||||||
<Card className="border-border/50 bg-card/50 p-6">
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
@@ -57,43 +59,43 @@ export default function Hardware() {
|
|||||||
<div className="grid gap-6 md:grid-cols-2">
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
{/* CPU Info */}
|
{/* CPU Info */}
|
||||||
{hardwareData?.cpu && Object.keys(hardwareData.cpu).length > 0 && (
|
{hardwareData?.cpu && Object.keys(hardwareData.cpu).length > 0 && (
|
||||||
<div className="space-y-3">
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Processor</h3>
|
<div className="mb-2 flex items-center gap-2">
|
||||||
<div className="space-y-2 rounded-lg border border-border/30 bg-background/50 p-4">
|
<CpuIcon className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<h3 className="text-sm font-semibold">CPU</h3>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
{hardwareData.cpu.model && (
|
{hardwareData.cpu.model && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm text-muted-foreground">Model</span>
|
<span className="text-muted-foreground">Model</span>
|
||||||
<span className="text-sm font-medium">{hardwareData.cpu.model}</span>
|
<span className="font-medium text-right">{hardwareData.cpu.model}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hardwareData.cpu.sockets && (
|
{hardwareData.cpu.cores_per_socket && hardwareData.cpu.sockets && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm text-muted-foreground">Sockets</span>
|
<span className="text-muted-foreground">Cores</span>
|
||||||
<span className="text-sm font-medium">{hardwareData.cpu.sockets}</span>
|
<span className="font-medium">
|
||||||
</div>
|
{hardwareData.cpu.sockets} × {hardwareData.cpu.cores_per_socket} ={" "}
|
||||||
)}
|
{hardwareData.cpu.sockets * hardwareData.cpu.cores_per_socket} cores
|
||||||
{hardwareData.cpu.cores_per_socket && (
|
</span>
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-sm text-muted-foreground">Cores per Socket</span>
|
|
||||||
<span className="text-sm font-medium">{hardwareData.cpu.cores_per_socket}</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hardwareData.cpu.total_threads && (
|
{hardwareData.cpu.total_threads && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm text-muted-foreground">Total Threads</span>
|
<span className="text-muted-foreground">Threads</span>
|
||||||
<span className="text-sm font-medium">{hardwareData.cpu.total_threads}</span>
|
<span className="font-medium">{hardwareData.cpu.total_threads}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hardwareData.cpu.current_mhz && (
|
{hardwareData.cpu.l3_cache && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm text-muted-foreground">Current Speed</span>
|
<span className="text-muted-foreground">L3 Cache</span>
|
||||||
<span className="text-sm font-medium">{hardwareData.cpu.current_mhz.toFixed(0)} MHz</span>
|
<span className="font-medium">{hardwareData.cpu.l3_cache}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hardwareData.cpu.max_mhz && (
|
{hardwareData.cpu.virtualization && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm text-muted-foreground">Max Speed</span>
|
<span className="text-muted-foreground">Virtualization</span>
|
||||||
<span className="text-sm font-medium">{hardwareData.cpu.max_mhz.toFixed(0)} MHz</span>
|
<span className="font-medium">{hardwareData.cpu.virtualization}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -102,51 +104,41 @@ export default function Hardware() {
|
|||||||
|
|
||||||
{/* Motherboard Info */}
|
{/* Motherboard Info */}
|
||||||
{hardwareData?.motherboard && Object.keys(hardwareData.motherboard).length > 0 && (
|
{hardwareData?.motherboard && Object.keys(hardwareData.motherboard).length > 0 && (
|
||||||
<div className="space-y-3">
|
<div>
|
||||||
<h3 className="text-sm font-semibold text-muted-foreground">Motherboard</h3>
|
<div className="mb-2 flex items-center gap-2">
|
||||||
<div className="space-y-2 rounded-lg border border-border/30 bg-background/50 p-4">
|
<Cpu className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<h3 className="text-sm font-semibold">Motherboard</h3>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
{hardwareData.motherboard.manufacturer && (
|
{hardwareData.motherboard.manufacturer && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm text-muted-foreground">Manufacturer</span>
|
<span className="text-muted-foreground">Manufacturer</span>
|
||||||
<span className="text-sm font-medium">{hardwareData.motherboard.manufacturer}</span>
|
<span className="font-medium text-right">{hardwareData.motherboard.manufacturer}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hardwareData.motherboard.model && (
|
{hardwareData.motherboard.model && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm text-muted-foreground">Model</span>
|
<span className="text-muted-foreground">Model</span>
|
||||||
<span className="text-sm font-medium">{hardwareData.motherboard.model}</span>
|
<span className="font-medium text-right">{hardwareData.motherboard.model}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hardwareData.motherboard.version && (
|
{hardwareData.motherboard.bios?.vendor && (
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm text-muted-foreground">Version</span>
|
<span className="text-muted-foreground">BIOS</span>
|
||||||
<span className="text-sm font-medium">{hardwareData.motherboard.version}</span>
|
<span className="font-medium text-right">{hardwareData.motherboard.bios.vendor}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{hardwareData.motherboard.bios && (
|
{hardwareData.motherboard.bios?.version && (
|
||||||
<>
|
<div className="flex justify-between text-sm">
|
||||||
<div className="mt-3 border-t border-border/30 pt-2">
|
<span className="text-muted-foreground">Version</span>
|
||||||
<span className="text-xs font-semibold text-muted-foreground">BIOS</span>
|
<span className="font-medium">{hardwareData.motherboard.bios.version}</span>
|
||||||
</div>
|
</div>
|
||||||
{hardwareData.motherboard.bios.vendor && (
|
)}
|
||||||
<div className="flex justify-between">
|
{hardwareData.motherboard.bios?.date && (
|
||||||
<span className="text-sm text-muted-foreground">Vendor</span>
|
<div className="flex justify-between text-sm">
|
||||||
<span className="text-sm font-medium">{hardwareData.motherboard.bios.vendor}</span>
|
<span className="text-muted-foreground">Date</span>
|
||||||
</div>
|
<span className="font-medium">{hardwareData.motherboard.bios.date}</span>
|
||||||
)}
|
</div>
|
||||||
{hardwareData.motherboard.bios.version && (
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-sm text-muted-foreground">Version</span>
|
|
||||||
<span className="text-sm font-medium">{hardwareData.motherboard.bios.version}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{hardwareData.motherboard.bios.date && (
|
|
||||||
<div className="flex justify-between">
|
|
||||||
<span className="text-sm text-muted-foreground">Date</span>
|
|
||||||
<span className="text-sm font-medium">{hardwareData.motherboard.bios.date}</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -155,7 +147,54 @@ export default function Hardware() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Thermal Monitoring */}
|
{/* Memory Modules */}
|
||||||
|
{hardwareData?.memory_modules && hardwareData.memory_modules.length > 0 && (
|
||||||
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
|
<div className="mb-4 flex items-center gap-2">
|
||||||
|
<MemoryStick className="h-5 w-5 text-primary" />
|
||||||
|
<h2 className="text-lg font-semibold">Memory Modules</h2>
|
||||||
|
<Badge variant="outline" className="ml-auto">
|
||||||
|
{hardwareData.memory_modules.length} installed
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{hardwareData.memory_modules.map((module, index) => (
|
||||||
|
<div key={index} className="rounded-lg border border-border/30 bg-background/50 p-4">
|
||||||
|
<div className="mb-2 font-medium text-sm">{module.slot}</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{module.size && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Size</span>
|
||||||
|
<span className="font-medium">{module.size}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{module.type && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Type</span>
|
||||||
|
<span className="font-medium">{module.type}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{module.speed && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Speed</span>
|
||||||
|
<span className="font-medium">{module.speed}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{module.manufacturer && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Manufacturer</span>
|
||||||
|
<span className="font-medium text-right">{module.manufacturer}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Thermal Monitoring - Restored blue progress bars */}
|
||||||
{hardwareData?.temperatures && hardwareData.temperatures.length > 0 && (
|
{hardwareData?.temperatures && hardwareData.temperatures.length > 0 && (
|
||||||
<Card className="border-border/50 bg-card/50 p-6">
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
@@ -182,7 +221,12 @@ export default function Hardware() {
|
|||||||
{temp.current.toFixed(1)}°C
|
{temp.current.toFixed(1)}°C
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={percentage} className="h-2" />
|
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary">
|
||||||
|
<div
|
||||||
|
className="h-full bg-blue-500 transition-all"
|
||||||
|
style={{ width: `${Math.min(percentage, 100)}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
{temp.adapter && <span className="text-xs text-muted-foreground">{temp.adapter}</span>}
|
{temp.adapter && <span className="text-xs text-muted-foreground">{temp.adapter}</span>}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
@@ -191,6 +235,89 @@ export default function Hardware() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* GPU Information - New dedicated GPU section */}
|
||||||
|
{hardwareData?.gpus && hardwareData.gpus.length > 0 && (
|
||||||
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
|
<div className="mb-4 flex items-center gap-2">
|
||||||
|
<Gpu className="h-5 w-5 text-primary" />
|
||||||
|
<h2 className="text-lg font-semibold">Graphics Cards</h2>
|
||||||
|
<Badge variant="outline" className="ml-auto">
|
||||||
|
{hardwareData.gpus.length} GPU{hardwareData.gpus.length > 1 ? "s" : ""}
|
||||||
|
</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-4 md:grid-cols-2">
|
||||||
|
{hardwareData.gpus.map((gpu, index) => (
|
||||||
|
<div key={index} className="rounded-lg border border-border/30 bg-background/50 p-4">
|
||||||
|
<div className="mb-3 flex items-center justify-between">
|
||||||
|
<span className="font-medium">{gpu.name}</span>
|
||||||
|
<Badge className={getDeviceTypeColor("graphics")}>{gpu.vendor}</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{gpu.memory_total && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Memory</span>
|
||||||
|
<span className="font-medium">
|
||||||
|
{gpu.memory_used} / {gpu.memory_total}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{gpu.temperature !== undefined && gpu.temperature > 0 && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Temperature</span>
|
||||||
|
<span className="font-semibold text-green-500">{gpu.temperature}°C</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary">
|
||||||
|
<div
|
||||||
|
className="h-full bg-blue-500 transition-all"
|
||||||
|
style={{ width: `${Math.min((gpu.temperature / 100) * 100, 100)}%` }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{gpu.utilization !== undefined && (
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Utilization</span>
|
||||||
|
<span className="font-medium">{gpu.utilization}%</span>
|
||||||
|
</div>
|
||||||
|
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary">
|
||||||
|
<div className="h-full bg-green-500 transition-all" style={{ width: `${gpu.utilization}%` }} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{gpu.power_draw && gpu.power_draw !== "N/A" && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Power Draw</span>
|
||||||
|
<span className="font-medium">{gpu.power_draw}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{gpu.driver_version && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Driver</span>
|
||||||
|
<span className="font-mono text-xs">{gpu.driver_version}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{gpu.type && (
|
||||||
|
<div className="flex justify-between text-sm">
|
||||||
|
<span className="text-muted-foreground">Type</span>
|
||||||
|
<span className="font-medium">{gpu.type}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* PCI Devices */}
|
{/* PCI Devices */}
|
||||||
{hardwareData?.pci_devices && hardwareData.pci_devices.length > 0 && (
|
{hardwareData?.pci_devices && hardwareData.pci_devices.length > 0 && (
|
||||||
<Card className="border-border/50 bg-card/50 p-6">
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
@@ -277,7 +404,7 @@ export default function Hardware() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Power Consumption */}
|
{/* Power Consumption - Only show if data exists */}
|
||||||
{hardwareData?.power_meter && (
|
{hardwareData?.power_meter && (
|
||||||
<Card className="border-border/50 bg-card/50 p-6">
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
@@ -302,7 +429,7 @@ export default function Hardware() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Fans */}
|
{/* Fans - Only show if data exists */}
|
||||||
{hardwareData?.fans && hardwareData.fans.length > 0 && (
|
{hardwareData?.fans && hardwareData.fans.length > 0 && (
|
||||||
<Card className="border-border/50 bg-card/50 p-6">
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
@@ -326,7 +453,9 @@ export default function Hardware() {
|
|||||||
{fan.speed.toFixed(0)} {fan.unit}
|
{fan.speed.toFixed(0)} {fan.unit}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<Progress value={percentage} className="h-2" />
|
<div className="h-2 w-full overflow-hidden rounded-full bg-secondary">
|
||||||
|
<div className="h-full bg-blue-500 transition-all" style={{ width: `${percentage}%` }} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -334,7 +463,7 @@ export default function Hardware() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Power Supplies */}
|
{/* Power Supplies - Only show if data exists */}
|
||||||
{hardwareData?.power_supplies && hardwareData.power_supplies.length > 0 && (
|
{hardwareData?.power_supplies && hardwareData.power_supplies.length > 0 && (
|
||||||
<Card className="border-border/50 bg-card/50 p-6">
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
@@ -362,8 +491,8 @@ export default function Hardware() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* UPS - Solo mostrar si hay datos */}
|
{/* UPS - Only show if data exists and has content */}
|
||||||
{hardwareData?.ups && Object.keys(hardwareData.ups).length > 0 && (
|
{hardwareData?.ups && Object.keys(hardwareData.ups).length > 0 && hardwareData.ups.model && (
|
||||||
<Card className="border-border/50 bg-card/50 p-6">
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
<Battery className="h-5 w-5 text-primary" />
|
<Battery className="h-5 w-5 text-primary" />
|
||||||
@@ -373,7 +502,7 @@ export default function Hardware() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="rounded-lg border border-border/30 bg-background/50 p-4">
|
<div className="rounded-lg border border-border/30 bg-background/50 p-4">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<span className="text-sm font-medium">{hardwareData.ups.model || "UPS"}</span>
|
<span className="text-sm font-medium">{hardwareData.ups.model}</span>
|
||||||
<Badge variant={hardwareData.ups.status === "OL" ? "default" : "destructive"}>
|
<Badge variant={hardwareData.ups.status === "OL" ? "default" : "destructive"}>
|
||||||
{hardwareData.ups.status}
|
{hardwareData.ups.status}
|
||||||
</Badge>
|
</Badge>
|
||||||
@@ -439,11 +568,11 @@ export default function Hardware() {
|
|||||||
.filter((d) => d.type.toLowerCase().includes("network"))
|
.filter((d) => d.type.toLowerCase().includes("network"))
|
||||||
.map((device, index) => (
|
.map((device, index) => (
|
||||||
<div key={index} className="rounded-lg border border-border/30 bg-background/50 p-3">
|
<div key={index} className="rounded-lg border border-border/30 bg-background/50 p-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between mb-1">
|
||||||
<span className="text-sm font-medium">{device.device}</span>
|
<span className="text-sm font-medium truncate">{device.device}</span>
|
||||||
<Badge className="bg-blue-500/10 text-blue-500 border-blue-500/20">Ethernet</Badge>
|
<Badge className="bg-blue-500/10 text-blue-500 border-blue-500/20 text-xs">Ethernet</Badge>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-1 text-xs text-muted-foreground">{device.vendor}</p>
|
<p className="text-xs text-muted-foreground truncate">{device.vendor}</p>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -453,7 +582,7 @@ export default function Hardware() {
|
|||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Storage Summary */}
|
{/* Storage Summary - Larger badges with blue color */}
|
||||||
{hardwareData?.storage_devices && hardwareData.storage_devices.length > 0 && (
|
{hardwareData?.storage_devices && hardwareData.storage_devices.length > 0 && (
|
||||||
<Card className="border-border/50 bg-card/50 p-6">
|
<Card className="border-border/50 bg-card/50 p-6">
|
||||||
<div className="mb-4 flex items-center gap-2">
|
<div className="mb-4 flex items-center gap-2">
|
||||||
@@ -467,14 +596,12 @@ export default function Hardware() {
|
|||||||
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid gap-3 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{hardwareData.storage_devices.map((device, index) => (
|
{hardwareData.storage_devices.map((device, index) => (
|
||||||
<div key={index} className="rounded-lg border border-border/30 bg-background/50 p-3">
|
<div key={index} className="rounded-lg border border-border/30 bg-background/50 p-3">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<span className="text-sm font-medium">{device.name}</span>
|
<span className="text-sm font-medium">{device.name}</span>
|
||||||
<Badge className="bg-blue-500/10 text-blue-500 border-blue-500/20 text-xs px-2 py-1">
|
<Badge className="bg-blue-500/10 text-blue-500 border-blue-500/20 px-2.5 py-0.5">{device.type}</Badge>
|
||||||
{device.type}
|
|
||||||
</Badge>
|
|
||||||
</div>
|
</div>
|
||||||
{device.size && <p className="mt-1 text-xs text-muted-foreground">{device.size}</p>}
|
{device.size && <p className="text-sm font-medium">{device.size}</p>}
|
||||||
{device.model && <p className="mt-1 text-xs text-muted-foreground">{device.model}</p>}
|
{device.model && <p className="text-xs text-muted-foreground truncate">{device.model}</p>}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1511,6 +1511,95 @@ def get_temperature_info():
|
|||||||
'power_meter': power_meter
|
'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():
|
def get_hardware_info():
|
||||||
"""Get comprehensive hardware information"""
|
"""Get comprehensive hardware information"""
|
||||||
try:
|
try:
|
||||||
@@ -1522,6 +1611,7 @@ def get_hardware_info():
|
|||||||
'storage_devices': [],
|
'storage_devices': [],
|
||||||
'network_cards': [],
|
'network_cards': [],
|
||||||
'graphics_cards': [],
|
'graphics_cards': [],
|
||||||
|
'gpus': [], # Added dedicated GPU array
|
||||||
'pci_devices': [],
|
'pci_devices': [],
|
||||||
'sensors': {
|
'sensors': {
|
||||||
'temperatures': [],
|
'temperatures': [],
|
||||||
@@ -1665,7 +1755,7 @@ def get_hardware_info():
|
|||||||
hardware_data['graphics_cards'].append({
|
hardware_data['graphics_cards'].append({
|
||||||
'name': parts[0].strip(),
|
'name': parts[0].strip(),
|
||||||
'memory': parts[1].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(),
|
'power_draw': parts[3].strip(),
|
||||||
'vendor': 'NVIDIA'
|
'vendor': 'NVIDIA'
|
||||||
})
|
})
|
||||||
@@ -1675,7 +1765,7 @@ def get_hardware_info():
|
|||||||
if result.returncode == 0:
|
if result.returncode == 0:
|
||||||
for line in result.stdout.split('\n'):
|
for line in result.stdout.split('\n'):
|
||||||
# Match VGA, 3D, Display controllers, and specific GPU keywords
|
# 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)
|
parts = line.split(':', 2)
|
||||||
if len(parts) >= 3:
|
if len(parts) >= 3:
|
||||||
gpu_name = parts[2].strip()
|
gpu_name = parts[2].strip()
|
||||||
@@ -1694,6 +1784,9 @@ def get_hardware_info():
|
|||||||
for existing_gpu in hardware_data['graphics_cards']:
|
for existing_gpu in hardware_data['graphics_cards']:
|
||||||
if gpu_name in existing_gpu['name'] or existing_gpu['name'] in gpu_name:
|
if gpu_name in existing_gpu['name'] or existing_gpu['name'] in gpu_name:
|
||||||
already_exists = True
|
already_exists = True
|
||||||
|
# Update vendor if it was previously unknown
|
||||||
|
if existing_gpu['vendor'] == 'Unknown':
|
||||||
|
existing_gpu['vendor'] = vendor
|
||||||
break
|
break
|
||||||
|
|
||||||
if not already_exists:
|
if not already_exists:
|
||||||
@@ -1888,6 +1981,10 @@ def get_hardware_info():
|
|||||||
if ups_info:
|
if ups_info:
|
||||||
hardware_data['ups'] = ups_info
|
hardware_data['ups'] = ups_info
|
||||||
|
|
||||||
|
gpus = get_gpu_info()
|
||||||
|
if gpus:
|
||||||
|
hardware_data['gpus'] = gpus
|
||||||
|
|
||||||
return hardware_data
|
return hardware_data
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -2051,7 +2148,8 @@ def api_hardware():
|
|||||||
'fans': hardware_info.get('ipmi_fans', []),
|
'fans': hardware_info.get('ipmi_fans', []),
|
||||||
'power_supplies': hardware_info.get('ipmi_power', {}).get('power_supplies', []),
|
'power_supplies': hardware_info.get('ipmi_power', {}).get('power_supplies', []),
|
||||||
'power_meter': hardware_info.get('power_meter'),
|
'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")
|
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 supplies: {len(formatted_data['power_supplies'])} PSUs")
|
||||||
print(f"[v0] - Power meter: {'Yes' if formatted_data['power_meter'] else 'No'}")
|
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] - UPS: {'Yes' if formatted_data['ups'] else 'No'}")
|
||||||
|
print(f"[v0] - GPUs: {len(formatted_data['gpus'])} found")
|
||||||
|
|
||||||
return jsonify(formatted_data)
|
return jsonify(formatted_data)
|
||||||
|
|
||||||
|
|||||||
369
AppImage/scripts/hardware_monitor.py
Normal file
369
AppImage/scripts/hardware_monitor.py
Normal file
@@ -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()
|
||||||
Reference in New Issue
Block a user