From 71054ac42915f244974fcc1e1a4600e2135ad0bd Mon Sep 17 00:00:00 2001 From: Ben Ridley Date: Fri, 18 Dec 2020 18:02:33 +1100 Subject: [PATCH] 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 +}