From 248b7214e3f1c47c208c7f93d37373390da66b5b Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Fri, 19 Mar 2021 10:12:55 +1100 Subject: [PATCH 01/14] Move netapi free back to a defer statement Signed-off-by: Ben Ridley --- headers/netapi32/netapi32.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/headers/netapi32/netapi32.go b/headers/netapi32/netapi32.go index 441ccfd2..a24e7ef0 100644 --- a/headers/netapi32/netapi32.go +++ b/headers/netapi32/netapi32.go @@ -75,17 +75,19 @@ func netApiBufferFree(buffer *wKSTAInfo102) { // NetWkstaGetInfo returns information about the configuration of a workstation. // WARNING: The caller must call netApiBufferFree to free the memory allocated by this function. // https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstagetinfo -func netWkstaGetInfo() (*wKSTAInfo102, uint32, error) { +func netWkstaGetInfo() (wKSTAInfo102, uint32, error) { var lpwi *wKSTAInfo102 pLevel := uintptr(102) r1, _, _ := procNetWkstaGetInfo.Call(0, pLevel, uintptr(unsafe.Pointer(&lpwi))) + defer netApiBufferFree(lpwi) if ret := *(*uint32)(unsafe.Pointer(&r1)); ret != 0 { - return nil, ret, errors.New(NetApiStatus[ret]) + return wKSTAInfo102{}, ret, errors.New(NetApiStatus[ret]) } - return lpwi, 0, nil + deref := *lpwi + return deref, 0, nil } // GetWorkstationInfo is an idiomatic wrapper for netWkstaGetInfo @@ -103,7 +105,5 @@ func GetWorkstationInfo() (WorkstationInfo, error) { LanRoot: windows.UTF16PtrToString(info.wki102_lanroot), LoggedOnUsers: info.wki102_logged_on_users, } - // Free the memory allocated by netapi - netApiBufferFree(info) return workstationInfo, nil } From 71054ac42915f244974fcc1e1a4600e2135ad0bd Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Fri, 18 Dec 2020 18:02:33 +1100 Subject: [PATCH 02/14] Replace the CS collector with native WinAPI calls to sysinfoapi Signed-off-by: Ben Ridley --- collector/cs.go | 48 +++++++---------- collector/cs_test.go | 23 ++++++++ sysinfoapi/sysinfoapi.go | 112 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 28 deletions(-) create mode 100644 collector/cs_test.go create mode 100644 sysinfoapi/sysinfoapi.go diff --git a/collector/cs.go b/collector/cs.go index 1978855c..585ab827 100644 --- a/collector/cs.go +++ b/collector/cs.go @@ -3,10 +3,8 @@ package collector import ( - "errors" - - "github.com/StackExchange/wmi" "github.com/prometheus-community/windows_exporter/log" + "github.com/prometheus-community/windows_exporter/sysinfoapi" "github.com/prometheus/client_golang/prometheus" ) @@ -60,51 +58,45 @@ func (c *CSCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) e return nil } -// Win32_ComputerSystem docs: -// - https://msdn.microsoft.com/en-us/library/aa394102 -type Win32_ComputerSystem struct { - NumberOfLogicalProcessors uint32 - TotalPhysicalMemory uint64 - DNSHostname string - Domain string - Workgroup *string -} - func (c *CSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) { - var dst []Win32_ComputerSystem - q := queryAll(&dst) - if err := wmi.Query(q, &dst); err != nil { + cs := sysinfoapi.GetNumLogicalProcessors() + + pm, err := sysinfoapi.GetPhysicalMemory() + if err != nil { return nil, err } - if len(dst) == 0 { - return nil, errors.New("WMI query returned empty result set") - } ch <- prometheus.MustNewConstMetric( c.LogicalProcessors, prometheus.GaugeValue, - float64(dst[0].NumberOfLogicalProcessors), + float64(cs), ) ch <- prometheus.MustNewConstMetric( c.PhysicalMemoryBytes, prometheus.GaugeValue, - float64(dst[0].TotalPhysicalMemory), + float64(pm), ) - var fqdn string - if dst[0].Workgroup == nil || dst[0].Domain != *dst[0].Workgroup { - fqdn = dst[0].DNSHostname + "." + dst[0].Domain - } else { - fqdn = dst[0].DNSHostname + hostname, err := sysinfoapi.GetComputerName(sysinfoapi.ComputerNameDNSHostname) + if err != nil { + return nil, err + } + domain, err := sysinfoapi.GetComputerName(sysinfoapi.ComputerNameDNSDomain) + if err != nil { + return nil, err + } + fqdn, err := sysinfoapi.GetComputerName(sysinfoapi.ComputerNameDNSFullyQualified) + if err != nil { + return nil, err } ch <- prometheus.MustNewConstMetric( c.Hostname, prometheus.GaugeValue, 1.0, - dst[0].DNSHostname, - dst[0].Domain, + hostname, + domain, fqdn, ) diff --git a/collector/cs_test.go b/collector/cs_test.go new file mode 100644 index 00000000..a4b3c9f1 --- /dev/null +++ b/collector/cs_test.go @@ -0,0 +1,23 @@ +package collector + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" +) + +func BenchmarkCsCollect(b *testing.B) { + c, err := NewCSCollector() + if err != nil { + b.Error(err) + } + metrics := make(chan prometheus.Metric) + go func() { + for { + <-metrics + } + }() + for i := 0; i < b.N; i++ { + c.Collect(&ScrapeContext{}, metrics) + } +} diff --git a/sysinfoapi/sysinfoapi.go b/sysinfoapi/sysinfoapi.go new file mode 100644 index 00000000..fcddf2d3 --- /dev/null +++ b/sysinfoapi/sysinfoapi.go @@ -0,0 +1,112 @@ +// Copyright 2020 Prometheus Team +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package sysinfoapi wraps some WinAPI sysinfoapi functions used by the exporter to produce metrics. +// It is fairly opinionated for the exporter's internal use, and is not intended to be a +// generic wrapper for the WinAPI's sysinfoapi. +package sysinfoapi + +import ( + "unicode/utf16" + "unsafe" + + "golang.org/x/sys/windows" +) + +// WinProcInfo is a wrapper for +type WinProcInfo struct { + WReserved uint16 + WProcessorArchitecture uint16 +} + +// WinSystemInfo is a wrapper for LPSYSTEM_INFO +type WinSystemInfo struct { + Arch WinProcInfo + DwPageSize uint32 + LpMinimumApplicationAddress *byte + LpMaximumApplicationAddress *byte + DwActiveProcessorMask *uint32 + DwNumberOfProcessors uint32 + DwProcessorType uint32 + DwAllocationGranularity uint32 + WProcessorLevel uint16 + WProcessorRevision uint16 +} + +// WinComputerNameFormat is a wrapper for COMPUTER_NAME_FORMAT +type WinComputerNameFormat int + +// Definitions for WinComputerNameFormat constants +const ( + ComputerNameNetBIOS WinComputerNameFormat = iota + ComputerNameDNSHostname + ComputerNameDNSDomain + ComputerNameDNSFullyQualified + ComputerNamePhysicalNetBIOS + ComputerNamePhysicalDNSHostname + ComputerNamePhysicalDNSDomain + ComputerNamePhysicalDNSFullyQualified + ComputerNameMax +) + +// WinMemoryStatus is a wrapper for LPMEMORYSTATUSEX +type WinMemoryStatus struct { + dwLength uint32 + dwMemoryLoad uint32 + ullTotalPhys uint64 + ullAvailPhys uint64 + ullTotalPageFile uint64 + ullAvailPageFile uint64 + ullTotalVirtual uint64 + ullAvailVirtual uint64 + ullAvailExtendedVirtual uint64 +} + +var ( + kernel32 = windows.NewLazySystemDLL("kernel32.dll") + procGetSystemInfo = kernel32.NewProc("GetSystemInfo") + procGlobalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx") + procGetComputerNameExW = kernel32.NewProc("GetComputerNameExW") +) + +// GetNumLogicalProcessors returns the number of logical processes provided by sysinfoapi's GetSystemInfo function. +func GetNumLogicalProcessors() int { + var sysInfo WinSystemInfo + pInfo := uintptr(unsafe.Pointer(&sysInfo)) + procGetSystemInfo.Call(pInfo) + return int(sysInfo.DwNumberOfProcessors) +} + +// GetPhysicalMemory returns the system's installed physical memory provided by sysinfoapi's GlobalMemoryStatusEx function. +func GetPhysicalMemory() (int, error) { + var wm WinMemoryStatus + wm.dwLength = (uint32)(unsafe.Sizeof(wm)) + r1, _, err := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&wm))) + if r1 != 1 { + return 0, err + } + return int(wm.ullTotalPhys), nil +} + +// GetComputerName provides the requested computer name provided by sysinfoapi's GetComputerNameEx function. +func GetComputerName(f WinComputerNameFormat) (string, error) { + size := 4096 + var buffer [4096]uint16 + r1, _, err := procGetComputerNameExW.Call(uintptr(f), uintptr(unsafe.Pointer(&buffer)), uintptr(unsafe.Pointer(&size))) + if r1 == 0 { + return "", err + } + bytes := buffer[0:size] + out := utf16.Decode(bytes) + return string(out), nil +} From f76334213dff459bbdf04f2abb296fd2d7b3be3e Mon Sep 17 00:00:00 2001 From: retryW Date: Mon, 21 Dec 2020 17:11:11 +1100 Subject: [PATCH 03/14] Convert os time and timezone from WMI to native go Signed-off-by: Ben Ridley --- collector/os.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/collector/os.go b/collector/os.go index f9db5796..fb9f8f14 100644 --- a/collector/os.go +++ b/collector/os.go @@ -171,15 +171,15 @@ func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er float64(dst[0].FreePhysicalMemory*1024), // KiB -> bytes ) - time := dst[0].LocalDateTime + currentTime := time.Now() ch <- prometheus.MustNewConstMetric( c.Time, prometheus.GaugeValue, - float64(time.Unix()), + float64(currentTime.Unix()), ) - timezoneName, _ := time.Zone() + timezoneName, _ := currentTime.Zone() ch <- prometheus.MustNewConstMetric( c.Timezone, From 048bff919e4fa081f51a984e121a81adbae8574e Mon Sep 17 00:00:00 2001 From: retryW Date: Wed, 13 Jan 2021 11:23:40 +1100 Subject: [PATCH 04/14] Converted most metrics to non-wmi Signed-off-by: Ben Ridley --- collector/os.go | 57 +++++++++++++++---- headers/custom/custom.go | 54 ++++++++++++++++++ headers/netapi32/netapi32.go | 89 +++++++++++++++++++++++++++++ headers/psapi/psapi.go | 98 ++++++++++++++++++++++++++++++++ headers/sysinfoapi/sysinfoapi.go | 43 ++++++++++++++ 5 files changed, 329 insertions(+), 12 deletions(-) create mode 100644 headers/custom/custom.go create mode 100644 headers/netapi32/netapi32.go create mode 100644 headers/psapi/psapi.go create mode 100644 headers/sysinfoapi/sysinfoapi.go diff --git a/collector/os.go b/collector/os.go index fb9f8f14..9c9c6af0 100644 --- a/collector/os.go +++ b/collector/os.go @@ -4,9 +4,14 @@ package collector import ( "errors" + "fmt" "time" "github.com/StackExchange/wmi" + "github.com/prometheus-community/windows_exporter/headers/custom" + "github.com/prometheus-community/windows_exporter/headers/netapi32" + "github.com/prometheus-community/windows_exporter/headers/psapi" + "github.com/prometheus-community/windows_exporter/headers/sysinfoapi" "github.com/prometheus-community/windows_exporter/log" "github.com/prometheus/client_golang/prometheus" ) @@ -157,18 +162,30 @@ func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er return nil, errors.New("WMI query returned empty result set") } + product, buildNum := custom.GetProductDetails() + + nwgi, _, err := netapi32.NetWkstaGetInfo() + if err != nil { + return nil, err + } + ch <- prometheus.MustNewConstMetric( c.OSInformation, prometheus.GaugeValue, 1.0, - dst[0].Caption, - dst[0].Version, + fmt.Sprintf("Microsoft %v", product), // Caption + fmt.Sprintf("%v.%v.%v", nwgi.Wki102_ver_major, nwgi.Wki102_ver_minor, buildNum), // Version ) + gmse, err := sysinfoapi.GlobalMemoryStatusEx() + if err != nil { + return nil, err + } + ch <- prometheus.MustNewConstMetric( c.PhysicalMemoryFreeBytes, prometheus.GaugeValue, - float64(dst[0].FreePhysicalMemory*1024), // KiB -> bytes + float64(gmse.UllAvailPhys), ) currentTime := time.Now() @@ -191,55 +208,71 @@ func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er ch <- prometheus.MustNewConstMetric( c.PagingFreeBytes, prometheus.GaugeValue, - float64(dst[0].FreeSpaceInPagingFiles*1024), // KiB -> bytes + float64(dst[0].FreeSpaceInPagingFiles*1024), + // Cannot find a way to get this without WMI. + // Can get from CIM_OperatingSystem which is where WMI gets it from, but I can't figure out how to access this from cimwin32.dll + // https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-operatingsystem#properties ) ch <- prometheus.MustNewConstMetric( c.VirtualMemoryFreeBytes, prometheus.GaugeValue, - float64(dst[0].FreeVirtualMemory*1024), // KiB -> bytes + float64(gmse.UllAvailPageFile), ) + // Windows has no defined limit, and is based off available resources. This currently isn't calculated by WMI and is set to default value. + // https://techcommunity.microsoft.com/t5/windows-blog-archive/pushing-the-limits-of-windows-processes-and-threads/ba-p/723824 + // https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-operatingsystem ch <- prometheus.MustNewConstMetric( c.ProcessesLimit, prometheus.GaugeValue, - float64(dst[0].MaxNumberOfProcesses), + float64(4294967295), ) ch <- prometheus.MustNewConstMetric( c.ProcessMemoryLimitBytes, prometheus.GaugeValue, - float64(dst[0].MaxProcessMemorySize*1024), // KiB -> bytes + float64(gmse.UllTotalVirtual), ) + gpi, err := psapi.GetLPPerformanceInfo() + if err != nil { + return nil, err + } + ch <- prometheus.MustNewConstMetric( c.Processes, prometheus.GaugeValue, - float64(dst[0].NumberOfProcesses), + float64(gpi.ProcessCount), ) ch <- prometheus.MustNewConstMetric( c.Users, prometheus.GaugeValue, - float64(dst[0].NumberOfUsers), + float64(nwgi.Wki102_logged_on_users), ) + fsipf, err := custom.GetSizeStoredInPagingFiles() + if err != nil { + return nil, err + } + ch <- prometheus.MustNewConstMetric( c.PagingLimitBytes, prometheus.GaugeValue, - float64(dst[0].SizeStoredInPagingFiles*1024), // KiB -> bytes + float64(fsipf), ) ch <- prometheus.MustNewConstMetric( c.VirtualMemoryBytes, prometheus.GaugeValue, - float64(dst[0].TotalVirtualMemorySize*1024), // KiB -> bytes + float64(gmse.UllTotalPageFile), ) ch <- prometheus.MustNewConstMetric( c.VisibleMemoryBytes, prometheus.GaugeValue, - float64(dst[0].TotalVisibleMemorySize*1024), // KiB -> bytes + float64(gmse.UllTotalPhys), ) return nil, nil diff --git a/headers/custom/custom.go b/headers/custom/custom.go new file mode 100644 index 00000000..752b33ec --- /dev/null +++ b/headers/custom/custom.go @@ -0,0 +1,54 @@ +package custom + +import ( + "log" + "os" + "strings" + + "golang.org/x/sys/windows/registry" +) + +// GetSizeStoredInPagingFiles returns the total size of paging files across all discs. +func GetSizeStoredInPagingFiles() (int64, error) { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management`, registry.QUERY_VALUE) + if err != nil { + return 0, err + } + pagingFiles, _, err := k.GetStringsValue("ExistingPageFiles") + if err != nil { + return 0, err + } + + var size int64 = 0 + for _, pagingFile := range pagingFiles { + fileString := strings.ReplaceAll(pagingFile, `\??\`, "") + file, err := os.Stat(fileString) + if err != nil { + return 0, err + } + size += file.Size() + } + return size, nil +} + +// GetProductDetails returns the ProductName and CurrentBuildNumber values from the registry. +func GetProductDetails() (string, string) { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + log.Fatal(err) + } + pn, _, err := k.GetStringValue("ProductName") + if err != nil { + log.Fatal(err) + } + + bn, _, err := k.GetStringValue("CurrentBuildNumber") + if err != nil { + log.Fatal(err) + } + + if err := k.Close(); err != nil { + log.Fatal(err) + } + return pn, bn +} diff --git a/headers/netapi32/netapi32.go b/headers/netapi32/netapi32.go new file mode 100644 index 00000000..d7605c04 --- /dev/null +++ b/headers/netapi32/netapi32.go @@ -0,0 +1,89 @@ +package netapi32 + +import ( + "errors" + "unsafe" + + "golang.org/x/sys/windows" +) + +// WKSTAInfo102 is a wrapper of WKSTA_Info_102 +//https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/ns-lmwksta-wksta_info_102 +type WKSTAInfo102 struct { + Wki102_platform_id uint32 + wki102_computername *uint16 + wki102_langroup *uint16 + Wki102_ver_major uint32 + Wki102_ver_minor uint32 + wki102_lanroot *uint16 + Wki102_logged_on_users uint32 +} + +var ( + netapi32 = windows.NewLazySystemDLL("netapi32") + procNetWkstaGetInfo = netapi32.NewProc("NetWkstaGetInfo") + procNetApiBufferFree = netapi32.NewProc("NetApiBufferFree") +) + +// NetApiStatus is a map of Network Management Error Codes. +// https://docs.microsoft.com/en-gb/windows/win32/netmgmt/network-management-error-codes?redirectedfrom=MSDN +var NetApiStatus = map[uint32]string{ + // Success + 0: "NERR_Success", + // This computer name is invalid. + 2351: "NERR_InvalidComputer", + // This operation is only allowed on the primary domain controller of the domain. + 2226: "NERR_NotPrimary", + /// This operation is not allowed on this special group. + 2234: "NERR_SpeGroupOp", + /// This operation is not allowed on the last administrative account. + 2452: "NERR_LastAdmin", + /// The password parameter is invalid. + 2203: "NERR_BadPassword", + /// The password does not meet the password policy requirements. + /// Check the minimum password length, password complexity and password history requirements. + 2245: "NERR_PasswordTooShort", + /// The user name could not be found. + 2221: "NERR_UserNotFound", + // Errors + 5: "ERROR_ACCESS_DENIED", + 8: "ERROR_NOT_ENOUGH_MEMORY", + 87: "ERROR_INVALID_PARAMETER", + 123: "ERROR_INVALID_NAME", + 124: "ERROR_INVALID_LEVEL", + 234: "ERROR_MORE_DATA", + 1219: "ERROR_SESSION_CREDENTIAL_CONFLICT", +} + +// NetApiBufferFree frees the memory other network management functions use internally to return information. +// https://docs.microsoft.com/en-us/windows/win32/api/lmapibuf/nf-lmapibuf-netapibufferfree +func NetApiBufferFree(buffer *WKSTAInfo102) { + procNetApiBufferFree.Call(uintptr(unsafe.Pointer(buffer))) +} + +// NetWkstaGetInfo returns information about the configuration of a workstation. +// https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstagetinfo +func NetWkstaGetInfo() (WKSTAInfo102, uint32, error) { + // Struct + var lpwi *WKSTAInfo102 + pLpwi := uintptr(unsafe.Pointer(&lpwi)) + + // Null value + var nullptr = uintptr(0) + + // Level + pLevel := uintptr(102) + + // Func call + r1, _, _ := procNetWkstaGetInfo.Call(nullptr, pLevel, pLpwi) + + if ret := *(*uint32)(unsafe.Pointer(&r1)); ret != 0 { + return WKSTAInfo102{}, ret, errors.New(NetApiStatus[ret]) + } + + // Derence the pointer and return the object so we can safely clear the buffer. + var deref WKSTAInfo102 = *lpwi + defer NetApiBufferFree(lpwi) + + return deref, 0, nil +} diff --git a/headers/psapi/psapi.go b/headers/psapi/psapi.go new file mode 100644 index 00000000..c16a42e4 --- /dev/null +++ b/headers/psapi/psapi.go @@ -0,0 +1,98 @@ +package psapi + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +// LPPerformanceInformation is a wrapper of the PERFORMANCE_INFORMATION struct. +// https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-performance_information +type LPPerformanceInformation struct { + cb uint32 + CommitTotal *uint32 + CommitLimit *uint32 + CommitPeak *uint32 + PhysicalTotal *uint32 + PhysicalAvailable *uint32 + SystemCache *uint32 + KernelTotal *uint32 + KernelPaged *uint32 + KernelNonpaged *uint32 + PageSize *uint32 + HandleCount uint32 + ProcessCount uint32 + ThreadCount uint32 +} + +// PerformanceInformation is a dereferenced version of LPPerformanceInformation +type PerformanceInformation struct { + cb uint32 + CommitTotal uint32 + CommitLimit uint32 + CommitPeak uint32 + PhysicalTotal uint32 + PhysicalAvailable uint32 + SystemCache uint32 + KernelTotal uint32 + KernelPaged uint32 + KernelNonpaged uint32 + PageSize uint32 + HandleCount uint32 + ProcessCount uint32 + ThreadCount uint32 +} + +var ( + psapi = windows.NewLazySystemDLL("psapi.dll") + procGetPerformanceInfo = psapi.NewProc("GetPerformanceInfo") +) + +// GetLPPerformanceInfo retrieves the performance values contained in the LPPerformanceInformation structure. +// https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getperformanceinfo +func GetLPPerformanceInfo() (LPPerformanceInformation, error) { + var pi LPPerformanceInformation + size := (uint32)(unsafe.Sizeof(pi)) + pi.cb = size + pPi := uintptr(unsafe.Pointer(&pi)) + r1, _, err := procGetPerformanceInfo.Call(pPi, uintptr(size)) + + if ret := *(*bool)(unsafe.Pointer(&r1)); ret == false { + // returned false + return LPPerformanceInformation{}, err + } + + return pi, nil +} + +// GetPerformanceInfo returns the dereferenced version of GetLPPerformanceInfo. +func GetPerformanceInfo() (PerformanceInformation, error) { + var lppi LPPerformanceInformation + size := (uint32)(unsafe.Sizeof(lppi)) + lppi.cb = size + pLppi := uintptr(unsafe.Pointer(&lppi)) + r1, _, err := procGetPerformanceInfo.Call(pLppi, uintptr(size)) + + if ret := *(*bool)(unsafe.Pointer(&r1)); ret == false { + // returned false + return PerformanceInformation{}, err + } + + var pi PerformanceInformation + pi.cb = lppi.cb + pi.CommitTotal = *(*uint32)(unsafe.Pointer(&lppi.CommitTotal)) + pi.CommitLimit = *(*uint32)(unsafe.Pointer(&lppi.CommitLimit)) + pi.CommitPeak = *(*uint32)(unsafe.Pointer(&lppi.CommitPeak)) + pi.PhysicalTotal = *(*uint32)(unsafe.Pointer(&lppi.PhysicalTotal)) + pi.PhysicalAvailable = *(*uint32)(unsafe.Pointer(&lppi.PhysicalAvailable)) + pi.SystemCache = *(*uint32)(unsafe.Pointer(&lppi.SystemCache)) + pi.KernelTotal = *(*uint32)(unsafe.Pointer(&lppi.KernelTotal)) + pi.KernelPaged = *(*uint32)(unsafe.Pointer(&lppi.KernelPaged)) + pi.KernelNonpaged = *(*uint32)(unsafe.Pointer(&lppi.KernelNonpaged)) + pi.PageSize = *(*uint32)(unsafe.Pointer(&lppi.PageSize)) + pi.HandleCount = lppi.HandleCount + pi.ProcessCount = lppi.ProcessCount + pi.ThreadCount = lppi.ThreadCount + + return pi, nil +} diff --git a/headers/sysinfoapi/sysinfoapi.go b/headers/sysinfoapi/sysinfoapi.go new file mode 100644 index 00000000..d9c29202 --- /dev/null +++ b/headers/sysinfoapi/sysinfoapi.go @@ -0,0 +1,43 @@ +package sysinfoapi + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +// MemoryStatusEx is a wrapper for MEMORYSTATUSEX +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex +type MemoryStatusEx struct { + dwLength uint32 + DwMemoryLoad uint32 + UllTotalPhys uint64 + UllAvailPhys uint64 + UllTotalPageFile uint64 + UllAvailPageFile uint64 + UllTotalVirtual uint64 + UllAvailVirtual uint64 + UllAvailExtendedVirtual uint64 +} + +var ( + kernel32 = windows.NewLazySystemDLL("kernel32.dll") + procGetSystemInfo = kernel32.NewProc("GetSystemInfo") + procGlobalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx") +) + +// GlobalMemoryStatusEx retrieves information about the system's current usage of both physical and virtual memory. +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex +func GlobalMemoryStatusEx() (MemoryStatusEx, error) { + var mse MemoryStatusEx + mse.dwLength = (uint32)(unsafe.Sizeof(mse)) + pMse := uintptr(unsafe.Pointer(&mse)) + r1, _, err := procGlobalMemoryStatusEx.Call(pMse) + + if ret := *(*bool)(unsafe.Pointer(&r1)); ret == false { + // returned false + return MemoryStatusEx{}, err + } + + return mse, nil +} From 3da11645cfe0d42bf5d26d4da5acde7c162f60ab Mon Sep 17 00:00:00 2001 From: retryW Date: Wed, 13 Jan 2021 13:54:51 +1100 Subject: [PATCH 05/14] added os_test.go and removed wmi for testing Signed-off-by: Ben Ridley --- collector/os.go | 8 +++----- collector/os_test.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 collector/os_test.go diff --git a/collector/os.go b/collector/os.go index 9c9c6af0..9245f86a 100644 --- a/collector/os.go +++ b/collector/os.go @@ -3,11 +3,9 @@ package collector import ( - "errors" "fmt" "time" - "github.com/StackExchange/wmi" "github.com/prometheus-community/windows_exporter/headers/custom" "github.com/prometheus-community/windows_exporter/headers/netapi32" "github.com/prometheus-community/windows_exporter/headers/psapi" @@ -152,7 +150,7 @@ type Win32_OperatingSystem struct { } func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) { - var dst []Win32_OperatingSystem + /*var dst []Win32_OperatingSystem q := queryAll(&dst) if err := wmi.Query(q, &dst); err != nil { return nil, err @@ -160,7 +158,7 @@ func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er if len(dst) == 0 { return nil, errors.New("WMI query returned empty result set") - } + }*/ product, buildNum := custom.GetProductDetails() @@ -208,7 +206,7 @@ func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er ch <- prometheus.MustNewConstMetric( c.PagingFreeBytes, prometheus.GaugeValue, - float64(dst[0].FreeSpaceInPagingFiles*1024), + float64(1234567), // Cannot find a way to get this without WMI. // Can get from CIM_OperatingSystem which is where WMI gets it from, but I can't figure out how to access this from cimwin32.dll // https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-operatingsystem#properties diff --git a/collector/os_test.go b/collector/os_test.go new file mode 100644 index 00000000..2e669486 --- /dev/null +++ b/collector/os_test.go @@ -0,0 +1,23 @@ +package collector + +import ( + "testing" + + "github.com/prometheus/client_golang/prometheus" +) + +func BenchmarkOsCollect(b *testing.B) { + o, err := NewOSCollector() + if err != nil { + b.Error(err) + } + metrics := make(chan prometheus.Metric) + go func() { + for { + <-metrics + } + }() + for i := 0; i < b.N; i++ { + o.Collect(&ScrapeContext{}, metrics) + } +} From 6f941044c7f80042064a8c0153752939151b8f54 Mon Sep 17 00:00:00 2001 From: retryW Date: Wed, 13 Jan 2021 14:31:26 +1100 Subject: [PATCH 06/14] Change Sprintf interpolation to use explicit types Signed-off-by: Ben Ridley --- collector/os.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/collector/os.go b/collector/os.go index 9245f86a..0bf2cdaf 100644 --- a/collector/os.go +++ b/collector/os.go @@ -171,8 +171,8 @@ func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er c.OSInformation, prometheus.GaugeValue, 1.0, - fmt.Sprintf("Microsoft %v", product), // Caption - fmt.Sprintf("%v.%v.%v", nwgi.Wki102_ver_major, nwgi.Wki102_ver_minor, buildNum), // Version + fmt.Sprintf("Microsoft %s", product), // Caption + fmt.Sprintf("%d.%d.%s", nwgi.Wki102_ver_major, nwgi.Wki102_ver_minor, buildNum), // Version ) gmse, err := sysinfoapi.GlobalMemoryStatusEx() From 7bd58abd278c1b1c858769ec6aab96c8a0af4dde Mon Sep 17 00:00:00 2001 From: retryW Date: Fri, 15 Jan 2021 15:08:33 +1100 Subject: [PATCH 07/14] Converted PagingFreeBytes to use perflib Signed-off-by: Ben Ridley --- collector/os.go | 44 ++++++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/collector/os.go b/collector/os.go index 0bf2cdaf..6af19159 100644 --- a/collector/os.go +++ b/collector/os.go @@ -4,6 +4,7 @@ package collector import ( "fmt" + "strings" "time" "github.com/prometheus-community/windows_exporter/headers/custom" @@ -15,7 +16,7 @@ import ( ) func init() { - registerCollector("os", NewOSCollector) + registerCollector("os", NewOSCollector, "Paging File") } // A OSCollector is a Prometheus collector for WMI metrics @@ -35,6 +36,12 @@ type OSCollector struct { Timezone *prometheus.Desc } +type pagingFileCounter struct { + Name string + Usage float64 `perflib:"% Usage"` + UsagePeak float64 `perflib:"% Usage Peak"` +} + // NewOSCollector ... func NewOSCollector() (Collector, error) { const subsystem = "os" @@ -124,7 +131,7 @@ func NewOSCollector() (Collector, error) { // Collect sends the metric values for each metric // to the provided prometheus Metric channel. func (c *OSCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) error { - if desc, err := c.collect(ch); err != nil { + if desc, err := c.collect(ctx, ch); err != nil { log.Error("failed collecting os metrics:", desc, err) return err } @@ -149,7 +156,7 @@ type Win32_OperatingSystem struct { Version string } -func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) { +func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) (*prometheus.Desc, error) { /*var dst []Win32_OperatingSystem q := queryAll(&dst) if err := wmi.Query(q, &dst); err != nil { @@ -203,13 +210,31 @@ func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er timezoneName, ) + fsipf, err := custom.GetSizeStoredInPagingFiles() + if err != nil { + return nil, err + } + + var pfc = make([]pagingFileCounter, 0) + if err := unmarshalObject(ctx.perfObjects["Paging File"], &pfc); err != nil { + return nil, err + } + + var pfbPercent float64 + for _, pageFile := range pfc { + if strings.Contains(strings.ToLower(pageFile.Name), "_total") { + pfbPercent = pageFile.Usage + } else { + continue + } + } + + fpbNum := float64(fsipf) - (float64(fsipf) / float64(100) * pfbPercent) + ch <- prometheus.MustNewConstMetric( c.PagingFreeBytes, prometheus.GaugeValue, - float64(1234567), - // Cannot find a way to get this without WMI. - // Can get from CIM_OperatingSystem which is where WMI gets it from, but I can't figure out how to access this from cimwin32.dll - // https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-operatingsystem#properties + float64(fpbNum), ) ch <- prometheus.MustNewConstMetric( @@ -250,11 +275,6 @@ func (c *OSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er float64(nwgi.Wki102_logged_on_users), ) - fsipf, err := custom.GetSizeStoredInPagingFiles() - if err != nil { - return nil, err - } - ch <- prometheus.MustNewConstMetric( c.PagingLimitBytes, prometheus.GaugeValue, From dd473c480734b717350bfe8e0710b1995f653065 Mon Sep 17 00:00:00 2001 From: retryW Date: Fri, 15 Jan 2021 16:38:52 +1100 Subject: [PATCH 08/14] Fixed paging free bytes moved Signed-off-by: Ben Ridley --- collector/os.go | 131 +++++++++++++++++++++++---------------- headers/custom/custom.go | 54 ---------------- 2 files changed, 79 insertions(+), 106 deletions(-) delete mode 100644 headers/custom/custom.go diff --git a/collector/os.go b/collector/os.go index 6af19159..aa914f39 100644 --- a/collector/os.go +++ b/collector/os.go @@ -4,15 +4,16 @@ package collector import ( "fmt" + "os" "strings" "time" - "github.com/prometheus-community/windows_exporter/headers/custom" "github.com/prometheus-community/windows_exporter/headers/netapi32" "github.com/prometheus-community/windows_exporter/headers/psapi" "github.com/prometheus-community/windows_exporter/headers/sysinfoapi" - "github.com/prometheus-community/windows_exporter/log" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" + "golang.org/x/sys/windows/registry" ) func init() { @@ -157,60 +158,63 @@ type Win32_OperatingSystem struct { } func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) (*prometheus.Desc, error) { - /*var dst []Win32_OperatingSystem - q := queryAll(&dst) - if err := wmi.Query(q, &dst); err != nil { - return nil, err - } - - if len(dst) == 0 { - return nil, errors.New("WMI query returned empty result set") - }*/ - - product, buildNum := custom.GetProductDetails() - nwgi, _, err := netapi32.NetWkstaGetInfo() if err != nil { return nil, err } - ch <- prometheus.MustNewConstMetric( - c.OSInformation, - prometheus.GaugeValue, - 1.0, - fmt.Sprintf("Microsoft %s", product), // Caption - fmt.Sprintf("%d.%d.%s", nwgi.Wki102_ver_major, nwgi.Wki102_ver_minor, buildNum), // Version - ) - gmse, err := sysinfoapi.GlobalMemoryStatusEx() if err != nil { return nil, err } - ch <- prometheus.MustNewConstMetric( - c.PhysicalMemoryFreeBytes, - prometheus.GaugeValue, - float64(gmse.UllAvailPhys), - ) - currentTime := time.Now() - - ch <- prometheus.MustNewConstMetric( - c.Time, - prometheus.GaugeValue, - float64(currentTime.Unix()), - ) - timezoneName, _ := currentTime.Zone() - ch <- prometheus.MustNewConstMetric( - c.Timezone, - prometheus.GaugeValue, - 1.0, - timezoneName, - ) + // Get total allocation of paging files across all disks. + memManKey, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management`, registry.QUERY_VALUE) + if err != nil { + return nil, err + } + pagingFiles, _, err := memManKey.GetStringsValue("ExistingPageFiles") + if err != nil { + return nil, err + } - fsipf, err := custom.GetSizeStoredInPagingFiles() + if err := memManKey.Close(); err != nil { + return nil, err + } + + // Get build number and product name from registry + ntKey, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return nil, err + } + pn, _, err := ntKey.GetStringValue("ProductName") + if err != nil { + return nil, err + } + + bn, _, err := ntKey.GetStringValue("CurrentBuildNumber") + if err != nil { + return nil, err + } + + if err := ntKey.Close(); err != nil { + return nil, err + } + + var fsipf float64 = 0 + for _, pagingFile := range pagingFiles { + fileString := strings.ReplaceAll(pagingFile, `\??\`, "") + file, err := os.Stat(fileString) + if err != nil { + return nil, err + } + fsipf += float64(file.Size()) + } + + gpi, err := psapi.GetPerformanceInfo() if err != nil { return nil, err } @@ -220,21 +224,49 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( return nil, err } - var pfbPercent float64 + // Get current page file usage. + var pfbRaw float64 = 0 for _, pageFile := range pfc { if strings.Contains(strings.ToLower(pageFile.Name), "_total") { - pfbPercent = pageFile.Usage - } else { continue } + pfbRaw += pageFile.Usage } - fpbNum := float64(fsipf) - (float64(fsipf) / float64(100) * pfbPercent) + // Subtract from total page file allocation on disk. + pfb := fsipf - (pfbRaw * float64(gpi.PageSize)) + + ch <- prometheus.MustNewConstMetric( + c.OSInformation, + prometheus.GaugeValue, + 1.0, + fmt.Sprintf("Microsoft %s", pn), // Caption + fmt.Sprintf("%d.%d.%s", nwgi.Wki102_ver_major, nwgi.Wki102_ver_minor, bn), // Version + ) + + ch <- prometheus.MustNewConstMetric( + c.PhysicalMemoryFreeBytes, + prometheus.GaugeValue, + float64(gmse.UllAvailPhys), + ) + + ch <- prometheus.MustNewConstMetric( + c.Time, + prometheus.GaugeValue, + float64(currentTime.Unix()), + ) + + ch <- prometheus.MustNewConstMetric( + c.Timezone, + prometheus.GaugeValue, + 1.0, + timezoneName, + ) ch <- prometheus.MustNewConstMetric( c.PagingFreeBytes, prometheus.GaugeValue, - float64(fpbNum), + pfb, ) ch <- prometheus.MustNewConstMetric( @@ -258,11 +290,6 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( float64(gmse.UllTotalVirtual), ) - gpi, err := psapi.GetLPPerformanceInfo() - if err != nil { - return nil, err - } - ch <- prometheus.MustNewConstMetric( c.Processes, prometheus.GaugeValue, diff --git a/headers/custom/custom.go b/headers/custom/custom.go deleted file mode 100644 index 752b33ec..00000000 --- a/headers/custom/custom.go +++ /dev/null @@ -1,54 +0,0 @@ -package custom - -import ( - "log" - "os" - "strings" - - "golang.org/x/sys/windows/registry" -) - -// GetSizeStoredInPagingFiles returns the total size of paging files across all discs. -func GetSizeStoredInPagingFiles() (int64, error) { - k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management`, registry.QUERY_VALUE) - if err != nil { - return 0, err - } - pagingFiles, _, err := k.GetStringsValue("ExistingPageFiles") - if err != nil { - return 0, err - } - - var size int64 = 0 - for _, pagingFile := range pagingFiles { - fileString := strings.ReplaceAll(pagingFile, `\??\`, "") - file, err := os.Stat(fileString) - if err != nil { - return 0, err - } - size += file.Size() - } - return size, nil -} - -// GetProductDetails returns the ProductName and CurrentBuildNumber values from the registry. -func GetProductDetails() (string, string) { - k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) - if err != nil { - log.Fatal(err) - } - pn, _, err := k.GetStringValue("ProductName") - if err != nil { - log.Fatal(err) - } - - bn, _, err := k.GetStringValue("CurrentBuildNumber") - if err != nil { - log.Fatal(err) - } - - if err := k.Close(); err != nil { - log.Fatal(err) - } - return pn, bn -} From d063bc08424e2cc7a0dce4c03ff97589ca9536af Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Wed, 20 Jan 2021 10:03:16 +1100 Subject: [PATCH 09/14] Add correct scrape context to OS benchmark Signed-off-by: Ben Ridley --- collector/os_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/collector/os_test.go b/collector/os_test.go index 2e669486..1b6b7d9d 100644 --- a/collector/os_test.go +++ b/collector/os_test.go @@ -17,7 +17,8 @@ func BenchmarkOsCollect(b *testing.B) { <-metrics } }() + s, err := PrepareScrapeContext([]string{"os"}) for i := 0; i < b.N; i++ { - o.Collect(&ScrapeContext{}, metrics) + o.Collect(s, metrics) } } From d947d0f6db2e98d1716089940586ad8d68ad72bd Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Wed, 20 Jan 2021 11:20:23 +1100 Subject: [PATCH 10/14] Refactor remaining sysinfoapi calls into header package Signed-off-by: Ben Ridley --- collector/cs.go | 13 ++-- headers/sysinfoapi/sysinfoapi.go | 65 ++++++++++++++++++ sysinfoapi/sysinfoapi.go | 112 ------------------------------- 3 files changed, 73 insertions(+), 117 deletions(-) delete mode 100644 sysinfoapi/sysinfoapi.go diff --git a/collector/cs.go b/collector/cs.go index 585ab827..a7ee1311 100644 --- a/collector/cs.go +++ b/collector/cs.go @@ -3,8 +3,9 @@ package collector import ( + "github.com/prometheus-community/windows_exporter/headers/sysinfoapi" "github.com/prometheus-community/windows_exporter/log" - "github.com/prometheus-community/windows_exporter/sysinfoapi" + "github.com/prometheus/client_golang/prometheus" ) @@ -59,9 +60,11 @@ func (c *CSCollector) Collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) e } func (c *CSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, error) { - cs := sysinfoapi.GetNumLogicalProcessors() + // Get systeminfo for number of processors + systemInfo := sysinfoapi.GetSystemInfo() - pm, err := sysinfoapi.GetPhysicalMemory() + // Get memory status for physical memory + mem, err := sysinfoapi.GlobalMemoryStatusEx() if err != nil { return nil, err } @@ -69,13 +72,13 @@ func (c *CSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er ch <- prometheus.MustNewConstMetric( c.LogicalProcessors, prometheus.GaugeValue, - float64(cs), + float64(systemInfo.DwNumberOfProcessors), ) ch <- prometheus.MustNewConstMetric( c.PhysicalMemoryBytes, prometheus.GaugeValue, - float64(pm), + float64(mem.UllTotalPhys), ) hostname, err := sysinfoapi.GetComputerName(sysinfoapi.ComputerNameDNSHostname) diff --git a/headers/sysinfoapi/sysinfoapi.go b/headers/sysinfoapi/sysinfoapi.go index d9c29202..181baab9 100644 --- a/headers/sysinfoapi/sysinfoapi.go +++ b/headers/sysinfoapi/sysinfoapi.go @@ -1,6 +1,7 @@ package sysinfoapi import ( + "unicode/utf16" "unsafe" "golang.org/x/sys/windows" @@ -20,10 +21,49 @@ type MemoryStatusEx struct { UllAvailExtendedVirtual uint64 } +// wProcessorArchitecture is a wrapper for the union found in LP_SYSTEM_INFO +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info +type wProcessorArchitecture struct { + WReserved uint16 + WProcessorArchitecture uint16 +} + +// LpSystemInfo is a wrapper for LPSYSTEM_INFO +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info +type LpSystemInfo struct { + Arch wProcessorArchitecture + DwPageSize uint32 + LpMinimumApplicationAddress *byte + LpMaximumApplicationAddress *byte + DwActiveProcessorMask *uint32 + DwNumberOfProcessors uint32 + DwProcessorType uint32 + DwAllocationGranularity uint32 + WProcessorLevel uint16 + WProcessorRevision uint16 +} + +// WinComputerNameFormat is a wrapper for COMPUTER_NAME_FORMAT +type WinComputerNameFormat int + +// Definitions for WinComputerNameFormat constants +const ( + ComputerNameNetBIOS WinComputerNameFormat = iota + ComputerNameDNSHostname + ComputerNameDNSDomain + ComputerNameDNSFullyQualified + ComputerNamePhysicalNetBIOS + ComputerNamePhysicalDNSHostname + ComputerNamePhysicalDNSDomain + ComputerNamePhysicalDNSFullyQualified + ComputerNameMax +) + var ( kernel32 = windows.NewLazySystemDLL("kernel32.dll") procGetSystemInfo = kernel32.NewProc("GetSystemInfo") procGlobalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx") + procGetComputerNameExW = kernel32.NewProc("GetComputerNameExW") ) // GlobalMemoryStatusEx retrieves information about the system's current usage of both physical and virtual memory. @@ -41,3 +81,28 @@ func GlobalMemoryStatusEx() (MemoryStatusEx, error) { return mse, nil } + +// GetSystemInfo wraps the GetSystemInfo function from sysinfoapi +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo +func GetSystemInfo() LpSystemInfo { + var info LpSystemInfo + pInfo := uintptr(unsafe.Pointer(&info)) + procGetSystemInfo.Call(pInfo) + return info +} + +// GetComputerName wraps the GetComputerNameW function in a more Go-like way +// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcomputernamew +func GetComputerName(f WinComputerNameFormat) (string, error) { + // 1kb buffer to accept computer name. This should be more than enough as the maximum size + // returned is the max length of a DNS name, which this author believes is 253 characters. + size := 1024 + var buffer [4096]uint16 + r1, _, err := procGetComputerNameExW.Call(uintptr(f), uintptr(unsafe.Pointer(&buffer)), uintptr(unsafe.Pointer(&size))) + if r1 == 0 { + return "", err + } + bytes := buffer[0:size] + out := utf16.Decode(bytes) + return string(out), nil +} diff --git a/sysinfoapi/sysinfoapi.go b/sysinfoapi/sysinfoapi.go deleted file mode 100644 index fcddf2d3..00000000 --- a/sysinfoapi/sysinfoapi.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2020 Prometheus Team -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// Package sysinfoapi wraps some WinAPI sysinfoapi functions used by the exporter to produce metrics. -// It is fairly opinionated for the exporter's internal use, and is not intended to be a -// generic wrapper for the WinAPI's sysinfoapi. -package sysinfoapi - -import ( - "unicode/utf16" - "unsafe" - - "golang.org/x/sys/windows" -) - -// WinProcInfo is a wrapper for -type WinProcInfo struct { - WReserved uint16 - WProcessorArchitecture uint16 -} - -// WinSystemInfo is a wrapper for LPSYSTEM_INFO -type WinSystemInfo struct { - Arch WinProcInfo - DwPageSize uint32 - LpMinimumApplicationAddress *byte - LpMaximumApplicationAddress *byte - DwActiveProcessorMask *uint32 - DwNumberOfProcessors uint32 - DwProcessorType uint32 - DwAllocationGranularity uint32 - WProcessorLevel uint16 - WProcessorRevision uint16 -} - -// WinComputerNameFormat is a wrapper for COMPUTER_NAME_FORMAT -type WinComputerNameFormat int - -// Definitions for WinComputerNameFormat constants -const ( - ComputerNameNetBIOS WinComputerNameFormat = iota - ComputerNameDNSHostname - ComputerNameDNSDomain - ComputerNameDNSFullyQualified - ComputerNamePhysicalNetBIOS - ComputerNamePhysicalDNSHostname - ComputerNamePhysicalDNSDomain - ComputerNamePhysicalDNSFullyQualified - ComputerNameMax -) - -// WinMemoryStatus is a wrapper for LPMEMORYSTATUSEX -type WinMemoryStatus struct { - dwLength uint32 - dwMemoryLoad uint32 - ullTotalPhys uint64 - ullAvailPhys uint64 - ullTotalPageFile uint64 - ullAvailPageFile uint64 - ullTotalVirtual uint64 - ullAvailVirtual uint64 - ullAvailExtendedVirtual uint64 -} - -var ( - kernel32 = windows.NewLazySystemDLL("kernel32.dll") - procGetSystemInfo = kernel32.NewProc("GetSystemInfo") - procGlobalMemoryStatusEx = kernel32.NewProc("GlobalMemoryStatusEx") - procGetComputerNameExW = kernel32.NewProc("GetComputerNameExW") -) - -// GetNumLogicalProcessors returns the number of logical processes provided by sysinfoapi's GetSystemInfo function. -func GetNumLogicalProcessors() int { - var sysInfo WinSystemInfo - pInfo := uintptr(unsafe.Pointer(&sysInfo)) - procGetSystemInfo.Call(pInfo) - return int(sysInfo.DwNumberOfProcessors) -} - -// GetPhysicalMemory returns the system's installed physical memory provided by sysinfoapi's GlobalMemoryStatusEx function. -func GetPhysicalMemory() (int, error) { - var wm WinMemoryStatus - wm.dwLength = (uint32)(unsafe.Sizeof(wm)) - r1, _, err := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&wm))) - if r1 != 1 { - return 0, err - } - return int(wm.ullTotalPhys), nil -} - -// GetComputerName provides the requested computer name provided by sysinfoapi's GetComputerNameEx function. -func GetComputerName(f WinComputerNameFormat) (string, error) { - size := 4096 - var buffer [4096]uint16 - r1, _, err := procGetComputerNameExW.Call(uintptr(f), uintptr(unsafe.Pointer(&buffer)), uintptr(unsafe.Pointer(&size))) - if r1 == 0 { - return "", err - } - bytes := buffer[0:size] - out := utf16.Decode(bytes) - return string(out), nil -} From 05f0f6f688cde8fe62bbbeb620ee07dd06a8859a Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Wed, 3 Mar 2021 15:35:04 +1100 Subject: [PATCH 11/14] Add idiomatic wrappers to be exposed publically, and hide low-level WinAPI operations Signed-off-by: Ben Ridley --- collector/cs.go | 4 +- collector/os.go | 16 +++--- headers/netapi32/netapi32.go | 66 +++++++++++++++--------- headers/sysinfoapi/sysinfoapi.go | 86 +++++++++++++++++++++++++++----- 4 files changed, 126 insertions(+), 46 deletions(-) diff --git a/collector/cs.go b/collector/cs.go index a7ee1311..63e87274 100644 --- a/collector/cs.go +++ b/collector/cs.go @@ -72,13 +72,13 @@ func (c *CSCollector) collect(ch chan<- prometheus.Metric) (*prometheus.Desc, er ch <- prometheus.MustNewConstMetric( c.LogicalProcessors, prometheus.GaugeValue, - float64(systemInfo.DwNumberOfProcessors), + float64(systemInfo.NumberOfProcessors), ) ch <- prometheus.MustNewConstMetric( c.PhysicalMemoryBytes, prometheus.GaugeValue, - float64(mem.UllTotalPhys), + float64(mem.TotalPhys), ) hostname, err := sysinfoapi.GetComputerName(sysinfoapi.ComputerNameDNSHostname) diff --git a/collector/os.go b/collector/os.go index aa914f39..5d3f9e42 100644 --- a/collector/os.go +++ b/collector/os.go @@ -158,7 +158,7 @@ type Win32_OperatingSystem struct { } func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) (*prometheus.Desc, error) { - nwgi, _, err := netapi32.NetWkstaGetInfo() + nwgi, err := netapi32.GetWorkstationInfo() if err != nil { return nil, err } @@ -241,13 +241,13 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( prometheus.GaugeValue, 1.0, fmt.Sprintf("Microsoft %s", pn), // Caption - fmt.Sprintf("%d.%d.%s", nwgi.Wki102_ver_major, nwgi.Wki102_ver_minor, bn), // Version + fmt.Sprintf("%d.%d.%s", nwgi.VersionMajor, nwgi.VersionMinor, bn), // Version ) ch <- prometheus.MustNewConstMetric( c.PhysicalMemoryFreeBytes, prometheus.GaugeValue, - float64(gmse.UllAvailPhys), + float64(gmse.AvailPhys), ) ch <- prometheus.MustNewConstMetric( @@ -272,7 +272,7 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( ch <- prometheus.MustNewConstMetric( c.VirtualMemoryFreeBytes, prometheus.GaugeValue, - float64(gmse.UllAvailPageFile), + float64(gmse.AvailPageFile), ) // Windows has no defined limit, and is based off available resources. This currently isn't calculated by WMI and is set to default value. @@ -287,7 +287,7 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( ch <- prometheus.MustNewConstMetric( c.ProcessMemoryLimitBytes, prometheus.GaugeValue, - float64(gmse.UllTotalVirtual), + float64(gmse.TotalVirtual), ) ch <- prometheus.MustNewConstMetric( @@ -299,7 +299,7 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( ch <- prometheus.MustNewConstMetric( c.Users, prometheus.GaugeValue, - float64(nwgi.Wki102_logged_on_users), + float64(nwgi.LoggedOnUsers), ) ch <- prometheus.MustNewConstMetric( @@ -311,13 +311,13 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( ch <- prometheus.MustNewConstMetric( c.VirtualMemoryBytes, prometheus.GaugeValue, - float64(gmse.UllTotalPageFile), + float64(gmse.TotalPageFile), ) ch <- prometheus.MustNewConstMetric( c.VisibleMemoryBytes, prometheus.GaugeValue, - float64(gmse.UllTotalPhys), + float64(gmse.TotalPhys), ) return nil, nil diff --git a/headers/netapi32/netapi32.go b/headers/netapi32/netapi32.go index d7605c04..441ccfd2 100644 --- a/headers/netapi32/netapi32.go +++ b/headers/netapi32/netapi32.go @@ -9,14 +9,25 @@ import ( // WKSTAInfo102 is a wrapper of WKSTA_Info_102 //https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/ns-lmwksta-wksta_info_102 -type WKSTAInfo102 struct { - Wki102_platform_id uint32 +type wKSTAInfo102 struct { + wki102_platform_id uint32 wki102_computername *uint16 wki102_langroup *uint16 - Wki102_ver_major uint32 - Wki102_ver_minor uint32 + wki102_ver_major uint32 + wki102_ver_minor uint32 wki102_lanroot *uint16 - Wki102_logged_on_users uint32 + wki102_logged_on_users uint32 +} + +// WorkstationInfo is an idiomatic wrapper of WKSTAInfo102 +type WorkstationInfo struct { + PlatformId uint32 + ComputerName string + LanGroup string + VersionMajor uint32 + VersionMinor uint32 + LanRoot string + LoggedOnUsers uint32 } var ( @@ -57,33 +68,42 @@ var NetApiStatus = map[uint32]string{ // NetApiBufferFree frees the memory other network management functions use internally to return information. // https://docs.microsoft.com/en-us/windows/win32/api/lmapibuf/nf-lmapibuf-netapibufferfree -func NetApiBufferFree(buffer *WKSTAInfo102) { +func netApiBufferFree(buffer *wKSTAInfo102) { procNetApiBufferFree.Call(uintptr(unsafe.Pointer(buffer))) } // NetWkstaGetInfo returns information about the configuration of a workstation. +// WARNING: The caller must call netApiBufferFree to free the memory allocated by this function. // https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstagetinfo -func NetWkstaGetInfo() (WKSTAInfo102, uint32, error) { - // Struct - var lpwi *WKSTAInfo102 - pLpwi := uintptr(unsafe.Pointer(&lpwi)) - - // Null value - var nullptr = uintptr(0) - - // Level +func netWkstaGetInfo() (*wKSTAInfo102, uint32, error) { + var lpwi *wKSTAInfo102 pLevel := uintptr(102) - // Func call - r1, _, _ := procNetWkstaGetInfo.Call(nullptr, pLevel, pLpwi) + r1, _, _ := procNetWkstaGetInfo.Call(0, pLevel, uintptr(unsafe.Pointer(&lpwi))) if ret := *(*uint32)(unsafe.Pointer(&r1)); ret != 0 { - return WKSTAInfo102{}, ret, errors.New(NetApiStatus[ret]) + return nil, ret, errors.New(NetApiStatus[ret]) } - // Derence the pointer and return the object so we can safely clear the buffer. - var deref WKSTAInfo102 = *lpwi - defer NetApiBufferFree(lpwi) - - return deref, 0, nil + return lpwi, 0, nil +} + +// GetWorkstationInfo is an idiomatic wrapper for netWkstaGetInfo +func GetWorkstationInfo() (WorkstationInfo, error) { + info, _, err := netWkstaGetInfo() + if err != nil { + return WorkstationInfo{}, err + } + workstationInfo := WorkstationInfo{ + PlatformId: info.wki102_platform_id, + ComputerName: windows.UTF16PtrToString(info.wki102_computername), + LanGroup: windows.UTF16PtrToString(info.wki102_langroup), + VersionMajor: info.wki102_ver_major, + VersionMinor: info.wki102_ver_minor, + LanRoot: windows.UTF16PtrToString(info.wki102_lanroot), + LoggedOnUsers: info.wki102_logged_on_users, + } + // Free the memory allocated by netapi + netApiBufferFree(info) + return workstationInfo, nil } diff --git a/headers/sysinfoapi/sysinfoapi.go b/headers/sysinfoapi/sysinfoapi.go index 181baab9..8ea8ff67 100644 --- a/headers/sysinfoapi/sysinfoapi.go +++ b/headers/sysinfoapi/sysinfoapi.go @@ -1,6 +1,7 @@ package sysinfoapi import ( + "fmt" "unicode/utf16" "unsafe" @@ -9,7 +10,7 @@ import ( // MemoryStatusEx is a wrapper for MEMORYSTATUSEX // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex -type MemoryStatusEx struct { +type memoryStatusEx struct { dwLength uint32 DwMemoryLoad uint32 UllTotalPhys uint64 @@ -21,6 +22,18 @@ type MemoryStatusEx struct { UllAvailExtendedVirtual uint64 } +// MemoryStatus is an idiomatic wrapper for MemoryStatusEx +type MemoryStatus struct { + MemoryLoad uint32 + TotalPhys uint64 + AvailPhys uint64 + TotalPageFile uint64 + AvailPageFile uint64 + TotalVirtual uint64 + AvailVirtual uint64 + AvailExtendedVirtual uint64 +} + // wProcessorArchitecture is a wrapper for the union found in LP_SYSTEM_INFO // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info type wProcessorArchitecture struct { @@ -28,14 +41,27 @@ type wProcessorArchitecture struct { WProcessorArchitecture uint16 } +// ProcessorArchitecture is an idiomatic wrapper for wProcessorArchitecture +type ProcessorArchitecture uint16 + +// Idiomatic values for wProcessorArchitecture +const ( + AMD64 ProcessorArchitecture = 9 + ARM = 5 + ARM64 = 12 + IA64 = 6 + INTEL = 0 + UNKNOWN = 0xffff +) + // LpSystemInfo is a wrapper for LPSYSTEM_INFO // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-system_info -type LpSystemInfo struct { +type lpSystemInfo struct { Arch wProcessorArchitecture DwPageSize uint32 - LpMinimumApplicationAddress *byte - LpMaximumApplicationAddress *byte - DwActiveProcessorMask *uint32 + LpMinimumApplicationAddress uintptr + LpMaximumApplicationAddress uintptr + DwActiveProcessorMask uint32 DwNumberOfProcessors uint32 DwProcessorType uint32 DwAllocationGranularity uint32 @@ -43,6 +69,20 @@ type LpSystemInfo struct { WProcessorRevision uint16 } +// SystemInfo is an idiomatic wrapper for LpSystemInfo +type SystemInfo struct { + Arch ProcessorArchitecture + PageSize uint32 + MinimumApplicationAddress uintptr + MaximumApplicationAddress uintptr + ActiveProcessorMask uint32 + NumberOfProcessors uint32 + ProcessorType uint32 + AllocationGranularity uint32 + ProcessorLevel uint16 + ProcessorRevision uint16 +} + // WinComputerNameFormat is a wrapper for COMPUTER_NAME_FORMAT type WinComputerNameFormat int @@ -68,27 +108,47 @@ var ( // GlobalMemoryStatusEx retrieves information about the system's current usage of both physical and virtual memory. // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex -func GlobalMemoryStatusEx() (MemoryStatusEx, error) { - var mse MemoryStatusEx +func GlobalMemoryStatusEx() (MemoryStatus, error) { + var mse memoryStatusEx mse.dwLength = (uint32)(unsafe.Sizeof(mse)) pMse := uintptr(unsafe.Pointer(&mse)) r1, _, err := procGlobalMemoryStatusEx.Call(pMse) if ret := *(*bool)(unsafe.Pointer(&r1)); ret == false { - // returned false - return MemoryStatusEx{}, err + return MemoryStatus{}, err } - return mse, nil + return MemoryStatus{ + MemoryLoad: mse.DwMemoryLoad, + TotalPhys: mse.UllTotalPhys, + AvailPhys: mse.UllAvailPhys, + TotalPageFile: mse.UllTotalPageFile, + AvailPageFile: mse.UllAvailPageFile, + TotalVirtual: mse.UllTotalVirtual, + AvailVirtual: mse.UllAvailVirtual, + AvailExtendedVirtual: mse.UllAvailExtendedVirtual, + }, nil } // GetSystemInfo wraps the GetSystemInfo function from sysinfoapi // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo -func GetSystemInfo() LpSystemInfo { - var info LpSystemInfo +func GetSystemInfo() SystemInfo { + var info lpSystemInfo pInfo := uintptr(unsafe.Pointer(&info)) procGetSystemInfo.Call(pInfo) - return info + fmt.Printf("%+v", info) + return SystemInfo{ + Arch: ProcessorArchitecture(info.Arch.WProcessorArchitecture), + PageSize: info.DwPageSize, + MinimumApplicationAddress: info.LpMinimumApplicationAddress, + MaximumApplicationAddress: info.LpMinimumApplicationAddress, + ActiveProcessorMask: info.DwActiveProcessorMask, + NumberOfProcessors: info.DwNumberOfProcessors, + ProcessorType: info.DwProcessorType, + AllocationGranularity: info.DwAllocationGranularity, + ProcessorLevel: info.WProcessorLevel, + ProcessorRevision: info.WProcessorRevision, + } } // GetComputerName wraps the GetComputerNameW function in a more Go-like way From df2a7a9ec09efc8999647c4f1f97ecb456e54c49 Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Thu, 18 Mar 2021 15:29:14 -0700 Subject: [PATCH 12/14] Remove temporary uintptr values, as the garbage collector can move addresses from under them. Signed-off-by: Ben Ridley --- headers/psapi/psapi.go | 32 ++++++++++++++------------------ headers/sysinfoapi/sysinfoapi.go | 6 ++---- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/headers/psapi/psapi.go b/headers/psapi/psapi.go index c16a42e4..c8ef66b4 100644 --- a/headers/psapi/psapi.go +++ b/headers/psapi/psapi.go @@ -54,11 +54,9 @@ func GetLPPerformanceInfo() (LPPerformanceInformation, error) { var pi LPPerformanceInformation size := (uint32)(unsafe.Sizeof(pi)) pi.cb = size - pPi := uintptr(unsafe.Pointer(&pi)) - r1, _, err := procGetPerformanceInfo.Call(pPi, uintptr(size)) + r1, _, err := procGetPerformanceInfo.Call(uintptr(unsafe.Pointer(&pi)), uintptr(size)) - if ret := *(*bool)(unsafe.Pointer(&r1)); ret == false { - // returned false + if ret := *(*bool)(unsafe.Pointer(&r1)); !ret { return LPPerformanceInformation{}, err } @@ -70,26 +68,24 @@ func GetPerformanceInfo() (PerformanceInformation, error) { var lppi LPPerformanceInformation size := (uint32)(unsafe.Sizeof(lppi)) lppi.cb = size - pLppi := uintptr(unsafe.Pointer(&lppi)) - r1, _, err := procGetPerformanceInfo.Call(pLppi, uintptr(size)) + r1, _, err := procGetPerformanceInfo.Call(uintptr(unsafe.Pointer(&lppi)), uintptr(size)) - if ret := *(*bool)(unsafe.Pointer(&r1)); ret == false { - // returned false + if ret := *(*bool)(unsafe.Pointer(&r1)); !ret { return PerformanceInformation{}, err } var pi PerformanceInformation pi.cb = lppi.cb - pi.CommitTotal = *(*uint32)(unsafe.Pointer(&lppi.CommitTotal)) - pi.CommitLimit = *(*uint32)(unsafe.Pointer(&lppi.CommitLimit)) - pi.CommitPeak = *(*uint32)(unsafe.Pointer(&lppi.CommitPeak)) - pi.PhysicalTotal = *(*uint32)(unsafe.Pointer(&lppi.PhysicalTotal)) - pi.PhysicalAvailable = *(*uint32)(unsafe.Pointer(&lppi.PhysicalAvailable)) - pi.SystemCache = *(*uint32)(unsafe.Pointer(&lppi.SystemCache)) - pi.KernelTotal = *(*uint32)(unsafe.Pointer(&lppi.KernelTotal)) - pi.KernelPaged = *(*uint32)(unsafe.Pointer(&lppi.KernelPaged)) - pi.KernelNonpaged = *(*uint32)(unsafe.Pointer(&lppi.KernelNonpaged)) - pi.PageSize = *(*uint32)(unsafe.Pointer(&lppi.PageSize)) + pi.CommitTotal = *(lppi.CommitTotal) + pi.CommitLimit = *(lppi.CommitLimit) + pi.CommitPeak = *(lppi.CommitPeak) + pi.PhysicalTotal = *(lppi.PhysicalTotal) + pi.PhysicalAvailable = *(lppi.PhysicalAvailable) + pi.SystemCache = *(lppi.SystemCache) + pi.KernelTotal = *(lppi.KernelTotal) + pi.KernelPaged = *(lppi.KernelPaged) + pi.KernelNonpaged = *(lppi.KernelNonpaged) + pi.PageSize = *(lppi.PageSize) pi.HandleCount = lppi.HandleCount pi.ProcessCount = lppi.ProcessCount pi.ThreadCount = lppi.ThreadCount diff --git a/headers/sysinfoapi/sysinfoapi.go b/headers/sysinfoapi/sysinfoapi.go index 8ea8ff67..7bfb5f85 100644 --- a/headers/sysinfoapi/sysinfoapi.go +++ b/headers/sysinfoapi/sysinfoapi.go @@ -111,8 +111,7 @@ var ( func GlobalMemoryStatusEx() (MemoryStatus, error) { var mse memoryStatusEx mse.dwLength = (uint32)(unsafe.Sizeof(mse)) - pMse := uintptr(unsafe.Pointer(&mse)) - r1, _, err := procGlobalMemoryStatusEx.Call(pMse) + r1, _, err := procGlobalMemoryStatusEx.Call(uintptr(unsafe.Pointer(&mse))) if ret := *(*bool)(unsafe.Pointer(&r1)); ret == false { return MemoryStatus{}, err @@ -134,8 +133,7 @@ func GlobalMemoryStatusEx() (MemoryStatus, error) { // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo func GetSystemInfo() SystemInfo { var info lpSystemInfo - pInfo := uintptr(unsafe.Pointer(&info)) - procGetSystemInfo.Call(pInfo) + procGetSystemInfo.Call(uintptr(unsafe.Pointer(&info))) fmt.Printf("%+v", info) return SystemInfo{ Arch: ProcessorArchitecture(info.Arch.WProcessorArchitecture), From ee3848141c7f031072362817d0ba990d722502cd Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Thu, 18 Mar 2021 16:08:52 -0700 Subject: [PATCH 13/14] Simplify struct usage and comments Signed-off-by: Ben Ridley --- headers/psapi/psapi.go | 75 ++++++-------------------------- headers/sysinfoapi/sysinfoapi.go | 4 +- 2 files changed, 14 insertions(+), 65 deletions(-) diff --git a/headers/psapi/psapi.go b/headers/psapi/psapi.go index c8ef66b4..77f837a9 100644 --- a/headers/psapi/psapi.go +++ b/headers/psapi/psapi.go @@ -6,38 +6,20 @@ import ( "golang.org/x/sys/windows" ) -// LPPerformanceInformation is a wrapper of the PERFORMANCE_INFORMATION struct. +// PerformanceInformation is a wrapper of the PERFORMANCE_INFORMATION struct. // https://docs.microsoft.com/en-us/windows/win32/api/psapi/ns-psapi-performance_information -type LPPerformanceInformation struct { - cb uint32 - CommitTotal *uint32 - CommitLimit *uint32 - CommitPeak *uint32 - PhysicalTotal *uint32 - PhysicalAvailable *uint32 - SystemCache *uint32 - KernelTotal *uint32 - KernelPaged *uint32 - KernelNonpaged *uint32 - PageSize *uint32 - HandleCount uint32 - ProcessCount uint32 - ThreadCount uint32 -} - -// PerformanceInformation is a dereferenced version of LPPerformanceInformation type PerformanceInformation struct { cb uint32 - CommitTotal uint32 - CommitLimit uint32 - CommitPeak uint32 - PhysicalTotal uint32 - PhysicalAvailable uint32 - SystemCache uint32 - KernelTotal uint32 - KernelPaged uint32 - KernelNonpaged uint32 - PageSize uint32 + CommitTotal uint + CommitLimit uint + CommitPeak uint + PhysicalTotal uint + PhysicalAvailable uint + SystemCache uint + KernelTotal uint + KernelPaged uint + KernelNonpaged uint + PageSize uint HandleCount uint32 ProcessCount uint32 ThreadCount uint32 @@ -48,24 +30,9 @@ var ( procGetPerformanceInfo = psapi.NewProc("GetPerformanceInfo") ) -// GetLPPerformanceInfo retrieves the performance values contained in the LPPerformanceInformation structure. -// https://docs.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getperformanceinfo -func GetLPPerformanceInfo() (LPPerformanceInformation, error) { - var pi LPPerformanceInformation - size := (uint32)(unsafe.Sizeof(pi)) - pi.cb = size - r1, _, err := procGetPerformanceInfo.Call(uintptr(unsafe.Pointer(&pi)), uintptr(size)) - - if ret := *(*bool)(unsafe.Pointer(&r1)); !ret { - return LPPerformanceInformation{}, err - } - - return pi, nil -} - // GetPerformanceInfo returns the dereferenced version of GetLPPerformanceInfo. func GetPerformanceInfo() (PerformanceInformation, error) { - var lppi LPPerformanceInformation + var lppi PerformanceInformation size := (uint32)(unsafe.Sizeof(lppi)) lppi.cb = size r1, _, err := procGetPerformanceInfo.Call(uintptr(unsafe.Pointer(&lppi)), uintptr(size)) @@ -74,21 +41,5 @@ func GetPerformanceInfo() (PerformanceInformation, error) { return PerformanceInformation{}, err } - var pi PerformanceInformation - pi.cb = lppi.cb - pi.CommitTotal = *(lppi.CommitTotal) - pi.CommitLimit = *(lppi.CommitLimit) - pi.CommitPeak = *(lppi.CommitPeak) - pi.PhysicalTotal = *(lppi.PhysicalTotal) - pi.PhysicalAvailable = *(lppi.PhysicalAvailable) - pi.SystemCache = *(lppi.SystemCache) - pi.KernelTotal = *(lppi.KernelTotal) - pi.KernelPaged = *(lppi.KernelPaged) - pi.KernelNonpaged = *(lppi.KernelNonpaged) - pi.PageSize = *(lppi.PageSize) - pi.HandleCount = lppi.HandleCount - pi.ProcessCount = lppi.ProcessCount - pi.ThreadCount = lppi.ThreadCount - - return pi, nil + return lppi, nil } diff --git a/headers/sysinfoapi/sysinfoapi.go b/headers/sysinfoapi/sysinfoapi.go index 7bfb5f85..7f48c3f7 100644 --- a/headers/sysinfoapi/sysinfoapi.go +++ b/headers/sysinfoapi/sysinfoapi.go @@ -1,7 +1,6 @@ package sysinfoapi import ( - "fmt" "unicode/utf16" "unsafe" @@ -129,12 +128,11 @@ func GlobalMemoryStatusEx() (MemoryStatus, error) { }, nil } -// GetSystemInfo wraps the GetSystemInfo function from sysinfoapi +// GetSystemInfo is an idiomatic wrapper for the GetSystemInfo function from sysinfoapi // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsysteminfo func GetSystemInfo() SystemInfo { var info lpSystemInfo procGetSystemInfo.Call(uintptr(unsafe.Pointer(&info))) - fmt.Printf("%+v", info) return SystemInfo{ Arch: ProcessorArchitecture(info.Arch.WProcessorArchitecture), PageSize: info.DwPageSize, From 33c6b2c6a53dde14316fd79457c8281c77873296 Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Mon, 29 Mar 2021 10:12:20 -0700 Subject: [PATCH 14/14] Address GitHub feedback - Defer registry close calls - Ensure size parameter in GetComputerName is properly specified - Clean up some comments to ensure correctness Signed-off-by: Ben Ridley --- collector/os.go | 13 +++++-------- headers/netapi32/netapi32.go | 1 - headers/sysinfoapi/sysinfoapi.go | 4 ++-- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/collector/os.go b/collector/os.go index 5d3f9e42..67e01ec6 100644 --- a/collector/os.go +++ b/collector/os.go @@ -173,6 +173,8 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( // Get total allocation of paging files across all disks. memManKey, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management`, registry.QUERY_VALUE) + defer memManKey.Close() + if err != nil { return nil, err } @@ -181,15 +183,14 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( return nil, err } - if err := memManKey.Close(); err != nil { - return nil, err - } - // Get build number and product name from registry ntKey, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + defer ntKey.Close() + if err != nil { return nil, err } + pn, _, err := ntKey.GetStringValue("ProductName") if err != nil { return nil, err @@ -200,10 +201,6 @@ func (c *OSCollector) collect(ctx *ScrapeContext, ch chan<- prometheus.Metric) ( return nil, err } - if err := ntKey.Close(); err != nil { - return nil, err - } - var fsipf float64 = 0 for _, pagingFile := range pagingFiles { fileString := strings.ReplaceAll(pagingFile, `\??\`, "") diff --git a/headers/netapi32/netapi32.go b/headers/netapi32/netapi32.go index 441ccfd2..46b415e6 100644 --- a/headers/netapi32/netapi32.go +++ b/headers/netapi32/netapi32.go @@ -73,7 +73,6 @@ func netApiBufferFree(buffer *wKSTAInfo102) { } // NetWkstaGetInfo returns information about the configuration of a workstation. -// WARNING: The caller must call netApiBufferFree to free the memory allocated by this function. // https://docs.microsoft.com/en-us/windows/win32/api/lmwksta/nf-lmwksta-netwkstagetinfo func netWkstaGetInfo() (*wKSTAInfo102, uint32, error) { var lpwi *wKSTAInfo102 diff --git a/headers/sysinfoapi/sysinfoapi.go b/headers/sysinfoapi/sysinfoapi.go index 7f48c3f7..41471bb9 100644 --- a/headers/sysinfoapi/sysinfoapi.go +++ b/headers/sysinfoapi/sysinfoapi.go @@ -148,12 +148,12 @@ func GetSystemInfo() SystemInfo { } // GetComputerName wraps the GetComputerNameW function in a more Go-like way -// https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-getcomputernamew +// https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getcomputernameexw func GetComputerName(f WinComputerNameFormat) (string, error) { // 1kb buffer to accept computer name. This should be more than enough as the maximum size // returned is the max length of a DNS name, which this author believes is 253 characters. size := 1024 - var buffer [4096]uint16 + var buffer [1024]uint16 r1, _, err := procGetComputerNameExW.Call(uintptr(f), uintptr(unsafe.Pointer(&buffer)), uintptr(unsafe.Pointer(&size))) if r1 == 0 { return "", err