Skip to content

Commit c493e0f

Browse files
committed
Updated: GPU widget improvements
1 parent 5c377d8 commit c493e0f

6 files changed

Lines changed: 455 additions & 139 deletions

File tree

internal/widget/gpu/gpu.go

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,24 +32,26 @@ const (
3232
)
3333

3434
// supportedMetrics lists metrics that are currently functional.
35-
// Memory metrics (memory_dedicated, memory_shared) are defined as constants but not yet
36-
// supported because PDH doesn't provide total VRAM counters needed for percentage calculation.
3735
var supportedMetrics = map[string]bool{
3836
MetricUtilization: true,
3937
MetricUtilization3D: true,
4038
MetricUtilizationCopy: true,
4139
MetricUtilizationEncode: true,
4240
MetricUtilizationDecode: true,
41+
MetricMemoryDedicated: true,
42+
MetricMemoryShared: true,
4343
}
4444

4545
// AdapterInfo contains information about a GPU adapter.
4646
// Index is a sequential number (0, 1, 2, ...) assigned during discovery.
4747
// On Windows, LUID (Locally Unique Identifier) is the primary key used to
4848
// distinguish adapters, since all GPUs report phys_0 in PDH instance names.
4949
type AdapterInfo struct {
50-
Index int
51-
Name string
52-
LUID string // Locally Unique Identifier from PDH instance names (Windows only)
50+
Index int
51+
Name string
52+
LUID string // Locally Unique Identifier from PDH instance names (Windows only)
53+
DedicatedVideoMemory uint64 // Total dedicated VRAM in bytes (from DXGI, Windows only)
54+
SharedSystemMemory uint64 // Total shared system memory in bytes (from DXGI, Windows only)
5355
}
5456

5557
// Reader is the interface for reading GPU metrics
@@ -75,8 +77,9 @@ type Widget struct {
7577
Renderer *render.MetricRenderer
7678

7779
// GPU configuration
78-
adapter int // GPU adapter index
79-
metric string // Metric to display
80+
adapter int // GPU adapter index
81+
metric string // Metric to display
82+
textFormat string // Format string for bar text overlay (from text.format)
8083

8184
// GPU metrics reader
8285
reader Reader
@@ -110,9 +113,15 @@ func New(cfg config.WidgetConfig) (*Widget, error) {
110113
}
111114
}
112115

116+
// Extract text format for bar overlay
117+
textFormat := ""
118+
if cfg.Text != nil {
119+
textFormat = cfg.Text.Format
120+
}
121+
113122
// Validate metric
114123
if !supportedMetrics[metric] {
115-
return nil, fmt.Errorf("unsupported GPU metric: %q (supported: utilization, utilization_3d, utilization_copy, utilization_video_encode, utilization_video_decode)", metric)
124+
return nil, fmt.Errorf("unsupported GPU metric: %q (supported: utilization, utilization_3d, utilization_copy, utilization_video_encode, utilization_video_decode, memory_dedicated, memory_shared)", metric)
116125
}
117126

