Add GPU_FOREIGN_UTIL_PCT: game detection via per-process GPU 3D-engine usage
Reads the same PDH counters as Task Manager (\GPU Engine(*)\Utilization Percentage, locale-independent via PdhAddEnglishCounterW), which cover graphics work under WDDM — games are caught without an exe list and the offender is named. Windows-only; a missing or failing counter disables the path with one log line. dwm (the desktop compositor) joins the default ignore list.
This commit is contained in:
@@ -45,7 +45,7 @@ func TestParseComputeApps(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDetect(t *testing.T) {
|
||||
d := New([]string{"Cyberpunk2077.exe", "hl2"}, 1024,
|
||||
d := New([]string{"Cyberpunk2077.exe", "hl2"}, 1024, 0,
|
||||
[]string{"ollama", "python", "pythonw"}, nil)
|
||||
ps := []Process{
|
||||
{PID: 10, Name: "ollama.exe"},
|
||||
@@ -58,7 +58,7 @@ func TestDetect(t *testing.T) {
|
||||
{PID: 40, UsedMB: 2048}, // foreign, above threshold
|
||||
{PID: 50, UsedMB: 100}, // foreign but below threshold
|
||||
}
|
||||
holders := d.detect(ps, apps)
|
||||
holders := d.detect(ps, apps, nil)
|
||||
if len(holders) != 2 {
|
||||
t.Fatalf("got %v, want 2 holders", holders)
|
||||
}
|
||||
@@ -70,13 +70,52 @@ func TestDetect(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectEngineUtil(t *testing.T) {
|
||||
d := New(nil, 0, 30, []string{"dwm", "python"}, nil)
|
||||
ps := []Process{
|
||||
{PID: 10, Name: "dwm.exe"},
|
||||
{PID: 20, Name: "game.exe"},
|
||||
{PID: 40, Name: "browser.exe"},
|
||||
}
|
||||
utils := map[int]float64{
|
||||
10: 45, // ignored: dwm
|
||||
20: 61, // foreign, above threshold
|
||||
30: 82, // foreign, unknown name
|
||||
40: 5, // below threshold
|
||||
}
|
||||
holders := d.detect(ps, nil, utils)
|
||||
want := []string{
|
||||
"game.exe (pid 20) using 61% GPU",
|
||||
"unknown process (pid 30) using 82% GPU",
|
||||
}
|
||||
if !slices.Equal(holders, want) {
|
||||
t.Errorf("got %v, want %v", holders, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDetectNothingConfigured(t *testing.T) {
|
||||
d := New(nil, 0, nil, nil)
|
||||
if got := d.detect([]Process{{PID: 1, Name: "game.exe"}}, nil); len(got) != 0 {
|
||||
d := New(nil, 0, 0, nil, nil)
|
||||
if got := d.detect([]Process{{PID: 1, Name: "game.exe"}}, nil, nil); len(got) != 0 {
|
||||
t.Errorf("got %v, want none", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseGPUEngineInstance(t *testing.T) {
|
||||
pid, eng, ok := parseGPUEngineInstance("pid_1234_luid_0x00000000_0x00011A2B_phys_0_eng_0_engtype_3D")
|
||||
if !ok || pid != 1234 || eng != "3D" {
|
||||
t.Errorf("got %d, %q, %v", pid, eng, ok)
|
||||
}
|
||||
pid, eng, ok = parseGPUEngineInstance("pid_42_luid_0x0_0x0_phys_0_eng_1_engtype_Copy")
|
||||
if !ok || pid != 42 || eng != "Copy" {
|
||||
t.Errorf("got %d, %q, %v", pid, eng, ok)
|
||||
}
|
||||
for _, bad := range []string{"", "something", "pid_", "pid_x_luid", "pid_-1_luid_0"} {
|
||||
if _, _, ok := parseGPUEngineInstance(bad); ok {
|
||||
t.Errorf("%q parsed, want failure", bad)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestProcessesLive(t *testing.T) {
|
||||
if runtime.GOOS != "windows" && runtime.GOOS != "linux" {
|
||||
t.Skip("no process listing on this platform")
|
||||
|
||||
Reference in New Issue
Block a user