Parent: #1296
Depends on: #1297
Problem
HardwareMonitorUpdate has flat, single-GPU fields:
pub struct HardwareMonitorUpdate {
pub gpu_usage: Option<f32>,
pub gpu_name: Option<String>,
pub gpu_temperature: Option<f32>,
pub gpu_source: Option<String>,
pub gpu_dedicated_memory_usage_kb: Option<f32>,
pub gpu_cooler_level: Option<u32>,
// ...
}
emit_hardware_update() in system_monitor.rs picks only gpu_samples.first(), discarding all other GPUs even though sample_gpu() already returns a Vec<GpuSample> with per-GPU data.
Proposed Changes
1. Add GpuMonitorData struct (models/hardware.rs)
#[derive(Debug, Clone, serde::Serialize, specta::Type)]
#[serde(rename_all = "camelCase")]
pub struct GpuMonitorData {
pub gpu_id: String,
pub gpu_name: String,
pub gpu_usage: Option<f32>,
pub gpu_temperature: Option<f32>,
pub gpu_source: String,
pub gpu_dedicated_memory_usage_kb: Option<f32>,
pub gpu_cooler_level: Option<u32>,
}
2. Replace flat GPU fields in HardwareMonitorUpdate
pub struct HardwareMonitorUpdate {
pub cpu_usage: f32,
pub memory_usage: f32,
pub gpus: Vec<GpuMonitorData>, // replaces 6 flat fields
pub processors_usage: Vec<f32>,
}
3. Rewrite emit_hardware_update() (system_monitor.rs)
- Iterate over all
gpu_samples instead of picking .first()
- Build
Vec<GpuMonitorData> from the full slice
- Apply temperature unit conversion per GPU
- Remove
GpuCapabilities struct (the Option fields communicate availability implicitly)
4. Add gpu_id to GpuSample (monitoring_service.rs)
Add a gpu_id: String field to GpuSample. Convention:
- Windows:
"pci:{bus}:{device}.{function}" (from ADL BDF)
- Linux:
"pci:{bus}:{device}.{function}" (from sysfs symlink)
- macOS: derived from GPU name (typically single GPU on Apple Silicon)
Update update_gpu_histories() to key on gpu_id instead of name for reliability.
5. Database migration
Add optional gpu_id column to GPU_DATA_ARCHIVE:
ALTER TABLE GPU_DATA_ARCHIVE ADD COLUMN gpu_id TEXT;
Existing rows retain NULL for gpu_id. New rows populate both fields. Frontend queries by gpu_name continue to work.
6. Clean up dead code
- Remove
HardwareMonitorState.gpu_history (VecDeque<f32>) — never populated by sample_gpu(), always returns empty
- Fix
get_gpu_usage_history command to read from gpu_usage_histories keyed by GPU
Affected Files
src-tauri/src/models/hardware.rs — new GpuMonitorData, redesign HardwareMonitorUpdate, remove dead gpu_history
src-tauri/src/workers/system_monitor.rs — rewrite emit_hardware_update(), remove GpuCapabilities
src-tauri/src/services/monitoring_service.rs — add gpu_id to GpuSample, update update_gpu_histories()
src-tauri/src/commands/hardware.rs — fix get_gpu_usage_history to read from per-GPU histories
src-tauri/src/infrastructure/database/migration.rs — add migration for gpu_id column
src-tauri/src/infrastructure/database/gpu_archive.rs — bind gpu_id on insert
Note
This is a breaking change to the event payload shape. bindings.ts will regenerate via tauri-specta, producing TypeScript compile errors in the frontend that guide the required frontend updates (#TBD).
Parent: #1296
Depends on: #1297
Problem
HardwareMonitorUpdatehas flat, single-GPU fields:emit_hardware_update()insystem_monitor.rspicks onlygpu_samples.first(), discarding all other GPUs even thoughsample_gpu()already returns aVec<GpuSample>with per-GPU data.Proposed Changes
1. Add
GpuMonitorDatastruct (models/hardware.rs)2. Replace flat GPU fields in
HardwareMonitorUpdate3. Rewrite
emit_hardware_update()(system_monitor.rs)gpu_samplesinstead of picking.first()Vec<GpuMonitorData>from the full sliceGpuCapabilitiesstruct (theOptionfields communicate availability implicitly)4. Add
gpu_idtoGpuSample(monitoring_service.rs)Add a
gpu_id: Stringfield toGpuSample. Convention:"pci:{bus}:{device}.{function}"(from ADL BDF)"pci:{bus}:{device}.{function}"(from sysfs symlink)Update
update_gpu_histories()to key ongpu_idinstead ofnamefor reliability.5. Database migration
Add optional
gpu_idcolumn toGPU_DATA_ARCHIVE:Existing rows retain
NULLforgpu_id. New rows populate both fields. Frontend queries bygpu_namecontinue to work.6. Clean up dead code
HardwareMonitorState.gpu_history(VecDeque<f32>) — never populated bysample_gpu(), always returns emptyget_gpu_usage_historycommand to read fromgpu_usage_historieskeyed by GPUAffected Files
src-tauri/src/models/hardware.rs— newGpuMonitorData, redesignHardwareMonitorUpdate, remove deadgpu_historysrc-tauri/src/workers/system_monitor.rs— rewriteemit_hardware_update(), removeGpuCapabilitiessrc-tauri/src/services/monitoring_service.rs— addgpu_idtoGpuSample, updateupdate_gpu_histories()src-tauri/src/commands/hardware.rs— fixget_gpu_usage_historyto read from per-GPU historiessrc-tauri/src/infrastructure/database/migration.rs— add migration forgpu_idcolumnsrc-tauri/src/infrastructure/database/gpu_archive.rs— bindgpu_idon insertNote
This is a breaking change to the event payload shape.
bindings.tswill regenerate via tauri-specta, producing TypeScript compile errors in the frontend that guide the required frontend updates (#TBD).