From e56f0c1ca65207341ada59348abf6b6e3a8433c2 Mon Sep 17 00:00:00 2001 From: vihas makwana Date: Sat, 23 Nov 2024 03:46:32 +0530 Subject: [PATCH 1/6] add raw counter array --- helpers/windows/pdh/pdh_query_windows.go | 15 +++++++-- helpers/windows/pdh/pdh_windows.go | 41 ++++++++++++++++++++++-- helpers/windows/pdh/zpdh_windows.go | 18 +++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) diff --git a/helpers/windows/pdh/pdh_query_windows.go b/helpers/windows/pdh/pdh_query_windows.go index cf1bc7f..4afc275 100644 --- a/helpers/windows/pdh/pdh_query_windows.go +++ b/helpers/windows/pdh/pdh_query_windows.go @@ -203,11 +203,22 @@ func (q *Query) GetCountersAndInstances(objectName string) ([]string, []string, return UTF16ToStringArray(counters), UTF16ToStringArray(instances), nil } -func (q *Query) GetRawCounterValue(counterName string) (*PdhRawCounter, error) { +func (q *Query) GetRawCounterValue(counterName string) (PdhRawCounter, error) { if _, ok := q.Counters[counterName]; !ok { - return nil, fmt.Errorf("%s doesn't exist in the map; call AddCounter()", counterName) + return PdhRawCounter{}, fmt.Errorf("%s doesn't exist in the map; call AddCounter()", counterName) } c, err := PdhGetRawCounterValue(q.Counters[counterName].handle) + if err != nil { + return PdhRawCounter{}, err + } + return c, nil +} + +func (q *Query) GetRawCounterArray(counterName string) ([]*PdhRawCounterItem, error) { + if _, ok := q.Counters[counterName]; !ok { + return nil, fmt.Errorf("%s doesn't exist in the map; call AddCounter()", counterName) + } + c, err := PdhGetRawCounterArray(q.Counters[counterName].handle) if err != nil { return nil, err } diff --git a/helpers/windows/pdh/pdh_windows.go b/helpers/windows/pdh/pdh_windows.go index 5c67f45..e2e81a5 100644 --- a/helpers/windows/pdh/pdh_windows.go +++ b/helpers/windows/pdh/pdh_windows.go @@ -108,6 +108,17 @@ type PdhRawCounter struct { SecondValue int64 MultiCount uint32 } +type pdhRawCounterItem struct { + // Pointer to a null-terminated string that specifies the instance name of the counter. The string is appended to the end of this structure. + SzName *uint16 + //A pdhRawCounter structure that contains the raw counter value of the instance + RawValue PdhRawCounter +} + +type PdhRawCounterItem struct { + InstanceName string + RawValue PdhRawCounter +} // PdhOpenQuery creates a new query. func PdhOpenQuery(dataSource string, userData uintptr) (PdhQueryHandle, error) { @@ -207,13 +218,37 @@ func PdhGetFormattedCounterValueLong(counter PdhCounterHandle) (uint32, *PdhCoun } // PdhGetRawCounterValue returns the raw value of a given counter. -func PdhGetRawCounterValue(counter PdhCounterHandle) (*PdhRawCounter, error) { +func PdhGetRawCounterValue(counter PdhCounterHandle) (PdhRawCounter, error) { var value PdhRawCounter if err := _PdhGetRawCounter(counter, uintptr(unsafe.Pointer(&value))); err != nil { - return &value, PdhErrno(err.(syscall.Errno)) + return value, PdhErrno(err.(syscall.Errno)) } - return &value, nil + return value, nil +} + +func PdhGetRawCounterArray(counter PdhCounterHandle) ([]*PdhRawCounterItem, error) { + var bufferSize, itemCount uint32 + buf := make([]byte, 1) + if err := _PdhGetRawCounterArray(counter, &bufferSize, &itemCount, &buf[0]); err != nil { + if PdhErrno(err.(syscall.Errno)) != PDH_MORE_DATA { + return nil, PdhErrno(err.(syscall.Errno)) + } + buf := make([]byte, bufferSize) + if err := _PdhGetRawCounterArray(counter, &bufferSize, &itemCount, &buf[0]); err != nil { + return nil, PdhErrno(err.(syscall.Errno)) + } + items := (*[1 << 20]pdhRawCounterItem)(unsafe.Pointer(&buf[0]))[:itemCount] + ret := make([]*PdhRawCounterItem, 0, len(items)) + for _, item := range items { + ret = append(ret, &PdhRawCounterItem{ + RawValue: item.RawValue, + InstanceName: windows.UTF16PtrToString(item.SzName), + }) + } + return ret, nil + } + return nil, PdhErrno(syscall.ERROR_NOT_FOUND) } // PdhExpandWildCardPath returns counter paths that match the given counter path. diff --git a/helpers/windows/pdh/zpdh_windows.go b/helpers/windows/pdh/zpdh_windows.go index a731058..7eba6af 100644 --- a/helpers/windows/pdh/zpdh_windows.go +++ b/helpers/windows/pdh/zpdh_windows.go @@ -68,6 +68,10 @@ var ( procPdhGetCounterInfoW = modpdh.NewProc("PdhGetCounterInfoW") procPdhEnumObjectItemsW = modpdh.NewProc("PdhEnumObjectItemsW") procPdhGetRawCounter = modpdh.NewProc("PdhGetRawCounterValue") + procPdhGetRawCounterArray = modpdh.NewProc("PdhGetRawCounterArrayW") + + libPdhDll = syscall.MustLoadDLL("pdh.dll") + pdhGetRawCounterArrayW = libPdhDll.MustFindProc("PdhGetRawCounterArrayW") ) func _PdhOpenQuery(dataSource *uint16, userData uintptr, query *PdhQueryHandle) (errcode error) { @@ -90,6 +94,20 @@ func __PdhGetRawCounter(query PdhCounterHandle, userData uintptr) (errcode error return } + +func _PdhGetRawCounterArray(hCounter PdhCounterHandle, lpdwBufferSize, lpdwBufferCount *uint32, itemBuffer *byte) (errcode error) { + r0, _, _ := syscall.SyscallN( + procPdhGetRawCounterArray.Addr(), + uintptr(hCounter), + uintptr(unsafe.Pointer(lpdwBufferSize)), + uintptr(unsafe.Pointer(lpdwBufferCount)), + uintptr(unsafe.Pointer(itemBuffer))) + if r0 != 0 { + errcode = syscall.Errno(r0) + } + return +} + func _PdhAddEnglishCounter(query PdhQueryHandle, counterPath string, userData uintptr, counter *PdhCounterHandle) (errcode error) { var _p0 *uint16 _p0, errcode = syscall.UTF16PtrFromString(counterPath) From fd8d0df9622b520e0dfd71c0c2ed9799c930e166 Mon Sep 17 00:00:00 2001 From: vihas makwana Date: Sat, 23 Nov 2024 17:11:36 +0530 Subject: [PATCH 2/6] chore: memory improvements --- helpers/windows/pdh/pdh_query_windows.go | 4 +- helpers/windows/pdh/pdh_query_windows_test.go | 46 ++++++++++++++++ helpers/windows/pdh/pdh_windows.go | 55 ++++++++++++++++--- 3 files changed, 95 insertions(+), 10 deletions(-) diff --git a/helpers/windows/pdh/pdh_query_windows.go b/helpers/windows/pdh/pdh_query_windows.go index 4afc275..e612543 100644 --- a/helpers/windows/pdh/pdh_query_windows.go +++ b/helpers/windows/pdh/pdh_query_windows.go @@ -214,11 +214,11 @@ func (q *Query) GetRawCounterValue(counterName string) (PdhRawCounter, error) { return c, nil } -func (q *Query) GetRawCounterArray(counterName string) ([]*PdhRawCounterItem, error) { +func (q *Query) GetRawCounterArray(counterName string, filterTotal bool) (RawCouterArray, error) { if _, ok := q.Counters[counterName]; !ok { return nil, fmt.Errorf("%s doesn't exist in the map; call AddCounter()", counterName) } - c, err := PdhGetRawCounterArray(q.Counters[counterName].handle) + c, err := PdhGetRawCounterArray(q.Counters[counterName].handle, filterTotal) if err != nil { return nil, err } diff --git a/helpers/windows/pdh/pdh_query_windows_test.go b/helpers/windows/pdh/pdh_query_windows_test.go index 340acf6..f2c2e49 100644 --- a/helpers/windows/pdh/pdh_query_windows_test.go +++ b/helpers/windows/pdh/pdh_query_windows_test.go @@ -193,3 +193,49 @@ func TestUTF16ToStringArray(t *testing.T) { assert.Contains(t, array, res) } } + +func TestSortOrder(t *testing.T) { + scenarios := []struct { + name string + counters []string + }{{ + name: "processor-information", + counters: []string{ + "\\Processor Information(*)\\% User Time", + "\\Processor Information(*)\\% Privileged Time", + "\\Processor Information(*)\\% Idle Time", + }, + }, { + name: "processor", + counters: []string{ + "\\Processor(*)\\% User Time", + "\\Processor(*)\\% Privileged Time", + "\\Processor(*)\\% Idle Time", + }, + }} + for _, s := range scenarios { + t.Run(s.name, func(t *testing.T) { + var q Query + assert.NoError(t, q.Open()) + for _, counter := range s.counters { + assert.NoError(t, q.AddCounter(counter, "", "", false)) + } + assert.NoError(t, q.CollectData()) + rawCounters := make([][]PdhRawCounterItem, 0) + for _, counter := range s.counters { + arr, err := q.GetRawCounterArray(counter, true) + assert.NoError(t, err) + rawCounters = append(rawCounters, arr) + } + for i := 0; i < len(rawCounters)-1; i++ { + assert.Equalf(t, len(rawCounters[i]), len(rawCounters[i+1]), "returned counters should be equal") + } + // confirm that each index corresponds to one particular instance (i.e. core) + for i := 0; i < len(rawCounters)-1; i++ { + for j := 0; j < len(rawCounters[i]); j++ { + assert.Equalf(t, rawCounters[i][j].InstanceName, rawCounters[i+1][j].InstanceName, "Instanse name should be equal") + } + } + }) + } +} diff --git a/helpers/windows/pdh/pdh_windows.go b/helpers/windows/pdh/pdh_windows.go index e2e81a5..cd9fe2d 100644 --- a/helpers/windows/pdh/pdh_windows.go +++ b/helpers/windows/pdh/pdh_windows.go @@ -20,7 +20,10 @@ package pdh import ( + "sort" "strconv" + "strings" + "sync" "syscall" "unicode/utf16" "unsafe" @@ -120,6 +123,20 @@ type PdhRawCounterItem struct { RawValue PdhRawCounter } +type RawCouterArray []PdhRawCounterItem + +func (a RawCouterArray) Len() int { + return len(a) +} + +func (a RawCouterArray) Swap(i, j int) { + a[i], a[j] = a[j], a[i] +} + +func (a RawCouterArray) Less(i, j int) bool { + return a[i].InstanceName < a[j].InstanceName +} + // PdhOpenQuery creates a new query. func PdhOpenQuery(dataSource string, userData uintptr) (PdhQueryHandle, error) { var dataSourcePtr *uint16 @@ -227,25 +244,47 @@ func PdhGetRawCounterValue(counter PdhCounterHandle) (PdhRawCounter, error) { return value, nil } -func PdhGetRawCounterArray(counter PdhCounterHandle) ([]*PdhRawCounterItem, error) { +// use buffer pool to resue the underlying byte slice. This reduces memory allocations +var bufPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 1024) + }, +} + +func PdhGetRawCounterArray(counter PdhCounterHandle, filterTotal bool) (RawCouterArray, error) { var bufferSize, itemCount uint32 - buf := make([]byte, 1) - if err := _PdhGetRawCounterArray(counter, &bufferSize, &itemCount, &buf[0]); err != nil { + if err := _PdhGetRawCounterArray(counter, &bufferSize, &itemCount, nil); err != nil { if PdhErrno(err.(syscall.Errno)) != PDH_MORE_DATA { return nil, PdhErrno(err.(syscall.Errno)) } - buf := make([]byte, bufferSize) + // Get the byte buffer + buf := bufPool.Get().([]byte) + + // if the buffer is lower than required size, then create a new one. + if len(buf) < int(bufferSize) { + buf = make([]byte, bufferSize) + } if err := _PdhGetRawCounterArray(counter, &bufferSize, &itemCount, &buf[0]); err != nil { return nil, PdhErrno(err.(syscall.Errno)) } - items := (*[1 << 20]pdhRawCounterItem)(unsafe.Pointer(&buf[0]))[:itemCount] - ret := make([]*PdhRawCounterItem, 0, len(items)) + items := unsafe.Slice((*pdhRawCounterItem)(unsafe.Pointer(&buf[0])), itemCount) + ret := make([]PdhRawCounterItem, 0, len(items)) for _, item := range items { - ret = append(ret, &PdhRawCounterItem{ + instance := windows.UTF16PtrToString(item.SzName) + if filterTotal && strings.Contains(instance, "_Total") { + continue + } + ret = append(ret, PdhRawCounterItem{ RawValue: item.RawValue, - InstanceName: windows.UTF16PtrToString(item.SzName), + InstanceName: instance, }) } + // we sort the array by the instance name to ensure that each index in the final array corresponds to a specific core + // This is important because we will be collecting three different types of counters, and sorting ensures that each index in each counter aligns with the correct core. + sort.Sort(RawCouterArray(ret)) + + // reuse the buffer + bufPool.Put(buf) return ret, nil } return nil, PdhErrno(syscall.ERROR_NOT_FOUND) From 70cb699b6ed5f2ce398b183d144b0b04fc2232b5 Mon Sep 17 00:00:00 2001 From: vihas makwana Date: Sat, 23 Nov 2024 17:23:55 +0530 Subject: [PATCH 3/6] remove sync.Pool --- helpers/windows/pdh/pdh_windows.go | 19 +------------------ helpers/windows/pdh/zpdh_windows.go | 3 --- 2 files changed, 1 insertion(+), 21 deletions(-) diff --git a/helpers/windows/pdh/pdh_windows.go b/helpers/windows/pdh/pdh_windows.go index cd9fe2d..8f28f8a 100644 --- a/helpers/windows/pdh/pdh_windows.go +++ b/helpers/windows/pdh/pdh_windows.go @@ -23,7 +23,6 @@ import ( "sort" "strconv" "strings" - "sync" "syscall" "unicode/utf16" "unsafe" @@ -244,26 +243,13 @@ func PdhGetRawCounterValue(counter PdhCounterHandle) (PdhRawCounter, error) { return value, nil } -// use buffer pool to resue the underlying byte slice. This reduces memory allocations -var bufPool = sync.Pool{ - New: func() interface{} { - return make([]byte, 1024) - }, -} - func PdhGetRawCounterArray(counter PdhCounterHandle, filterTotal bool) (RawCouterArray, error) { var bufferSize, itemCount uint32 if err := _PdhGetRawCounterArray(counter, &bufferSize, &itemCount, nil); err != nil { if PdhErrno(err.(syscall.Errno)) != PDH_MORE_DATA { return nil, PdhErrno(err.(syscall.Errno)) } - // Get the byte buffer - buf := bufPool.Get().([]byte) - - // if the buffer is lower than required size, then create a new one. - if len(buf) < int(bufferSize) { - buf = make([]byte, bufferSize) - } + buf := make([]byte, bufferSize) if err := _PdhGetRawCounterArray(counter, &bufferSize, &itemCount, &buf[0]); err != nil { return nil, PdhErrno(err.(syscall.Errno)) } @@ -282,9 +268,6 @@ func PdhGetRawCounterArray(counter PdhCounterHandle, filterTotal bool) (RawCoute // we sort the array by the instance name to ensure that each index in the final array corresponds to a specific core // This is important because we will be collecting three different types of counters, and sorting ensures that each index in each counter aligns with the correct core. sort.Sort(RawCouterArray(ret)) - - // reuse the buffer - bufPool.Put(buf) return ret, nil } return nil, PdhErrno(syscall.ERROR_NOT_FOUND) diff --git a/helpers/windows/pdh/zpdh_windows.go b/helpers/windows/pdh/zpdh_windows.go index 7eba6af..e68c1fd 100644 --- a/helpers/windows/pdh/zpdh_windows.go +++ b/helpers/windows/pdh/zpdh_windows.go @@ -69,9 +69,6 @@ var ( procPdhEnumObjectItemsW = modpdh.NewProc("PdhEnumObjectItemsW") procPdhGetRawCounter = modpdh.NewProc("PdhGetRawCounterValue") procPdhGetRawCounterArray = modpdh.NewProc("PdhGetRawCounterArrayW") - - libPdhDll = syscall.MustLoadDLL("pdh.dll") - pdhGetRawCounterArrayW = libPdhDll.MustFindProc("PdhGetRawCounterArrayW") ) func _PdhOpenQuery(dataSource *uint16, userData uintptr, query *PdhQueryHandle) (errcode error) { From 22c04ae05ffece0ea30d72ab64a0d90c58de8f49 Mon Sep 17 00:00:00 2001 From: Vihas Makwana Date: Mon, 25 Nov 2024 22:26:31 +0530 Subject: [PATCH 4/6] chore: add english counter --- helpers/windows/pdh/pdh_query_windows.go | 9 +++++++-- helpers/windows/pdh/pdh_query_windows_test.go | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/helpers/windows/pdh/pdh_query_windows.go b/helpers/windows/pdh/pdh_query_windows.go index e612543..95aafdb 100644 --- a/helpers/windows/pdh/pdh_query_windows.go +++ b/helpers/windows/pdh/pdh_query_windows.go @@ -80,7 +80,7 @@ func (q *Query) AddEnglishCounter(counterPath string) (PdhCounterHandle, error) } // AddCounter adds the specified counter to the query. -func (q *Query) AddCounter(counterPath string, instance string, format string, wildcard bool) error { +func (q *Query) AddCounter(counterPath string, instance string, format string, wildcard bool, english bool) error { if _, found := q.Counters[counterPath]; found { return nil } @@ -95,7 +95,12 @@ func (q *Query) AddCounter(counterPath string, instance string, format string, w } else { instanceName = instance } - h, err := PdhAddCounter(q.Handle, counterPath, 0) + var h PdhCounterHandle + if english { + h, err = PdhAddEnglishCounter(q.Handle, counterPath, 0) + } else { + h, err = PdhAddCounter(q.Handle, counterPath, 0) + } if err != nil { return err } diff --git a/helpers/windows/pdh/pdh_query_windows_test.go b/helpers/windows/pdh/pdh_query_windows_test.go index f2c2e49..75bb23e 100644 --- a/helpers/windows/pdh/pdh_query_windows_test.go +++ b/helpers/windows/pdh/pdh_query_windows_test.go @@ -38,7 +38,7 @@ func TestAddCounterInvalidArgWhenQueryClosed(t *testing.T) { queryPath, err := q.GetCounterPaths(validQuery) // if windows os language is ENG then err will be nil, else the GetCounterPaths will execute the AddCounter if assert.NoError(t, err) { - err = q.AddCounter(queryPath[0], "TestInstanceName", "float", false) + err = q.AddCounter(queryPath[0], "TestInstanceName", "float", false, false) assert.Error(t, err, PDH_INVALID_HANDLE) } else { assert.Error(t, err, PDH_INVALID_ARGUMENT) @@ -73,7 +73,7 @@ func TestSuccessfulQuery(t *testing.T) { if err != nil { t.Fatal(err) } - err = q.AddCounter(queryPath[0], "TestInstanceName", "floar", false) + err = q.AddCounter(queryPath[0], "TestInstanceName", "floar", false, false) if err != nil { t.Fatal(err) } From d1bdb5ee81162678575bcf5ffa650b8e07295850 Mon Sep 17 00:00:00 2001 From: Vihas Makwana Date: Mon, 25 Nov 2024 22:31:00 +0530 Subject: [PATCH 5/6] test fix --- helpers/windows/pdh/pdh_query_windows_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/helpers/windows/pdh/pdh_query_windows_test.go b/helpers/windows/pdh/pdh_query_windows_test.go index 75bb23e..9a9d92d 100644 --- a/helpers/windows/pdh/pdh_query_windows_test.go +++ b/helpers/windows/pdh/pdh_query_windows_test.go @@ -218,7 +218,7 @@ func TestSortOrder(t *testing.T) { var q Query assert.NoError(t, q.Open()) for _, counter := range s.counters { - assert.NoError(t, q.AddCounter(counter, "", "", false)) + assert.NoError(t, q.AddCounter(counter, "", "", false, false)) } assert.NoError(t, q.CollectData()) rawCounters := make([][]PdhRawCounterItem, 0) From 0d290c679f51bcf5f7d74a42ddf8e36a4ab4f878 Mon Sep 17 00:00:00 2001 From: Vihas Makwana Date: Tue, 26 Nov 2024 18:21:31 +0530 Subject: [PATCH 6/6] fix: spelling --- helpers/windows/pdh/pdh_query_windows.go | 2 +- helpers/windows/pdh/pdh_query_windows_test.go | 2 +- helpers/windows/pdh/pdh_windows.go | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/helpers/windows/pdh/pdh_query_windows.go b/helpers/windows/pdh/pdh_query_windows.go index 95aafdb..857753e 100644 --- a/helpers/windows/pdh/pdh_query_windows.go +++ b/helpers/windows/pdh/pdh_query_windows.go @@ -219,7 +219,7 @@ func (q *Query) GetRawCounterValue(counterName string) (PdhRawCounter, error) { return c, nil } -func (q *Query) GetRawCounterArray(counterName string, filterTotal bool) (RawCouterArray, error) { +func (q *Query) GetRawCounterArray(counterName string, filterTotal bool) (RawCounterArray, error) { if _, ok := q.Counters[counterName]; !ok { return nil, fmt.Errorf("%s doesn't exist in the map; call AddCounter()", counterName) } diff --git a/helpers/windows/pdh/pdh_query_windows_test.go b/helpers/windows/pdh/pdh_query_windows_test.go index 9a9d92d..17b6f85 100644 --- a/helpers/windows/pdh/pdh_query_windows_test.go +++ b/helpers/windows/pdh/pdh_query_windows_test.go @@ -233,7 +233,7 @@ func TestSortOrder(t *testing.T) { // confirm that each index corresponds to one particular instance (i.e. core) for i := 0; i < len(rawCounters)-1; i++ { for j := 0; j < len(rawCounters[i]); j++ { - assert.Equalf(t, rawCounters[i][j].InstanceName, rawCounters[i+1][j].InstanceName, "Instanse name should be equal") + assert.Equalf(t, rawCounters[i][j].InstanceName, rawCounters[i+1][j].InstanceName, "Instance name should be equal") } } }) diff --git a/helpers/windows/pdh/pdh_windows.go b/helpers/windows/pdh/pdh_windows.go index 8f28f8a..d44ec56 100644 --- a/helpers/windows/pdh/pdh_windows.go +++ b/helpers/windows/pdh/pdh_windows.go @@ -122,17 +122,17 @@ type PdhRawCounterItem struct { RawValue PdhRawCounter } -type RawCouterArray []PdhRawCounterItem +type RawCounterArray []PdhRawCounterItem -func (a RawCouterArray) Len() int { +func (a RawCounterArray) Len() int { return len(a) } -func (a RawCouterArray) Swap(i, j int) { +func (a RawCounterArray) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a RawCouterArray) Less(i, j int) bool { +func (a RawCounterArray) Less(i, j int) bool { return a[i].InstanceName < a[j].InstanceName } @@ -243,7 +243,7 @@ func PdhGetRawCounterValue(counter PdhCounterHandle) (PdhRawCounter, error) { return value, nil } -func PdhGetRawCounterArray(counter PdhCounterHandle, filterTotal bool) (RawCouterArray, error) { +func PdhGetRawCounterArray(counter PdhCounterHandle, filterTotal bool) (RawCounterArray, error) { var bufferSize, itemCount uint32 if err := _PdhGetRawCounterArray(counter, &bufferSize, &itemCount, nil); err != nil { if PdhErrno(err.(syscall.Errno)) != PDH_MORE_DATA { @@ -267,7 +267,7 @@ func PdhGetRawCounterArray(counter PdhCounterHandle, filterTotal bool) (RawCoute } // we sort the array by the instance name to ensure that each index in the final array corresponds to a specific core // This is important because we will be collecting three different types of counters, and sorting ensures that each index in each counter aligns with the correct core. - sort.Sort(RawCouterArray(ret)) + sort.Sort(RawCounterArray(ret)) return ret, nil } return nil, PdhErrno(syscall.ERROR_NOT_FOUND)