From bf233ad3e36b7001eb682d325051263e1dc9c044 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Sun, 3 Nov 2024 17:23:26 +0100 Subject: [PATCH] mi: replace all WMI calls with MI calls (#1714) --- .golangci.yaml | 2 +- go.mod | 17 +- go.sum | 36 +-- internal/collector/ad/ad.go | 4 +- internal/collector/adcs/adcs.go | 4 +- internal/collector/adfs/adfs.go | 4 +- internal/collector/cache/cache.go | 4 +- internal/collector/container/container.go | 4 +- internal/collector/cpu/cpu.go | 4 +- internal/collector/cpu_info/cpu_info.go | 63 ++-- internal/collector/cs/cs.go | 4 +- internal/collector/dfsr/dfsr.go | 4 +- internal/collector/dhcp/dhcp.go | 4 +- internal/collector/diskdrive/diskdrive.go | 47 +-- internal/collector/dns/dns.go | 4 +- internal/collector/exchange/exchange.go | 4 +- internal/collector/filetime/filetime.go | 4 +- internal/collector/fsrmquota/fsrmquota.go | 49 +-- internal/collector/hyperv/hyperv.go | 267 ++++++++-------- internal/collector/iis/iis.go | 4 +- internal/collector/license/license.go | 4 +- .../collector/logical_disk/logical_disk.go | 4 +- internal/collector/logon/logon.go | 4 +- internal/collector/memory/memory.go | 4 +- internal/collector/mscluster/mscluster.go | 12 +- .../collector/mscluster/mscluster_cluster.go | 164 +++++----- .../collector/mscluster/mscluster_network.go | 20 +- .../collector/mscluster/mscluster_node.go | 38 ++- .../collector/mscluster/mscluster_resource.go | 48 +-- .../mscluster/mscluster_resourcegroup.go | 38 ++- internal/collector/msmq/msmq.go | 32 +- internal/collector/mssql/mssql.go | 4 +- internal/collector/net/net.go | 4 +- .../collector/netframework/netframework.go | 12 +- .../netframework_clrexceptions.go | 20 +- .../netframework/netframework_clrinterop.go | 20 +- .../netframework/netframework_clrjit.go | 24 +- .../netframework/netframework_clrloading.go | 42 +-- .../netframework_clrlocksandthreads.go | 30 +- .../netframework/netframework_clrmemory.go | 60 ++-- .../netframework/netframework_clrremoting.go | 24 +- .../netframework/netframework_clrsecurity.go | 22 +- internal/collector/nps/nps.go | 91 +++--- internal/collector/os/os.go | 10 +- internal/collector/perfdata/perfdata.go | 4 +- .../collector/physical_disk/physical_disk.go | 4 +- internal/collector/printer/printer.go | 68 ++-- internal/collector/process/process.go | 36 ++- internal/collector/remote_fx/remote_fx.go | 4 +- .../scheduled_task/scheduled_task.go | 6 +- internal/collector/service/service.go | 4 +- internal/collector/smb/smb.go | 4 +- internal/collector/smbclient/smbclient.go | 4 +- internal/collector/smtp/smtp.go | 10 +- internal/collector/system/system.go | 4 +- internal/collector/tcp/tcp.go | 4 +- .../terminal_services/terminal_services.go | 15 +- internal/collector/textfile/textfile.go | 4 +- internal/collector/thermalzone/thermalzone.go | 37 ++- internal/collector/time/time.go | 4 +- internal/collector/update/update.go | 6 +- internal/collector/vmware/vmware.go | 77 +++-- internal/httphandler/httphandler.go | 2 +- internal/mi/application.go | 283 +++++++++++++++++ internal/mi/callbacks.go | 154 +++++++++ internal/mi/doc.go | 7 + internal/mi/errors.go | 10 + internal/mi/instance.go | 179 +++++++++++ internal/mi/mi_bench_test.go | 43 +++ internal/mi/mi_test.go | 294 ++++++++++++++++++ internal/mi/operation.go | 272 ++++++++++++++++ internal/mi/result.go | 102 ++++++ internal/mi/session.go | 235 ++++++++++++++ internal/mi/types.go | 137 ++++++++ internal/mi/value.go | 112 +++++++ internal/testutils/handle.go | 28 ++ internal/testutils/testutils.go | 14 +- internal/utils/collector.go | 8 + internal/utils/utils.go | 11 + main.go | 1 + pkg/collector/collector.go | 52 +++- pkg/collector/types.go | 6 +- 82 files changed, 2771 insertions(+), 738 deletions(-) create mode 100644 internal/mi/application.go create mode 100644 internal/mi/callbacks.go create mode 100644 internal/mi/doc.go create mode 100644 internal/mi/errors.go create mode 100644 internal/mi/instance.go create mode 100644 internal/mi/mi_bench_test.go create mode 100644 internal/mi/mi_test.go create mode 100644 internal/mi/operation.go create mode 100644 internal/mi/result.go create mode 100644 internal/mi/session.go create mode 100644 internal/mi/types.go create mode 100644 internal/mi/value.go create mode 100644 internal/testutils/handle.go create mode 100644 main.go diff --git a/.golangci.yaml b/.golangci.yaml index 49c1839b..05434077 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -51,7 +51,7 @@ linters-settings: forbidigo: forbid: - "^(fmt\\.Print(|f|ln)|print|println)$" - - p: "^syscall\\..*$" + - p: "^syscall\\.(.{1,7}|.{7}[^N]|.{9,})$" msg: use golang.org/x/sys/windows instead of syscall - p: "^windows\\.NewLazyDLL$" msg: use NewLazySystemDLL instead NewLazyDLL diff --git a/go.mod b/go.mod index ebd3ec58..9a0c33cb 100644 --- a/go.mod +++ b/go.mod @@ -14,14 +14,13 @@ require ( github.com/prometheus/common v0.60.1 github.com/prometheus/exporter-toolkit v0.13.0 github.com/stretchr/testify v1.9.0 - github.com/yusufpapurcu/wmi v1.2.4 golang.org/x/sys v0.26.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/Microsoft/go-winio v0.6.2 // indirect - github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 // indirect + github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/containerd/cgroups/v3 v3.0.3 // indirect @@ -31,7 +30,7 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/jpillora/backoff v1.0.0 // indirect - github.com/klauspost/compress v1.17.10 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/mdlayher/socket v0.5.1 // indirect github.com/mdlayher/vsock v1.2.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -42,13 +41,13 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect go.opencensus.io v0.24.0 // indirect - golang.org/x/crypto v0.27.0 // indirect - golang.org/x/net v0.29.0 // indirect + golang.org/x/crypto v0.28.0 // indirect + golang.org/x/net v0.30.0 // indirect golang.org/x/oauth2 v0.23.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/text v0.18.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect - google.golang.org/grpc v1.67.0 // indirect - google.golang.org/protobuf v1.34.2 // indirect + golang.org/x/text v0.19.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/grpc v1.67.1 // indirect + google.golang.org/protobuf v1.35.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 6aa52dfa..dc625f8b 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/Microsoft/hcsshim v0.12.8 h1:BtDWYlFMcWhorrvSSo2M7z0csPdw6t7no/C3FsSv github.com/Microsoft/hcsshim v0.12.8/go.mod h1:cibQ4BqhJ32FXDwPdQhKhwrwophnh3FuT4nwQZF907w= github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30 h1:t3eaIm0rUkzbrIewtiFmMK5RXHej2XnoXNhxVsAYUfg= -github.com/alecthomas/units v0.0.0-20240626203959-61d1e3462e30/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= +github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b/go.mod h1:fvzegU4vN3H1qMT+8wDmzjAcDONcgo2/SZ/TyfdUOFs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= @@ -34,7 +34,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -65,8 +64,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= -github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -113,14 +112,12 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= -github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= -github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= @@ -131,8 +128,8 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= -golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4= +golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= @@ -144,7 +141,6 @@ golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -152,8 +148,8 @@ golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM= +golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -165,15 +161,15 @@ google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7 google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.67.0 h1:IdH9y6PF5MPSdAntIcpjQ+tXO41pcQsfZV2RxtQgVcw= -google.golang.org/grpc v1.67.0/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= +google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= +google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -183,8 +179,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= +google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/internal/collector/ad/ad.go b/internal/collector/ad/ad.go index 09b72058..12643f78 100644 --- a/internal/collector/ad/ad.go +++ b/internal/collector/ad/ad.go @@ -8,10 +8,10 @@ import ( "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "ad" @@ -118,7 +118,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { counters := []string{ abANRPerSec, abBrowsesPerSec, diff --git a/internal/collector/adcs/adcs.go b/internal/collector/adcs/adcs.go index d484793a..de465f0b 100644 --- a/internal/collector/adcs/adcs.go +++ b/internal/collector/adcs/adcs.go @@ -8,12 +8,12 @@ import ( "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "adcs" @@ -74,7 +74,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { counters := []string{ requestsPerSecond, diff --git a/internal/collector/adfs/adfs.go b/internal/collector/adfs/adfs.go index a481ead2..99e70dc7 100644 --- a/internal/collector/adfs/adfs.go +++ b/internal/collector/adfs/adfs.go @@ -11,12 +11,12 @@ import ( "slices" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "adfs" @@ -107,7 +107,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { counters := []string{ adLoginConnectionFailures, diff --git a/internal/collector/cache/cache.go b/internal/collector/cache/cache.go index 051c6946..ecd863c7 100644 --- a/internal/collector/cache/cache.go +++ b/internal/collector/cache/cache.go @@ -8,13 +8,13 @@ import ( "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "cache" @@ -92,7 +92,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { counters := []string{ asyncCopyReadsTotal, diff --git a/internal/collector/container/container.go b/internal/collector/container/container.go index b6b53650..d32cc825 100644 --- a/internal/collector/container/container.go +++ b/internal/collector/container/container.go @@ -10,10 +10,10 @@ import ( "github.com/Microsoft/hcsshim" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "container" @@ -86,7 +86,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { c.containerAvailable = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "available"), "Available", diff --git a/internal/collector/cpu/cpu.go b/internal/collector/cpu/cpu.go index e48ae8fd..e881516e 100644 --- a/internal/collector/cpu/cpu.go +++ b/internal/collector/cpu/cpu.go @@ -8,12 +8,12 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "cpu" @@ -83,7 +83,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { counters := []string{ c1TimeSeconds, diff --git a/internal/collector/cpu_info/cpu_info.go b/internal/collector/cpu_info/cpu_info.go index f5335ac7..6c576b52 100644 --- a/internal/collector/cpu_info/cpu_info.go +++ b/internal/collector/cpu_info/cpu_info.go @@ -4,14 +4,15 @@ package cpu_info import ( "errors" + "fmt" "log/slog" "strconv" "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "cpu_info" @@ -22,9 +23,9 @@ var ConfigDefaults = Config{} // A Collector is a Prometheus Collector for a few WMI metrics in Win32_Processor. type Collector struct { - config Config - - wmiClient *wmi.Client + config Config + miSession *mi.Session + miQuery mi.Query cpuInfo *prometheus.Desc cpuCoreCount *prometheus.Desc @@ -63,12 +64,19 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + miQuery, err := mi.NewQuery("SELECT Architecture, DeviceId, Description, Family, L2CacheSize, L3CacheSize, Name, ThreadCount, NumberOfCores, NumberOfEnabledCore, NumberOfLogicalProcessors FROM Win32_Processor") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQuery = miQuery + c.miSession = miSession + c.cpuInfo = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, "", Name), "Labelled CPU information as provided by Win32_Processor", @@ -133,18 +141,20 @@ func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { return nil } -type win32Processor struct { - Architecture uint32 - DeviceID string - Description string - Family uint16 - L2CacheSize uint32 - L3CacheSize uint32 - Name string - ThreadCount uint32 - NumberOfCores uint32 - NumberOfEnabledCore uint32 - NumberOfLogicalProcessors uint32 +type miProcessor struct { + Architecture uint32 `mi:"Architecture"` + DeviceID string `mi:"DeviceID"` + Description string `mi:"Description"` + Family uint16 `mi:"Family"` + L2CacheSize uint32 `mi:"L2CacheSize"` + L3CacheSize uint32 `mi:"L3CacheSize"` + Name string `mi:"Name"` + ThreadCount uint32 `mi:"ThreadCount"` + NumberOfCores uint32 `mi:"NumberOfCores"` + NumberOfEnabledCore uint32 `mi:"NumberOfEnabledCore"` + NumberOfLogicalProcessors uint32 `mi:"NumberOfLogicalProcessors"` + + Total int } // Collect sends the metric values for each metric @@ -163,16 +173,9 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan } func (c *Collector) collect(ch chan<- prometheus.Metric) error { - var dst []win32Processor - // We use a static query here because the provided methods in wmi.go all issue a SELECT *; - // This results in the time-consuming LoadPercentage field being read which seems to measure each CPU - // serially over a 1 second interval, so the scrape time is at least 1s * num_sockets - if err := c.wmiClient.Query("SELECT Architecture, DeviceId, Description, Family, L2CacheSize, L3CacheSize, Name, ThreadCount, NumberOfCores, NumberOfEnabledCore, NumberOfLogicalProcessors FROM Win32_Processor", &dst); err != nil { - return err - } - - if len(dst) == 0 { - return errors.New("WMI query returned empty result set") + var dst []miProcessor + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, c.miQuery); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } // Some CPUs end up exposing trailing spaces for certain strings, so clean them up diff --git a/internal/collector/cs/cs.go b/internal/collector/cs/cs.go index bee7ab34..b6bc1ae5 100644 --- a/internal/collector/cs/cs.go +++ b/internal/collector/cs/cs.go @@ -7,9 +7,9 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/headers/sysinfoapi" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "cs" @@ -61,7 +61,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { logger.Warn("The cs collector is deprecated and will be removed in a future release. " + "Logical processors has been moved to cpu_info collector. " + "Physical memory has been moved to memory collector. " + diff --git a/internal/collector/dfsr/dfsr.go b/internal/collector/dfsr/dfsr.go index b9392c56..96522f90 100644 --- a/internal/collector/dfsr/dfsr.go +++ b/internal/collector/dfsr/dfsr.go @@ -10,12 +10,12 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "dfsr" @@ -167,7 +167,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { logger = logger.With(slog.String("collector", Name)) logger.Info("dfsr collector is in an experimental state! Metrics for this collector have not been tested.") diff --git a/internal/collector/dhcp/dhcp.go b/internal/collector/dhcp/dhcp.go index 7a330873..8af64bc2 100644 --- a/internal/collector/dhcp/dhcp.go +++ b/internal/collector/dhcp/dhcp.go @@ -8,13 +8,13 @@ import ( "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "dhcp" @@ -88,7 +88,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { counters := []string{ acksTotal, diff --git a/internal/collector/diskdrive/diskdrive.go b/internal/collector/diskdrive/diskdrive.go index 06857e5f..0eec9438 100644 --- a/internal/collector/diskdrive/diskdrive.go +++ b/internal/collector/diskdrive/diskdrive.go @@ -4,19 +4,17 @@ package diskdrive import ( "errors" + "fmt" "log/slog" "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) -const ( - Name = "diskdrive" - win32DiskQuery = "SELECT DeviceID, Model, Caption, Name, Partitions, Size, Status, Availability FROM WIN32_DiskDrive" -) +const Name = "diskdrive" type Config struct{} @@ -25,7 +23,8 @@ var ConfigDefaults = Config{} // A Collector is a Prometheus Collector for a few WMI metrics in Win32_DiskDrive. type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session + miQuery mi.Query availability *prometheus.Desc diskInfo *prometheus.Desc @@ -62,12 +61,19 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + miQuery, err := mi.NewQuery("SELECT DeviceID, Model, Caption, Name, Partitions, Size, Status, Availability FROM WIN32_DiskDrive") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQuery = miQuery + c.miSession = miSession + c.diskInfo = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "info"), "General drive information", @@ -108,14 +114,14 @@ func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { } type win32_DiskDrive struct { - DeviceID string - Model string - Size uint64 - Name string - Caption string - Partitions uint32 - Status string - Availability uint16 + DeviceID string `mi:"DeviceID"` + Model string `mi:"Model"` + Size uint64 `mi:"Size"` + Name string `mi:"Name"` + Caption string `mi:"Caption"` + Partitions uint32 `mi:"Partitions"` + Status string `mi:"Status"` + Availability uint16 `mi:"Availability"` } var ( @@ -175,9 +181,8 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan func (c *Collector) collect(ch chan<- prometheus.Metric) error { var dst []win32_DiskDrive - - if err := c.wmiClient.Query(win32DiskQuery, &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, c.miQuery); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } if len(dst) == 0 { diff --git a/internal/collector/dns/dns.go b/internal/collector/dns/dns.go index d83472f9..9853c8fe 100644 --- a/internal/collector/dns/dns.go +++ b/internal/collector/dns/dns.go @@ -8,11 +8,11 @@ import ( "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "dns" @@ -79,7 +79,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { counters := []string{ axfrRequestReceived, axfrRequestSent, diff --git a/internal/collector/exchange/exchange.go b/internal/collector/exchange/exchange.go index c9076985..31582582 100644 --- a/internal/collector/exchange/exchange.go +++ b/internal/collector/exchange/exchange.go @@ -10,11 +10,11 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "exchange" @@ -207,7 +207,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { collectorFuncs := map[string]func() error{ adAccessProcesses: c.buildADAccessProcesses, diff --git a/internal/collector/filetime/filetime.go b/internal/collector/filetime/filetime.go index 0dd8ccb7..bb2d00f0 100644 --- a/internal/collector/filetime/filetime.go +++ b/internal/collector/filetime/filetime.go @@ -12,9 +12,9 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/bmatcuk/doublestar/v4" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "filetime" @@ -85,7 +85,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { logger.Info("filetime collector is in an experimental state! It may subject to change.", slog.String("collector", Name), ) diff --git a/internal/collector/fsrmquota/fsrmquota.go b/internal/collector/fsrmquota/fsrmquota.go index 6d2ed377..d45fe1b5 100644 --- a/internal/collector/fsrmquota/fsrmquota.go +++ b/internal/collector/fsrmquota/fsrmquota.go @@ -4,13 +4,14 @@ package fsrmquota import ( "errors" + "fmt" "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "fsrmquota" @@ -21,7 +22,8 @@ var ConfigDefaults = Config{} type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session + miQuery mi.Query quotasCount *prometheus.Desc peakUsage *prometheus.Desc @@ -63,12 +65,18 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + miQuery, err := mi.NewQuery("SELECT * FROM MSFT_FSRMQuota") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQuery = miQuery + c.miSession = miSession c.quotasCount = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "count"), @@ -146,29 +154,28 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan // MSFT_FSRMQuota docs: // https://docs.microsoft.com/en-us/previous-versions/windows/desktop/fsrm/msft-fsrmquota type MSFT_FSRMQuota struct { - Name string + Name string `mi:"Name"` - Path string - PeakUsage uint64 - Size uint64 - Usage uint64 - Description string - Template string - // Threshold string - Disabled bool - MatchesTemplate bool - SoftLimit bool + Path string `mi:"Path"` + PeakUsage uint64 `mi:"PeakUsage"` + Size uint64 `mi:"Size"` + Usage uint64 `mi:"Usage"` + Description string `mi:"Description"` + Template string `mi:"Template"` + // Threshold string `mi:"Threshold"` + Disabled bool `mi:"Disabled"` + MatchesTemplate bool `mi:"MatchesTemplate"` + SoftLimit bool `mi:"SoftLimit"` } func (c *Collector) collect(ch chan<- prometheus.Metric) error { var dst []MSFT_FSRMQuota + if err := c.miSession.Query(&dst, mi.NamespaceRootWindowsFSRM, c.miQuery); err != nil { + return fmt.Errorf("WMI query failed: %w", err) + } var count int - if err := c.wmiClient.Query("SELECT * FROM MSFT_FSRMQuota", &dst, nil, "root/microsoft/windows/fsrm"); err != nil { - return err - } - for _, quota := range dst { count++ path := quota.Path diff --git a/internal/collector/hyperv/hyperv.go b/internal/collector/hyperv/hyperv.go index c64f5e3b..fead371e 100644 --- a/internal/collector/hyperv/hyperv.go +++ b/internal/collector/hyperv/hyperv.go @@ -9,9 +9,10 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "hyperv" @@ -23,7 +24,7 @@ var ConfigDefaults = Config{} // Collector is a Prometheus Collector for hyper-v. type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session // Win32_PerfRawData_VmmsVirtualMachineStats_HyperVVirtualMachineHealthSummary healthCritical *prometheus.Desc @@ -168,12 +169,12 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + c.miSession = miSession buildSubsystemName := func(component string) string { return "hyperv_" + component } @@ -858,14 +859,14 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan // Win32_PerfRawData_VmmsVirtualMachineStats_HyperVVirtualMachineHealthSummary vm health status. type Win32_PerfRawData_VmmsVirtualMachineStats_HyperVVirtualMachineHealthSummary struct { - HealthCritical uint32 - HealthOk uint32 + HealthCritical uint32 `mi:"HealthCritical"` + HealthOk uint32 `mi:"HealthOK"` } func (c *Collector) collectVmHealth(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_VmmsVirtualMachineStats_HyperVVirtualMachineHealthSummary - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_VmmsVirtualMachineStats_HyperVVirtualMachineHealthSummary", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_VmmsVirtualMachineStats_HyperVVirtualMachineHealthSummary"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, health := range dst { @@ -887,16 +888,16 @@ func (c *Collector) collectVmHealth(ch chan<- prometheus.Metric) error { // Win32_PerfRawData_VidPerfProvider_HyperVVMVidPartition ..,. type Win32_PerfRawData_VidPerfProvider_HyperVVMVidPartition struct { - Name string - PhysicalPagesAllocated uint64 - PreferredNUMANodeIndex uint64 - RemotePhysicalPages uint64 + Name string `mi:"Name"` + PhysicalPagesAllocated uint64 `mi:"PhysicalPagesAllocated"` + PreferredNUMANodeIndex uint64 `mi:"PreferredNUMANodeIndex"` + RemotePhysicalPages uint64 `mi:"RemotePhysicalPages"` } func (c *Collector) collectVmVid(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_VidPerfProvider_HyperVVMVidPartition - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_VidPerfProvider_HyperVVMVidPartition", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_VidPerfProvider_HyperVVMVidPartition"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, page := range dst { @@ -931,34 +932,34 @@ func (c *Collector) collectVmVid(ch chan<- prometheus.Metric) error { // Win32_PerfRawData_HvStats_HyperVHypervisorRootPartition ... type Win32_PerfRawData_HvStats_HyperVHypervisorRootPartition struct { - Name string - AddressSpaces uint64 - AttachedDevices uint64 - DepositedPages uint64 - DeviceDMAErrors uint64 - DeviceInterruptErrors uint64 - DeviceInterruptMappings uint64 - DeviceInterruptThrottleEvents uint64 - GPAPages uint64 - GPASpaceModificationsPersec uint64 - IOTLBFlushCost uint64 - IOTLBFlushesPersec uint64 - RecommendedVirtualTLBSize uint64 - SkippedTimerTicks uint64 - Value1Gdevicepages uint64 - Value1GGPApages uint64 - Value2Mdevicepages uint64 - Value2MGPApages uint64 - Value4Kdevicepages uint64 - Value4KGPApages uint64 - VirtualTLBFlushEntiresPersec uint64 - VirtualTLBPages uint64 + Name string `mi:"Name"` + AddressSpaces uint64 `mi:"AddressSpaces"` + AttachedDevices uint64 `mi:"AttachedDevices"` + DepositedPages uint64 `mi:"DepositedPages"` + DeviceDMAErrors uint64 `mi:"DeviceDMAErrors"` + DeviceInterruptErrors uint64 `mi:"DeviceInterruptErrors"` + DeviceInterruptMappings uint64 `mi:"DeviceInterruptMappings"` + DeviceInterruptThrottleEvents uint64 `mi:"DeviceInterruptThrottleEvents"` + GPAPages uint64 `mi:"GPAPages"` + GPASpaceModificationsPersec uint64 `mi:"GPASpaceModificationsPersec"` + IOTLBFlushCost uint64 `mi:"IOTLBFlushCost"` + IOTLBFlushesPersec uint64 `mi:"IOTLBFlushesPersec"` + RecommendedVirtualTLBSize uint64 `mi:"RecommendedVirtualTLBSize"` + SkippedTimerTicks uint64 `mi:"SkippedTimerTicks"` + Value1Gdevicepages uint64 `mi:"Value1Gdevicepages"` + Value1GGPApages uint64 `mi:"Value1GGPApages"` + Value2Mdevicepages uint64 `mi:"Value2Mdevicepages"` + Value2MGPApages uint64 `mi:"Value2MGPApages"` + Value4Kdevicepages uint64 `mi:"Value4Kdevicepages"` + Value4KGPApages uint64 `mi:"Value4KGPApages"` + VirtualTLBFlushEntiresPersec uint64 `mi:"VirtualTLBFlushEntiresPersec"` + VirtualTLBPages uint64 `mi:"VirtualTLBPages"` } func (c *Collector) collectVmHv(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_HvStats_HyperVHypervisorRootPartition - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_HvStats_HyperVHypervisorRootPartition", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_HvStats_HyperVHypervisorRootPartition"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1087,14 +1088,14 @@ func (c *Collector) collectVmHv(ch chan<- prometheus.Metric) error { // Win32_PerfRawData_HvStats_HyperVHypervisor ... type Win32_PerfRawData_HvStats_HyperVHypervisor struct { - LogicalProcessors uint64 - VirtualProcessors uint64 + LogicalProcessors uint64 `mi:"LogicalProcessors"` + VirtualProcessors uint64 `mi:"VirtualProcessors"` } func (c *Collector) collectVmProcessor(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_HvStats_HyperVHypervisor - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_HvStats_HyperVHypervisor", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_HvStats_HyperVHypervisor"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1116,16 +1117,16 @@ func (c *Collector) collectVmProcessor(ch chan<- prometheus.Metric) error { // Win32_PerfRawData_HvStats_HyperVHypervisorLogicalProcessor ... type Win32_PerfRawData_HvStats_HyperVHypervisorLogicalProcessor struct { - Name string - PercentGuestRunTime uint64 - PercentHypervisorRunTime uint64 - PercentTotalRunTime uint + Name string `mi:"Name"` + PercentGuestRunTime uint64 `mi:"PercentGuestRunTime"` + PercentHypervisorRunTime uint64 `mi:"PercentHypervisorRunTime"` + PercentTotalRunTime uint64 `mi:"PercentTotalRunTime"` } func (c *Collector) collectHostLPUsage(logger *slog.Logger, ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_HvStats_HyperVHypervisorLogicalProcessor - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_HvStats_HyperVHypervisorLogicalProcessor", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_HvStats_HyperVHypervisorLogicalProcessor"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1170,18 +1171,18 @@ func (c *Collector) collectHostLPUsage(logger *slog.Logger, ch chan<- prometheus // Win32_PerfRawData_HvStats_HyperVHypervisorRootVirtualProcessor ... type Win32_PerfRawData_HvStats_HyperVHypervisorRootVirtualProcessor struct { - Name string - PercentGuestRunTime uint64 - PercentHypervisorRunTime uint64 - PercentRemoteRunTime uint64 - PercentTotalRunTime uint64 - CPUWaitTimePerDispatch uint64 + Name string `mi:"Name"` + PercentGuestRunTime uint64 `mi:"PercentGuestRunTime"` + PercentHypervisorRunTime uint64 `mi:"PercentHypervisorRunTime"` + PercentRemoteRunTime uint64 `mi:"PercentRemoteRunTime"` + PercentTotalRunTime uint64 `mi:"PercentTotalRunTime"` + CPUWaitTimePerDispatch uint64 `mi:"CPUWaitTimePerDispatch"` } func (c *Collector) collectHostCpuUsage(logger *slog.Logger, ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_HvStats_HyperVHypervisorRootVirtualProcessor - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_HvStats_HyperVHypervisorRootVirtualProcessor", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_HvStats_HyperVHypervisorRootVirtualProcessor"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1240,18 +1241,18 @@ func (c *Collector) collectHostCpuUsage(logger *slog.Logger, ch chan<- prometheu // Win32_PerfRawData_HvStats_HyperVHypervisorVirtualProcessor ... type Win32_PerfRawData_HvStats_HyperVHypervisorVirtualProcessor struct { - Name string - PercentGuestRunTime uint64 - PercentHypervisorRunTime uint64 - PercentRemoteRunTime uint64 - PercentTotalRunTime uint64 - CPUWaitTimePerDispatch uint64 + Name string `mi:"Name"` + PercentGuestRunTime uint64 `mi:"PercentGuestRunTime"` + PercentHypervisorRunTime uint64 `mi:"PercentHypervisorRunTime"` + PercentRemoteRunTime uint64 `mi:"PercentRemoteRunTime"` + PercentTotalRunTime uint64 `mi:"PercentTotalRunTime"` + CPUWaitTimePerDispatch uint64 `mi:"CPUWaitTimePerDispatch"` } func (c *Collector) collectVmCpuUsage(logger *slog.Logger, ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_HvStats_HyperVHypervisorVirtualProcessor - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_HvStats_HyperVHypervisorVirtualProcessor", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_HvStats_HyperVHypervisorVirtualProcessor"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1318,37 +1319,37 @@ func (c *Collector) collectVmCpuUsage(logger *slog.Logger, ch chan<- prometheus. // Win32_PerfRawData_NvspSwitchStats_HyperVVirtualSwitch ... type Win32_PerfRawData_NvspSwitchStats_HyperVVirtualSwitch struct { - Name string - BroadcastPacketsReceivedPersec uint64 - BroadcastPacketsSentPersec uint64 - BytesPersec uint64 - BytesReceivedPersec uint64 - BytesSentPersec uint64 - DirectedPacketsReceivedPersec uint64 - DirectedPacketsSentPersec uint64 - DroppedPacketsIncomingPersec uint64 - DroppedPacketsOutgoingPersec uint64 - ExtensionsDroppedPacketsIncomingPersec uint64 - ExtensionsDroppedPacketsOutgoingPersec uint64 - LearnedMacAddresses uint64 - LearnedMacAddressesPersec uint64 - MulticastPacketsReceivedPersec uint64 - MulticastPacketsSentPersec uint64 - NumberofSendChannelMovesPersec uint64 - NumberofVMQMovesPersec uint64 - PacketsFlooded uint64 - PacketsFloodedPersec uint64 - PacketsPersec uint64 - PacketsReceivedPersec uint64 - PacketsSentPersec uint64 - PurgedMacAddresses uint64 - PurgedMacAddressesPersec uint64 + Name string `mi:"Name"` + BroadcastPacketsReceivedPersec uint64 `mi:"BroadcastPacketsReceivedPersec"` + BroadcastPacketsSentPersec uint64 `mi:"BroadcastPacketsSentPersec"` + BytesPersec uint64 `mi:"BytesPersec"` + BytesReceivedPersec uint64 `mi:"BytesReceivedPersec"` + BytesSentPersec uint64 `mi:"BytesSentPersec"` + DirectedPacketsReceivedPersec uint64 `mi:"DirectedPacketsReceivedPersec"` + DirectedPacketsSentPersec uint64 `mi:"DirectedPacketsSentPersec"` + DroppedPacketsIncomingPersec uint64 `mi:"DroppedPacketsIncomingPersec"` + DroppedPacketsOutgoingPersec uint64 `mi:"DroppedPacketsOutgoingPersec"` + ExtensionsDroppedPacketsIncomingPersec uint64 `mi:"ExtensionsDroppedPacketsIncomingPersec"` + ExtensionsDroppedPacketsOutgoingPersec uint64 `mi:"ExtensionsDroppedPacketsOutgoingPersec"` + LearnedMacAddresses uint64 `mi:"LearnedMacAddresses"` + LearnedMacAddressesPersec uint64 `mi:"LearnedMacAddressesPersec"` + MulticastPacketsReceivedPersec uint64 `mi:"MulticastPacketsReceivedPersec"` + MulticastPacketsSentPersec uint64 `mi:"MulticastPacketsSentPersec"` + NumberofSendChannelMovesPersec uint64 `mi:"NumberofSendChannelMovesPersec"` + NumberofVMQMovesPersec uint64 `mi:"NumberofVMQMovesPersec"` + PacketsFlooded uint64 `mi:"PacketsFlooded"` + PacketsFloodedPersec uint64 `mi:"PacketsFloodedPersec"` + PacketsPersec uint64 `mi:"PacketsPersec"` + PacketsReceivedPersec uint64 `mi:"PacketsReceivedPersec"` + PacketsSentPersec uint64 `mi:"PacketsSentPersec"` + PurgedMacAddresses uint64 `mi:"PurgedMacAddresses"` + PurgedMacAddressesPersec uint64 `mi:"PurgedMacAddressesPersec"` } func (c *Collector) collectVmSwitch(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NvspSwitchStats_HyperVVirtualSwitch - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NvspSwitchStats_HyperVVirtualSwitch", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NvspSwitchStats_HyperVVirtualSwitch"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1500,19 +1501,19 @@ func (c *Collector) collectVmSwitch(ch chan<- prometheus.Metric) error { // Win32_PerfRawData_EthernetPerfProvider_HyperVLegacyNetworkAdapter ... type Win32_PerfRawData_EthernetPerfProvider_HyperVLegacyNetworkAdapter struct { - Name string - BytesDropped uint64 - BytesReceivedPersec uint64 - BytesSentPersec uint64 - FramesDropped uint64 - FramesReceivedPersec uint64 - FramesSentPersec uint64 + Name string `mi:"Name"` + BytesDropped uint64 `mi:"BytesDropped"` + BytesReceivedPersec uint64 `mi:"BytesReceivedPersec"` + BytesSentPersec uint64 `mi:"BytesSentPersec"` + FramesDropped uint64 `mi:"FramesDropped"` + FramesReceivedPersec uint64 `mi:"FramesReceivedPersec"` + FramesSentPersec uint64 `mi:"FramesSentPersec"` } func (c *Collector) collectVmEthernet(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_EthernetPerfProvider_HyperVLegacyNetworkAdapter - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_EthernetPerfProvider_HyperVLegacyNetworkAdapter", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_EthernetPerfProvider_HyperVLegacyNetworkAdapter"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1568,19 +1569,19 @@ func (c *Collector) collectVmEthernet(ch chan<- prometheus.Metric) error { // Win32_PerfRawData_Counters_HyperVVirtualStorageDevice ... type Win32_PerfRawData_Counters_HyperVVirtualStorageDevice struct { - Name string - ErrorCount uint64 - QueueLength uint32 - ReadBytesPersec uint64 - ReadOperationsPerSec uint64 - WriteBytesPersec uint64 - WriteOperationsPerSec uint64 + Name string `mi:"Name"` + ErrorCount uint64 `mi:"ErrorCount"` + QueueLength uint32 `mi:"QueueLength"` + ReadBytesPersec uint64 `mi:"ReadBytesPersec"` + ReadOperationsPerSec uint64 `mi:"ReadOperationsPerSec"` + WriteBytesPersec uint64 `mi:"WriteBytesPersec"` + WriteOperationsPerSec uint64 `mi:"WriteOperationsPerSec"` } func (c *Collector) collectVmStorage(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_Counters_HyperVVirtualStorageDevice - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_Counters_HyperVVirtualStorageDevice", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_Counters_HyperVVirtualStorageDevice"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1636,19 +1637,19 @@ func (c *Collector) collectVmStorage(ch chan<- prometheus.Metric) error { // Win32_PerfRawData_NvspNicStats_HyperVVirtualNetworkAdapter ... type Win32_PerfRawData_NvspNicStats_HyperVVirtualNetworkAdapter struct { - Name string - BytesReceivedPersec uint64 - BytesSentPersec uint64 - DroppedPacketsIncomingPersec uint64 - DroppedPacketsOutgoingPersec uint64 - PacketsReceivedPersec uint64 - PacketsSentPersec uint64 + Name string `mi:"Name"` + BytesReceivedPersec uint64 `mi:"BytesReceivedPersec"` + BytesSentPersec uint64 `mi:"BytesSentPersec"` + DroppedPacketsIncomingPersec uint64 `mi:"DroppedPacketsIncomingPersec"` + DroppedPacketsOutgoingPersec uint64 `mi:"DroppedPacketsOutgoingPersec"` + PacketsReceivedPersec uint64 `mi:"PacketsReceivedPersec"` + PacketsSentPersec uint64 `mi:"PacketsSentPersec"` } func (c *Collector) collectVmNetwork(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NvspNicStats_HyperVVirtualNetworkAdapter - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NvspNicStats_HyperVVirtualNetworkAdapter", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NvspNicStats_HyperVVirtualNetworkAdapter"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { @@ -1704,23 +1705,23 @@ func (c *Collector) collectVmNetwork(ch chan<- prometheus.Metric) error { // Win32_PerfRawData_BalancerStats_HyperVDynamicMemoryVM ... type Win32_PerfRawData_BalancerStats_HyperVDynamicMemoryVM struct { - Name string - AddedMemory uint64 - AveragePressure uint64 - CurrentPressure uint64 - GuestVisiblePhysicalMemory uint64 - MaximumPressure uint64 - MemoryAddOperations uint64 - MemoryRemoveOperations uint64 - MinimumPressure uint64 - PhysicalMemory uint64 - RemovedMemory uint64 + Name string `mi:"Name"` + AddedMemory uint64 `mi:"AddedMemory"` + AveragePressure uint64 `mi:"AveragePressure"` + CurrentPressure uint64 `mi:"CurrentPressure"` + GuestVisiblePhysicalMemory uint64 `mi:"GuestVisiblePhysicalMemory"` + MaximumPressure uint64 `mi:"MaximumPressure"` + MemoryAddOperations uint64 `mi:"MemoryAddOperations"` + MemoryRemoveOperations uint64 `mi:"MemoryRemoveOperations"` + MinimumPressure uint64 `mi:"MinimumPressure"` + PhysicalMemory uint64 `mi:"PhysicalMemory"` + RemovedMemory uint64 `mi:"RemovedMemory"` } func (c *Collector) collectVmMemory(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_BalancerStats_HyperVDynamicMemoryVM - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_BalancerStats_HyperVDynamicMemoryVM", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_BalancerStats_HyperVDynamicMemoryVM"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, obj := range dst { diff --git a/internal/collector/iis/iis.go b/internal/collector/iis/iis.go index 31e12602..dbd93319 100644 --- a/internal/collector/iis/iis.go +++ b/internal/collector/iis/iis.go @@ -10,10 +10,10 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows/registry" ) @@ -262,7 +262,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { logger = logger.With(slog.String("collector", Name)) c.iisVersion = getIISVersion(logger) diff --git a/internal/collector/license/license.go b/internal/collector/license/license.go index 9d0aa04a..74d22109 100644 --- a/internal/collector/license/license.go +++ b/internal/collector/license/license.go @@ -7,9 +7,9 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/headers/slc" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "license" @@ -61,7 +61,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { c.licenseStatus = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "status"), "Status of windows license", diff --git a/internal/collector/logical_disk/logical_disk.go b/internal/collector/logical_disk/logical_disk.go index dd2374c4..145c1095 100644 --- a/internal/collector/logical_disk/logical_disk.go +++ b/internal/collector/logical_disk/logical_disk.go @@ -13,13 +13,13 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" ) @@ -141,7 +141,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { counters := []string{ currentDiskQueueLength, diff --git a/internal/collector/logon/logon.go b/internal/collector/logon/logon.go index 3c40410d..63a45238 100644 --- a/internal/collector/logon/logon.go +++ b/internal/collector/logon/logon.go @@ -8,9 +8,9 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/headers/secur32" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "logon" @@ -54,7 +54,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { c.sessionInfo = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "session_logon_timestamp_seconds"), "timestamp of the logon session in seconds.", diff --git a/internal/collector/memory/memory.go b/internal/collector/memory/memory.go index 573a1470..977e3a54 100644 --- a/internal/collector/memory/memory.go +++ b/internal/collector/memory/memory.go @@ -12,13 +12,13 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/headers/sysinfoapi" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "memory" @@ -105,7 +105,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { counters := []string{ availableBytes, diff --git a/internal/collector/mscluster/mscluster.go b/internal/collector/mscluster/mscluster.go index 6a5d09e8..40f03c01 100644 --- a/internal/collector/mscluster/mscluster.go +++ b/internal/collector/mscluster/mscluster.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "mscluster" @@ -32,7 +32,7 @@ var ConfigDefaults = Config{ // A Collector is a Prometheus Collector for WMI MSCluster_Cluster metrics. type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session // cluster clusterAddEvictDelay *prometheus.Desc @@ -221,16 +221,16 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { if len(c.config.CollectorsEnabled) == 0 { return nil } - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + c.miSession = miSession if slices.Contains(c.config.CollectorsEnabled, "cluster") { c.buildCluster() diff --git a/internal/collector/mscluster/mscluster_cluster.go b/internal/collector/mscluster/mscluster_cluster.go index fba00da5..08291088 100644 --- a/internal/collector/mscluster/mscluster_cluster.go +++ b/internal/collector/mscluster/mscluster_cluster.go @@ -1,7 +1,11 @@ package mscluster import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -10,85 +14,85 @@ const nameCluster = Name + "_cluster" // msClusterCluster represents the MSCluster_Cluster WMI class // - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/cluswmi/mscluster-cluster type msClusterCluster struct { - Name string + Name string `mi:"Name"` - AddEvictDelay uint - AdminAccessPoint uint - AutoAssignNodeSite uint - AutoBalancerLevel uint - AutoBalancerMode uint - BackupInProgress uint - BlockCacheSize uint - ClusSvcHangTimeout uint - ClusSvcRegroupOpeningTimeout uint - ClusSvcRegroupPruningTimeout uint - ClusSvcRegroupStageTimeout uint - ClusSvcRegroupTickInMilliseconds uint - ClusterEnforcedAntiAffinity uint - ClusterFunctionalLevel uint - ClusterGroupWaitDelay uint - ClusterLogLevel uint - ClusterLogSize uint - ClusterUpgradeVersion uint - CrossSiteDelay uint - CrossSiteThreshold uint - CrossSubnetDelay uint - CrossSubnetThreshold uint - CsvBalancer uint - DatabaseReadWriteMode uint - DefaultNetworkRole uint - DetectedCloudPlatform uint - DetectManagedEvents uint - DetectManagedEventsThreshold uint - DisableGroupPreferredOwnerRandomization uint - DrainOnShutdown uint - DynamicQuorumEnabled uint - EnableSharedVolumes uint - FixQuorum uint - GracePeriodEnabled uint - GracePeriodTimeout uint - GroupDependencyTimeout uint - HangRecoveryAction uint - IgnorePersistentStateOnStartup uint - LogResourceControls uint - LowerQuorumPriorityNodeId uint - MaxNumberOfNodes uint - MessageBufferLength uint - MinimumNeverPreemptPriority uint - MinimumPreemptorPriority uint - NetftIPSecEnabled uint - PlacementOptions uint - PlumbAllCrossSubnetRoutes uint - PreventQuorum uint - QuarantineDuration uint - QuarantineThreshold uint - QuorumArbitrationTimeMax uint - QuorumArbitrationTimeMin uint - QuorumLogFileSize uint - QuorumTypeValue uint - RequestReplyTimeout uint - ResiliencyDefaultPeriod uint - ResiliencyLevel uint - ResourceDllDeadlockPeriod uint - RootMemoryReserved uint - RouteHistoryLength uint - S2DBusTypes uint - S2DCacheDesiredState uint - S2DCacheFlashReservePercent uint - S2DCachePageSizeKBytes uint - S2DEnabled uint - S2DIOLatencyThreshold uint - S2DOptimizations uint - SameSubnetDelay uint - SameSubnetThreshold uint - SecurityLevel uint - SecurityLevelForStorage uint - SharedVolumeVssWriterOperationTimeout uint - ShutdownTimeoutInMinutes uint - UseClientAccessNetworksForSharedVolumes uint - WitnessDatabaseWriteTimeout uint - WitnessDynamicWeight uint - WitnessRestartInterval uint + AddEvictDelay uint `mi:"AddEvictDelay"` + AdminAccessPoint uint `mi:"AdminAccessPoint"` + AutoAssignNodeSite uint `mi:"AutoAssignNodeSite"` + AutoBalancerLevel uint `mi:"AutoBalancerLevel"` + AutoBalancerMode uint `mi:"AutoBalancerMode"` + BackupInProgress uint `mi:"BackupInProgress"` + BlockCacheSize uint `mi:"BlockCacheSize"` + ClusSvcHangTimeout uint `mi:"ClusSvcHangTimeout"` + ClusSvcRegroupOpeningTimeout uint `mi:"ClusSvcRegroupOpeningTimeout"` + ClusSvcRegroupPruningTimeout uint `mi:"ClusSvcRegroupPruningTimeout"` + ClusSvcRegroupStageTimeout uint `mi:"ClusSvcRegroupStageTimeout"` + ClusSvcRegroupTickInMilliseconds uint `mi:"ClusSvcRegroupTickInMilliseconds"` + ClusterEnforcedAntiAffinity uint `mi:"ClusterEnforcedAntiAffinity"` + ClusterFunctionalLevel uint `mi:"ClusterFunctionalLevel"` + ClusterGroupWaitDelay uint `mi:"ClusterGroupWaitDelay"` + ClusterLogLevel uint `mi:"ClusterLogLevel"` + ClusterLogSize uint `mi:"ClusterLogSize"` + ClusterUpgradeVersion uint `mi:"ClusterUpgradeVersion"` + CrossSiteDelay uint `mi:"CrossSiteDelay"` + CrossSiteThreshold uint `mi:"CrossSiteThreshold"` + CrossSubnetDelay uint `mi:"CrossSubnetDelay"` + CrossSubnetThreshold uint `mi:"CrossSubnetThreshold"` + CsvBalancer uint `mi:"CsvBalancer"` + DatabaseReadWriteMode uint `mi:"DatabaseReadWriteMode"` + DefaultNetworkRole uint `mi:"DefaultNetworkRole"` + DetectedCloudPlatform uint `mi:"DetectedCloudPlatform"` + DetectManagedEvents uint `mi:"DetectManagedEvents"` + DetectManagedEventsThreshold uint `mi:"DetectManagedEventsThreshold"` + DisableGroupPreferredOwnerRandomization uint `mi:"DisableGroupPreferredOwnerRandomization"` + DrainOnShutdown uint `mi:"DrainOnShutdown"` + DynamicQuorumEnabled uint `mi:"DynamicQuorumEnabled"` + EnableSharedVolumes uint `mi:"EnableSharedVolumes"` + FixQuorum uint `mi:"FixQuorum"` + GracePeriodEnabled uint `mi:"GracePeriodEnabled"` + GracePeriodTimeout uint `mi:"GracePeriodTimeout"` + GroupDependencyTimeout uint `mi:"GroupDependencyTimeout"` + HangRecoveryAction uint `mi:"HangRecoveryAction"` + IgnorePersistentStateOnStartup uint `mi:"IgnorePersistentStateOnStartup"` + LogResourceControls uint `mi:"LogResourceControls"` + LowerQuorumPriorityNodeId uint `mi:"LowerQuorumPriorityNodeId"` + MaxNumberOfNodes uint `mi:"MaxNumberOfNodes"` + MessageBufferLength uint `mi:"MessageBufferLength"` + MinimumNeverPreemptPriority uint `mi:"MinimumNeverPreemptPriority"` + MinimumPreemptorPriority uint `mi:"MinimumPreemptorPriority"` + NetftIPSecEnabled uint `mi:"NetftIPSecEnabled"` + PlacementOptions uint `mi:"PlacementOptions"` + PlumbAllCrossSubnetRoutes uint `mi:"PlumbAllCrossSubnetRoutes"` + PreventQuorum uint `mi:"PreventQuorum"` + QuarantineDuration uint `mi:"QuarantineDuration"` + QuarantineThreshold uint `mi:"QuarantineThreshold"` + QuorumArbitrationTimeMax uint `mi:"QuorumArbitrationTimeMax"` + QuorumArbitrationTimeMin uint `mi:"QuorumArbitrationTimeMin"` + QuorumLogFileSize uint `mi:"QuorumLogFileSize"` + QuorumTypeValue uint `mi:"QuorumTypeValue"` + RequestReplyTimeout uint `mi:"RequestReplyTimeout"` + ResiliencyDefaultPeriod uint `mi:"ResiliencyDefaultPeriod"` + ResiliencyLevel uint `mi:"ResiliencyLevel"` + ResourceDllDeadlockPeriod uint `mi:"ResourceDllDeadlockPeriod"` + RootMemoryReserved uint `mi:"RootMemoryReserved"` + RouteHistoryLength uint `mi:"RouteHistoryLength"` + S2DBusTypes uint `mi:"S2DBusTypes"` + S2DCacheDesiredState uint `mi:"S2DCacheDesiredState"` + S2DCacheFlashReservePercent uint `mi:"S2DCacheFlashReservePercent"` + S2DCachePageSizeKBytes uint `mi:"S2DCachePageSizeKBytes"` + S2DEnabled uint `mi:"S2DEnabled"` + S2DIOLatencyThreshold uint `mi:"S2DIOLatencyThreshold"` + S2DOptimizations uint `mi:"S2DOptimizations"` + SameSubnetDelay uint `mi:"SameSubnetDelay"` + SameSubnetThreshold uint `mi:"SameSubnetThreshold"` + SecurityLevel uint `mi:"SecurityLevel"` + SecurityLevelForStorage uint `mi:"SecurityLevelForStorage"` + SharedVolumeVssWriterOperationTimeout uint `mi:"SharedVolumeVssWriterOperationTimeout"` + ShutdownTimeoutInMinutes uint `mi:"ShutdownTimeoutInMinutes"` + UseClientAccessNetworksForSharedVolumes uint `mi:"UseClientAccessNetworksForSharedVolumes"` + WitnessDatabaseWriteTimeout uint `mi:"WitnessDatabaseWriteTimeout"` + WitnessDynamicWeight uint `mi:"WitnessDynamicWeight"` + WitnessRestartInterval uint `mi:"WitnessRestartInterval"` } func (c *Collector) buildCluster() { @@ -558,8 +562,8 @@ func (c *Collector) buildCluster() { func (c *Collector) collectCluster(ch chan<- prometheus.Metric) error { var dst []msClusterCluster - if err := c.wmiClient.Query("SELECT * FROM MSCluster_Cluster", &dst, nil, "root/MSCluster"); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootMSCluster, utils.Must(mi.NewQuery("SELECT * MSCluster_Cluster"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, v := range dst { diff --git a/internal/collector/mscluster/mscluster_network.go b/internal/collector/mscluster/mscluster_network.go index c013fff9..b0ed2874 100644 --- a/internal/collector/mscluster/mscluster_network.go +++ b/internal/collector/mscluster/mscluster_network.go @@ -1,7 +1,11 @@ package mscluster import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -10,13 +14,13 @@ const nameNetwork = Name + "_network" // msClusterNetwork represents the MSCluster_Network WMI class // - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/cluswmi/mscluster-network type msClusterNetwork struct { - Name string + Name string `mi:"Name"` - Characteristics uint - Flags uint - Metric uint - Role uint - State uint + Characteristics uint `mi:"Characteristics"` + Flags uint `mi:"Flags"` + Metric uint `mi:"Metric"` + Role uint `mi:"Role"` + State uint `mi:"State"` } func (c *Collector) buildNetwork() { @@ -57,8 +61,8 @@ func (c *Collector) buildNetwork() { func (c *Collector) collectNetwork(ch chan<- prometheus.Metric) error { var dst []msClusterNetwork - if err := c.wmiClient.Query("SELECT * FROM MSCluster_Network", &dst, nil, "root/MSCluster"); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootMSCluster, utils.Must(mi.NewQuery("SELECT * MSCluster_Node"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, v := range dst { diff --git a/internal/collector/mscluster/mscluster_node.go b/internal/collector/mscluster/mscluster_node.go index 523d2686..f89a8bc8 100644 --- a/internal/collector/mscluster/mscluster_node.go +++ b/internal/collector/mscluster/mscluster_node.go @@ -1,7 +1,11 @@ package mscluster import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -10,22 +14,22 @@ const nameNode = Name + "_node" // msClusterNode represents the MSCluster_Node WMI class // - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/cluswmi/mscluster-node type msClusterNode struct { - Name string + Name string `mi:"Name"` - BuildNumber uint - Characteristics uint - DetectedCloudPlatform uint - DynamicWeight uint - Flags uint - MajorVersion uint - MinorVersion uint - NeedsPreventQuorum uint - NodeDrainStatus uint - NodeHighestVersion uint - NodeLowestVersion uint - NodeWeight uint - State uint - StatusInformation uint + BuildNumber uint `mi:"BuildNumber"` + Characteristics uint `mi:"Characteristics"` + DetectedCloudPlatform uint `mi:"DetectedCloudPlatform"` + DynamicWeight uint `mi:"DynamicWeight"` + Flags uint `mi:"Flags"` + MajorVersion uint `mi:"MajorVersion"` + MinorVersion uint `mi:"MinorVersion"` + NeedsPreventQuorum uint `mi:"NeedsPreventQuorum"` + NodeDrainStatus uint `mi:"NodeDrainStatus"` + NodeHighestVersion uint `mi:"NodeHighestVersion"` + NodeLowestVersion uint `mi:"NodeLowestVersion"` + NodeWeight uint `mi:"NodeWeight"` + State uint `mi:"State"` + StatusInformation uint `mi:"StatusInformation"` } func (c *Collector) buildNode() { @@ -120,8 +124,8 @@ func (c *Collector) buildNode() { func (c *Collector) collectNode(ch chan<- prometheus.Metric) ([]string, error) { var dst []msClusterNode - if err := c.wmiClient.Query("SELECT * FROM MSCluster_Node", &dst, nil, "root/MSCluster"); err != nil { - return nil, err + if err := c.miSession.Query(&dst, mi.NamespaceRootMSCluster, utils.Must(mi.NewQuery("SELECT * FROM MSCluster_Node"))); err != nil { + return nil, fmt.Errorf("WMI query failed: %w", err) } nodeNames := make([]string, 0, len(dst)) diff --git a/internal/collector/mscluster/mscluster_resource.go b/internal/collector/mscluster/mscluster_resource.go index 990775c4..64a231a7 100644 --- a/internal/collector/mscluster/mscluster_resource.go +++ b/internal/collector/mscluster/mscluster_resource.go @@ -1,7 +1,11 @@ package mscluster import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -10,27 +14,27 @@ const nameResource = Name + "_resource" // msClusterResource represents the MSCluster_Resource WMI class // - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/cluswmi/mscluster-resource type msClusterResource struct { - Name string - Type string - OwnerGroup string - OwnerNode string + Name string `mi:"Name"` + Type string `mi:"Type"` + OwnerGroup string `mi:"OwnerGroup"` + OwnerNode string `mi:"OwnerNode"` - Characteristics uint - DeadlockTimeout uint - EmbeddedFailureAction uint - Flags uint - IsAlivePollInterval uint - LooksAlivePollInterval uint - MonitorProcessId uint - PendingTimeout uint - ResourceClass uint - RestartAction uint - RestartDelay uint - RestartPeriod uint - RestartThreshold uint - RetryPeriodOnFailure uint - State uint - Subclass uint + Characteristics uint `mi:"Characteristics"` + DeadlockTimeout uint `mi:"DeadlockTimeout"` + EmbeddedFailureAction uint `mi:"EmbeddedFailureAction"` + Flags uint `mi:"Flags"` + IsAlivePollInterval uint `mi:"IsAlivePollInterval"` + LooksAlivePollInterval uint `mi:"LooksAlivePollInterval"` + MonitorProcessId uint `mi:"MonitorProcessId"` + PendingTimeout uint `mi:"PendingTimeout"` + ResourceClass uint `mi:"ResourceClass"` + RestartAction uint `mi:"RestartAction"` + RestartDelay uint `mi:"RestartDelay"` + RestartPeriod uint `mi:"RestartPeriod"` + RestartThreshold uint `mi:"RestartThreshold"` + RetryPeriodOnFailure uint `mi:"RetryPeriodOnFailure"` + State uint `mi:"State"` + Subclass uint `mi:"Subclass"` } func (c *Collector) buildResource() { @@ -149,8 +153,8 @@ func (c *Collector) buildResource() { func (c *Collector) collectResource(ch chan<- prometheus.Metric, nodeNames []string) error { var dst []msClusterResource - if err := c.wmiClient.Query("SELECT * FROM MSCluster_Resource", &dst, nil, "root/MSCluster"); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootMSCluster, utils.Must(mi.NewQuery("SELECT * FROM MSCluster_Resource"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, v := range dst { diff --git a/internal/collector/mscluster/mscluster_resourcegroup.go b/internal/collector/mscluster/mscluster_resourcegroup.go index fb5df81b..94a60481 100644 --- a/internal/collector/mscluster/mscluster_resourcegroup.go +++ b/internal/collector/mscluster/mscluster_resourcegroup.go @@ -1,7 +1,11 @@ package mscluster import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -10,22 +14,22 @@ const nameResourceGroup = Name + "_resourcegroup" // msClusterResourceGroup represents the MSCluster_ResourceGroup WMI class // - https://docs.microsoft.com/en-us/previous-versions/windows/desktop/cluswmi/mscluster-resourcegroup type msClusterResourceGroup struct { - Name string + Name string `mi:"Name"` - AutoFailbackType uint - Characteristics uint - ColdStartSetting uint - DefaultOwner uint - FailbackWindowEnd int - FailbackWindowStart int - FailoverPeriod uint - FailoverThreshold uint - Flags uint - GroupType uint - OwnerNode string - Priority uint - ResiliencyPeriod uint - State uint + AutoFailbackType uint `mi:"AutoFailbackType"` + Characteristics uint `mi:"Characteristics"` + ColdStartSetting uint `mi:"ColdStartSetting"` + DefaultOwner uint `mi:"DefaultOwner"` + FailbackWindowEnd int `mi:"FailbackWindowEnd"` + FailbackWindowStart int `mi:"FailbackWindowStart"` + FailoverPeriod uint `mi:"FailoverPeriod"` + FailoverThreshold uint `mi:"FailoverThreshold"` + Flags uint `mi:"Flags"` + GroupType uint `mi:"GroupType"` + OwnerNode string `mi:"OwnerNode"` + Priority uint `mi:"Priority"` + ResiliencyPeriod uint `mi:"ResiliencyPeriod"` + State uint `mi:"State"` } func (c *Collector) buildResourceGroup() { @@ -126,8 +130,8 @@ func (c *Collector) buildResourceGroup() { func (c *Collector) collectResourceGroup(ch chan<- prometheus.Metric, nodeNames []string) error { var dst []msClusterResourceGroup - if err := c.wmiClient.Query("SELECT * FROM MSCluster_ResourceGroup", &dst, nil, "root/MSCluster"); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootMSCluster, utils.Must(mi.NewQuery("SELECT * FROM MSCluster_ResourceGroup"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, v := range dst { diff --git a/internal/collector/msmq/msmq.go b/internal/collector/msmq/msmq.go index b0b97ed0..b128c4ac 100644 --- a/internal/collector/msmq/msmq.go +++ b/internal/collector/msmq/msmq.go @@ -4,14 +4,15 @@ package msmq import ( "errors" + "fmt" "log/slog" "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "msmq" @@ -27,7 +28,7 @@ var ConfigDefaults = Config{ // A Collector is a Prometheus Collector for WMI Win32_PerfRawData_MSMQ_MSMQQueue metrics. type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session bytesInJournalQueue *prometheus.Desc bytesInQueue *prometheus.Desc @@ -75,14 +76,14 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, wmiClient *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error { logger = logger.With(slog.String("collector", Name)) - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + c.miSession = miSession if *c.config.QueryWhereClause == "" { logger.Warn("No where-clause specified for msmq collector. This will generate a very large number of metrics!") @@ -132,12 +133,12 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan } type msmqQueue struct { - Name string + Name string `mi:"Name"` - BytesInJournalQueue uint64 - BytesInQueue uint64 - MessagesInJournalQueue uint64 - MessagesInQueue uint64 + BytesInJournalQueue uint64 `mi:"BytesInJournalQueue"` + BytesInQueue uint64 `mi:"BytesInQueue"` + MessagesInJournalQueue uint64 `mi:"MessagesInJournalQueue"` + MessagesInQueue uint64 `mi:"MessagesInQueue"` } func (c *Collector) collect(ch chan<- prometheus.Metric) error { @@ -148,8 +149,13 @@ func (c *Collector) collect(ch chan<- prometheus.Metric) error { query += " WHERE " + *c.config.QueryWhereClause } - if err := c.wmiClient.Query(query, &dst); err != nil { - return err + queryExpression, err := mi.NewQuery(query) + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, queryExpression); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, msmq := range dst { diff --git a/internal/collector/mssql/mssql.go b/internal/collector/mssql/mssql.go index 271173f3..d040210b 100644 --- a/internal/collector/mssql/mssql.go +++ b/internal/collector/mssql/mssql.go @@ -13,10 +13,10 @@ import ( "time" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows/registry" ) @@ -508,7 +508,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { // Result must order, to prevent test failures. sort.Strings(c.config.CollectorsEnabled) diff --git a/internal/collector/net/net.go b/internal/collector/net/net.go index a3870187..80f73f77 100644 --- a/internal/collector/net/net.go +++ b/internal/collector/net/net.go @@ -13,12 +13,12 @@ import ( "unsafe" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" ) @@ -149,7 +149,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { if utils.PDHEnabled() { counters := []string{ BytesReceivedPerSec, diff --git a/internal/collector/netframework/netframework.go b/internal/collector/netframework/netframework.go index 530933e6..cae84034 100644 --- a/internal/collector/netframework/netframework.go +++ b/internal/collector/netframework/netframework.go @@ -9,9 +9,9 @@ import ( "slices" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "netframework" @@ -47,7 +47,7 @@ const ( // A Collector is a Prometheus Collector for WMI Win32_PerfRawData_NETFramework_NETCLRExceptions metrics. type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session // clrexceptions numberOfExceptionsThrown *prometheus.Desc @@ -143,12 +143,12 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + c.miSession = miSession if slices.Contains(c.config.CollectorsEnabled, collectorClrExceptions) { c.buildClrExceptions() diff --git a/internal/collector/netframework/netframework_clrexceptions.go b/internal/collector/netframework/netframework_clrexceptions.go index 4f98b3e4..059befca 100644 --- a/internal/collector/netframework/netframework_clrexceptions.go +++ b/internal/collector/netframework/netframework_clrexceptions.go @@ -3,7 +3,11 @@ package netframework import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -35,19 +39,19 @@ func (c *Collector) buildClrExceptions() { } type Win32_PerfRawData_NETFramework_NETCLRExceptions struct { - Name string + Name string `mi:"Name"` - NumberofExcepsThrown uint32 - NumberofExcepsThrownPersec uint32 - NumberofFiltersPersec uint32 - NumberofFinallysPersec uint32 - ThrowToCatchDepthPersec uint32 + NumberofExcepsThrown uint32 `mi:"NumberofExcepsThrown"` + NumberofExcepsThrownPersec uint32 `mi:"NumberofExcepsThrownPersec"` + NumberofFiltersPersec uint32 `mi:"NumberofFiltersPersec"` + NumberofFinallysPersec uint32 `mi:"NumberofFinallysPersec"` + ThrowToCatchDepthPersec uint32 `mi:"ThrowToCatchDepthPersec"` } func (c *Collector) collectClrExceptions(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NETFramework_NETCLRExceptions - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRExceptions", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NETFramework_NETCLRExceptions"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, process := range dst { diff --git a/internal/collector/netframework/netframework_clrinterop.go b/internal/collector/netframework/netframework_clrinterop.go index 0d499268..7fca0e26 100644 --- a/internal/collector/netframework/netframework_clrinterop.go +++ b/internal/collector/netframework/netframework_clrinterop.go @@ -3,7 +3,11 @@ package netframework import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -29,19 +33,19 @@ func (c *Collector) buildClrInterop() { } type Win32_PerfRawData_NETFramework_NETCLRInterop struct { - Name string + Name string `mi:"Name"` - NumberofCCWs uint32 - Numberofmarshalling uint32 - NumberofStubs uint32 - NumberofTLBexportsPersec uint32 - NumberofTLBimportsPersec uint32 + NumberofCCWs uint32 `mi:"NumberofCCWs"` + Numberofmarshalling uint32 `mi:"Numberofmarshalling"` + NumberofStubs uint32 `mi:"NumberofStubs"` + NumberofTLBexportsPersec uint32 `mi:"NumberofTLBexportsPersec"` + NumberofTLBimportsPersec uint32 `mi:"NumberofTLBimportsPersec"` } func (c *Collector) collectClrInterop(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NETFramework_NETCLRInterop - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRInterop", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NETFramework_NETCLRInterop"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, process := range dst { diff --git a/internal/collector/netframework/netframework_clrjit.go b/internal/collector/netframework/netframework_clrjit.go index d8438913..db12fa4a 100644 --- a/internal/collector/netframework/netframework_clrjit.go +++ b/internal/collector/netframework/netframework_clrjit.go @@ -3,7 +3,11 @@ package netframework import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -35,21 +39,21 @@ func (c *Collector) buildClrJIT() { } type Win32_PerfRawData_NETFramework_NETCLRJit struct { - Name string + Name string `mi:"Name"` - Frequency_PerfTime uint32 - ILBytesJittedPersec uint32 - NumberofILBytesJitted uint32 - NumberofMethodsJitted uint32 - PercentTimeinJit uint32 - StandardJitFailures uint32 - TotalNumberofILBytesJitted uint32 + Frequency_PerfTime uint32 `mi:"Frequency_PerfTime"` + ILBytesJittedPersec uint32 `mi:"ILBytesJittedPersec"` + NumberofILBytesJitted uint32 `mi:"NumberofILBytesJitted"` + NumberofMethodsJitted uint32 `mi:"NumberofMethodsJitted"` + PercentTimeinJit uint32 `mi:"PercentTimeinJit"` + StandardJitFailures uint32 `mi:"StandardJitFailures"` + TotalNumberofILBytesJitted uint32 `mi:"TotalNumberofILBytesJitted"` } func (c *Collector) collectClrJIT(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NETFramework_NETCLRJit - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRJit", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NETFramework_NETCLRJit"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, process := range dst { diff --git a/internal/collector/netframework/netframework_clrloading.go b/internal/collector/netframework/netframework_clrloading.go index 1dfa5c70..5f95fbe8 100644 --- a/internal/collector/netframework/netframework_clrloading.go +++ b/internal/collector/netframework/netframework_clrloading.go @@ -3,7 +3,11 @@ package netframework import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -65,30 +69,30 @@ func (c *Collector) buildClrLoading() { } type Win32_PerfRawData_NETFramework_NETCLRLoading struct { - Name string + Name string `mi:"Name"` - AssemblySearchLength uint32 - BytesinLoaderHeap uint64 - Currentappdomains uint32 - CurrentAssemblies uint32 - CurrentClassesLoaded uint32 - PercentTimeLoading uint64 - Rateofappdomains uint32 - Rateofappdomainsunloaded uint32 - RateofAssemblies uint32 - RateofClassesLoaded uint32 - RateofLoadFailures uint32 - TotalAppdomains uint32 - Totalappdomainsunloaded uint32 - TotalAssemblies uint32 - TotalClassesLoaded uint32 - TotalNumberofLoadFailures uint32 + AssemblySearchLength uint32 `mi:"AssemblySearchLength"` + BytesinLoaderHeap uint64 `mi:"BytesinLoaderHeap"` + Currentappdomains uint32 `mi:"Currentappdomains"` + CurrentAssemblies uint32 `mi:"CurrentAssemblies"` + CurrentClassesLoaded uint32 `mi:"CurrentClassesLoaded"` + PercentTimeLoading uint64 `mi:"PercentTimeLoading"` + Rateofappdomains uint32 `mi:"Rateofappdomains"` + Rateofappdomainsunloaded uint32 `mi:"Rateofappdomainsunloaded"` + RateofAssemblies uint32 `mi:"RateofAssemblies"` + RateofClassesLoaded uint32 `mi:"RateofClassesLoaded"` + RateofLoadFailures uint32 `mi:"RateofLoadFailures"` + TotalAppdomains uint32 `mi:"TotalAppdomains"` + Totalappdomainsunloaded uint32 `mi:"Totalappdomainsunloaded"` + TotalAssemblies uint32 `mi:"TotalAssemblies"` + TotalClassesLoaded uint32 `mi:"TotalClassesLoaded"` + TotalNumberofLoadFailures uint32 `mi:"TotalNumberofLoadFailures"` } func (c *Collector) collectClrLoading(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NETFramework_NETCLRLoading - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRLoading", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NETFramework_NETCLRLoading"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, process := range dst { diff --git a/internal/collector/netframework/netframework_clrlocksandthreads.go b/internal/collector/netframework/netframework_clrlocksandthreads.go index 812e67b7..6bcd5239 100644 --- a/internal/collector/netframework/netframework_clrlocksandthreads.go +++ b/internal/collector/netframework/netframework_clrlocksandthreads.go @@ -3,7 +3,11 @@ package netframework import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -53,24 +57,24 @@ func (c *Collector) buildClrLocksAndThreads() { } type Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads struct { - Name string + Name string `mi:"Name"` - ContentionRatePersec uint32 - CurrentQueueLength uint32 - NumberofcurrentlogicalThreads uint32 - NumberofcurrentphysicalThreads uint32 - Numberofcurrentrecognizedthreads uint32 - Numberoftotalrecognizedthreads uint32 - QueueLengthPeak uint32 - QueueLengthPersec uint32 - RateOfRecognizedThreadsPersec uint32 - TotalNumberofContentions uint32 + ContentionRatePersec uint32 `mi:"ContentionRatePersec"` + CurrentQueueLength uint32 `mi:"CurrentQueueLength"` + NumberofcurrentlogicalThreads uint32 `mi:"NumberofcurrentlogicalThreads"` + NumberofcurrentphysicalThreads uint32 `mi:"NumberofcurrentphysicalThreads"` + Numberofcurrentrecognizedthreads uint32 `mi:"Numberofcurrentrecognizedthreads"` + Numberoftotalrecognizedthreads uint32 `mi:"Numberoftotalrecognizedthreads"` + QueueLengthPeak uint32 `mi:"QueueLengthPeak"` + QueueLengthPersec uint32 `mi:"QueueLengthPersec"` + RateOfRecognizedThreadsPersec uint32 `mi:"RateOfRecognizedThreadsPersec"` + TotalNumberofContentions uint32 `mi:"TotalNumberofContentions"` } func (c *Collector) collectClrLocksAndThreads(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NETFramework_NETCLRLocksAndThreads"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, process := range dst { diff --git a/internal/collector/netframework/netframework_clrmemory.go b/internal/collector/netframework/netframework_clrmemory.go index f324f0af..afc394b8 100644 --- a/internal/collector/netframework/netframework_clrmemory.go +++ b/internal/collector/netframework/netframework_clrmemory.go @@ -3,7 +3,11 @@ package netframework import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -83,43 +87,43 @@ func (c *Collector) buildClrMemory() { } type Win32_PerfRawData_NETFramework_NETCLRMemory struct { - Name string + Name string `mi:"Name"` - AllocatedBytesPersec uint64 - FinalizationSurvivors uint64 - Frequency_PerfTime uint64 - Gen0heapsize uint64 - Gen0PromotedBytesPerSec uint64 - Gen1heapsize uint64 - Gen1PromotedBytesPerSec uint64 - Gen2heapsize uint64 - LargeObjectHeapsize uint64 - NumberBytesinallHeaps uint64 - NumberGCHandles uint64 - NumberGen0Collections uint64 - NumberGen1Collections uint64 - NumberGen2Collections uint64 - NumberInducedGC uint64 - NumberofPinnedObjects uint64 - NumberofSinkBlocksinuse uint64 - NumberTotalcommittedBytes uint64 - NumberTotalreservedBytes uint64 + AllocatedBytesPersec uint64 `mi:"AllocatedBytesPersec"` + FinalizationSurvivors uint64 `mi:"FinalizationSurvivors"` + Frequency_PerfTime uint64 `mi:"Frequency_PerfTime"` + Gen0heapsize uint64 `mi:"Gen0heapsize"` + Gen0PromotedBytesPerSec uint64 `mi:"Gen0PromotedBytesPersec"` + Gen1heapsize uint64 `mi:"Gen1heapsize"` + Gen1PromotedBytesPerSec uint64 `mi:"Gen1PromotedBytesPersec"` + Gen2heapsize uint64 `mi:"Gen2heapsize"` + LargeObjectHeapsize uint64 `mi:"LargeObjectHeapsize"` + NumberBytesinallHeaps uint64 `mi:"NumberBytesinallHeaps"` + NumberGCHandles uint64 `mi:"NumberGCHandles"` + NumberGen0Collections uint64 `mi:"NumberGen0Collections"` + NumberGen1Collections uint64 `mi:"NumberGen1Collections"` + NumberGen2Collections uint64 `mi:"NumberGen2Collections"` + NumberInducedGC uint64 `mi:"NumberInducedGC"` + NumberofPinnedObjects uint64 `mi:"NumberofPinnedObjects"` + NumberofSinkBlocksinuse uint64 `mi:"NumberofSinkBlocksinuse"` + NumberTotalcommittedBytes uint64 `mi:"NumberTotalcommittedBytes"` + NumberTotalreservedBytes uint64 `mi:"NumberTotalreservedBytes"` // PercentTimeinGC has countertype=PERF_RAW_FRACTION. // Formula: (100 * CounterValue) / BaseValue // By docs https://docs.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/scripting-articles/ms974615(v=msdn.10)#perf_raw_fraction - PercentTimeinGC uint32 + PercentTimeinGC uint32 `mi:"PercentTimeinGC"` // BaseValue is just a "magic" number used to make the calculation come out right. - PercentTimeinGC_base uint32 - ProcessID uint64 - PromotedFinalizationMemoryfromGen0 uint64 - PromotedMemoryfromGen0 uint64 - PromotedMemoryfromGen1 uint64 + PercentTimeinGC_base uint32 `mi:"PercentTimeinGC_base"` + ProcessID uint64 `mi:"ProcessID"` + PromotedFinalizationMemoryfromGen0 uint64 `mi:"PromotedFinalizationMemoryfromGen0"` + PromotedMemoryfromGen0 uint64 `mi:"PromotedMemoryfromGen0"` + PromotedMemoryfromGen1 uint64 `mi:"PromotedMemoryfromGen1"` } func (c *Collector) collectClrMemory(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NETFramework_NETCLRMemory - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRMemory", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NETFramework_NETCLRMemory"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, process := range dst { diff --git a/internal/collector/netframework/netframework_clrremoting.go b/internal/collector/netframework/netframework_clrremoting.go index a2fa7d89..a83b7388 100644 --- a/internal/collector/netframework/netframework_clrremoting.go +++ b/internal/collector/netframework/netframework_clrremoting.go @@ -3,7 +3,11 @@ package netframework import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -47,21 +51,21 @@ func (c *Collector) buildClrRemoting() { } type Win32_PerfRawData_NETFramework_NETCLRRemoting struct { - Name string + Name string `mi:"Name"` - Channels uint32 - ContextBoundClassesLoaded uint32 - ContextBoundObjectsAllocPersec uint32 - ContextProxies uint32 - Contexts uint32 - RemoteCallsPersec uint32 - TotalRemoteCalls uint32 + Channels uint32 `mi:"Channels"` + ContextBoundClassesLoaded uint32 `mi:"ContextBoundClassesLoaded"` + ContextBoundObjectsAllocPersec uint32 `mi:"ContextBoundObjectsAllocPersec"` + ContextProxies uint32 `mi:"ContextProxies"` + Contexts uint32 `mi:"Contexts"` + RemoteCallsPersec uint32 `mi:"RemoteCallsPersec"` + TotalRemoteCalls uint32 `mi:"TotalRemoteCalls"` } func (c *Collector) collectClrRemoting(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NETFramework_NETCLRRemoting - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRRemoting", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NETFramework_NETCLRRemoting"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, process := range dst { diff --git a/internal/collector/netframework/netframework_clrsecurity.go b/internal/collector/netframework/netframework_clrsecurity.go index 7e3dfdc3..ef30b2ab 100644 --- a/internal/collector/netframework/netframework_clrsecurity.go +++ b/internal/collector/netframework/netframework_clrsecurity.go @@ -3,7 +3,11 @@ package netframework import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" ) @@ -35,20 +39,20 @@ func (c *Collector) buildClrSecurity() { } type Win32_PerfRawData_NETFramework_NETCLRSecurity struct { - Name string + Name string `mi:"Name"` - Frequency_PerfTime uint32 - NumberLinkTimeChecks uint32 - PercentTimeinRTchecks uint32 - PercentTimeSigAuthenticating uint64 - StackWalkDepth uint32 - TotalRuntimeChecks uint32 + Frequency_PerfTime uint32 `mi:"Frequency_PerfTime"` + NumberLinkTimeChecks uint32 `mi:"NumberLinkTimeChecks"` + PercentTimeinRTchecks uint32 `mi:"PercentTimeinRTchecks"` + PercentTimeSigAuthenticating uint64 `mi:"PercentTimeSigAuthenticating"` + StackWalkDepth uint32 `mi:"StackWalkDepth"` + TotalRuntimeChecks uint32 `mi:"TotalRuntimeChecks"` } func (c *Collector) collectClrSecurity(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_NETFramework_NETCLRSecurity - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_NETFramework_NETCLRSecurity", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * Win32_PerfRawData_NETFramework_NETCLRSecurity"))); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, process := range dst { diff --git a/internal/collector/nps/nps.go b/internal/collector/nps/nps.go index 06cd9abb..86a5bece 100644 --- a/internal/collector/nps/nps.go +++ b/internal/collector/nps/nps.go @@ -6,9 +6,9 @@ import ( "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "nps" @@ -20,7 +20,10 @@ var ConfigDefaults = Config{} // Collector is a Prometheus Collector for WMI Win32_PerfRawData_IAS_NPSAuthenticationServer and Win32_PerfRawData_IAS_NPSAccountingServer metrics. type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session + + miQueryAuthenticationServer mi.Query + miQueryAccountingServer mi.Query accessAccepts *prometheus.Desc accessChallenges *prometheus.Desc @@ -78,12 +81,26 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + miQuery, err := mi.NewQuery("SELECT Name, AccessAccepts, AccessChallenges, AccessRejects, AccessRequests, AccessBadAuthenticators, AccessDroppedPackets, AccessInvalidRequests, AccessMalformedPackets, AccessPacketsReceived, AccessPacketsSent, AccessServerResetTime, AccessServerUpTime, AccessUnknownType FROM Win32_PerfRawData_IAS_NPSAuthenticationServer") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQueryAuthenticationServer = miQuery + + miQuery, err = mi.NewQuery("SELECT Name, AccountingRequests, AccountingResponses, AccountingBadAuthenticators, AccountingDroppedPackets, AccountingInvalidRequests, AccountingMalformedPackets, AccountingNoRecord, AccountingPacketsReceived, AccountingPacketsSent, AccountingServerResetTime, AccountingServerUpTime, AccountingUnknownType FROM Win32_PerfRawData_IAS_NPSAccountingServer") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQueryAccountingServer = miQuery + c.miSession = miSession + c.accessAccepts = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "access_accepts"), "(AccessAccepts)", @@ -261,46 +278,46 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan // Win32_PerfRawData_IAS_NPSAuthenticationServer docs: // at the moment there is no Microsoft documentation. type Win32_PerfRawData_IAS_NPSAuthenticationServer struct { - Name string + Name string `mi:"Name"` - AccessAccepts uint32 - AccessChallenges uint32 - AccessRejects uint32 - AccessRequests uint32 - AccessBadAuthenticators uint32 - AccessDroppedPackets uint32 - AccessInvalidRequests uint32 - AccessMalformedPackets uint32 - AccessPacketsReceived uint32 - AccessPacketsSent uint32 - AccessServerResetTime uint32 - AccessServerUpTime uint32 - AccessUnknownType uint32 + AccessAccepts uint32 `mi:"AccessAccepts"` + AccessChallenges uint32 `mi:"AccessChallenges"` + AccessRejects uint32 `mi:"AccessRejects"` + AccessRequests uint32 `mi:"AccessRequests"` + AccessBadAuthenticators uint32 `mi:"AccessBadAuthenticators"` + AccessDroppedPackets uint32 `mi:"AccessDroppedPackets"` + AccessInvalidRequests uint32 `mi:"AccessInvalidRequests"` + AccessMalformedPackets uint32 `mi:"AccessMalformedPackets"` + AccessPacketsReceived uint32 `mi:"AccessPacketsReceived"` + AccessPacketsSent uint32 `mi:"AccessPacketsSent"` + AccessServerResetTime uint32 `mi:"AccessServerResetTime"` + AccessServerUpTime uint32 `mi:"AccessServerUpTime"` + AccessUnknownType uint32 `mi:"AccessUnknownType"` } type Win32_PerfRawData_IAS_NPSAccountingServer struct { - Name string + Name string `mi:"Name"` - AccountingRequests uint32 - AccountingResponses uint32 - AccountingBadAuthenticators uint32 - AccountingDroppedPackets uint32 - AccountingInvalidRequests uint32 - AccountingMalformedPackets uint32 - AccountingNoRecord uint32 - AccountingPacketsReceived uint32 - AccountingPacketsSent uint32 - AccountingServerResetTime uint32 - AccountingServerUpTime uint32 - AccountingUnknownType uint32 + AccountingRequests uint32 `mi:"AccountingRequests"` + AccountingResponses uint32 `mi:"AccountingResponses"` + AccountingBadAuthenticators uint32 `mi:"AccountingBadAuthenticators"` + AccountingDroppedPackets uint32 `mi:"AccountingDroppedPackets"` + AccountingInvalidRequests uint32 `mi:"AccountingInvalidRequests"` + AccountingMalformedPackets uint32 `mi:"AccountingMalformedPackets"` + AccountingNoRecord uint32 `mi:"AccountingNoRecord"` + AccountingPacketsReceived uint32 `mi:"AccountingPacketsReceived"` + AccountingPacketsSent uint32 `mi:"AccountingPacketsSent"` + AccountingServerResetTime uint32 `mi:"AccountingServerResetTime"` + AccountingServerUpTime uint32 `mi:"AccountingServerUpTime"` + AccountingUnknownType uint32 `mi:"AccountingUnknownType"` } // CollectAccept sends the metric values for each metric // to the provided prometheus Metric channel. func (c *Collector) CollectAccept(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_IAS_NPSAuthenticationServer - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_IAS_NPSAuthenticationServer", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, c.miQueryAuthenticationServer); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } ch <- prometheus.MustNewConstMetric( @@ -386,8 +403,8 @@ func (c *Collector) CollectAccept(ch chan<- prometheus.Metric) error { func (c *Collector) CollectAccounting(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_IAS_NPSAccountingServer - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_IAS_NPSAccountingServer", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, c.miQueryAccountingServer); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } ch <- prometheus.MustNewConstMetric( diff --git a/internal/collector/os/os.go b/internal/collector/os/os.go index e0acaf0d..4920cecf 100644 --- a/internal/collector/os/os.go +++ b/internal/collector/os/os.go @@ -16,10 +16,10 @@ import ( "github.com/prometheus-community/windows_exporter/internal/headers/netapi32" "github.com/prometheus-community/windows_exporter/internal/headers/psapi" "github.com/prometheus-community/windows_exporter/internal/headers/sysinfoapi" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" ) @@ -109,9 +109,11 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { - logger.Warn("The os collect holds a number of deprecated metrics and will be removed mid 2025. " + - "See https://github.com/prometheus-community/windows_exporter/pull/1596 for more information.") +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { + logger.Warn("The os collect holds a number of deprecated metrics and will be removed mid 2025. "+ + "See https://github.com/prometheus-community/windows_exporter/pull/1596 for more information.", + slog.String("collector", Name), + ) workstationInfo, err := netapi32.GetWorkstationInfo() if err != nil { diff --git a/internal/collector/perfdata/perfdata.go b/internal/collector/perfdata/perfdata.go index b0e6b263..82023e81 100644 --- a/internal/collector/perfdata/perfdata.go +++ b/internal/collector/perfdata/perfdata.go @@ -11,11 +11,11 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "perfdata" @@ -92,7 +92,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { logger.Warn("The perfdata collector is in an experimental state! The configuration may change in future. Please report any issues.") for i, object := range c.config.Objects { diff --git a/internal/collector/physical_disk/physical_disk.go b/internal/collector/physical_disk/physical_disk.go index 7ff8b6d4..2f833007 100644 --- a/internal/collector/physical_disk/physical_disk.go +++ b/internal/collector/physical_disk/physical_disk.go @@ -9,11 +9,11 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "physical_disk" @@ -114,7 +114,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { c.requestsQueued = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "requests_queued"), "The number of requests queued to the disk (PhysicalDisk.CurrentDiskQueueLength)", diff --git a/internal/collector/printer/printer.go b/internal/collector/printer/printer.go index 1617c143..7ca9dd72 100644 --- a/internal/collector/printer/printer.go +++ b/internal/collector/printer/printer.go @@ -10,9 +10,9 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "printer" @@ -39,8 +39,10 @@ var ConfigDefaults = Config{ } type Collector struct { - config Config - wmiClient *wmi.Client + config Config + miSession *mi.Session + miQueryPrinterJobs mi.Query + miQueryPrinter mi.Query printerStatus *prometheus.Desc printerJobStatus *prometheus.Desc @@ -107,12 +109,25 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + miQuery, err := mi.NewQuery("SELECT Name, Default, PrinterStatus, JobCountSinceLastReset FROM win32_Printer") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQueryPrinter = miQuery + + miQuery, err = mi.NewQuery("SELECT Name, Status FROM win32_PrintJob") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQueryPrinterJobs = miQuery + c.miSession = miSession c.printerJobStatus = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "job_status"), @@ -143,42 +158,35 @@ func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) { } type wmiPrinter struct { - Name string - Default bool - PrinterStatus uint16 - JobCountSinceLastReset uint32 + Name string `mi:"Name"` + Default bool `mi:"Default"` + PrinterStatus uint16 `mi:"PrinterStatus"` + JobCountSinceLastReset uint32 `mi:"JobCountSinceLastReset"` } type wmiPrintJob struct { - Name string - Status string + Name string `mi:"Name"` + Status string `mi:"Status"` } -func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error { - logger = logger.With(slog.String("collector", Name)) - if err := c.collectPrinterStatus(ch); err != nil { - logger.Error("failed to collect printer status metrics", - slog.Any("err", err), - ) +func (c *Collector) Collect(_ *types.ScrapeContext, _ *slog.Logger, ch chan<- prometheus.Metric) error { + var errs []error - return err + if err := c.collectPrinterStatus(ch); err != nil { + errs = append(errs, fmt.Errorf("failed to collect printer status metrics: %w", err)) } if err := c.collectPrinterJobStatus(ch); err != nil { - logger.Error("failed to collect printer job status metrics", - slog.Any("err", err), - ) - - return err + errs = append(errs, fmt.Errorf("failed to collect printer job status metrics: %w", err)) } - return nil + return errors.Join(errs...) } func (c *Collector) collectPrinterStatus(ch chan<- prometheus.Metric) error { var printers []wmiPrinter - if err := c.wmiClient.Query("SELECT * FROM win32_Printer", &printers); err != nil { - return err + if err := c.miSession.Query(&printers, mi.NamespaceRootCIMv2, c.miQueryPrinter); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } for _, printer := range printers { @@ -215,8 +223,8 @@ func (c *Collector) collectPrinterStatus(ch chan<- prometheus.Metric) error { func (c *Collector) collectPrinterJobStatus(ch chan<- prometheus.Metric) error { var printJobs []wmiPrintJob - if err := c.wmiClient.Query("SELECT * FROM win32_PrintJob", &printJobs); err != nil { - return err + if err := c.miSession.Query(&printJobs, mi.NamespaceRootCIMv2, c.miQueryPrinterJobs); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } groupedPrintJobs := c.groupPrintJobs(printJobs) diff --git a/internal/collector/process/process.go b/internal/collector/process/process.go index 3d108939..6bb66069 100644 --- a/internal/collector/process/process.go +++ b/internal/collector/process/process.go @@ -12,13 +12,13 @@ import ( "unsafe" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" v2 "github.com/prometheus-community/windows_exporter/internal/perfdata/v2" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" ) @@ -38,7 +38,9 @@ var ConfigDefaults = Config{ type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session + + workerProcessMIQueryQuery mi.Query perfDataCollector perfdata.Collector @@ -139,14 +141,20 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, wmiClient *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error { logger = logger.With(slog.String("collector", Name)) - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + miQuery, err := mi.NewQuery("SELECT AppPoolName, ProcessId FROM WorkerProcess") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.workerProcessMIQueryQuery = miQuery + c.miSession = miSession if utils.PDHEnabled() { counters := []string{ @@ -302,8 +310,8 @@ func (c *Collector) Build(logger *slog.Logger, wmiClient *wmi.Client) error { } type WorkerProcess struct { - AppPoolName string - ProcessId uint64 + AppPoolName string `mi:"AppPoolName"` + ProcessId uint64 `mi:"ProcessId"` } func (c *Collector) Collect(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error { @@ -333,10 +341,8 @@ func (c *Collector) collect(ctx *types.ScrapeContext, logger *slog.Logger, ch ch var workerProcesses []WorkerProcess if c.config.EnableWorkerProcess { - if err := c.wmiClient.Query("SELECT * FROM WorkerProcess", &workerProcesses, nil, "root\\WebAdministration"); err != nil { - logger.Debug("Could not query WebAdministration namespace for IIS worker processes", - slog.Any("err", err), - ) + if err := c.miSession.Query(&workerProcesses, mi.NamespaceRootWebAdministration, c.workerProcessMIQueryQuery); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } } @@ -540,10 +546,8 @@ func (c *Collector) collectPDH(logger *slog.Logger, ch chan<- prometheus.Metric) var workerProcesses []WorkerProcess if c.config.EnableWorkerProcess { - if err := c.wmiClient.Query("SELECT * FROM WorkerProcess", &workerProcesses, nil, "root\\WebAdministration"); err != nil { - logger.Debug("Could not query WebAdministration namespace for IIS worker processes", - slog.Any("err", err), - ) + if err := c.miSession.Query(&workerProcesses, mi.NamespaceRootWebAdministration, c.workerProcessMIQueryQuery); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } } diff --git a/internal/collector/remote_fx/remote_fx.go b/internal/collector/remote_fx/remote_fx.go index 92921bca..d4f44545 100644 --- a/internal/collector/remote_fx/remote_fx.go +++ b/internal/collector/remote_fx/remote_fx.go @@ -7,11 +7,11 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "remote_fx" @@ -81,7 +81,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(*slog.Logger, *wmi.Client) error { +func (c *Collector) Build(*slog.Logger, *mi.Session) error { // net c.baseTCPRTT = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "net_base_tcp_rtt_seconds"), diff --git a/internal/collector/scheduled_task/scheduled_task.go b/internal/collector/scheduled_task/scheduled_task.go index 810ebcfa..5a8bae0f 100644 --- a/internal/collector/scheduled_task/scheduled_task.go +++ b/internal/collector/scheduled_task/scheduled_task.go @@ -13,9 +13,9 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "scheduled_task" @@ -148,7 +148,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { initErrCh := make(chan error) c.scheduledTasksReqCh = make(chan struct{}) c.scheduledTasksCh = make(chan *scheduledTaskResults) @@ -281,7 +281,7 @@ func (c *Collector) initializeScheduleService(initErrCh chan<- error) { if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil { var oleCode *ole.OleError - if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != wmi.S_FALSE { + if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != 0x00000001 { initErrCh <- err return diff --git a/internal/collector/service/service.go b/internal/collector/service/service.go index c15cb611..714b9fcd 100644 --- a/internal/collector/service/service.go +++ b/internal/collector/service/service.go @@ -11,9 +11,9 @@ import ( "unsafe" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" "golang.org/x/sys/windows/svc/mgr" ) @@ -106,7 +106,7 @@ func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) { return []string{}, nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { logger = logger.With(slog.String("collector", Name)) if c.config.ServiceInclude.String() == "^(?:.*)$" && c.config.ServiceExclude.String() == "^(?:)$" { diff --git a/internal/collector/smb/smb.go b/internal/collector/smb/smb.go index dd97598a..8a1d212c 100644 --- a/internal/collector/smb/smb.go +++ b/internal/collector/smb/smb.go @@ -7,10 +7,10 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "smb" @@ -56,7 +56,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { // desc creates a new prometheus description desc := func(metricName string, description string, labels ...string) *prometheus.Desc { return prometheus.NewDesc( diff --git a/internal/collector/smbclient/smbclient.go b/internal/collector/smbclient/smbclient.go index 2b579e40..ad5bde41 100644 --- a/internal/collector/smbclient/smbclient.go +++ b/internal/collector/smbclient/smbclient.go @@ -7,11 +7,11 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const ( @@ -79,7 +79,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { // desc creates a new prometheus description desc := func(metricName string, description string, labels []string) *prometheus.Desc { return prometheus.NewDesc( diff --git a/internal/collector/smtp/smtp.go b/internal/collector/smtp/smtp.go index 35977e09..0ddf3fa1 100644 --- a/internal/collector/smtp/smtp.go +++ b/internal/collector/smtp/smtp.go @@ -8,10 +8,10 @@ import ( "regexp" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "smtp" @@ -141,10 +141,10 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { - logger = logger.With(slog.String("collector", Name)) - - logger.Info("smtp collector is in an experimental state! Metrics for this collector have not been tested.") +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { + logger.Info("smtp collector is in an experimental state! Metrics for this collector have not been tested.", + slog.String("collector", Name), + ) c.badMailedMessagesBadPickupFileTotal = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "badmailed_messages_bad_pickup_file_total"), diff --git a/internal/collector/system/system.go b/internal/collector/system/system.go index 6465debb..4dd70cca 100644 --- a/internal/collector/system/system.go +++ b/internal/collector/system/system.go @@ -7,10 +7,10 @@ import ( "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "system" @@ -61,7 +61,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { c.contextSwitchesTotal = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "context_switches_total"), "Total number of context switches (WMI source: PerfOS_System.ContextSwitchesPersec)", diff --git a/internal/collector/tcp/tcp.go b/internal/collector/tcp/tcp.go index 24b8d435..ef96d3d1 100644 --- a/internal/collector/tcp/tcp.go +++ b/internal/collector/tcp/tcp.go @@ -10,11 +10,11 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/headers/iphlpapi" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" ) @@ -100,7 +100,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { counters := []string{ connectionFailures, connectionsActive, diff --git a/internal/collector/terminal_services/terminal_services.go b/internal/collector/terminal_services/terminal_services.go index 31b8d573..93a36bfd 100644 --- a/internal/collector/terminal_services/terminal_services.go +++ b/internal/collector/terminal_services/terminal_services.go @@ -11,10 +11,11 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/headers/wtsapi32" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/utils" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" ) @@ -31,9 +32,9 @@ type Win32_ServerFeature struct { ID uint32 } -func isConnectionBrokerServer(logger *slog.Logger, wmiClient *wmi.Client) bool { +func isConnectionBrokerServer(logger *slog.Logger, miSession *mi.Session) bool { var dst []Win32_ServerFeature - if err := wmiClient.Query("SELECT * FROM Win32_ServerFeature", &dst); err != nil { + if err := miSession.Query(&dst, mi.NamespaceRootCIMv2, utils.Must(mi.NewQuery("SELECT * FROM Win32_ServerFeature"))); err != nil { return false } @@ -112,10 +113,14 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, wmiClient *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") + } + logger = logger.With(slog.String("collector", Name)) - c.connectionBrokerEnabled = isConnectionBrokerServer(logger, wmiClient) + c.connectionBrokerEnabled = isConnectionBrokerServer(logger, miSession) c.sessionInfo = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "session_info"), diff --git a/internal/collector/textfile/textfile.go b/internal/collector/textfile/textfile.go index da5005d8..fce5ac4e 100644 --- a/internal/collector/textfile/textfile.go +++ b/internal/collector/textfile/textfile.go @@ -29,11 +29,11 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/dimchansky/utfbom" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" "github.com/prometheus/common/expfmt" - "github.com/yusufpapurcu/wmi" ) const Name = "textfile" @@ -104,7 +104,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { logger.Info("textfile Collector directories: "+strings.Join(c.config.TextFileDirectories, ","), slog.String("collector", Name), ) diff --git a/internal/collector/thermalzone/thermalzone.go b/internal/collector/thermalzone/thermalzone.go index 6828b163..fa5a7f5a 100644 --- a/internal/collector/thermalzone/thermalzone.go +++ b/internal/collector/thermalzone/thermalzone.go @@ -4,12 +4,13 @@ package thermalzone import ( "errors" + "fmt" "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "thermalzone" @@ -21,7 +22,8 @@ var ConfigDefaults = Config{} // A Collector is a Prometheus Collector for WMI Win32_PerfRawData_Counters_ThermalZoneInformation metrics. type Collector struct { config Config - wmiClient *wmi.Client + miSession *mi.Session + miQuery mi.Query percentPassiveLimit *prometheus.Desc temperature *prometheus.Desc @@ -56,12 +58,19 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + miQuery, err := mi.NewQuery("SELECT Name, HighPrecisionTemperature, PercentPassiveLimit, ThrottleReasons FROM Win32_PerfRawData_Counters_ThermalZoneInformation") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQuery = miQuery + c.miSession = miSession + c.temperature = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "temperature_celsius"), "(Temperature)", @@ -108,22 +117,20 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan // Win32_PerfRawData_Counters_ThermalZoneInformation docs: // https://wutils.com/wmi/root/cimv2/win32_perfrawdata_counters_thermalzoneinformation/ type Win32_PerfRawData_Counters_ThermalZoneInformation struct { - Name string - - HighPrecisionTemperature uint32 - PercentPassiveLimit uint32 - ThrottleReasons uint32 + Name string `mi:"Name"` + HighPrecisionTemperature uint32 `mi:"HighPrecisionTemperature"` + PercentPassiveLimit uint32 `mi:"PercentPassiveLimit"` + ThrottleReasons uint32 `mi:"ThrottleReasons"` } func (c *Collector) collect(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_Counters_ThermalZoneInformation - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_Counters_ThermalZoneInformation", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, c.miQuery); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } - // ThermalZone collector has been known to 'successfully' return an empty result. if len(dst) == 0 { - return errors.New("empty results set for collector") + return errors.New("WMI query returned empty result set") } for _, info := range dst { diff --git a/internal/collector/time/time.go b/internal/collector/time/time.go index 7b0b8487..c955bbc8 100644 --- a/internal/collector/time/time.go +++ b/internal/collector/time/time.go @@ -9,10 +9,10 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/headers/kernel32" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" "golang.org/x/sys/windows" ) @@ -64,7 +64,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error { c.currentTime = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "current_timestamp_seconds"), "OperatingSystem.LocalDateTime", diff --git a/internal/collector/update/update.go b/internal/collector/update/update.go index f32591c0..969c050c 100644 --- a/internal/collector/update/update.go +++ b/internal/collector/update/update.go @@ -15,9 +15,9 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/go-ole/go-ole" "github.com/go-ole/go-ole/oleutil" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "update" @@ -80,7 +80,7 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error { +func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { logger = logger.With(slog.String("collector", Name)) logger.Info("update collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.") @@ -147,7 +147,7 @@ func (c *Collector) scheduleUpdateStatus(logger *slog.Logger, initErrCh chan<- e if err := ole.CoInitializeEx(0, ole.COINIT_MULTITHREADED); err != nil { var oleCode *ole.OleError - if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != wmi.S_FALSE { + if errors.As(err, &oleCode) && oleCode.Code() != ole.S_OK && oleCode.Code() != 0x00000001 { initErrCh <- fmt.Errorf("CoInitializeEx: %w", err) return diff --git a/internal/collector/vmware/vmware.go b/internal/collector/vmware/vmware.go index ba5d0ccf..812fe8ec 100644 --- a/internal/collector/vmware/vmware.go +++ b/internal/collector/vmware/vmware.go @@ -4,13 +4,14 @@ package vmware import ( "errors" + "fmt" "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) const Name = "vmware" @@ -21,8 +22,10 @@ var ConfigDefaults = Config{} // A Collector is a Prometheus Collector for WMI Win32_PerfRawData_vmGuestLib_VMem/Win32_PerfRawData_vmGuestLib_VCPU metrics. type Collector struct { - config Config - wmiClient *wmi.Client + config Config + miSession *mi.Session + miQueryCPU mi.Query + miQueryMem mi.Query memActive *prometheus.Desc memBallooned *prometheus.Desc @@ -74,12 +77,25 @@ func (c *Collector) Close(_ *slog.Logger) error { return nil } -func (c *Collector) Build(_ *slog.Logger, wmiClient *wmi.Client) error { - if wmiClient == nil || wmiClient.SWbemServicesClient == nil { - return errors.New("wmiClient or SWbemServicesClient is nil") +func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error { + if miSession == nil { + return errors.New("miSession is nil") } - c.wmiClient = wmiClient + miQuery, err := mi.NewQuery("SELECT * FROM Win32_PerfRawData_vmGuestLib_VCPU") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQueryCPU = miQuery + + miQuery, err = mi.NewQuery("SELECT * FROM Win32_PerfRawData_vmGuestLib_VMem") + if err != nil { + return fmt.Errorf("failed to create WMI query: %w", err) + } + + c.miQueryMem = miQuery + c.miSession = miSession c.memActive = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "mem_active_bytes"), @@ -224,34 +240,34 @@ func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan } type Win32_PerfRawData_vmGuestLib_VMem struct { - MemActiveMB uint64 - MemBalloonedMB uint64 - MemLimitMB uint64 - MemMappedMB uint64 - MemOverheadMB uint64 - MemReservationMB uint64 - MemSharedMB uint64 - MemSharedSavedMB uint64 - MemShares uint64 - MemSwappedMB uint64 - MemTargetSizeMB uint64 - MemUsedMB uint64 + MemActiveMB uint64 `mi:"MemActiveMB"` + MemBalloonedMB uint64 `mi:"MemBalloonedMB"` + MemLimitMB uint64 `mi:"MemLimitMB"` + MemMappedMB uint64 `mi:"MemMappedMB"` + MemOverheadMB uint64 `mi:"MemOverheadMB"` + MemReservationMB uint64 `mi:"MemReservationMB"` + MemSharedMB uint64 `mi:"MemSharedMB"` + MemSharedSavedMB uint64 `mi:"MemSharedSavedMB"` + MemShares uint64 `mi:"MemShares"` + MemSwappedMB uint64 `mi:"MemSwappedMB"` + MemTargetSizeMB uint64 `mi:"MemTargetSizeMB"` + MemUsedMB uint64 `mi:"MemUsedMB"` } type Win32_PerfRawData_vmGuestLib_VCPU struct { - CpuLimitMHz uint64 - CpuReservationMHz uint64 - CpuShares uint64 - CpuStolenMs uint64 - CpuTimePercents uint64 - EffectiveVMSpeedMHz uint64 - HostProcessorSpeedMHz uint64 + CpuLimitMHz uint64 `mi:"CpuLimitMHz"` + CpuReservationMHz uint64 `mi:"CpuReservationMHz"` + CpuShares uint64 `mi:"CpuShares"` + CpuStolenMs uint64 `mi:"CpuStolenMs"` + CpuTimePercents uint64 `mi:"CpuTimePercents"` + EffectiveVMSpeedMHz uint64 `mi:"EffectiveVMSpeedMHz"` + HostProcessorSpeedMHz uint64 `mi:"HostProcessorSpeedMHz"` } func (c *Collector) collectMem(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_vmGuestLib_VMem - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_vmGuestLib_VMem", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, c.miQueryMem); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } if len(dst) == 0 { @@ -333,14 +349,15 @@ func (c *Collector) collectMem(ch chan<- prometheus.Metric) error { return nil } +// mbToBytes moved to utils package func mbToBytes(mb uint64) float64 { return float64(mb * 1024 * 1024) } func (c *Collector) collectCpu(ch chan<- prometheus.Metric) error { var dst []Win32_PerfRawData_vmGuestLib_VCPU - if err := c.wmiClient.Query("SELECT * FROM Win32_PerfRawData_vmGuestLib_VCPU", &dst); err != nil { - return err + if err := c.miSession.Query(&dst, mi.NamespaceRootCIMv2, c.miQueryCPU); err != nil { + return fmt.Errorf("WMI query failed: %w", err) } if len(dst) == 0 { diff --git a/internal/httphandler/httphandler.go b/internal/httphandler/httphandler.go index 8efa3320..d587d05c 100644 --- a/internal/httphandler/httphandler.go +++ b/internal/httphandler/httphandler.go @@ -129,7 +129,7 @@ func (c *MetricsHTTPHandler) handlerFactory(logger *slog.Logger, scrapeTimeout t metricCollectors = &collector.MetricCollectors{ Collectors: filteredCollectors, - WMIClient: c.metricCollectors.WMIClient, + MISession: c.metricCollectors.MISession, PerfCounterQuery: c.metricCollectors.PerfCounterQuery, } } diff --git a/internal/mi/application.go b/internal/mi/application.go new file mode 100644 index 00000000..851241ab --- /dev/null +++ b/internal/mi/application.go @@ -0,0 +1,283 @@ +//go:build windows + +package mi + +import ( + "errors" + "fmt" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + applicationID = "windows_exporter" + + LocaleEnglish = "en-us" +) + +var ( + // DestinationOptionsTimeout is the key for the timeout option. + // + // https://github.com/microsoft/win32metadata/blob/527806d20d83d3abd43d16cd3fa8795d8deba343/generation/WinSDK/RecompiledIdlHeaders/um/mi.h#L7830 + DestinationOptionsTimeout = UTF16PtrFromString[*uint16]("__MI_DESTINATIONOPTIONS_TIMEOUT") + + // DestinationOptionsUILocale is the key for the UI locale option. + // + // https://github.com/microsoft/win32metadata/blob/527806d20d83d3abd43d16cd3fa8795d8deba343/generation/WinSDK/RecompiledIdlHeaders/um/mi.h#L8248 + DestinationOptionsUILocale = UTF16PtrFromString[*uint16]("__MI_DESTINATIONOPTIONS_UI_LOCALE") +) + +var ( + modMi = windows.NewLazySystemDLL("mi.dll") + + procMIApplicationInitialize = modMi.NewProc("MI_Application_InitializeV1") +) + +// Application represents the MI application. +// https://learn.microsoft.com/de-de/windows/win32/api/mi/ns-mi-mi_application +type Application struct { + reserved1 uint64 + reserved2 uintptr + ft *ApplicationFT +} + +// ApplicationFT represents the function table of the MI application. +// https://learn.microsoft.com/de-de/windows/win32/api/mi/ns-mi-mi_applicationft +type ApplicationFT struct { + Close uintptr + NewSession uintptr + NewHostedProvider uintptr + NewInstance uintptr + NewDestinationOptions uintptr + NewOperationOptions uintptr + NewSubscriptionDeliveryOptions uintptr + NewSerializer uintptr + NewDeserializer uintptr + NewInstanceFromClass uintptr + NewClass uintptr +} + +type DestinationOptions struct { + reserved1 uint64 + reserved2 uintptr + ft *DestinationOptionsFT +} + +type DestinationOptionsFT struct { + Delete uintptr + SetString uintptr + SetNumber uintptr + AddCredentials uintptr + GetString uintptr + GetNumber uintptr + GetOptionCount uintptr + GetOptionAt uintptr + GetOption uintptr + GetCredentialsCount uintptr + GetCredentialsAt uintptr + GetCredentialsPasswordAt uintptr + Clone uintptr + SetInterval uintptr + GetInterval uintptr +} + +// Application_Initialize initializes the MI [Application]. +// It is recommended to have only one Application per process. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_initializev1 +func Application_Initialize() (*Application, error) { + application := &Application{} + + applicationId, err := windows.UTF16PtrFromString(applicationID) + if err != nil { + return nil, err + } + + r0, _, err := procMIApplicationInitialize.Call( + 0, + uintptr(unsafe.Pointer(applicationId)), + 0, + uintptr(unsafe.Pointer(application)), + ) + + if !errors.Is(err, windows.NOERROR) { + return nil, fmt.Errorf("syscall returned: %w", err) + } + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return nil, result + } + + return application, nil +} + +// Close deinitializes the management infrastructure client API that was initialized through a call to Application_Initialize. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_close +func (application *Application) Close() error { + if application == nil || application.ft == nil { + return ErrNotInitialized + } + + r0, _, _ := syscall.SyscallN(application.ft.Close, uintptr(unsafe.Pointer(application))) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} + +// NewSession creates a session used to share connections for a set of operations to a single destination. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_newsession +func (application *Application) NewSession(options *DestinationOptions) (*Session, error) { + if application == nil || application.ft == nil { + return nil, ErrNotInitialized + } + + session := &Session{} + + r0, _, _ := syscall.SyscallN( + application.ft.NewSession, + uintptr(unsafe.Pointer(application)), + 0, + 0, + uintptr(unsafe.Pointer(options)), + 0, + 0, + uintptr(unsafe.Pointer(session)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return nil, result + } + + defaultOperationOptions, err := application.NewOperationOptions() + if err != nil { + return nil, fmt.Errorf("failed to create default operation options: %w", err) + } + + if err = defaultOperationOptions.SetTimeout(5 * time.Second); err != nil { + return nil, fmt.Errorf("failed to set timeout: %w", err) + } + + session.defaultOperationOptions = defaultOperationOptions + + return session, nil +} + +// NewOperationOptions creates an OperationOptions object that can be used with the operation functions on the Session object. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_newoperationoptions +func (application *Application) NewOperationOptions() (*OperationOptions, error) { + if application == nil || application.ft == nil { + return nil, ErrNotInitialized + } + + operationOptions := &OperationOptions{} + mustUnderstand := True + + r0, _, _ := syscall.SyscallN( + application.ft.NewOperationOptions, + uintptr(unsafe.Pointer(application)), + uintptr(mustUnderstand), + uintptr(unsafe.Pointer(operationOptions)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return nil, result + } + + return operationOptions, nil +} + +// NewDestinationOptions creates an DestinationOptions object that can be used with the Application.NewSession function. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_application_newdestinationoptions +func (application *Application) NewDestinationOptions() (*DestinationOptions, error) { + if application == nil || application.ft == nil { + return nil, ErrNotInitialized + } + + operationOptions := &DestinationOptions{} + + r0, _, _ := syscall.SyscallN( + application.ft.NewDestinationOptions, + uintptr(unsafe.Pointer(application)), + uintptr(unsafe.Pointer(operationOptions)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return nil, result + } + + return operationOptions, nil +} + +// SetTimeout sets the timeout for the destination options. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_destinationoptions_settimeout +func (do *DestinationOptions) SetTimeout(timeout time.Duration) error { + if do == nil || do.ft == nil { + return ErrNotInitialized + } + + r0, _, _ := syscall.SyscallN( + do.ft.SetInterval, + uintptr(unsafe.Pointer(do)), + uintptr(unsafe.Pointer(DestinationOptionsTimeout)), + uintptr(unsafe.Pointer(NewInterval(timeout))), + 0, + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} + +// SetLocale sets the locale for the destination options. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_destinationoptions_setuilocale +func (do *DestinationOptions) SetLocale(locale string) error { + if do == nil || do.ft == nil { + return ErrNotInitialized + } + + localeUTF16, err := windows.UTF16PtrFromString(locale) + if err != nil { + return fmt.Errorf("failed to convert locale: %w", err) + } + + r0, _, _ := syscall.SyscallN( + do.ft.SetString, + uintptr(unsafe.Pointer(do)), + uintptr(unsafe.Pointer(DestinationOptionsUILocale)), + uintptr(unsafe.Pointer(localeUTF16)), + 0, + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} + +func (do *DestinationOptions) Delete() error { + r0, _, _ := syscall.SyscallN( + do.ft.Delete, + uintptr(unsafe.Pointer(do)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} diff --git a/internal/mi/callbacks.go b/internal/mi/callbacks.go new file mode 100644 index 00000000..13bcf816 --- /dev/null +++ b/internal/mi/callbacks.go @@ -0,0 +1,154 @@ +//go:build windows + +package mi + +import ( + "errors" + "fmt" + "reflect" + "sync" + "unsafe" + + "golang.org/x/sys/windows" +) + +// We have to registry a global callback function, since the amount of callbacks is limited. +var operationUnmarshalCallbacksInstanceResult = sync.OnceValue[uintptr](func() uintptr { + return windows.NewCallback(func( + operation *Operation, + callbacks *OperationUnmarshalCallbacks, + instance *Instance, + moreResults Boolean, + instanceResult ResultError, + errorMessageUTF16 *uint16, + errorDetails *Instance, + _ uintptr, + ) uintptr { + if moreResults == False { + defer operation.Close() + } + + return callbacks.InstanceResult(operation, instance, moreResults, instanceResult, errorMessageUTF16, errorDetails) + }) +}) + +type OperationUnmarshalCallbacks struct { + dst any + dv reflect.Value + errCh chan<- error + + elemType reflect.Type + elemValue reflect.Value +} + +func NewUnmarshalOperationsCallbacks(dst any, errCh chan<- error) (*OperationCallbacks[OperationUnmarshalCallbacks], error) { + dv := reflect.ValueOf(dst) + if dv.Kind() != reflect.Ptr || dv.IsNil() { + return nil, ErrInvalidEntityType + } + + dv = dv.Elem() + + elemType := dv.Type().Elem() + elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem() + + if dv.Kind() != reflect.Slice || elemType.Kind() != reflect.Struct { + return nil, ErrInvalidEntityType + } + + dv.Set(reflect.MakeSlice(dv.Type(), 0, 0)) + + return &OperationCallbacks[OperationUnmarshalCallbacks]{ + CallbackContext: &OperationUnmarshalCallbacks{ + errCh: errCh, + dst: dst, + dv: dv, + elemType: elemType, + elemValue: elemValue, + }, + InstanceResult: operationUnmarshalCallbacksInstanceResult(), + }, nil +} + +func (o *OperationUnmarshalCallbacks) InstanceResult( + _ *Operation, + instance *Instance, + moreResults Boolean, + instanceResult ResultError, + errorMessageUTF16 *uint16, + _ *Instance, +) uintptr { + defer func() { + if moreResults == False { + close(o.errCh) + } + }() + + if !errors.Is(instanceResult, MI_RESULT_OK) { + o.errCh <- fmt.Errorf("%w: %s", instanceResult, windows.UTF16PtrToString(errorMessageUTF16)) + + return 0 + } + + if instance == nil { + return 0 + } + + counter, err := instance.GetElementCount() + if err != nil { + o.errCh <- fmt.Errorf("failed to get element count: %w", err) + + return 0 + } + + if counter == 0 { + return 0 + } + + for i := range o.elemType.NumField() { + field := o.elemValue.Field(i) + + // Check if the field has an `mi` tag + miTag := o.elemType.Field(i).Tag.Get("mi") + if miTag == "" { + continue + } + + element, err := instance.GetElement(miTag) + if err != nil { + o.errCh <- fmt.Errorf("failed to get element: %w", err) + + return 0 + } + + switch element.valueType { + case ValueTypeBOOLEAN: + field.SetBool(element.value == 1) + case ValueTypeUINT8, ValueTypeUINT16, ValueTypeUINT32, ValueTypeUINT64: + field.SetUint(uint64(element.value)) + case ValueTypeSINT8, ValueTypeSINT16, ValueTypeSINT32, ValueTypeSINT64: + field.SetInt(int64(element.value)) + case ValueTypeSTRING: + if element.value == 0 { + o.errCh <- fmt.Errorf("%s: invalid pointer: value is nil", miTag) + + return 0 + } + + // Convert the UTF-16 string to a Go string + stringValue := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(element.value))) + + field.SetString(stringValue) + case ValueTypeREAL32, ValueTypeREAL64: + field.SetFloat(float64(element.value)) + default: + o.errCh <- fmt.Errorf("unsupported value type: %d", element.valueType) + + return 0 + } + } + + o.dv.Set(reflect.Append(o.dv, o.elemValue)) + + return 0 +} diff --git a/internal/mi/doc.go b/internal/mi/doc.go new file mode 100644 index 00000000..49ac0ca7 --- /dev/null +++ b/internal/mi/doc.go @@ -0,0 +1,7 @@ +//go:build windows + +// mi is a package that provides a Go API for Windows Management Infrastructure (MI) functions. +// It requires Windows Management Framework 3.0 or later. +// +// https://learn.microsoft.com/de-de/previous-versions/windows/desktop/wmi_v2/why-use-mi- +package mi diff --git a/internal/mi/errors.go b/internal/mi/errors.go new file mode 100644 index 00000000..c6abbcd7 --- /dev/null +++ b/internal/mi/errors.go @@ -0,0 +1,10 @@ +//go:build windows + +package mi + +import "errors" + +var ( + ErrNotInitialized = errors.New("not initialized") + ErrInvalidEntityType = errors.New("invalid entity type") +) diff --git a/internal/mi/instance.go b/internal/mi/instance.go new file mode 100644 index 00000000..90eebe49 --- /dev/null +++ b/internal/mi/instance.go @@ -0,0 +1,179 @@ +//go:build windows + +package mi + +import ( + "errors" + "fmt" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +type Instance struct { + ft *InstanceFT + classDecl *ClassDecl + serverName *uint16 + nameSpace *uint16 + _ [4]uintptr +} + +type InstanceFT struct { + Clone uintptr + Destruct uintptr + Delete uintptr + IsA uintptr + GetClassName uintptr + SetNameSpace uintptr + GetNameSpace uintptr + GetElementCount uintptr + AddElement uintptr + SetElement uintptr + SetElementAt uintptr + GetElement uintptr + GetElementAt uintptr + ClearElement uintptr + ClearElementAt uintptr + GetServerName uintptr + SetServerName uintptr + GetClass uintptr +} + +type ClassDecl struct { + Flags uint32 + Code uint32 + Name *uint16 + Mqualifiers uintptr + NumQualifiers uint32 + Mproperties uintptr + NumProperties uint32 + Size uint32 + SuperClass *uint16 + SuperClassDecl uintptr + Methods uintptr + NumMethods uint32 + + Schema uintptr + ProviderFT uintptr + OwningClass uintptr +} + +func (instance *Instance) Delete() error { + if instance == nil || instance.ft == nil { + return ErrNotInitialized + } + + r0, _, _ := syscall.SyscallN(instance.ft.Delete, uintptr(unsafe.Pointer(instance))) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} + +func (instance *Instance) GetElement(elementName string) (*Element, error) { + if instance == nil || instance.ft == nil { + return nil, ErrNotInitialized + } + + elementNameUTF16, err := windows.UTF16PtrFromString(elementName) + if err != nil { + return nil, fmt.Errorf("failed to convert element name %s to UTF-16: %w", elementName, err) + } + + var ( + value uintptr + valueType ValueType + ) + + r0, _, _ := syscall.SyscallN( + instance.ft.GetElement, + uintptr(unsafe.Pointer(instance)), + uintptr(unsafe.Pointer(elementNameUTF16)), + uintptr(unsafe.Pointer(&value)), + uintptr(unsafe.Pointer(&valueType)), + 0, + 0, + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return nil, result + } + + return &Element{ + value: value, + valueType: valueType, + }, nil +} + +func (instance *Instance) GetElementCount() (uint32, error) { + if instance == nil || instance.ft == nil { + return 0, ErrNotInitialized + } + + var count uint32 + + r0, _, _ := syscall.SyscallN( + instance.ft.GetElementCount, + uintptr(unsafe.Pointer(instance)), + uintptr(unsafe.Pointer(&count)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return 0, result + } + + return count, nil +} + +func (instance *Instance) GetClassName() (string, error) { + if instance == nil || instance.ft == nil { + return "", ErrNotInitialized + } + + var classNameUTF16 *uint16 + + r0, _, _ := syscall.SyscallN( + instance.ft.GetClassName, + uintptr(unsafe.Pointer(instance)), + uintptr(unsafe.Pointer(&classNameUTF16)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return "", result + } + + if classNameUTF16 == nil { + return "", errors.New("class name is nil") + } + + return windows.UTF16PtrToString(classNameUTF16), nil +} + +func Instance_Print(instance *Instance) (string, error) { + elementMap := map[string]any{} + + properties := instance.classDecl.Properties() + + count, err := instance.GetElementCount() + if err != nil { + return "", err + } + + if count == 0 { + return "", nil + } + + for _, property := range properties { + name := windows.UTF16PtrToString(property.Name) + + element, _ := instance.GetElement(name) + value, _ := element.GetValue() + + elementMap[windows.UTF16PtrToString(property.Name)] = value + } + + return fmt.Sprintf("%v", elementMap), nil +} diff --git a/internal/mi/mi_bench_test.go b/internal/mi/mi_bench_test.go new file mode 100644 index 00000000..c6f1ea58 --- /dev/null +++ b/internal/mi/mi_bench_test.go @@ -0,0 +1,43 @@ +//go:build windows + +package mi_test + +import ( + "testing" + + "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/stretchr/testify/require" +) + +func Benchmark_MI_Query_Unmarshal(b *testing.B) { + application, err := mi.Application_Initialize() + require.NoError(b, err) + require.NotEmpty(b, application) + + session, err := application.NewSession(nil) + require.NoError(b, err) + require.NotEmpty(b, session) + + b.ResetTimer() + + var processes []win32Process + + query, err := mi.NewQuery("SELECT Name FROM Win32_Process WHERE Handle = 0 OR Handle = 4") + require.NoError(b, err) + + for i := 0; i < b.N; i++ { + err := session.QueryUnmarshal(&processes, mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, query) + require.NoError(b, err) + require.Equal(b, []win32Process{{Name: "System Idle Process"}, {Name: "System"}}, processes) + } + + b.StopTimer() + + err = session.Close() + require.NoError(b, err) + + err = application.Close() + require.NoError(b, err) + + b.ReportAllocs() +} diff --git a/internal/mi/mi_test.go b/internal/mi/mi_test.go new file mode 100644 index 00000000..d457753b --- /dev/null +++ b/internal/mi/mi_test.go @@ -0,0 +1,294 @@ +//go:build windows + +package mi_test + +import ( + "testing" + "time" + + "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/testutils" + "github.com/stretchr/testify/require" + "golang.org/x/sys/windows" +) + +type win32Process struct { + Name string `mi:"Name"` +} + +type wmiPrinter struct { + Name string `mi:"Name"` + Default bool `mi:"Default"` + PrinterStatus uint16 `mi:"PrinterStatus"` + JobCountSinceLastReset uint32 `mi:"JobCountSinceLastReset"` +} + +type wmiPrintJob struct { + Name string `mi:"Name"` + Status string `mi:"Status"` +} + +func Test_MI_Application_Initialize(t *testing.T) { + application, err := mi.Application_Initialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + err = application.Close() + require.NoError(t, err) +} + +func Test_MI_Application_TestConnection(t *testing.T) { + application, err := mi.Application_Initialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + destinationOptions, err := application.NewDestinationOptions() + require.NoError(t, err) + require.NotEmpty(t, destinationOptions) + + err = destinationOptions.SetTimeout(1 * time.Second) + require.NoError(t, err) + + err = destinationOptions.SetLocale(mi.LocaleEnglish) + require.NoError(t, err) + + session, err := application.NewSession(destinationOptions) + require.NoError(t, err) + require.NotEmpty(t, session) + + err = session.TestConnection() + require.NoError(t, err) + require.NotEmpty(t, session) + + err = session.Close() + require.NoError(t, err) + + err = application.Close() + require.NoError(t, err) +} + +func Test_MI_Query(t *testing.T) { + application, err := mi.Application_Initialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + destinationOptions, err := application.NewDestinationOptions() + require.NoError(t, err) + require.NotEmpty(t, destinationOptions) + + err = destinationOptions.SetTimeout(1 * time.Second) + require.NoError(t, err) + + err = destinationOptions.SetLocale(mi.LocaleEnglish) + require.NoError(t, err) + + session, err := application.NewSession(destinationOptions) + require.NoError(t, err) + require.NotEmpty(t, session) + + operation, err := session.QueryInstances(mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, "select Name from win32_process where handle = 0") + + require.NoError(t, err) + require.NotEmpty(t, operation) + + instance, moreResults, err := operation.GetInstance() + require.NoError(t, err) + require.NotEmpty(t, instance) + + count, err := instance.GetElementCount() + require.NoError(t, err) + require.NotZero(t, count) + + element, err := instance.GetElement("Name") + require.NoError(t, err) + require.NotEmpty(t, element) + + value, err := element.GetValue() + require.NoError(t, err) + require.Equal(t, "System Idle Process", value) + require.NotEmpty(t, value) + + require.False(t, moreResults) + + err = operation.Close() + require.NoError(t, err) + + err = session.Close() + require.NoError(t, err) + + err = application.Close() + require.NoError(t, err) +} + +func Test_MI_QueryUnmarshal(t *testing.T) { + application, err := mi.Application_Initialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + destinationOptions, err := application.NewDestinationOptions() + require.NoError(t, err) + require.NotEmpty(t, destinationOptions) + + err = destinationOptions.SetTimeout(1 * time.Second) + require.NoError(t, err) + + err = destinationOptions.SetLocale(mi.LocaleEnglish) + require.NoError(t, err) + + session, err := application.NewSession(destinationOptions) + require.NoError(t, err) + require.NotEmpty(t, session) + + var processes []win32Process + + queryProcess, err := mi.NewQuery("select Name from win32_process where handle = 0") + require.NoError(t, err) + + err = session.QueryUnmarshal(&processes, mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, queryProcess) + require.NoError(t, err) + require.Equal(t, []win32Process{{Name: "System Idle Process"}}, processes) + + err = session.Close() + require.NoError(t, err) + + err = application.Close() + require.NoError(t, err) +} + +func Test_MI_EmptyQuery(t *testing.T) { + application, err := mi.Application_Initialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + destinationOptions, err := application.NewDestinationOptions() + require.NoError(t, err) + require.NotEmpty(t, destinationOptions) + + err = destinationOptions.SetTimeout(1 * time.Second) + require.NoError(t, err) + + err = destinationOptions.SetLocale(mi.LocaleEnglish) + require.NoError(t, err) + + session, err := application.NewSession(destinationOptions) + require.NoError(t, err) + require.NotEmpty(t, session) + + operation, err := session.QueryInstances(mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, "SELECT Name, Status FROM win32_PrintJob") + + require.NoError(t, err) + require.NotEmpty(t, operation) + + instance, moreResults, err := operation.GetInstance() + require.NoError(t, err) + require.Empty(t, instance) + require.False(t, moreResults) + + err = operation.Close() + require.NoError(t, err) + + err = session.Close() + require.NoError(t, err) + + err = application.Close() + require.NoError(t, err) +} + +func Test_MI_Query_Unmarshal(t *testing.T) { + application, err := mi.Application_Initialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + destinationOptions, err := application.NewDestinationOptions() + require.NoError(t, err) + require.NotEmpty(t, destinationOptions) + + err = destinationOptions.SetTimeout(1 * time.Second) + require.NoError(t, err) + + err = destinationOptions.SetLocale(mi.LocaleEnglish) + require.NoError(t, err) + + session, err := application.NewSession(destinationOptions) + require.NoError(t, err) + require.NotEmpty(t, session) + + operation, err := session.QueryInstances(mi.OperationFlagsStandardRTTI, nil, mi.NamespaceRootCIMv2, mi.QueryDialectWQL, "SELECT Name FROM Win32_Process WHERE Handle = 0 OR Handle = 4") + + require.NoError(t, err) + require.NotEmpty(t, operation) + + var processes []win32Process + + err = operation.Unmarshal(&processes) + require.NoError(t, err) + require.Equal(t, []win32Process{{Name: "System Idle Process"}, {Name: "System"}}, processes) + + err = operation.Close() + require.NoError(t, err) + + err = session.Close() + require.NoError(t, err) + + err = application.Close() + require.NoError(t, err) +} + +func Test_MI_FD_Leak(t *testing.T) { + t.Skip("This test is disabled because it is not deterministic and may fail on some systems.") + + application, err := mi.Application_Initialize() + require.NoError(t, err) + require.NotEmpty(t, application) + + session, err := application.NewSession(nil) + require.NoError(t, err) + require.NotEmpty(t, session) + + currentFileHandle, err := testutils.GetProcessHandleCount(windows.CurrentProcess()) + require.NoError(t, err) + + t.Log("Current File Handle Count: ", currentFileHandle) + + queryPrinter, err := mi.NewQuery("SELECT Name, Default, PrinterStatus, JobCountSinceLastReset FROM win32_Printer") + require.NoError(t, err) + + queryPrinterJob, err := mi.NewQuery("SELECT Name, Status FROM win32_PrintJob") + require.NoError(t, err) + + for range 1000 { + var wmiPrinters []wmiPrinter + err := session.Query(&wmiPrinters, mi.NamespaceRootCIMv2, queryPrinter) + require.NoError(t, err) + + var wmiPrintJobs []wmiPrintJob + err = session.Query(&wmiPrintJobs, mi.NamespaceRootCIMv2, queryPrinterJob) + require.NoError(t, err) + + currentFileHandle, err = testutils.GetProcessHandleCount(windows.CurrentProcess()) + require.NoError(t, err) + + t.Log("Current File Handle Count: ", currentFileHandle) + } + + currentFileHandle, err = testutils.GetProcessHandleCount(windows.CurrentProcess()) + require.NoError(t, err) + + t.Log("Current File Handle Count: ", currentFileHandle) + + err = session.Close() + require.NoError(t, err) + + currentFileHandle, err = testutils.GetProcessHandleCount(windows.CurrentProcess()) + require.NoError(t, err) + + t.Log("Current File Handle Count: ", currentFileHandle) + + err = application.Close() + require.NoError(t, err) + + currentFileHandle, err = testutils.GetProcessHandleCount(windows.CurrentProcess()) + require.NoError(t, err) + + t.Log("Current File Handle Count: ", currentFileHandle) +} diff --git a/internal/mi/operation.go b/internal/mi/operation.go new file mode 100644 index 00000000..728cb5ba --- /dev/null +++ b/internal/mi/operation.go @@ -0,0 +1,272 @@ +//go:build windows + +package mi + +import ( + "errors" + "fmt" + "reflect" + "syscall" + "time" + "unsafe" + + "golang.org/x/sys/windows" +) + +// OperationOptionsTimeout is the key for the timeout option. +// +// https://github.com/microsoft/win32metadata/blob/527806d20d83d3abd43d16cd3fa8795d8deba343/generation/WinSDK/RecompiledIdlHeaders/um/mi.h#L9240 +var OperationOptionsTimeout = UTF16PtrFromString[*uint16]("__MI_OPERATIONOPTIONS_TIMEOUT") + +// OperationFlags represents the flags for an operation. +// +// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/wmi_v2/mi-flags +type OperationFlags uint32 + +const ( + OperationFlagsDefaultRTTI OperationFlags = 0x0000 + OperationFlagsBasicRTTI OperationFlags = 0x0002 + OperationFlagsNoRTTI OperationFlags = 0x0400 + OperationFlagsStandardRTTI OperationFlags = 0x0800 + OperationFlagsFullRTTI OperationFlags = 0x0004 +) + +// Operation represents an operation. +// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_operation +type Operation struct { + reserved1 uint64 + reserved2 uintptr + ft *OperationFT +} + +// OperationFT represents the function table for Operation. +// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_operationft +type OperationFT struct { + Close uintptr + Cancel uintptr + GetSession uintptr + GetInstance uintptr + GetIndication uintptr + GetClass uintptr +} + +type OperationOptions struct { + reserved1 uint64 + reserved2 uintptr + ft *OperationOptionsFT +} + +type OperationOptionsFT struct { + Delete uintptr + SetString uintptr + SetNumber uintptr + SetCustomOption uintptr + GetString uintptr + GetNumber uintptr + GetOptionCount uintptr + GetOptionAt uintptr + GetOption uintptr + GetEnabledChannels uintptr + Clone uintptr + SetInterval uintptr + GetInterval uintptr +} + +type OperationCallbacks[T any] struct { + CallbackContext *T + PromptUser uintptr + WriteError uintptr + WriteMessage uintptr + WriteProgress uintptr + InstanceResult uintptr + IndicationResult uintptr + ClassResult uintptr + StreamedParameterResult uintptr +} + +// Close closes an operation handle. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_operation_close +func (o *Operation) Close() error { + if o == nil || o.ft == nil { + return ErrNotInitialized + } + + r0, _, _ := syscall.SyscallN(o.ft.Close, uintptr(unsafe.Pointer(o))) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} + +func (o *Operation) Cancel() error { + if o == nil || o.ft == nil { + return ErrNotInitialized + } + + r0, _, _ := syscall.SyscallN(o.ft.Close, uintptr(unsafe.Pointer(o)), 0) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} + +func (o *Operation) GetInstance() (*Instance, bool, error) { + if o == nil || o.ft == nil { + return nil, false, ErrNotInitialized + } + + var ( + instance *Instance + errorDetails *Instance + moreResults Boolean + instanceResult ResultError + errorMessageUTF16 *uint16 + ) + + r0, _, _ := syscall.SyscallN( + o.ft.GetInstance, + uintptr(unsafe.Pointer(o)), + uintptr(unsafe.Pointer(&instance)), + uintptr(unsafe.Pointer(&moreResults)), + uintptr(unsafe.Pointer(&instanceResult)), + uintptr(unsafe.Pointer(&errorMessageUTF16)), + uintptr(unsafe.Pointer(&errorDetails)), + ) + + if !errors.Is(instanceResult, MI_RESULT_OK) { + return nil, false, fmt.Errorf("instance result: %w (%s)", instanceResult, windows.UTF16PtrToString(errorMessageUTF16)) + } + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return nil, false, result + } + + return instance, moreResults == True, nil +} + +func (o *Operation) Unmarshal(dst any) error { + if o == nil || o.ft == nil { + return ErrNotInitialized + } + + dv := reflect.ValueOf(dst) + if dv.Kind() != reflect.Ptr || dv.IsNil() { + return ErrInvalidEntityType + } + + dv = dv.Elem() + + elemType := dv.Type().Elem() + elemValue := reflect.ValueOf(reflect.New(elemType).Interface()).Elem() + + if dv.Kind() != reflect.Slice || elemType.Kind() != reflect.Struct { + return ErrInvalidEntityType + } + + dv.Set(reflect.MakeSlice(dv.Type(), 0, 0)) + + for { + instance, moreResults, err := o.GetInstance() + if err != nil { + return fmt.Errorf("failed to get instance: %w", err) + } + + // If WMI returns nil, it means there are no more results. + if instance == nil { + break + } + + counter, err := instance.GetElementCount() + if err != nil { + return fmt.Errorf("failed to get element count: %w", err) + } + + if counter == 0 { + break + } + + for i := range elemType.NumField() { + field := elemValue.Field(i) + + // Check if the field has an `mi` tag + miTag := elemType.Field(i).Tag.Get("mi") + if miTag == "" { + continue + } + + element, err := instance.GetElement(miTag) + if err != nil { + return fmt.Errorf("failed to get element: %w", err) + } + + switch element.valueType { + case ValueTypeBOOLEAN: + field.SetBool(element.value == 1) + case ValueTypeUINT8, ValueTypeUINT16, ValueTypeUINT32, ValueTypeUINT64: + field.SetUint(uint64(element.value)) + case ValueTypeSINT8, ValueTypeSINT16, ValueTypeSINT32, ValueTypeSINT64: + field.SetInt(int64(element.value)) + case ValueTypeSTRING: + if element.value == 0 { + return fmt.Errorf("%s: invalid pointer: value is nil", miTag) + } + + // Convert the UTF-16 string to a Go string + stringValue := windows.UTF16PtrToString((*uint16)(unsafe.Pointer(element.value))) + + field.SetString(stringValue) + case ValueTypeREAL32, ValueTypeREAL64: + field.SetFloat(float64(element.value)) + default: + return fmt.Errorf("unsupported value type: %d", element.valueType) + } + } + + dv.Set(reflect.Append(dv, elemValue)) + + if !moreResults { + break + } + } + + return nil +} + +func (o *OperationOptions) SetTimeout(timeout time.Duration) error { + if o == nil || o.ft == nil { + return ErrNotInitialized + } + + r0, _, _ := syscall.SyscallN( + o.ft.SetInterval, + uintptr(unsafe.Pointer(o)), + uintptr(unsafe.Pointer(OperationOptionsTimeout)), + uintptr(unsafe.Pointer(NewInterval(timeout))), + 0, + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} + +func (o *OperationOptions) Delete() error { + if o == nil || o.ft == nil { + return ErrNotInitialized + } + + r0, _, _ := syscall.SyscallN(o.ft.Delete, uintptr(unsafe.Pointer(o))) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} diff --git a/internal/mi/result.go b/internal/mi/result.go new file mode 100644 index 00000000..209ea172 --- /dev/null +++ b/internal/mi/result.go @@ -0,0 +1,102 @@ +//go:build windows + +package mi + +import "errors" + +type ResultError uint32 + +const ( + MI_RESULT_OK ResultError = iota + MI_RESULT_FAILED + MI_RESULT_ACCESS_DENIED + MI_RESULT_INVALID_NAMESPACE + MI_RESULT_INVALID_PARAMETER + MI_RESULT_INVALID_CLASS + MI_RESULT_NOT_FOUND + MI_RESULT_NOT_SUPPORTED + MI_RESULT_CLASS_HAS_CHILDREN + MI_RESULT_CLASS_HAS_INSTANCES + MI_RESULT_INVALID_SUPERCLASS + MI_RESULT_ALREADY_EXISTS + MI_RESULT_NO_SUCH_PROPERTY + MI_RESULT_TYPE_MISMATCH + MI_RESULT_QUERY_LANGUAGE_NOT_SUPPORTED + MI_RESULT_INVALID_QUERY + MI_RESULT_METHOD_NOT_AVAILABLE + MI_RESULT_METHOD_NOT_FOUND + MI_RESULT_NAMESPACE_NOT_EMPTY + MI_RESULT_INVALID_ENUMERATION_CONTEXT + MI_RESULT_INVALID_OPERATION_TIMEOUT + MI_RESULT_PULL_HAS_BEEN_ABANDONED + MI_RESULT_PULL_CANNOT_BE_ABANDONED + MI_RESULT_FILTERED_ENUMERATION_NOT_SUPPORTED + MI_RESULT_CONTINUATION_ON_ERROR_NOT_SUPPORTED + MI_RESULT_SERVER_LIMITS_EXCEEDED + MI_RESULT_SERVER_IS_SHUTTING_DOWN +) + +func (r ResultError) Error() string { + return r.String() +} + +func (r ResultError) String() string { + switch { + case errors.Is(r, MI_RESULT_OK): + return "MI_RESULT_OK" + case errors.Is(r, MI_RESULT_FAILED): + return "MI_RESULT_FAILED" + case errors.Is(r, MI_RESULT_ACCESS_DENIED): + return "MI_RESULT_ACCESS_DENIED" + case errors.Is(r, MI_RESULT_INVALID_NAMESPACE): + return "MI_RESULT_INVALID_NAMESPACE" + case errors.Is(r, MI_RESULT_INVALID_PARAMETER): + return "MI_RESULT_INVALID_PARAMETER" + case errors.Is(r, MI_RESULT_INVALID_CLASS): + return "MI_RESULT_INVALID_CLASS" + case errors.Is(r, MI_RESULT_NOT_FOUND): + return "MI_RESULT_NOT_FOUND" + case errors.Is(r, MI_RESULT_NOT_SUPPORTED): + return "MI_RESULT_NOT_SUPPORTED" + case errors.Is(r, MI_RESULT_CLASS_HAS_CHILDREN): + return "MI_RESULT_CLASS_HAS_CHILDREN" + case errors.Is(r, MI_RESULT_CLASS_HAS_INSTANCES): + return "MI_RESULT_CLASS_HAS_INSTANCES" + case errors.Is(r, MI_RESULT_INVALID_SUPERCLASS): + return "MI_RESULT_INVALID_SUPERCLASS" + case errors.Is(r, MI_RESULT_ALREADY_EXISTS): + return "MI_RESULT_ALREADY_EXISTS" + case errors.Is(r, MI_RESULT_NO_SUCH_PROPERTY): + return "MI_RESULT_NO_SUCH_PROPERTY" + case errors.Is(r, MI_RESULT_TYPE_MISMATCH): + return "MI_RESULT_TYPE_MISMATCH" + case errors.Is(r, MI_RESULT_QUERY_LANGUAGE_NOT_SUPPORTED): + return "MI_RESULT_QUERY_LANGUAGE_NOT_SUPPORTED" + case errors.Is(r, MI_RESULT_INVALID_QUERY): + return "MI_RESULT_INVALID_QUERY" + case errors.Is(r, MI_RESULT_METHOD_NOT_AVAILABLE): + return "MI_RESULT_METHOD_NOT_AVAILABLE" + case errors.Is(r, MI_RESULT_METHOD_NOT_FOUND): + return "MI_RESULT_METHOD_NOT_FOUND" + case errors.Is(r, MI_RESULT_NAMESPACE_NOT_EMPTY): + return "MI_RESULT_NAMESPACE_NOT_EMPTY" + case errors.Is(r, MI_RESULT_INVALID_ENUMERATION_CONTEXT): + return "MI_RESULT_INVALID_ENUMERATION_CONTEXT" + case errors.Is(r, MI_RESULT_INVALID_OPERATION_TIMEOUT): + return "MI_RESULT_INVALID_OPERATION_TIMEOUT" + case errors.Is(r, MI_RESULT_PULL_HAS_BEEN_ABANDONED): + return "MI_RESULT_PULL_HAS_BEEN_ABANDONED" + case errors.Is(r, MI_RESULT_PULL_CANNOT_BE_ABANDONED): + return "MI_RESULT_PULL_CANNOT_BE_ABANDONED" + case errors.Is(r, MI_RESULT_FILTERED_ENUMERATION_NOT_SUPPORTED): + return "MI_RESULT_FILTERED_ENUMERATION_NOT_SUPPORTED" + case errors.Is(r, MI_RESULT_CONTINUATION_ON_ERROR_NOT_SUPPORTED): + return "MI_RESULT_CONTINUATION_ON_ERROR_NOT_SUPPORTED" + case errors.Is(r, MI_RESULT_SERVER_LIMITS_EXCEEDED): + return "MI_RESULT_SERVER_LIMITS_EXCEEDED" + case errors.Is(r, MI_RESULT_SERVER_IS_SHUTTING_DOWN): + return "MI_RESULT_SERVER_IS_SHUTTING_DOWN" + default: + return "MI_RESULT_UNKNOWN" + } +} diff --git a/internal/mi/session.go b/internal/mi/session.go new file mode 100644 index 00000000..6920c524 --- /dev/null +++ b/internal/mi/session.go @@ -0,0 +1,235 @@ +//go:build windows + +package mi + +import ( + "errors" + "fmt" + "runtime" + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +// Session represents a session. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_session +type Session struct { + reserved1 uint64 + reserved2 uintptr + ft *SessionFT + + defaultOperationOptions *OperationOptions +} + +// SessionFT represents the function table for Session. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/ns-mi-mi_session +type SessionFT struct { + Close uintptr + GetApplication uintptr + GetInstance uintptr + ModifyInstance uintptr + CreateInstance uintptr + DeleteInstance uintptr + Invoke uintptr + EnumerateInstances uintptr + QueryInstances uintptr + AssociatorInstances uintptr + ReferenceInstances uintptr + Subscribe uintptr + GetClass uintptr + EnumerateClasses uintptr + TestConnection uintptr +} + +// Close closes a session and releases all associated memory. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_close +func (s *Session) Close() error { + if s == nil || s.ft == nil { + return ErrNotInitialized + } + + if s.defaultOperationOptions != nil { + _ = s.defaultOperationOptions.Delete() + } + + r0, _, _ := syscall.SyscallN(s.ft.Close, + uintptr(unsafe.Pointer(s)), + 0, + 0, + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + return nil +} + +// TestConnection queries instances. It is used to test the connection. +// The function returns an operation that can be used to retrieve the result with [Operation.GetInstance]. The operation must be closed with [Operation.Close]. +// The instance returned by [Operation.GetInstance] is always nil. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_testconnection +func (s *Session) TestConnection() error { + if s == nil || s.ft == nil { + return ErrNotInitialized + } + + operation := &Operation{} + + // ref: https://github.com/KurtDeGreeff/omi/blob/9caa55032a1070a665e14fd282a091f6247d13c3/Unix/scriptext/py/PMI_Session.c#L92-L105 + r0, _, _ := syscall.SyscallN( + s.ft.TestConnection, + uintptr(unsafe.Pointer(s)), + 0, + 0, + uintptr(unsafe.Pointer(operation)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + var err error + + if _, _, err = operation.GetInstance(); err != nil { + return fmt.Errorf("failed to get instance: %w", err) + } + + if err = operation.Close(); err != nil { + return fmt.Errorf("failed to close operation: %w", err) + } + + return nil +} + +// GetApplication gets the Application handle that was used to create the specified session. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_getapplication +func (s *Session) GetApplication() (*Application, error) { + if s == nil || s.ft == nil { + return nil, ErrNotInitialized + } + + application := &Application{} + + r0, _, _ := syscall.SyscallN( + s.ft.GetApplication, + uintptr(unsafe.Pointer(s)), + uintptr(unsafe.Pointer(application)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return nil, result + } + + return application, nil +} + +// QueryInstances queries for a set of instances based on a query expression. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_queryinstances +func (s *Session) QueryInstances(flags OperationFlags, operationOptions *OperationOptions, namespaceName Namespace, + queryDialect QueryDialect, queryExpression string, +) (*Operation, error) { + if s == nil || s.ft == nil { + return nil, ErrNotInitialized + } + + queryExpressionUTF16, err := windows.UTF16PtrFromString(queryExpression) + if err != nil { + return nil, err + } + + operation := &Operation{} + + if operationOptions == nil { + operationOptions = s.defaultOperationOptions + } + + r0, _, _ := syscall.SyscallN( + s.ft.QueryInstances, + uintptr(unsafe.Pointer(s)), + uintptr(flags), + uintptr(unsafe.Pointer(operationOptions)), + uintptr(unsafe.Pointer(namespaceName)), + uintptr(unsafe.Pointer(queryDialect)), + uintptr(unsafe.Pointer(queryExpressionUTF16)), + 0, + uintptr(unsafe.Pointer(operation)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return nil, result + } + + return operation, nil +} + +// QueryUnmarshal queries for a set of instances based on a query expression. +// +// https://learn.microsoft.com/en-us/windows/win32/api/mi/nf-mi-mi_session_queryinstances +func (s *Session) QueryUnmarshal(dst any, + flags OperationFlags, operationOptions *OperationOptions, + namespaceName Namespace, queryDialect QueryDialect, queryExpression Query, +) error { + if s == nil || s.ft == nil { + return ErrNotInitialized + } + + operation := &Operation{} + + if operationOptions == nil { + operationOptions = s.defaultOperationOptions + } + + errCh := make(chan error, 1) + + operationCallbacks, err := NewUnmarshalOperationsCallbacks(dst, errCh) + if err != nil { + return err + } + + r0, _, _ := syscall.SyscallN( + s.ft.QueryInstances, + uintptr(unsafe.Pointer(s)), + uintptr(flags), + uintptr(unsafe.Pointer(operationOptions)), + uintptr(unsafe.Pointer(namespaceName)), + uintptr(unsafe.Pointer(queryDialect)), + uintptr(unsafe.Pointer(queryExpression)), + uintptr(unsafe.Pointer(operationCallbacks)), + uintptr(unsafe.Pointer(operation)), + ) + + if result := ResultError(r0); !errors.Is(result, MI_RESULT_OK) { + return result + } + + errs := make([]error, 0) + + for err := range errCh { + if err != nil { + errs = append(errs, err) + } + } + + // KeepAlive is used to ensure that the callbacks are not garbage collected before the operation is closed. + runtime.KeepAlive(operationCallbacks.CallbackContext) + + return errors.Join(errs...) +} + +// Query queries for a set of instances based on a query expression. +func (s *Session) Query(dst any, namespaceName Namespace, queryExpression Query) error { + err := s.QueryUnmarshal(dst, OperationFlagsStandardRTTI, nil, namespaceName, QueryDialectWQL, queryExpression) + if err != nil { + return fmt.Errorf("WMI query failed: %w", err) + } + + return nil +} diff --git a/internal/mi/types.go b/internal/mi/types.go new file mode 100644 index 00000000..46db7c41 --- /dev/null +++ b/internal/mi/types.go @@ -0,0 +1,137 @@ +//go:build windows + +package mi + +import ( + "time" + "unsafe" + + "github.com/prometheus-community/windows_exporter/internal/utils" + "golang.org/x/sys/windows" +) + +type Boolean uint8 + +const ( + False Boolean = 0 + True Boolean = 1 +) + +type QueryDialect *uint16 + +func NewQueryDialect(queryDialect string) (QueryDialect, error) { + return windows.UTF16PtrFromString(queryDialect) +} + +var ( + QueryDialectWQL = utils.Must(NewQueryDialect("WQL")) + QueryDialectCQL = utils.Must(NewQueryDialect("CQL")) +) + +type Namespace *uint16 + +func NewNamespace(namespace string) (Namespace, error) { + return windows.UTF16PtrFromString(namespace) +} + +var ( + NamespaceRootCIMv2 = utils.Must(NewNamespace("root/CIMv2")) + NamespaceRootWindowsFSRM = utils.Must(NewNamespace("root/microsoft/windows/fsrm")) + NamespaceRootWebAdministration = utils.Must(NewNamespace("root/WebAdministration")) + NamespaceRootMSCluster = utils.Must(NewNamespace("root/MSCluster")) +) + +type Query *uint16 + +func NewQuery(query string) (Query, error) { + return windows.UTF16PtrFromString(query) +} + +// UTF16PtrFromString converts a string to a UTF-16 pointer at initialization time. +// +//nolint:ireturn +func UTF16PtrFromString[T *uint16](s string) T { + val, err := windows.UTF16PtrFromString(s) + if err != nil { + panic(err) + } + + return val +} + +type Timestamp struct { + Year uint32 + Month uint32 + Day uint32 + Hour uint32 + Minute uint32 + Second uint32 + Microseconds uint32 + UTC int32 +} + +type Interval struct { + Days uint32 + Hours uint32 + Minutes uint32 + Seconds uint32 + Microseconds uint32 + Padding1 uint32 + Padding2 uint32 + Padding3 uint32 +} + +func NewInterval(interval time.Duration) *Interval { + // Convert the duration to a number of microseconds + microseconds := interval.Microseconds() + + // Create a new interval with the microseconds + return &Interval{ + Days: uint32(microseconds / (24 * 60 * 60 * 1000000)), + Hours: uint32(microseconds / (60 * 60 * 1000000)), + Minutes: uint32(microseconds / (60 * 1000000)), + Seconds: uint32(microseconds / 1000000), + Microseconds: uint32(microseconds % 1000000), + } +} + +type Datetime struct { + IsTimestamp bool + Timestamp *Timestamp // Used when IsTimestamp is true + Interval *Interval // Used when IsTimestamp is false +} + +type PropertyDecl struct { + Flags uint32 + Code uint32 + Name *uint16 + Mqualifiers uintptr + NumQualifiers uint32 + PropertyType ValueType + ClassName *uint16 + Subscript uint32 + Offset uint32 + Origin *uint16 + Propagator *uint16 + Value uintptr +} + +func (c *ClassDecl) Properties() []*PropertyDecl { + // Create a slice to hold the properties + properties := make([]*PropertyDecl, c.NumProperties) + + // Mproperties is a pointer to an array of pointers to PropertyDecl + propertiesArray := (**PropertyDecl)(unsafe.Pointer(c.Mproperties)) + + // Iterate over the number of properties and fetch each property + for i := range c.NumProperties { + // Get the property pointer at index i + propertyPtr := *(**PropertyDecl)(unsafe.Pointer(uintptr(unsafe.Pointer(propertiesArray)) + uintptr(i)*unsafe.Sizeof(uintptr(0)))) + + // Append the property to the slice + properties[i] = propertyPtr + } + + // Return the slice of properties + return properties +} diff --git a/internal/mi/value.go b/internal/mi/value.go new file mode 100644 index 00000000..4c4954ab --- /dev/null +++ b/internal/mi/value.go @@ -0,0 +1,112 @@ +//go:build windows + +package mi + +import ( + "errors" + "fmt" + "unsafe" + + "golang.org/x/sys/windows" +) + +type ValueType int + +const ( + ValueTypeBOOLEAN ValueType = iota + ValueTypeUINT8 + ValueTypeSINT8 + ValueTypeUINT16 + ValueTypeSINT16 + ValueTypeUINT32 + ValueTypeSINT32 + ValueTypeUINT64 + ValueTypeSINT64 + ValueTypeREAL32 + ValueTypeREAL64 + ValueTypeCHAR16 + ValueTypeDATETIME + ValueTypeSTRING + ValueTypeREFERENCE + ValueTypeINSTANCE + ValueTypeBOOLEANA + ValueTypeUINT8A + ValueTypeSINT8A + ValueTypeUINT16A + ValueTypeSINT16A + ValueTypeUINT32A + ValueTypeSINT32A + ValueTypeUINT64A + ValueTypeSINT64A + ValueTypeREAL32A + ValueTypeREAL64A + ValueTypeCHAR16A + ValueTypeDATETIMEA + ValueTypeSTRINGA + ValueTypeREFERENCEA + ValueTypeINSTANCEA + ValueTypeARRAY ValueType = 16 +) + +type Element struct { + value uintptr + valueType ValueType +} + +func (e *Element) GetValue() (any, error) { + switch e.valueType { + case ValueTypeBOOLEAN: + return e.value == 1, nil + case ValueTypeUINT8: + return uint8(e.value), nil + case ValueTypeSINT8: + return int8(e.value), nil + case ValueTypeUINT16: + return uint16(e.value), nil + case ValueTypeSINT16: + return int16(e.value), nil + case ValueTypeUINT32: + return uint32(e.value), nil + case ValueTypeSINT32: + return int32(e.value), nil + case ValueTypeUINT64: + return uint64(e.value), nil + case ValueTypeSINT64: + return int64(e.value), nil + case ValueTypeREAL32: + return float32(e.value), nil + case ValueTypeREAL64: + return float64(e.value), nil + case ValueTypeCHAR16: + return uint16(e.value), nil + case ValueTypeDATETIME: + if e.value == 0 { + return nil, errors.New("invalid pointer: value is nil") + } + + return *(*Datetime)(unsafe.Pointer(e.value)), nil + case ValueTypeSTRING: + if e.value == 0 { + return nil, errors.New("invalid pointer: value is nil") + } + + // Convert the UTF-16 string to a Go string + return windows.UTF16PtrToString((*uint16)(unsafe.Pointer(e.value))), nil + case ValueTypeSTRINGA: + if e.value == 0 { + return nil, errors.New("invalid pointer: value is nil") + } + + // Assuming array of pointers to UTF-16 strings + ptrArray := *(*[]*uint16)(unsafe.Pointer(e.value)) + strArray := make([]string, len(ptrArray)) + + for i, ptr := range ptrArray { + strArray[i] = windows.UTF16PtrToString(ptr) + } + + return strArray, nil + default: + return nil, fmt.Errorf("unsupported value type: %d", e.valueType) + } +} diff --git a/internal/testutils/handle.go b/internal/testutils/handle.go new file mode 100644 index 00000000..d177d263 --- /dev/null +++ b/internal/testutils/handle.go @@ -0,0 +1,28 @@ +package testutils + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +var ( + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + + procGetProcessHandleCount = modkernel32.NewProc("GetProcessHandleCount") +) + +func GetProcessHandleCount(handle windows.Handle) (uint32, error) { + var count uint32 + + r1, _, err := procGetProcessHandleCount.Call( + uintptr(handle), + uintptr(unsafe.Pointer(&count)), + ) + + if r1 != 1 { + return 0, err + } + + return count, nil +} diff --git a/internal/testutils/testutils.go b/internal/testutils/testutils.go index a202d463..5be1c508 100644 --- a/internal/testutils/testutils.go +++ b/internal/testutils/testutils.go @@ -10,10 +10,10 @@ import ( "time" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/pkg/collector" "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/require" - "github.com/yusufpapurcu/wmi" ) func FuncBenchmarkCollector[C collector.Collector](b *testing.B, name string, collectFunc collector.BuilderWithFlags[C]) { @@ -57,14 +57,16 @@ func TestCollector[C collector.Collector, V interface{}](t *testing.T, fn func(* c := fn(conf) ch := make(chan prometheus.Metric, 10000) - wmiClient := &wmi.Client{ - AllowMissingFields: true, - } - wmiClient.SWbemServicesClient, err = wmi.InitializeSWbemServices(wmiClient) + miApp, err := mi.Application_Initialize() + require.NoError(t, err) + + miSession, err := miApp.NewSession(nil) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, c.Close(logger)) + require.NoError(t, miSession.Close()) + require.NoError(t, miApp.Close()) }) wg := sync.WaitGroup{} @@ -78,7 +80,7 @@ func TestCollector[C collector.Collector, V interface{}](t *testing.T, fn func(* } }() - require.NoError(t, c.Build(logger, wmiClient)) + require.NoError(t, c.Build(logger, miSession)) time.Sleep(1 * time.Second) diff --git a/internal/utils/collector.go b/internal/utils/collector.go index 76d1ab1a..5c44e49b 100644 --- a/internal/utils/collector.go +++ b/internal/utils/collector.go @@ -35,3 +35,11 @@ func PDHEnabled() bool { return false } + +func MIEnabled() bool { + if v, ok := os.LookupEnv("WINDOWS_EXPORTER_WMI_ENGINE"); ok && v == "mi" { + return true + } + + return false +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 1823ad53..5daf902e 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -17,3 +17,14 @@ func BoolToFloat(b bool) float64 { func ToPTR[t any](v t) *t { return &v } + +// Must panics if the error is not nil. +// +//nolint:ireturn +func Must[T any](v T, err error) T { + if err != nil { + panic(err) + } + + return v +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..06ab7d0f --- /dev/null +++ b/main.go @@ -0,0 +1 @@ +package main diff --git a/pkg/collector/collector.go b/pkg/collector/collector.go index e5424abe..006b5bb2 100644 --- a/pkg/collector/collector.go +++ b/pkg/collector/collector.go @@ -57,9 +57,9 @@ import ( "github.com/prometheus-community/windows_exporter/internal/collector/time" "github.com/prometheus-community/windows_exporter/internal/collector/update" "github.com/prometheus-community/windows_exporter/internal/collector/vmware" + "github.com/prometheus-community/windows_exporter/internal/mi" v1 "github.com/prometheus-community/windows_exporter/internal/perfdata/v1" "github.com/prometheus-community/windows_exporter/internal/types" - "github.com/yusufpapurcu/wmi" ) // NewWithFlags To be called by the exporter for collector initialization before running kingpin.Parse. @@ -132,9 +132,6 @@ func NewWithConfig(config Config) *MetricCollectors { func New(collectors Map) *MetricCollectors { return &MetricCollectors{ Collectors: collectors, - WMIClient: &wmi.Client{ - AllowMissingFields: true, - }, } } @@ -186,11 +183,9 @@ func (c *MetricCollectors) Enable(enabledCollectors []string) error { // Build To be called by the exporter for collector initialization. func (c *MetricCollectors) Build(logger *slog.Logger) error { - var err error - - c.WMIClient.SWbemServicesClient, err = wmi.InitializeSWbemServices(c.WMIClient) + err := c.initMI() if err != nil { - return fmt.Errorf("initialize SWbemServices: %w", err) + return fmt.Errorf("error from initialize MI: %w", err) } wg := sync.WaitGroup{} @@ -203,7 +198,7 @@ func (c *MetricCollectors) Build(logger *slog.Logger) error { go func() { defer wg.Done() - if err = collector.Build(logger, c.WMIClient); err != nil { + if err = collector.Build(logger, c.MISession); err != nil { errCh <- fmt.Errorf("error build collector %s: %w", collector.GetName(), err) } }() @@ -245,11 +240,42 @@ func (c *MetricCollectors) Close(logger *slog.Logger) error { } } - if c.WMIClient != nil && c.WMIClient.SWbemServicesClient != nil { - if err := c.WMIClient.SWbemServicesClient.Close(); err != nil { - errs = append(errs, err) - } + app, err := c.MISession.GetApplication() + if err != nil && !errors.Is(err, mi.ErrNotInitialized) { + errs = append(errs, err) + } + + if err := c.MISession.Close(); err != nil && !errors.Is(err, mi.ErrNotInitialized) { + errs = append(errs, err) + } + + if err := app.Close(); err != nil && !errors.Is(err, mi.ErrNotInitialized) { + errs = append(errs, err) } return errors.Join(errs...) } + +// Close To be called by the exporter for collector cleanup. +func (c *MetricCollectors) initMI() error { + app, err := mi.Application_Initialize() + if err != nil { + return fmt.Errorf("error from initialize MI application: %w", err) + } + + destinationOptions, err := app.NewDestinationOptions() + if err != nil { + return fmt.Errorf("error from create NewDestinationOptions: %w", err) + } + + if err = destinationOptions.SetLocale(mi.LocaleEnglish); err != nil { + return fmt.Errorf("error from set locale: %w", err) + } + + c.MISession, err = app.NewSession(destinationOptions) + if err != nil { + return fmt.Errorf("error from create NewSession: %w", err) + } + + return nil +} diff --git a/pkg/collector/types.go b/pkg/collector/types.go index 98385889..8de5d2b0 100644 --- a/pkg/collector/types.go +++ b/pkg/collector/types.go @@ -4,14 +4,14 @@ import ( "log/slog" "github.com/alecthomas/kingpin/v2" + "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" - "github.com/yusufpapurcu/wmi" ) type MetricCollectors struct { Collectors Map - WMIClient *wmi.Client + MISession *mi.Session PerfCounterQuery string } @@ -22,7 +22,7 @@ type ( // Collector interface that a collector has to implement. type Collector interface { - Build(logger *slog.Logger, wmiClient *wmi.Client) error + Build(logger *slog.Logger, miSession *mi.Session) error // Close closes the collector Close(logger *slog.Logger) error // GetName get the name of the collector