118127
// Initialize reader (platform-specific)
@@ -142,6 +151,7 @@ func New(cfg config.WidgetConfig) (*Widget, error) {
142151
Renderer: mr.Renderer,
143152
adapter: adapter,
144153
metric: metric,
154+
textFormat: textFormat,
145155
reader: reader,
146156
readerFailed: readerFailed,
147157
history: util.NewRingBuffer[float64](mr.HistoryLen),
@@ -203,11 +213,17 @@ func (w *Widget) Render() (image.Image, error) {
203213
return img, nil
204214
}
205215

216+
// Determine text format: use configured format for text mode, default for others
217+
textFmt := "%.0f"
218+
if w.textFormat != "" {
219+
textFmt = w.textFormat
220+
}
221+
206222
// Use strategy pattern for rendering
207223
w.strategy.Render(img, render.MetricData{
208224
Value: w.currentValue,
209225
History: w.history.ToSlice(),
210-
TextFormat: "%.0f",
226+
TextFormat: textFmt,
211227
ContentArea: image.Rect(content.X, content.Y, content.X+content.Width, content.Y+content.Height),
212228
GaugeArea: image.Rect(0, 0, pos.W, pos.H),
213229
}, w.Renderer)

internal/widget/gpu/gpu_test.go

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ func TestNew_WithGPUConfig(t *testing.T) {
139139
{"adapter 0 copy", 0, MetricUtilizationCopy},
140140
{"adapter 0 encode", 0, MetricUtilizationEncode},
141141
{"adapter 0 decode", 0, MetricUtilizationDecode},
142+
{"adapter 0 memory dedicated", 0, MetricMemoryDedicated},
143+
{"adapter 0 memory shared", 0, MetricMemoryShared},
142144
}
143145

144146
for _, tt := range tests {
@@ -180,8 +182,6 @@ func TestNew_InvalidMetric(t *testing.T) {
180182
metric string
181183
}{
182184
{"unknown metric", "nonexistent"},
183-
{"memory_dedicated (unsupported)", MetricMemoryDedicated},
184-
{"memory_shared (unsupported)", MetricMemoryShared},
185185
{"empty with explicit config", ""},
186186
}
187187

@@ -208,7 +208,7 @@ func TestNew_InvalidMetric(t *testing.T) {
208208
}
209209
}
210210

211-
// TestNew_SupportedMetricsCompleteness verifies that all utilization metric constants
211+
// TestNew_SupportedMetricsCompleteness verifies that all metric constants
212212
// are present in the supportedMetrics map
213213
func TestNew_SupportedMetricsCompleteness(t *testing.T) {
214214
expectedSupported := []string{
@@ -217,24 +217,15 @@ func TestNew_SupportedMetricsCompleteness(t *testing.T) {
217217
MetricUtilizationCopy,
218218
MetricUtilizationEncode,
219219
MetricUtilizationDecode,
220+
MetricMemoryDedicated,
221+
MetricMemoryShared,
220222
}
221223

222224
for _, metric := range expectedSupported {
223225
if !supportedMetrics[metric] {
224226
t.Errorf("metric %q should be in supportedMetrics", metric)
225227
}
226228
}
227-
228-
expectedUnsupported := []string{
229-
MetricMemoryDedicated,
230-
MetricMemoryShared,
231-
}
232-
233-
for _, metric := range expectedUnsupported {
234-
if supportedMetrics[metric] {
235-
t.Errorf("metric %q should NOT be in supportedMetrics (memory metrics not yet supported)", metric)
236-
}
237-
}
238229
}
239230

240231
// TestWidget_Render_BeforeUpdate tests rendering before first update
@@ -417,3 +408,41 @@ func TestWidget_ValueClamping(t *testing.T) {
417408
})
418409
}
419410
}
411+
412+
// TestNew_TextFormat tests that text format is correctly extracted from config
413+
func TestNew_TextFormat(t *testing.T) {
414+
tests := []struct {
415+
name string
416+
textConfig *config.TextConfig
417+
wantFormat string
418+
}{
419+
{"no text config", nil, ""},
420+
{"empty format", &config.TextConfig{Size: 10}, ""},
421+
{"custom format", &config.TextConfig{Format: "GPU %.0f%%", Size: 10}, "GPU %.0f%%"},
422+
{"vram format", &config.TextConfig{Format: "VRAM %.0f%%", Size: 10}, "VRAM %.0f%%"},
423+
}
424+
425+
for _, tt := range tests {
426+
t.Run(tt.name, func(t *testing.T) {
427+
cfg := config.WidgetConfig{
428+
Type: "gpu",
429+
ID: "test_gpu_format",
430+
Enabled: config.BoolPtr(true),
431+
Position: config.PositionConfig{
432+
X: 0, Y: 0, W: 128, H: 40,
433+
},
434+
Mode: "bar",
435+
Text: tt.textConfig,
436+
}
437+
438+
w, err := New(cfg)
439+
if err != nil {
440+
t.Fatalf("New() error = %v", err)
441+
}
442+
443+
if w.textFormat != tt.wantFormat {
444+
t.Errorf("textFormat = %q, want %q", w.textFormat, tt.wantFormat)
445+
}
446+
})
447+
}
448+
}

0 commit comments

Comments
 (0)