feat: enhance metrics collection with disk and network I/O rates, and update frontend to display new data

This commit is contained in:
Jonathan Atta
2026-03-11 18:10:28 +01:00
parent 29e8f9b887
commit d58003feb7
4 changed files with 275 additions and 242 deletions

View File

@@ -1,6 +1,6 @@
import React, { useEffect, useState, useCallback, useRef } from 'react'
import { EventsOn, EventsOff } from '../wailsjs/runtime/runtime'
import { Activity, Cpu, Database, Monitor, ChevronDown, ChevronRight, Layers, X, RefreshCw, OctagonX, Search } from 'lucide-react'
import { Activity, Cpu, Database, Monitor, ChevronDown, ChevronRight, X, RefreshCw, OctagonX, Search, HardDrive, Network } from 'lucide-react'
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'
import { Progress } from '@/components/ui/progress'
import { cn } from '@/lib/utils'
@@ -14,6 +14,8 @@ type ProcessInfo = {
name: string
cpu: number
mem: number
read_bps: number
write_bps: number
}
type GPUProcessInfo = {
@@ -35,6 +37,10 @@ type Metrics = {
gpu_used_mem?: number
gpu_util_percent?: number
gpu_processes?: GPUProcessInfo[]
disk_read_bps: number
disk_write_bps: number
net_recv_bps: number
net_send_bps: number
}
type ProcessDetail = {
@@ -64,7 +70,7 @@ type ProcessDetail = {
conn_count: number
}
type SortField = 'cpu' | 'mem'
type SortField = 'cpu' | 'mem' | 'disk' | 'vram'
type NavItem = { pid: number; name: string }
// ---------------------------------------------------------------------------
@@ -520,10 +526,7 @@ export default function App() {
const [sortBy, setSortBy] = useState<SortField>('cpu')
const [selectedPid, setSelectedPid] = useState<number | null>(null)
const [page, setPage] = useState(0)
const [gpuPage, setGpuPage] = useState(0)
const [procOpen, setProcOpen] = useState(true)
const [gpuOpen, setGpuOpen] = useState(true)
const [gpuSortBy, setGpuSortBy] = useState<'vram' | 'ram'>('vram')
const [navStack, setNavStack] = useState<NavItem[]>([])
const [search, setSearch] = useState('')
@@ -533,7 +536,6 @@ export default function App() {
}, [])
const [pageSize, setPageSize] = useState(20)
const [gpuPageSize, setGpuPageSize] = useState(4)
useEffect(() => {
const handler = (m: Metrics) => setMetrics(m)
@@ -556,17 +558,18 @@ export default function App() {
const gpuUtilPct = metrics?.gpu_util_percent ?? 0
const searchLower = search.toLowerCase()
const gpuProcesses = [...(metrics?.gpu_processes ?? [])]
.filter(p => !search || p.name.toLowerCase().includes(searchLower) || String(p.pid).includes(search))
.sort((a, b) => gpuSortBy === 'vram' ? b.vram_mb - a.vram_mb : b.ram - a.ram)
const gpuTotalPages = Math.ceil(gpuProcesses.length / gpuPageSize)
const safeGpuPage = Math.min(gpuPage, Math.max(0, gpuTotalPages - 1))
const pagedGpuProcesses = gpuProcesses.slice(safeGpuPage * gpuPageSize, (safeGpuPage + 1) * gpuPageSize)
const gpuByPid = new Map<number, GPUProcessInfo>(
(metrics?.gpu_processes ?? []).map(g => [g.pid, g])
)
const sortedProcesses = metrics
? [...metrics.processes]
.filter(p => !search || p.name.toLowerCase().includes(searchLower) || String(p.pid).includes(search))
.sort((a, b) => b[sortBy] - a[sortBy])
.sort((a, b) => {
if (sortBy === 'disk') return (b.read_bps + b.write_bps) - (a.read_bps + a.write_bps)
if (sortBy === 'vram') return (gpuByPid.get(b.pid)?.vram_mb ?? 0) - (gpuByPid.get(a.pid)?.vram_mb ?? 0)
if (sortBy === 'cpu') return b.cpu - a.cpu
return b.mem - a.mem
})
: []
const totalPages = Math.ceil(sortedProcesses.length / pageSize)
const safePage = Math.min(page, Math.max(0, totalPages - 1))
@@ -601,12 +604,12 @@ export default function App() {
type="text"
placeholder="Search by name or PID…"
value={search}
onChange={e => { setSearch(e.target.value); setPage(0); setGpuPage(0) }}
onChange={e => { setSearch(e.target.value); setPage(0) }}
className="w-full bg-zinc-900/60 border border-zinc-800 rounded-lg pl-8 pr-8 py-2 text-sm text-zinc-300 placeholder-zinc-700 focus:outline-none focus:border-zinc-600 transition-colors"
/>
{search && (
<button
onClick={() => { setSearch(''); setPage(0); setGpuPage(0) }}
onClick={() => { setSearch(''); setPage(0) }}
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-zinc-600 hover:text-zinc-400 transition-colors"
>
<X className="w-3.5 h-3.5" />
@@ -640,12 +643,24 @@ export default function App() {
</div>
<div className="text-right">
<p className="text-[10px] text-zinc-600 mb-0.5">Memory</p>
<span className={cn('text-sm font-bold tabular-nums', memPct >= 90 ? 'text-red-400' : memPct >= 70 ? 'text-amber-400' : 'text-zinc-100')}>{fmtBytes(usedMem)}</span>
<span className={cn('text-sm font-bold tabular-nums', memPct >= 90 ? 'text-red-400' : memPct >= 70 ? 'text-amber-400' : 'text-zinc-100')}>{memPct.toFixed(1)}%</span>
</div>
{metrics?.gpu_name && (
<div className="text-right">
<p className="text-[10px] text-zinc-600 mb-0.5">VRAM</p>
<span className={cn('text-sm font-bold tabular-nums', gpuUsedPct >= 90 ? 'text-red-400' : gpuUsedPct >= 70 ? 'text-amber-400' : 'text-zinc-100')}>{metrics.gpu_used_mem ? fmtBytes(metrics.gpu_used_mem) : '—'}</span>
</div>
)}
{metrics?.gpu_name && (
<div className="text-right">
<p className="text-[10px] text-zinc-600 mb-0.5">GPU</p>
<span className={cn('text-sm font-bold tabular-nums', gpuUtilPct >= 90 ? 'text-red-400' : gpuUtilPct >= 70 ? 'text-amber-400' : 'text-zinc-100')}>{gpuUtilPct.toFixed(0)}%</span>
</div>
)}
</div>
{procOpen && (
<div className="flex gap-1 ml-2">
{(['cpu', 'mem'] as SortField[]).map((field) => (
{(['cpu', 'mem', 'disk', ...(metrics?.gpu_name ? ['vram'] : [])] as SortField[]).map((field) => (
<button
key={field}
onClick={() => toggleSort(field)}
@@ -683,6 +698,38 @@ export default function App() {
</div>
<Progress value={memPct} />
</div>
<div>
<div className="flex justify-between text-xs text-zinc-600 mb-1.5">
<span className="flex items-center gap-1"><HardDrive className="w-3 h-3" /> Disk</span>
<span className="font-mono text-zinc-400">{fmtBytes(metrics?.disk_read_bps ?? 0)}/s {fmtBytes(metrics?.disk_write_bps ?? 0)}/s</span>
</div>
<Progress value={Math.min(100, ((metrics?.disk_read_bps ?? 0) + (metrics?.disk_write_bps ?? 0)) / (500 * 1024 * 1024) * 100)} />
</div>
<div>
<div className="flex justify-between text-xs text-zinc-600 mb-1.5">
<span className="flex items-center gap-1"><Network className="w-3 h-3" /> Network</span>
<span className="font-mono text-zinc-400">{fmtBytes(metrics?.net_recv_bps ?? 0)}/s {fmtBytes(metrics?.net_send_bps ?? 0)}/s</span>
</div>
<Progress value={Math.min(100, ((metrics?.net_recv_bps ?? 0) + (metrics?.net_send_bps ?? 0)) / (125 * 1024 * 1024) * 100)} />
</div>
{metrics?.gpu_name && (
<div>
<div className="flex justify-between text-xs text-zinc-600 mb-1.5">
<span className="flex items-center gap-1"><Monitor className="w-3 h-3" /> VRAM <span className="text-zinc-700 ml-1 normal-case tracking-normal font-normal">{metrics.gpu_name}</span></span>
<span className={cn('font-mono', gpuUsedPct >= 90 ? 'text-red-400' : gpuUsedPct >= 70 ? 'text-amber-400' : 'text-zinc-400')}>{metrics.gpu_used_mem ? fmtBytes(metrics.gpu_used_mem) : '—'} / {metrics.gpu_total_mem ? fmtBytes(metrics.gpu_total_mem) : '—'}</span>
</div>
<Progress value={gpuUsedPct} />
</div>
)}
{metrics?.gpu_name && (
<div>
<div className="flex justify-between text-xs text-zinc-600 mb-1.5">
<span className="flex items-center gap-1"><Monitor className="w-3 h-3" /> GPU Util</span>
<span className={cn('font-mono', gpuUtilPct >= 90 ? 'text-red-400' : gpuUtilPct >= 70 ? 'text-amber-400' : 'text-zinc-400')}>{gpuUtilPct.toFixed(1)}%</span>
</div>
<Progress value={gpuUtilPct} />
</div>
)}
</div>
<table className="w-full text-sm">
<thead>
@@ -691,6 +738,8 @@ export default function App() {
<th className="py-2 text-left text-xs font-medium text-zinc-600 uppercase tracking-wider cursor-help" title="Executable name of the process">Name</th>
<th className="py-2 px-5 text-right text-xs font-medium text-zinc-600 uppercase tracking-wider w-28 cursor-help" title="CPU usage as a % of a single core (can exceed 100% on multi-threaded processes)">CPU</th>
<th className="py-2 px-5 text-right text-xs font-medium text-zinc-600 uppercase tracking-wider w-28 cursor-help" title="Physical RAM (RSS) currently used by this process">Memory</th>
<th className="py-2 px-3 text-right text-xs font-medium text-zinc-600 uppercase tracking-wider w-24 cursor-help" title="Disk I/O — combined read + write throughput for this process">Disk</th>
{metrics?.gpu_name && <th className="py-2 px-3 text-right text-xs font-medium text-zinc-600 uppercase tracking-wider w-24 cursor-help" title="GPU type and VRAM — C = Compute, G = Graphics, C+G = both">GPU</th>}
</tr>
</thead>
<tbody>
@@ -707,26 +756,61 @@ export default function App() {
>
<td className="py-2 px-5 text-zinc-700 text-xs font-mono">{p.pid}</td>
<td className="py-2 text-zinc-300 truncate max-w-xs">{p.name}</td>
<td className="py-2 px-5 text-right">
<span
className={cn(
'tabular-nums font-mono text-xs',
p.cpu >= 50 ? 'text-red-400' : p.cpu >= 20 ? 'text-amber-400' : 'text-zinc-500'
)}
>
{p.cpu.toFixed(1)}%
</span>
<td className="py-2 px-3 text-right">
<div className="flex flex-col items-end gap-0.5">
<span className={cn('tabular-nums font-mono text-xs', p.cpu >= 50 ? 'text-red-400' : p.cpu >= 20 ? 'text-amber-400' : 'text-zinc-500')}>
{p.cpu.toFixed(1)}%
</span>
<div className="w-14 h-1 bg-zinc-800 rounded-full overflow-hidden">
<div className="h-full rounded-full" style={{ width: `${Math.min(100, p.cpu)}%`, backgroundColor: p.cpu >= 50 ? 'rgb(248 113 113/0.7)' : p.cpu >= 20 ? 'rgb(251 191 36/0.7)' : 'rgb(113 113 122/0.4)' }} />
</div>
</div>
</td>
<td className="py-2 px-5 text-right">
<span className="tabular-nums font-mono text-xs text-zinc-500">
{fmtBytes(p.mem)}
</span>
<td className="py-2 px-3 text-right">
<div className="flex flex-col items-end gap-0.5">
<span className="tabular-nums font-mono text-xs text-zinc-500">
{metrics ? ((p.mem / metrics.total_mem) * 100).toFixed(1) : '0'}%
</span>
<div className="w-14 h-1 bg-zinc-800 rounded-full overflow-hidden">
<div className="h-full bg-sky-700/50 rounded-full" style={{ width: `${metrics ? Math.min(100, (p.mem / metrics.total_mem) * 100) : 0}%` }} />
</div>
</div>
</td>
<td className="py-2 px-3 text-right">
{(p.read_bps + p.write_bps) > 0 ? (
<div className="flex flex-col items-end gap-0.5">
<span className="tabular-nums font-mono text-[10px] text-zinc-500">{fmtBytes(p.read_bps + p.write_bps)}/s</span>
<div className="w-14 h-1 bg-zinc-800 rounded-full overflow-hidden">
<div className="h-full bg-sky-600/40 rounded-full" style={{ width: `${Math.min(100, (p.read_bps + p.write_bps) / (100 * 1024 * 1024) * 100)}%` }} />
</div>
</div>
) : (
<span className="font-mono text-xs text-zinc-800"></span>
)}
</td>
{metrics?.gpu_name && (gpuByPid.has(p.pid) ? (
<td className="py-2 px-3 text-right">
<div className="flex flex-col items-end gap-0.5">
<span
title={gpuByPid.get(p.pid)!.type === 'C' ? 'Compute (CUDA/OpenCL)' : gpuByPid.get(p.pid)!.type === 'G' ? 'Graphics (rendering)' : 'Compute + Graphics'}
className={cn('text-[10px] font-mono px-1 py-0.5 rounded cursor-help',
gpuByPid.get(p.pid)!.type === 'C' ? 'bg-violet-900/50 text-violet-300' :
gpuByPid.get(p.pid)!.type === 'G' ? 'bg-blue-900/50 text-blue-300' :
'bg-indigo-900/50 text-indigo-300'
)}>
{gpuByPid.get(p.pid)!.type}
</span>
<span className="tabular-nums font-mono text-[10px] text-zinc-600">{gpuByPid.get(p.pid)!.vram_mb} MB</span>
</div>
</td>
) : (
<td className="py-2 px-3 text-right text-zinc-800 text-xs"></td>
))}
</tr>
))}
{pagedProcesses.length === 0 && (
<tr>
<td colSpan={4} className="py-10 text-center text-zinc-700 text-sm">
<td colSpan={metrics?.gpu_name ? 6 : 5} className="py-10 text-center text-zinc-700 text-sm">
Waiting for metrics
</td>
</tr>
@@ -802,185 +886,6 @@ export default function App() {
)}
</Card>
{/* GPU */}
{metrics?.gpu_name && (
<Card>
<CardHeader
className="cursor-pointer select-none"
onClick={() => setGpuOpen(o => !o)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-1.5">
{gpuOpen
? <ChevronDown className="w-3.5 h-3.5 text-zinc-600" />
: <ChevronRight className="w-3.5 h-3.5 text-zinc-600" />
}
<Monitor className="w-3.5 h-3.5 text-zinc-500" />
<CardTitle>GPU</CardTitle>
<span className="text-xs text-zinc-600 font-normal normal-case tracking-normal ml-1">
{metrics.gpu_name}
</span>
</div>
<div className="flex items-center gap-4" onClick={e => e.stopPropagation()}>
<div className="text-right">
<p className="text-[10px] text-zinc-600 mb-0.5">VRAM</p>
<span className={cn('text-sm font-bold tabular-nums', gpuUsedPct >= 90 ? 'text-red-400' : gpuUsedPct >= 70 ? 'text-amber-400' : 'text-zinc-100')}>{metrics.gpu_used_mem ? fmtBytes(metrics.gpu_used_mem) : '—'}</span>
</div>
<div className="text-right">
<p className="text-[10px] text-zinc-600 mb-0.5">Util</p>
<span className={cn('text-sm font-bold tabular-nums', gpuUtilPct >= 90 ? 'text-red-400' : gpuUtilPct >= 70 ? 'text-amber-400' : 'text-zinc-100')}>{gpuUtilPct.toFixed(0)}%</span>
</div>
{gpuOpen && (
<div className="flex gap-1 ml-2">
{(['vram', 'ram'] as const).map((field) => (
<button
key={field}
onClick={() => { setGpuSortBy(field); setGpuPage(0) }}
className={cn(
'flex items-center gap-0.5 px-2.5 py-1 rounded-md text-xs font-medium transition-colors',
gpuSortBy === field
? 'bg-zinc-800 text-zinc-100'
: 'text-zinc-600 hover:text-zinc-400 hover:bg-zinc-900'
)}
>
{field.toUpperCase()}
{gpuSortBy === field && <ChevronDown className="w-3 h-3 ml-0.5" />}
</button>
))}
</div>
)}
</div>
</div>
</CardHeader>
{gpuOpen && (
<CardContent>
<div className="grid grid-cols-2 gap-4">
<div>
<div className="flex justify-between text-xs text-zinc-600 mb-1.5">
<span>VRAM usage</span>
<span>
{metrics.gpu_used_mem ? fmtBytes(metrics.gpu_used_mem) : '—'}
{' / '}
{metrics.gpu_total_mem ? fmtBytes(metrics.gpu_total_mem) : '—'}
</span>
</div>
<Progress value={gpuUsedPct} />
</div>
<div>
<div className="flex justify-between text-xs text-zinc-600 mb-1.5">
<span>GPU utilization</span>
<span>{gpuUtilPct.toFixed(1)}%</span>
</div>
<Progress value={gpuUtilPct} />
</div>
</div>
{metrics.gpu_processes && metrics.gpu_processes.length > 0 && (
<div className="mt-4 pt-4 border-t border-zinc-800/60">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-1.5">
<Layers className="w-3 h-3 text-zinc-600" />
<span className="text-xs font-semibold uppercase tracking-widest text-zinc-500">
GPU Processes
</span>
<span className="text-[10px] text-zinc-700">{gpuProcesses.length} total</span>
</div>
</div>
<table className="w-full">
<thead>
<tr>
<th className="pb-1.5 text-left text-xs font-medium text-zinc-700 uppercase tracking-wider w-14 cursor-help" title="Process ID">PID</th>
<th className="pb-1.5 text-left text-xs font-medium text-zinc-700 uppercase tracking-wider cursor-help" title="Process name using this GPU">Name</th>
<th className="pb-1.5 text-left text-xs font-medium text-zinc-700 uppercase tracking-wider w-16 cursor-help" title="C = Compute (CUDA/OpenCL), G = Graphics (rendering), C+G = both">Type</th>
<th className="pb-1.5 text-right text-xs font-medium text-zinc-700 uppercase tracking-wider w-24 cursor-help" title="GPU VRAM used by this process (video memory on the graphics card)">VRAM</th>
<th className="pb-1.5 text-right text-xs font-medium text-zinc-700 uppercase tracking-wider w-28 cursor-help" title="System RAM (CPU-side memory) used by this process">RAM</th>
</tr>
</thead>
<tbody>
{pagedGpuProcesses.map((p) => (
<tr
key={p.pid}
className="border-t border-zinc-900 hover:bg-zinc-800/30 transition-colors cursor-pointer"
onClick={() => openDetail(p.pid)}
>
<td className="py-1.5 text-zinc-700 text-xs font-mono">{p.pid}</td>
<td className="py-1.5 text-zinc-300 text-xs truncate max-w-[180px]">{p.name}</td>
<td className="py-1.5">
<span
title={
p.type === 'C' ? 'C — Compute: uses the GPU for compute workloads (CUDA, OpenCL, Vulkan Compute…)' :
p.type === 'G' ? 'G — Graphics: uses the GPU for rendering and display output' :
'C+G — Compute + Graphics: uses the GPU for both compute and rendering workloads'
}
className={cn(
'inline-block px-1.5 py-0.5 rounded text-xs font-mono font-semibold cursor-help',
p.type === 'C' ? 'bg-violet-900/50 text-violet-300' :
p.type === 'G' ? 'bg-blue-900/50 text-blue-300' :
'bg-indigo-900/50 text-indigo-300'
)}
>
{p.type}
</span>
</td>
<td className="py-1.5 text-right text-xs font-mono text-zinc-400">{p.vram_mb} MB</td>
<td className="py-1.5 text-right text-xs font-mono text-zinc-500">{p.ram > 0 ? fmtBytes(p.ram) : '—'}</td>
</tr>
))}
</tbody>
</table>
{/* GPU pagination */}
{gpuTotalPages > 0 && (
<div className="flex items-center justify-between pt-2 mt-1 border-t border-zinc-900">
<div className="flex items-center gap-2">
<span className="text-xs text-zinc-600">
{safeGpuPage * gpuPageSize + 1}{Math.min((safeGpuPage + 1) * gpuPageSize, gpuProcesses.length)} of {gpuProcesses.length}
</span>
<div className="flex rounded border border-zinc-800 overflow-hidden" title="Items per page">
{[4, 10, 20, 50].map(n => (
<button key={n} onClick={() => { setGpuPageSize(n); setGpuPage(0) }}
className={cn('px-1.5 py-0.5 text-xs transition-colors', gpuPageSize === n ? 'bg-zinc-700 text-zinc-200' : 'bg-zinc-900 text-zinc-600 hover:text-zinc-400 hover:bg-zinc-800')}>
{n}
</button>
))}
</div>
</div>
<div className="flex items-center gap-1">
<button onClick={() => setGpuPage(0)} disabled={safeGpuPage === 0}
className="px-2 py-1 rounded text-xs text-zinc-600 hover:text-zinc-300 hover:bg-zinc-800 disabled:opacity-30 disabled:cursor-not-allowed transition-colors">«</button>
<button onClick={() => setGpuPage(p => Math.max(0, p - 1))} disabled={safeGpuPage === 0}
className="px-2 py-1 rounded text-xs text-zinc-600 hover:text-zinc-300 hover:bg-zinc-800 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"></button>
{Array.from({ length: gpuTotalPages }, (_, i) => i)
.filter(i => i === 0 || i === gpuTotalPages - 1 || Math.abs(i - safeGpuPage) <= 1)
.reduce<(number | '…')[]>((acc, i, idx, arr) => {
if (idx > 0 && typeof arr[idx - 1] === 'number' && (i as number) - (arr[idx - 1] as number) > 1) acc.push('…')
acc.push(i)
return acc
}, [])
.map((item, idx) =>
item === '…' ? (
<span key={`ge${idx}`} className="px-1 text-xs text-zinc-700"></span>
) : (
<button key={item} onClick={() => setGpuPage(item as number)}
className={cn('w-7 h-7 rounded text-xs font-medium transition-colors',
safeGpuPage === item ? 'bg-zinc-800 text-zinc-100' : 'text-zinc-600 hover:text-zinc-300 hover:bg-zinc-800'
)}>{(item as number) + 1}</button>
)
)
}
<button onClick={() => setGpuPage(p => Math.min(gpuTotalPages - 1, p + 1))} disabled={safeGpuPage >= gpuTotalPages - 1}
className="px-2 py-1 rounded text-xs text-zinc-600 hover:text-zinc-300 hover:bg-zinc-800 disabled:opacity-30 disabled:cursor-not-allowed transition-colors"></button>
<button onClick={() => setGpuPage(gpuTotalPages - 1)} disabled={safeGpuPage >= gpuTotalPages - 1}
className="px-2 py-1 rounded text-xs text-zinc-600 hover:text-zinc-300 hover:bg-zinc-800 disabled:opacity-30 disabled:cursor-not-allowed transition-colors">»</button>
</div>
</div>
)}
</div>
)}
</CardContent>
)}
</Card>
)}
</main>
{/* Detail panel */}

View File

@@ -25,6 +25,8 @@ export namespace backend {
name: string;
cpu: number;
mem: number;
read_bps: number;
write_bps: number;
static createFrom(source: any = {}) {
return new ProcessInfo(source);
@@ -36,6 +38,8 @@ export namespace backend {
this.name = source["name"];
this.cpu = source["cpu"];
this.mem = source["mem"];
this.read_bps = source["read_bps"];
this.write_bps = source["write_bps"];
}
}
export class Metrics {
@@ -49,6 +53,10 @@ export namespace backend {
gpu_used_mem?: number;
gpu_util_percent?: number;
gpu_processes?: GPUProcessInfo[];
disk_read_bps: number;
disk_write_bps: number;
net_recv_bps: number;
net_send_bps: number;
static createFrom(source: any = {}) {
return new Metrics(source);
@@ -66,6 +74,10 @@ export namespace backend {
this.gpu_used_mem = source["gpu_used_mem"];
this.gpu_util_percent = source["gpu_util_percent"];
this.gpu_processes = this.convertValues(source["gpu_processes"], GPUProcessInfo);
this.disk_read_bps = source["disk_read_bps"];
this.disk_write_bps = source["disk_write_bps"];
this.net_recv_bps = source["net_recv_bps"];
this.net_send_bps = source["net_send_bps"];
}
convertValues(a: any, classs: any, asMap: boolean = false): any {