//go:build windows package perf import ( "fmt" "strconv" "strings" "unsafe" "golang.org/x/sys/windows" ) var ( pdhDLL = windows.NewLazySystemDLL("pdh.dll") procPdhOpenQuery = pdhDLL.NewProc("PdhOpenQueryW") procPdhAddEnglishCounter = pdhDLL.NewProc("PdhAddEnglishCounterW") procPdhCollectQueryData = pdhDLL.NewProc("PdhCollectQueryData") procPdhGetFormattedCounterArray = pdhDLL.NewProc("PdhGetFormattedCounterArrayW") procPdhCloseQuery = pdhDLL.NewProc("PdhCloseQuery") ) const ( pdhFmtDouble = 0x00000200 pdhMoreData = 0x800007D2 pdhNoData = 0x800007D5 ) type pdhCounterValue struct { CStatus uint32 DblVal float64 } type pdhCounterValueItem struct { SzName *uint16 FmtValue pdhCounterValue } func init() { var item pdhCounterValueItem if unsafe.Sizeof(item) != 24 { panic(fmt.Sprintf("pdhCounterValueItem size %d != expected 24 on x64", unsafe.Sizeof(item))) } } type pdhGpuUtil struct { query uintptr counter uintptr } // initPdhGpuUtil creates a PDH query for the GPU Engine utilization counter. // Returns nil with an error if PDH or the counter is unavailable. func initPdhGpuUtil() (*pdhGpuUtil, error) { var query uintptr if ret, _, _ := procPdhOpenQuery.Call(0, 0, uintptr(unsafe.Pointer(&query))); ret != 0 { return nil, fmt.Errorf("PdhOpenQuery: 0x%x", ret) } path, _ := windows.UTF16PtrFromString(`\GPU Engine(*)\Utilization Percentage`) var counter uintptr if ret, _, _ := procPdhAddEnglishCounter.Call( query, uintptr(unsafe.Pointer(path)), 0, uintptr(unsafe.Pointer(&counter)), ); ret != 0 { procPdhCloseQuery.Call(query) return nil, fmt.Errorf("PdhAddEnglishCounter(GPU Engine): 0x%x", ret) } procPdhCollectQueryData.Call(query) return &pdhGpuUtil{query: query, counter: counter}, nil } // close releases the PDH query handle. func (p *pdhGpuUtil) close() { if p.query != 0 { procPdhCloseQuery.Call(p.query) p.query = 0 } } // collect reads the PDH counter and returns a map of adapter LUID to // aggregated GPU utilization percentage, summed across all engine instances // per adapter and clamped to 100%. func (p *pdhGpuUtil) collect() map[LUID]float64 { ret, _, _ := procPdhCollectQueryData.Call(p.query) if ret != 0 && ret != pdhNoData { return nil } var bufSize uint32 var itemCount uint32 ret, _, _ = procPdhGetFormattedCounterArray.Call( p.counter, pdhFmtDouble, uintptr(unsafe.Pointer(&bufSize)), uintptr(unsafe.Pointer(&itemCount)), 0, ) if ret != pdhMoreData || itemCount == 0 { return nil } buf := make([]byte, bufSize) ret, _, _ = procPdhGetFormattedCounterArray.Call( p.counter, pdhFmtDouble, uintptr(unsafe.Pointer(&bufSize)), uintptr(unsafe.Pointer(&itemCount)), uintptr(unsafe.Pointer(&buf[0])), ) if ret != 0 { return nil } itemSize := uint32(unsafe.Sizeof(pdhCounterValueItem{})) result := make(map[LUID]float64) for i := uint32(0); i < itemCount; i++ { item := (*pdhCounterValueItem)(unsafe.Pointer(&buf[i*itemSize])) if item.FmtValue.CStatus != 0 { continue } luid, ok := parsePdhLuid(windows.UTF16PtrToString(item.SzName)) if !ok { continue } result[luid] += item.FmtValue.DblVal } for luid := range result { if result[luid] > 100.0 { result[luid] = 100.0 } } return result } // parsePdhLuid extracts the adapter LUID (high and low parts) from a PDH // GPU Engine instance name (e.g. "pid_1234_luid_0x00000000_0x000148BF_phys_0_eng_2_engtype_Compute"). func parsePdhLuid(name string) (LUID, bool) { idx := strings.Index(name, "luid_0x") if idx < 0 { return LUID{}, false } rest := name[idx+7:] parts := strings.SplitN(rest, "_", 4) if len(parts) < 3 { return LUID{}, false } hp, err := strconv.ParseUint(parts[0], 16, 32) if err != nil { return LUID{}, false } lpStr := strings.TrimPrefix(parts[1], "0x") lp, err := strconv.ParseUint(lpStr, 16, 32) if err != nil { return LUID{}, false } return LUID{LowPart: uint32(lp), HighPart: int32(hp)}, true }