From 4ebbb5612d931f19888a5d727f494ca58bdd1cfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Thu, 20 Feb 2025 08:58:25 +0100 Subject: [PATCH] refactor container MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jan-Otto Kröpke --- .golangci.yaml | 13 +- docs/collector.container.md | 42 +- go.mod | 13 - go.sum | 127 ---- internal/collector/container/container.go | 618 +++++++++++++++--- internal/collector/cs/cs.go | 2 +- internal/collector/dhcp/dhcp.go | 2 +- internal/collector/hyperv/hyperv.go | 2 +- internal/collector/license/license.go | 2 +- internal/collector/logon/logon.go | 2 +- internal/collector/memory/memory.go | 2 +- .../collector/mscluster/mscluster_cluster.go | 2 +- .../collector/mscluster/mscluster_node.go | 2 +- internal/collector/net/net.go | 33 +- internal/collector/os/os.go | 6 +- internal/collector/pagefile/pagefile.go | 2 +- .../collector/performancecounter/types.go | 2 +- internal/collector/process/process.go | 2 +- internal/collector/tcp/tcp.go | 2 +- .../terminal_services/terminal_services.go | 2 +- internal/collector/time/time.go | 2 +- internal/osversion/osversion_windows.go | 73 +++ internal/osversion/osversion_windows_test.go | 19 + internal/osversion/windowsbuilds.go | 84 +++ internal/pdh/pdh.go | 2 +- .../{headers => win32}/dhcpsapi/dhcpsapi.go | 0 .../dhcpsapi/dhcpsapi_test.go | 0 internal/{headers => win32}/dhcpsapi/types.go | 2 +- internal/win32/guid/guid.go | 79 +++ internal/win32/hcn/hcn.go | 30 + internal/win32/hcn/hcn_test.go | 30 + internal/win32/hcn/syscall.go | 117 ++++ internal/win32/hcn/types.go | 36 + internal/win32/hcs/hcs.go | 80 +++ internal/win32/hcs/hcs_test.go | 38 ++ internal/win32/hcs/syscall.go | 113 ++++ internal/win32/hcs/types.go | 64 ++ internal/{headers => win32}/iphlpapi/const.go | 0 .../{headers => win32}/iphlpapi/iphlpapi.go | 41 +- .../iphlpapi/iphlpapi_test.go | 2 +- internal/{headers => win32}/iphlpapi/types.go | 53 ++ internal/win32/kernel32/job.go | 54 ++ .../{headers => win32}/kernel32/kernel32.go | 2 + internal/win32/kernel32/types.go | 58 ++ .../{headers => win32}/netapi32/netapi32.go | 0 internal/{headers => win32}/psapi/psapi.go | 0 .../schedule_service/schedule_service.go | 0 .../{headers => win32}/secur32/secur32.go | 0 .../secur32/secur32_test.go | 2 +- internal/{headers => win32}/secur32/types.go | 0 internal/{headers => win32}/slc/slc.go | 0 .../sysinfoapi/sysinfoapi.go | 0 internal/{headers => win32}/win32api/types.go | 0 .../{headers => win32}/wtsapi32/wtsapi32.go | 0 54 files changed, 1551 insertions(+), 308 deletions(-) create mode 100644 internal/osversion/osversion_windows.go create mode 100644 internal/osversion/osversion_windows_test.go create mode 100644 internal/osversion/windowsbuilds.go rename internal/{headers => win32}/dhcpsapi/dhcpsapi.go (100%) rename internal/{headers => win32}/dhcpsapi/dhcpsapi_test.go (100%) rename internal/{headers => win32}/dhcpsapi/types.go (97%) create mode 100644 internal/win32/guid/guid.go create mode 100644 internal/win32/hcn/hcn.go create mode 100644 internal/win32/hcn/hcn_test.go create mode 100644 internal/win32/hcn/syscall.go create mode 100644 internal/win32/hcn/types.go create mode 100644 internal/win32/hcs/hcs.go create mode 100644 internal/win32/hcs/hcs_test.go create mode 100644 internal/win32/hcs/syscall.go create mode 100644 internal/win32/hcs/types.go rename internal/{headers => win32}/iphlpapi/const.go (100%) rename internal/{headers => win32}/iphlpapi/iphlpapi.go (67%) rename internal/{headers => win32}/iphlpapi/iphlpapi_test.go (94%) rename internal/{headers => win32}/iphlpapi/types.go (58%) create mode 100644 internal/win32/kernel32/job.go rename internal/{headers => win32}/kernel32/kernel32.go (94%) create mode 100644 internal/win32/kernel32/types.go rename internal/{headers => win32}/netapi32/netapi32.go (100%) rename internal/{headers => win32}/psapi/psapi.go (100%) rename internal/{headers => win32}/schedule_service/schedule_service.go (100%) rename internal/{headers => win32}/secur32/secur32.go (100%) rename internal/{headers => win32}/secur32/secur32_test.go (91%) rename internal/{headers => win32}/secur32/types.go (100%) rename internal/{headers => win32}/slc/slc.go (100%) rename internal/{headers => win32}/sysinfoapi/sysinfoapi.go (100%) rename internal/{headers => win32}/win32api/types.go (100%) rename internal/{headers => win32}/wtsapi32/wtsapi32.go (100%) diff --git a/.golangci.yaml b/.golangci.yaml index 1d6c733fc..68f6f897f 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -3,6 +3,7 @@ linters: disable: - cyclop - depguard + - dogsled - dupl - err113 - execinquery @@ -16,14 +17,14 @@ linters: - gocyclo - godot - gomnd - - paralleltest - lll - maintidx - mnd + - paralleltest + - tagliatelle - testpackage - varnamelen - wrapcheck - run: timeout: 15m @@ -42,14 +43,6 @@ linters-settings: - standard # Standard section: captures all standard packages. - default # Default section: contains all imports that could not be matched to another section type. custom-order: true - tagliatelle: - case: - use-field-name: true - rules: - # Any struct tag type can be used. - # Support string case: `camel`, `pascal`, `kebab`, `snake`, `upperSnake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower`, `header` - json: camel - yaml: snake forbidigo: forbid: - "^(fmt\\.Print(|f|ln)|print|println)$" diff --git a/docs/collector.container.md b/docs/collector.container.md index d16ca01cf..ae9743039 100644 --- a/docs/collector.container.md +++ b/docs/collector.container.md @@ -5,7 +5,7 @@ The container collector exposes metrics about containers running on a Hyper-V sy ||| -|- Metric name prefix | `container` -Data source | [hcsshim](https://github.com/Microsoft/hcsshim) +Data source | [HCS](https://learn.microsoft.com/en-us/virtualization/api/hcs/overview) Enabled by default? | No ## Flags @@ -14,26 +14,26 @@ None ## Metrics -Name | Description | Type | Labels ------|-------------|------|------- -`windows_container_available` | Available | counter | `container_id` -`windows_container_count` | Number of containers | gauge | `container_id` -`windows_container_cpu_usage_seconds_kernelmode` | Run time in Kernel mode in Seconds | counter | `container_id` -`windows_container_cpu_usage_seconds_usermode` | Run Time in User mode in Seconds | counter | `container_id` -`windows_container_cpu_usage_seconds_total` | Total Run time in Seconds | counter | `container_id` -`windows_container_memory_usage_commit_bytes` | Memory Usage Commit Bytes | gauge | `container_id` -`windows_container_memory_usage_commit_peak_bytes` | Memory Usage Commit Peak Bytes | gauge | `container_id` -`windows_container_memory_usage_private_working_set_bytes` | Memory Usage Private Working Set Bytes | gauge | `container_id` -`windows_container_network_receive_bytes_total` | Bytes Received on Interface | counter | `container_id`, `interface` -`windows_container_network_receive_packets_total` | Packets Received on Interface | counter | `container_id`, `interface` -`windows_container_network_receive_packets_dropped_total` | Dropped Incoming Packets on Interface | counter | `container_id`, `interface` -`windows_container_network_transmit_bytes_total` | Bytes Sent on Interface | counter | `container_id`, `interface` -`windows_container_network_transmit_packets_total` | Packets Sent on Interface | counter | `container_id`, `interface` -`windows_container_network_transmit_packets_dropped_total` | Dropped Outgoing Packets on Interface | counter | `container_id`, `interface` -`windows_container_storage_read_count_normalized_total` | Read Count Normalized | counter | `container_id` -`windows_container_storage_read_size_bytes_total` | Read Size Bytes | counter | `container_id` -`windows_container_storage_write_count_normalized_total` | Write Count Normalized | counter | `container_id` -`windows_container_storage_write_size_bytes_total` | Write Size Bytes | counter | `container_id` +| Name | Description | Type | Labels | +|------------------------------------------------------------|----------------------------------------|---------|----------------------------------------------------------| +| `windows_container_available` | Available | counter | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_count` | Number of containers | gauge | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_cpu_usage_seconds_kernelmode` | Run time in Kernel mode in Seconds | counter | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_cpu_usage_seconds_usermode` | Run Time in User mode in Seconds | counter | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_cpu_usage_seconds_total` | Total Run time in Seconds | counter | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_memory_usage_commit_bytes` | Memory Usage Commit Bytes | gauge | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_memory_usage_commit_peak_bytes` | Memory Usage Commit Peak Bytes | gauge | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_memory_usage_private_working_set_bytes` | Memory Usage Private Working Set Bytes | gauge | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_network_receive_bytes_total` | Bytes Received on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` | +| `windows_container_network_receive_packets_total` | Packets Received on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` | +| `windows_container_network_receive_packets_dropped_total` | Dropped Incoming Packets on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` | +| `windows_container_network_transmit_bytes_total` | Bytes Sent on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` | +| `windows_container_network_transmit_packets_total` | Packets Sent on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` | +| `windows_container_network_transmit_packets_dropped_total` | Dropped Outgoing Packets on Interface | counter | `container_id`,`namespace`,`pod`,`container`,`interface` | +| `windows_container_storage_read_count_normalized_total` | Read Count Normalized | counter | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_storage_read_size_bytes_total` | Read Size Bytes | counter | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_storage_write_count_normalized_total` | Write Count Normalized | counter | `container_id`,`namespace`,`pod`,`container`, | +| `windows_container_storage_write_size_bytes_total` | Write Size Bytes | counter | `container_id`,`namespace`,`pod`,`container`, | ### Example metric _windows_container_network_receive_bytes_total{container_id="docker://1bd30e8b8ac28cbd76a9b697b4d7bb9d760267b0733d1bc55c60024e98d1e43e",interface="822179E7-002C-4280-ABBA-28BCFE401826"} 9.3305343e+07_ diff --git a/go.mod b/go.mod index 354582511..7d6b157cf 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/prometheus-community/windows_exporter go 1.23 require ( - github.com/Microsoft/hcsshim v0.12.9 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/bmatcuk/doublestar/v4 v4.8.1 github.com/dimchansky/utfbom v1.1.1 @@ -18,37 +17,25 @@ require ( ) require ( - github.com/Microsoft/go-winio v0.6.2 // 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.5 // indirect - github.com/containerd/errdefs v1.0.0 // indirect - github.com/containerd/errdefs/pkg v0.3.0 // indirect - github.com/containerd/typeurl/v2 v2.2.3 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect github.com/jpillora/backoff v1.0.0 // 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 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect - 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.32.0 // indirect golang.org/x/net v0.34.0 // indirect golang.org/x/oauth2 v0.26.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/text v0.22.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489 // indirect - google.golang.org/grpc v1.70.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index 48a383002..86a4d7063 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,3 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= -github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= -github.com/Microsoft/hcsshim v0.12.9 h1:2zJy5KA+l0loz1HzEGqyNnjd3fyZA31ZBCGKacp6lLg= -github.com/Microsoft/hcsshim v0.12.9/go.mod h1:fJ0gkFAna6ukt0bLdKB8djt4XIJhF/vEPuoIWYVvZ8Y= 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-20240927000941-0f3dac36c52b h1:mimo19zliBX/vSQ6PWWSL9lK8qwHozUj03+zLoEB8O0= @@ -12,19 +6,8 @@ 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.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38= github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo= -github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= -github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= -github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= -github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= -github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -32,44 +15,13 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -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.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= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= -github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.1.2/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/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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= @@ -86,13 +38,10 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.21.0 h1:DIsaGmiaBkSangBgMtWdNfxbMNdku5IK6iNhrEqWvdA= github.com/prometheus/client_golang v1.21.0/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= @@ -103,105 +52,31 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.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/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -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= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= golang.org/x/sync v0.11.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-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= golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.30.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.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -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= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -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-20250204164813-702378808489 h1:5bKytslY8ViY0Cj/ewmRtrWHW64bNF03cAatUUFCdFI= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250204164813-702378808489/go.mod h1:8BS3B93F/U1juMFq9+EDk+qOT5CO1R9IzXxG3PTqiRk= -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.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ= -google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw= -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= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -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.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -212,5 +87,3 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/collector/container/container.go b/internal/collector/container/container.go index 5d28e4f14..41b7b53df 100644 --- a/internal/collector/container/container.go +++ b/internal/collector/container/container.go @@ -16,25 +16,48 @@ package container import ( + "encoding/json" "errors" "fmt" + "io/fs" "log/slog" + "os" + "slices" "strings" + "unsafe" - "github.com/Microsoft/hcsshim" "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/win32/guid" + "github.com/prometheus-community/windows_exporter/internal/win32/hcn" + "github.com/prometheus-community/windows_exporter/internal/win32/hcs" + "github.com/prometheus-community/windows_exporter/internal/win32/iphlpapi" + "github.com/prometheus-community/windows_exporter/internal/win32/kernel32" "github.com/prometheus/client_golang/prometheus" + "golang.org/x/sys/windows" ) -const Name = "container" +const ( + Name = "container" -type Config struct{} + subCollectorHCS = "hcs" + subCollectorHostprocess = "hostprocess" + containerDStateDir = `C:\ProgramData\containerd\state\io.containerd.runtime.v2.task\k8s.io\` +) + +type Config struct { + CollectorsEnabled []string `yaml:"collectors_enabled"` +} //nolint:gochecknoglobals -var ConfigDefaults = Config{} +var ConfigDefaults = Config{ + CollectorsEnabled: []string{ + subCollectorHCS, + subCollectorHostprocess, + }, +} // A Collector is a Prometheus Collector for containers metrics. type Collector struct { @@ -42,6 +65,9 @@ type Collector struct { logger *slog.Logger + annotationsCacheHCS map[string]containerInfo + annotationsCacheJob map[string]containerInfo + // Presence containerAvailable *prometheus.Desc @@ -73,12 +99,27 @@ type Collector struct { writeSizeBytes *prometheus.Desc } +type containerInfo struct { + id string + namespace string + pod string + container string +} + +type ociSpec struct { + Annotations map[string]string `json:"annotations"` +} + // New constructs a new Collector. func New(config *Config) *Collector { if config == nil { config = &ConfigDefaults } + if config.CollectorsEnabled == nil { + config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled + } + c := &Collector{ config: *config, } @@ -86,8 +127,26 @@ func New(config *Config) *Collector { return c } -func NewWithFlags(_ *kingpin.Application) *Collector { - return &Collector{} +func NewWithFlags(app *kingpin.Application) *Collector { + c := &Collector{ + config: ConfigDefaults, + } + c.config.CollectorsEnabled = make([]string, 0) + + var collectorsEnabled string + + app.Flag( + "collector.container.enabled", + "Comma-separated list of collectors to use. Defaults to all, if not specified.", + ).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled) + + app.Action(func(*kingpin.ParseContext) error { + c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",") + + return nil + }) + + return c } func (c *Collector) GetName() string { @@ -101,10 +160,16 @@ func (c *Collector) Close() error { func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { c.logger = logger.With(slog.String("collector", Name)) + for _, collector := range c.config.CollectorsEnabled { + if !slices.Contains([]string{subCollectorHCS, subCollectorHostprocess}, collector) { + return fmt.Errorf("unknown collector: %s", collector) + } + } + c.containerAvailable = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "available"), "Available", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.containersCount = prometheus.NewDesc( @@ -116,97 +181,97 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { c.usageCommitBytes = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "memory_usage_commit_bytes"), "Memory Usage Commit Bytes", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.usageCommitPeakBytes = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "memory_usage_commit_peak_bytes"), "Memory Usage Commit Peak Bytes", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.usagePrivateWorkingSetBytes = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "memory_usage_private_working_set_bytes"), "Memory Usage Private Working Set Bytes", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.runtimeTotal = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_total"), "Total Run time in Seconds", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.runtimeUser = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_usermode"), "Run Time in User mode in Seconds", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.runtimeKernel = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "cpu_usage_seconds_kernelmode"), "Run time in Kernel mode in Seconds", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.bytesReceived = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "network_receive_bytes_total"), "Bytes Received on Interface", - []string{"container_id", "interface"}, + []string{"container_id", "namespace", "pod", "container", "interface"}, nil, ) c.bytesSent = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "network_transmit_bytes_total"), "Bytes Sent on Interface", - []string{"container_id", "interface"}, + []string{"container_id", "namespace", "pod", "container", "interface"}, nil, ) c.packetsReceived = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "network_receive_packets_total"), "Packets Received on Interface", - []string{"container_id", "interface"}, + []string{"container_id", "namespace", "pod", "container", "interface"}, nil, ) c.packetsSent = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "network_transmit_packets_total"), "Packets Sent on Interface", - []string{"container_id", "interface"}, + []string{"container_id", "namespace", "pod", "container", "interface"}, nil, ) c.droppedPacketsIncoming = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "network_receive_packets_dropped_total"), "Dropped Incoming Packets on Interface", - []string{"container_id", "interface"}, + []string{"container_id", "namespace", "pod", "container", "interface"}, nil, ) c.droppedPacketsOutgoing = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "network_transmit_packets_dropped_total"), "Dropped Outgoing Packets on Interface", - []string{"container_id", "interface"}, + []string{"container_id", "namespace", "pod", "container", "interface"}, nil, ) c.readCountNormalized = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "storage_read_count_normalized_total"), "Read Count Normalized", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.readSizeBytes = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "storage_read_size_bytes_total"), "Read Size Bytes", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.writeCountNormalized = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "storage_write_count_normalized_total"), "Write Count Normalized", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) c.writeSizeBytes = prometheus.NewDesc( prometheus.BuildFQName(types.Namespace, Name, "storage_write_size_bytes_total"), "Write Size Bytes", - []string{"container_id"}, + []string{"container_id", "namespace", "pod", "container"}, nil, ) @@ -216,39 +281,85 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { // Collect sends the metric values for each metric // to the provided prometheus Metric channel. func (c *Collector) Collect(ch chan<- prometheus.Metric) error { + errs := make([]error, 0) + + if slices.Contains(c.config.CollectorsEnabled, subCollectorHCS) { + if err := c.collectHCS(ch); err != nil { + errs = append(errs, err) + } + } + + if slices.Contains(c.config.CollectorsEnabled, subCollectorHostprocess) { + if err := c.collectJobContainers(ch); err != nil { + errs = append(errs, err) + } + } + + return errors.Join(errs...) +} + +func (c *Collector) collectHCS(ch chan<- prometheus.Metric) error { // Types Container is passed to get the containers compute systems only - containers, err := hcsshim.GetContainers(hcsshim.ComputeSystemQuery{Types: []string{"Container"}}) + containers, err := hcs.GetContainers() if err != nil { return fmt.Errorf("error in fetching containers: %w", err) } count := len(containers) - - ch <- prometheus.MustNewConstMetric( - c.containersCount, - prometheus.GaugeValue, - float64(count), - ) - if count == 0 { + ch <- prometheus.MustNewConstMetric( + c.containersCount, + prometheus.GaugeValue, + 0, + ) + return nil } - containerPrefixes := make(map[string]string) + var countersCount float64 + + containerIDs := make([]string, 0, len(containers)) collectErrors := make([]error, 0, len(containers)) - for _, containerDetails := range containers { - containerIdWithPrefix := getContainerIdWithPrefix(containerDetails) + for _, container := range containers { + if container.State != "Running" { + continue + } + + containerIDs = append(containerIDs, container.ID) + + countersCount++ - if err = c.collectContainer(ch, containerDetails, containerIdWithPrefix); err != nil { - if hcsshim.IsNotExist(err) { + var ( + namespace string + podName string + containerName string + ) + + if _, ok := c.annotationsCacheHCS[container.ID]; !ok { + if spec, err := getContainerAnnotations(container.ID); err == nil { + namespace = spec.Annotations["io.kubernetes.cri.sandbox-namespace"] + podName = spec.Annotations["io.kubernetes.cri.sandbox-name"] + containerName = spec.Annotations["io.kubernetes.cri.container-name"] + } + + c.annotationsCacheHCS[container.ID] = containerInfo{ + id: getContainerIdWithPrefix(container), + namespace: namespace, + pod: podName, + container: containerName, + } + } + + if err = c.collectHCSContainer(ch, container, c.annotationsCacheHCS[container.ID]); err != nil { + if errors.Is(err, hcs.ErrIDNotFound) { c.logger.Debug("err in fetching container statistics", - slog.String("container_id", containerDetails.ID), + slog.String("container_id", container.ID), slog.Any("err", err), ) } else { c.logger.Error("err in fetching container statistics", - slog.String("container_id", containerDetails.ID), + slog.String("container_id", container.ID), slog.Any("err", err), ) @@ -257,14 +368,25 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error { continue } - - containerPrefixes[containerDetails.ID] = containerIdWithPrefix } - if err = c.collectNetworkMetrics(ch, containerPrefixes); err != nil { + ch <- prometheus.MustNewConstMetric( + c.containersCount, + prometheus.GaugeValue, + countersCount, + ) + + if err = c.collectNetworkMetrics(ch); err != nil { return fmt.Errorf("error in fetching container network statistics: %w", err) } + // Remove containers that are no longer running + for _, containerID := range c.annotationsCacheHCS { + if !slices.Contains(containerIDs, containerID.id) { + delete(c.annotationsCacheHCS, containerID.id) + } + } + if len(collectErrors) > 0 { return fmt.Errorf("errors while fetching container statistics: %w", errors.Join(collectErrors...)) } @@ -272,94 +394,87 @@ func (c *Collector) Collect(ch chan<- prometheus.Metric) error { return nil } -func (c *Collector) collectContainer(ch chan<- prometheus.Metric, containerDetails hcsshim.ContainerProperties, containerIdWithPrefix string) error { - container, err := hcsshim.OpenContainer(containerDetails.ID) +func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDetails hcs.Properties, containerInfo containerInfo) error { + containerStats, err := hcs.GetContainerStatistics(containerDetails.ID) if err != nil { - return fmt.Errorf("error in opening container: %w", err) - } - - defer func() { - if container == nil { - return - } - - if err := container.Close(); err != nil { - c.logger.Error("error in closing container", - slog.Any("err", err), - ) - } - }() - - containerStats, err := container.Statistics() - if err != nil { - return fmt.Errorf("error in fetching container statistics: %w", err) + return fmt.Errorf("error fetching container statistics: %w", err) } ch <- prometheus.MustNewConstMetric( c.containerAvailable, prometheus.CounterValue, 1, - containerIdWithPrefix, + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.usageCommitBytes, prometheus.GaugeValue, - float64(containerStats.Memory.UsageCommitBytes), - containerIdWithPrefix, + float64(containerStats.Memory.MemoryUsageCommitBytes), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.usageCommitPeakBytes, prometheus.GaugeValue, - float64(containerStats.Memory.UsageCommitPeakBytes), - containerIdWithPrefix, + float64(containerStats.Memory.MemoryUsageCommitPeakBytes), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.usagePrivateWorkingSetBytes, prometheus.GaugeValue, - float64(containerStats.Memory.UsagePrivateWorkingSetBytes), - containerIdWithPrefix, + float64(containerStats.Memory.MemoryUsagePrivateWorkingSetBytes), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.runtimeTotal, prometheus.CounterValue, float64(containerStats.Processor.TotalRuntime100ns)*pdh.TicksToSecondScaleFactor, - containerIdWithPrefix, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.runtimeUser, prometheus.CounterValue, float64(containerStats.Processor.RuntimeUser100ns)*pdh.TicksToSecondScaleFactor, - containerIdWithPrefix, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.runtimeKernel, prometheus.CounterValue, float64(containerStats.Processor.RuntimeKernel100ns)*pdh.TicksToSecondScaleFactor, - containerIdWithPrefix, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.readCountNormalized, prometheus.CounterValue, float64(containerStats.Storage.ReadCountNormalized), - containerIdWithPrefix, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.readSizeBytes, prometheus.CounterValue, float64(containerStats.Storage.ReadSizeBytes), - containerIdWithPrefix, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.writeCountNormalized, prometheus.CounterValue, float64(containerStats.Storage.WriteCountNormalized), - containerIdWithPrefix, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) ch <- prometheus.MustNewConstMetric( c.writeSizeBytes, prometheus.CounterValue, float64(containerStats.Storage.WriteSizeBytes), - containerIdWithPrefix, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, ) return nil @@ -369,73 +484,105 @@ func (c *Collector) collectContainer(ch chan<- prometheus.Metric, containerDetai // With HNSv2, the network stats must be collected from hcsshim.HNSListEndpointRequest. // Network statistics from the container.Statistics() are providing data only, if HNSv1 is used. // Ref: https://github.com/prometheus-community/windows_exporter/pull/1218 -func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric, containerPrefixes map[string]string) error { - hnsEndpoints, err := hcsshim.HNSListEndpointRequest() +func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error { + endpoints, err := hcn.EnumerateEndpoints() if err != nil { - return fmt.Errorf("error in fetching HNS endpoints: %w", err) + return fmt.Errorf("error in fetching HCN endpoints: %w", err) } - if len(hnsEndpoints) == 0 { - return errors.New("no network stats for containers to collect") + if len(endpoints) == 0 { + return nil } - for _, endpoint := range hnsEndpoints { - endpointStats, err := hcsshim.GetHNSEndpointStats(endpoint.Id) + for _, endpoint := range endpoints { + properties, err := hcn.GetEndpointProperties(endpoint) + if err != nil { + c.logger.Warn("Failed to collect properties for interface "+endpoint.String(), + slog.Any("err", err), + ) + + continue + } + + var nicGUID *guid.GUID + + for _, allocator := range properties.Resources.Allocators { + if allocator.AdapterNetCfgInstanceId != nil { + nicGUID = allocator.AdapterNetCfgInstanceId + + break + } + } + + if nicGUID == nil { + c.logger.Warn("Failed to get nic GUID for endpoint " + endpoint.String()) + + continue + } + + luid, err := iphlpapi.ConvertInterfaceGUIDToLUID(*nicGUID) if err != nil { - c.logger.Warn("Failed to collect network stats for interface "+endpoint.Id, + return fmt.Errorf("error in converting interface GUID to LUID: %w", err) + } + + var endpointStats iphlpapi.MIB_IF_ROW2 + endpointStats.InterfaceLuid = luid + + if err := iphlpapi.GetIfEntry2Ex(&endpointStats); err != nil { + c.logger.Warn("Failed to get interface entry for endpoint "+endpoint.String(), slog.Any("err", err), ) continue } - for _, containerId := range endpoint.SharedContainers { - containerIdWithPrefix, ok := containerPrefixes[containerId] + for _, containerId := range properties.SharedContainers { + containerInfo, ok := c.annotationsCacheHCS[containerId] if !ok { - c.logger.Debug("Failed to collect network stats for container " + containerId) + c.logger.Debug("Unknown container " + containerId + " for endpoint " + endpoint.String()) continue } - endpointId := strings.ToUpper(endpoint.Id) + endpointId := strings.ToUpper(endpoint.String()) ch <- prometheus.MustNewConstMetric( c.bytesReceived, prometheus.CounterValue, - float64(endpointStats.BytesReceived), - containerIdWithPrefix, endpointId, + float64(endpointStats.InOctets), + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.bytesSent, prometheus.CounterValue, - float64(endpointStats.BytesSent), - containerIdWithPrefix, endpointId, + float64(endpointStats.OutOctets), + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.packetsReceived, prometheus.CounterValue, - float64(endpointStats.PacketsReceived), - containerIdWithPrefix, endpointId, + float64(endpointStats.InUcastPkts+endpointStats.InNUcastPkts), + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.packetsSent, prometheus.CounterValue, - float64(endpointStats.PacketsSent), - containerIdWithPrefix, endpointId, + float64(endpointStats.OutUcastPkts+endpointStats.OutNUcastPkts), + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.droppedPacketsIncoming, prometheus.CounterValue, - float64(endpointStats.DroppedPacketsIncoming), - containerIdWithPrefix, endpointId, + float64(endpointStats.InDiscards+endpointStats.InErrors), + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.droppedPacketsOutgoing, prometheus.CounterValue, - float64(endpointStats.DroppedPacketsOutgoing), - containerIdWithPrefix, endpointId, + float64(endpointStats.OutDiscards+endpointStats.OutErrors), + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) } } @@ -443,12 +590,277 @@ func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric, container return nil } -func getContainerIdWithPrefix(containerDetails hcsshim.ContainerProperties) string { - switch containerDetails.Owner { +// collectJobContainers collects container metrics for job containers. +// Job container based on Win32 Job objects. +// https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects +// +// Job containers are containers that aren't managed by HCS, e.g host process containers. +func (c *Collector) collectJobContainers(ch chan<- prometheus.Metric) error { + containerDStateFS := os.DirFS(`C:\ProgramData\containerd\state\io.containerd.runtime.v2.task\k8s.io\`) + + allContainerIDs := make([]string, 0, len(c.annotationsCacheJob)+len(c.annotationsCacheHCS)) + jobContainerIDs := make([]string, 0, len(allContainerIDs)) + + if err := fs.WalkDir(containerDStateFS, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if !d.IsDir() { + return nil + } + + if _, err := os.Stat(path + "\\config.json"); err != nil { + containerID := strings.TrimPrefix(strings.Replace(path, containerDStateDir, "", 1), `\`) + allContainerIDs = append(allContainerIDs, containerID) + } + + // Skip the directory content + return fs.SkipDir + }); err != nil { + return err + } + + errs := make([]error, 0) + + for _, containerID := range allContainerIDs { + if err := c.collectJobContainer(ch, containerID); err != nil { + errs = append(errs, err) + } else { + jobContainerIDs = append(jobContainerIDs, containerID) + } + } + + // Remove containers that are no longer running + for _, containerID := range c.annotationsCacheJob { + if !slices.Contains(jobContainerIDs, containerID.id) { + delete(c.annotationsCacheJob, containerID.id) + } + } + + return errors.Join(errs...) +} + +func (c *Collector) collectJobContainer(ch chan<- prometheus.Metric, containerID string) error { + jobObjectHandle, err := kernel32.OpenJobObject("JobContainer_" + containerID) + if err != nil { + if errors.Is(err, windows.ERROR_FILE_NOT_FOUND) { + return nil + } + + return fmt.Errorf("error in opening job object: %w", err) + } + + defer func(fd windows.Handle) { + _ = windows.Close(fd) + }(jobObjectHandle) + + if _, ok := c.annotationsCacheJob[containerID]; !ok { + var ( + namespace string + podName string + containerName string + ) + + if spec, err := getContainerAnnotations(containerID); err == nil { + namespace = spec.Annotations["io.kubernetes.cri.sandbox-namespace"] + podName = spec.Annotations["io.kubernetes.cri.sandbox-name"] + containerName = spec.Annotations["io.kubernetes.cri.container-name"] + } + + c.annotationsCacheJob[containerID] = containerInfo{ + id: "containerd://" + containerID, + namespace: namespace, + pod: podName, + container: containerName, + } + } + + var jobInfo kernel32.JobObjectExtendedLimitInformation + + retLen := uint32(unsafe.Sizeof(jobInfo)) + + if err := windows.QueryInformationJobObject( + jobObjectHandle, + windows.JobObjectExtendedLimitInformation, + uintptr(unsafe.Pointer(&jobInfo)), + retLen, &retLen); err != nil { + return err + } + + privateWorkingSetBytes, err := calculatePrivateWorkingSetBytes(jobObjectHandle) + if err != nil { + c.logger.Debug("error in calculating private working set bytes", slog.Any("err", err)) + } + + containerInfo := c.annotationsCacheJob[containerID] + + ch <- prometheus.MustNewConstMetric( + c.containerAvailable, + prometheus.CounterValue, + 1, + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.usageCommitBytes, + prometheus.GaugeValue, + float64(jobInfo.JobMemoryLimit), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.usageCommitPeakBytes, + prometheus.GaugeValue, + float64(jobInfo.PeakProcessMemoryUsed), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.usagePrivateWorkingSetBytes, + prometheus.GaugeValue, + float64(privateWorkingSetBytes), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.runtimeTotal, + prometheus.CounterValue, + (float64(jobInfo.BasicInfo.ThisPeriodTotalKernelTime)+float64(jobInfo.BasicInfo.ThisPeriodTotalUserTime))*pdh.TicksToSecondScaleFactor, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.runtimeUser, + prometheus.CounterValue, + float64(jobInfo.BasicInfo.ThisPeriodTotalUserTime)*pdh.TicksToSecondScaleFactor, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.runtimeKernel, + prometheus.CounterValue, + float64(jobInfo.BasicInfo.ThisPeriodTotalKernelTime)*pdh.TicksToSecondScaleFactor, + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.readCountNormalized, + prometheus.CounterValue, + float64(jobInfo.IoInfo.ReadOperationCount), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.readSizeBytes, + prometheus.CounterValue, + float64(jobInfo.IoInfo.ReadTransferCount), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.writeCountNormalized, + prometheus.CounterValue, + float64(jobInfo.IoInfo.WriteOperationCount), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + ch <- prometheus.MustNewConstMetric( + c.writeSizeBytes, + prometheus.CounterValue, + float64(jobInfo.IoInfo.WriteTransferCount), + + containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, + ) + + return nil +} + +func getContainerIdWithPrefix(container hcs.Properties) string { + switch container.Owner { case "containerd-shim-runhcs-v1.exe": - return "containerd://" + containerDetails.ID + return "containerd://" + container.ID default: // default to docker or if owner is not set - return "docker://" + containerDetails.ID + return "docker://" + container.ID + } +} + +func getContainerAnnotations(containerID string) (ociSpec, error) { + configJSON, err := os.OpenFile(containerDStateDir+containerID+`\config.json`, os.O_RDONLY, 0) + if err != nil { + return ociSpec{}, fmt.Errorf("error in opening config.json file: %w", err) + } + + var annotations ociSpec + + if err = json.NewDecoder(configJSON).Decode(&annotations); err != nil { + return ociSpec{}, fmt.Errorf("error in decoding config.json file: %w", err) } + + return annotations, nil +} + +func calculatePrivateWorkingSetBytes(jobObjectHandle windows.Handle) (uint64, error) { + var pidList kernel32.JobObjectBasicProcessIDList + + retLen := uint32(unsafe.Sizeof(pidList)) + + if err := windows.QueryInformationJobObject( + jobObjectHandle, + windows.JobObjectBasicProcessIdList, + uintptr(unsafe.Pointer(&pidList)), + retLen, &retLen); err != nil { + return 0, err + } + + var ( + privateWorkingSetBytes uint64 + vmCounters kernel32.PROCESS_VM_COUNTERS + ) + + retLen = uint32(unsafe.Sizeof(vmCounters)) + + getPrivateWorkingSetBytes := func(pid uint32) (uint64, error) { + processHandle, err := windows.OpenProcess(windows.PROCESS_QUERY_LIMITED_INFORMATION, false, pid) + if err != nil { + return 0, fmt.Errorf("error in opening process: %w", err) + } + + defer func(fd windows.Handle) { + _ = windows.Close(fd) + }(processHandle) + + var isInJob bool + + if err := kernel32.IsProcessInJob(processHandle, jobObjectHandle, &isInJob); err != nil { + return 0, fmt.Errorf("error in checking if process is in job: %w", err) + } + + if !isInJob { + return 0, nil + } + + if err := windows.NtQueryInformationProcess( + processHandle, + windows.ProcessVmCounters, + unsafe.Pointer(&vmCounters), + retLen, + &retLen, + ); err != nil { + return 0, fmt.Errorf("error in querying process information: %w", err) + } + + return uint64(vmCounters.PrivateWorkingSetSize), nil + } + + for _, pid := range pidList.PIDs() { + privateWorkingSetSize, err := getPrivateWorkingSetBytes(pid) + if err != nil { + return 0, fmt.Errorf("error in getting private working set bytes: %w", err) + } + + privateWorkingSetBytes += privateWorkingSetSize + } + + return privateWorkingSetBytes, nil } diff --git a/internal/collector/cs/cs.go b/internal/collector/cs/cs.go index 61a19ed5a..f5f36eee6 100644 --- a/internal/collector/cs/cs.go +++ b/internal/collector/cs/cs.go @@ -19,9 +19,9 @@ import ( "log/slog" "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-community/windows_exporter/internal/win32/sysinfoapi" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/dhcp/dhcp.go b/internal/collector/dhcp/dhcp.go index 49991e64f..7cd29ce79 100644 --- a/internal/collector/dhcp/dhcp.go +++ b/internal/collector/dhcp/dhcp.go @@ -24,10 +24,10 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" - "github.com/prometheus-community/windows_exporter/internal/headers/dhcpsapi" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/win32/dhcpsapi" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/hyperv/hyperv.go b/internal/collector/hyperv/hyperv.go index ea4aeb8cd..9efe86eb3 100644 --- a/internal/collector/hyperv/hyperv.go +++ b/internal/collector/hyperv/hyperv.go @@ -23,9 +23,9 @@ import ( "strings" "sync" - "github.com/Microsoft/hcsshim/osversion" "github.com/alecthomas/kingpin/v2" "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/osversion" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/license/license.go b/internal/collector/license/license.go index e40caa519..2af228440 100644 --- a/internal/collector/license/license.go +++ b/internal/collector/license/license.go @@ -19,9 +19,9 @@ import ( "log/slog" "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-community/windows_exporter/internal/win32/slc" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/logon/logon.go b/internal/collector/logon/logon.go index 6d91829bc..1c33b6bb8 100644 --- a/internal/collector/logon/logon.go +++ b/internal/collector/logon/logon.go @@ -20,9 +20,9 @@ import ( "log/slog" "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-community/windows_exporter/internal/win32/secur32" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/memory/memory.go b/internal/collector/memory/memory.go index 3c779e837..6aba03a10 100644 --- a/internal/collector/memory/memory.go +++ b/internal/collector/memory/memory.go @@ -24,10 +24,10 @@ import ( "log/slog" "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/pdh" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/win32/sysinfoapi" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/mscluster/mscluster_cluster.go b/internal/collector/mscluster/mscluster_cluster.go index 0099779b3..0c30fa851 100644 --- a/internal/collector/mscluster/mscluster_cluster.go +++ b/internal/collector/mscluster/mscluster_cluster.go @@ -18,8 +18,8 @@ package mscluster import ( "fmt" - "github.com/Microsoft/hcsshim/osversion" "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/osversion" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/mscluster/mscluster_node.go b/internal/collector/mscluster/mscluster_node.go index 3b45370fb..18e5876f1 100644 --- a/internal/collector/mscluster/mscluster_node.go +++ b/internal/collector/mscluster/mscluster_node.go @@ -18,8 +18,8 @@ package mscluster import ( "fmt" - "github.com/Microsoft/hcsshim/osversion" "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/osversion" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/net/net.go b/internal/collector/net/net.go index 7e445c096..2d4e15651 100644 --- a/internal/collector/net/net.go +++ b/internal/collector/net/net.go @@ -33,7 +33,12 @@ import ( "golang.org/x/sys/windows" ) -const Name = "net" +const ( + Name = "net" + + subCollectorMetrics = "metrics" + subCollectorNICAddresses = "nic_addresses" +) type Config struct { NicExclude *regexp.Regexp `yaml:"nic_exclude"` @@ -46,8 +51,8 @@ var ConfigDefaults = Config{ NicExclude: types.RegExpEmpty, NicInclude: types.RegExpAny, CollectorsEnabled: []string{ - "metrics", - "nic_addresses", + subCollectorMetrics, + subCollectorNICAddresses, }, } @@ -157,14 +162,13 @@ func (c *Collector) Close() error { } func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { - var err error - - c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "Network Interface", pdh.InstancesAll) - if err != nil { - return fmt.Errorf("failed to create Network Interface collector: %w", err) + for _, collector := range c.config.CollectorsEnabled { + if !slices.Contains([]string{subCollectorMetrics, subCollectorNICAddresses}, collector) { + return fmt.Errorf("unknown collector: %s", collector) + } } - if slices.Contains(c.config.CollectorsEnabled, "addresses") { + if slices.Contains(c.config.CollectorsEnabled, subCollectorNICAddresses) { logger.Info("nic/addresses collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.", slog.String("collector", Name), ) @@ -261,6 +265,13 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { nil, ) + var err error + + c.perfDataCollector, err = pdh.NewCollector[perfDataCounterValues](pdh.CounterTypeRaw, "Network Interface", pdh.InstancesAll) + if err != nil { + return fmt.Errorf("failed to create Network Interface collector: %w", err) + } + return nil } @@ -269,13 +280,13 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { func (c *Collector) Collect(ch chan<- prometheus.Metric) error { errs := make([]error, 0, 2) - if slices.Contains(c.config.CollectorsEnabled, "metrics") { + if slices.Contains(c.config.CollectorsEnabled, subCollectorMetrics) { if err := c.collect(ch); err != nil { errs = append(errs, fmt.Errorf("failed collecting metrics: %w", err)) } } - if slices.Contains(c.config.CollectorsEnabled, "nic_addresses") { + if slices.Contains(c.config.CollectorsEnabled, subCollectorNICAddresses) { if err := c.collectNICAddresses(ch); err != nil { errs = append(errs, fmt.Errorf("failed collecting net addresses: %w", err)) } diff --git a/internal/collector/os/os.go b/internal/collector/os/os.go index c5cdbfe94..add12064b 100644 --- a/internal/collector/os/os.go +++ b/internal/collector/os/os.go @@ -23,11 +23,11 @@ import ( "time" "github.com/alecthomas/kingpin/v2" - "github.com/prometheus-community/windows_exporter/internal/headers/kernel32" - "github.com/prometheus-community/windows_exporter/internal/headers/netapi32" - "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-community/windows_exporter/internal/win32/kernel32" + "github.com/prometheus-community/windows_exporter/internal/win32/netapi32" + "github.com/prometheus-community/windows_exporter/internal/win32/sysinfoapi" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/windows" "golang.org/x/sys/windows/registry" diff --git a/internal/collector/pagefile/pagefile.go b/internal/collector/pagefile/pagefile.go index 849e13198..b35155d25 100644 --- a/internal/collector/pagefile/pagefile.go +++ b/internal/collector/pagefile/pagefile.go @@ -22,10 +22,10 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" - "github.com/prometheus-community/windows_exporter/internal/headers/psapi" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/win32/psapi" "github.com/prometheus/client_golang/prometheus" ) diff --git a/internal/collector/performancecounter/types.go b/internal/collector/performancecounter/types.go index b57097e58..e46780a43 100644 --- a/internal/collector/performancecounter/types.go +++ b/internal/collector/performancecounter/types.go @@ -25,7 +25,7 @@ type Object struct { Type pdh.CounterType `json:"type" yaml:"type"` Instances []string `json:"instances" yaml:"instances"` Counters []Counter `json:"counters" yaml:"counters"` - InstanceLabel string `json:"instance_label" yaml:"instance_label"` //nolint:tagliatelle + InstanceLabel string `json:"instance_label" yaml:"instance_label"` collector *pdh.Collector perfDataObject any diff --git a/internal/collector/process/process.go b/internal/collector/process/process.go index bd9bbc9c2..e10c24117 100644 --- a/internal/collector/process/process.go +++ b/internal/collector/process/process.go @@ -39,7 +39,7 @@ const Name = "process" type Config struct { ProcessInclude *regexp.Regexp `yaml:"process_include"` ProcessExclude *regexp.Regexp `yaml:"process_exclude"` - EnableWorkerProcess bool `yaml:"enable_iis_worker_process"` //nolint:tagliatelle + EnableWorkerProcess bool `yaml:"enable_iis_worker_process"` } //nolint:gochecknoglobals diff --git a/internal/collector/tcp/tcp.go b/internal/collector/tcp/tcp.go index b54c05136..655303e1a 100644 --- a/internal/collector/tcp/tcp.go +++ b/internal/collector/tcp/tcp.go @@ -23,10 +23,10 @@ import ( "strings" "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/pdh" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/win32/iphlpapi" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/windows" ) diff --git a/internal/collector/terminal_services/terminal_services.go b/internal/collector/terminal_services/terminal_services.go index 107345a2b..2d79eabf0 100644 --- a/internal/collector/terminal_services/terminal_services.go +++ b/internal/collector/terminal_services/terminal_services.go @@ -23,11 +23,11 @@ import ( "strings" "github.com/alecthomas/kingpin/v2" - "github.com/prometheus-community/windows_exporter/internal/headers/wtsapi32" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" + "github.com/prometheus-community/windows_exporter/internal/win32/wtsapi32" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/windows" ) diff --git a/internal/collector/time/time.go b/internal/collector/time/time.go index b60763d0f..cc416be13 100644 --- a/internal/collector/time/time.go +++ b/internal/collector/time/time.go @@ -24,10 +24,10 @@ import ( "time" "github.com/alecthomas/kingpin/v2" - "github.com/prometheus-community/windows_exporter/internal/headers/kernel32" "github.com/prometheus-community/windows_exporter/internal/mi" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" + "github.com/prometheus-community/windows_exporter/internal/win32/kernel32" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/windows" ) diff --git a/internal/osversion/osversion_windows.go b/internal/osversion/osversion_windows.go new file mode 100644 index 000000000..26edcc1b7 --- /dev/null +++ b/internal/osversion/osversion_windows.go @@ -0,0 +1,73 @@ +package osversion + +import ( + "fmt" + "sync" + + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/registry" +) + +// OSVersion is a wrapper for Windows version information +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms724439(v=vs.85).aspx +type OSVersion struct { + Version uint32 + MajorVersion uint8 + MinorVersion uint8 + Build uint16 +} + +//nolint:gochecknoglobals +var osv = sync.OnceValue(func() OSVersion { + v := *windows.RtlGetVersion() + + return OSVersion{ + MajorVersion: uint8(v.MajorVersion), + MinorVersion: uint8(v.MinorVersion), + Build: uint16(v.BuildNumber), + // Fill version value so that existing clients don't break + Version: v.BuildNumber<<16 | (v.MinorVersion << 8) | v.MajorVersion, + } +}) + +// Get gets the operating system version on Windows. +// The calling application must be manifested to get the correct version information. +func Get() OSVersion { + return osv() +} + +// Build gets the build-number on Windows +// The calling application must be manifested to get the correct version information. +func Build() uint16 { + return Get().Build +} + +// String returns the OSVersion formatted as a string. It implements the +// [fmt.Stringer] interface. +func (osv OSVersion) String() string { + return fmt.Sprintf("%d.%d.%d", osv.MajorVersion, osv.MinorVersion, osv.Build) +} + +// ToString returns the OSVersion formatted as a string. +// +// Deprecated: use [OSVersion.String]. +func (osv OSVersion) ToString() string { + return osv.String() +} + +// Running `cmd /c ver` shows something like "10.0.20348.1000". The last component ("1000") is the revision +// number +func BuildRevision() (uint32, error) { + k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE) + if err != nil { + return 0, fmt.Errorf("open `CurrentVersion` registry key: %w", err) + } + defer k.Close() + + s, _, err := k.GetIntegerValue("UBR") + if err != nil { + return 0, fmt.Errorf("read `UBR` from registry: %w", err) + } + + return uint32(s), nil +} diff --git a/internal/osversion/osversion_windows_test.go b/internal/osversion/osversion_windows_test.go new file mode 100644 index 000000000..6b48e4315 --- /dev/null +++ b/internal/osversion/osversion_windows_test.go @@ -0,0 +1,19 @@ +package osversion + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestOSVersionString(t *testing.T) { + v := OSVersion{ + Version: 809042555, + MajorVersion: 123, + MinorVersion: 2, + Build: 12345, + } + + assert.Equal(t, "the version is: 123.2.12345", fmt.Sprintf("the version is: %s", v)) +} diff --git a/internal/osversion/windowsbuilds.go b/internal/osversion/windowsbuilds.go new file mode 100644 index 000000000..446369591 --- /dev/null +++ b/internal/osversion/windowsbuilds.go @@ -0,0 +1,84 @@ +package osversion + +// Windows Client and Server build numbers. +// +// See: +// https://learn.microsoft.com/en-us/windows/release-health/release-information +// https://learn.microsoft.com/en-us/windows/release-health/windows-server-release-info +// https://learn.microsoft.com/en-us/windows/release-health/windows11-release-information +const ( + // RS1 (version 1607, codename "Redstone 1") corresponds to Windows Server + // 2016 (ltsc2016) and Windows 10 (Anniversary Update). + RS1 = 14393 + // V1607 (version 1607, codename "Redstone 1") is an alias for [RS1]. + V1607 = RS1 + // LTSC2016 (Windows Server 2016) is an alias for [RS1]. + LTSC2016 = RS1 + + // RS2 (version 1703, codename "Redstone 2") was a client-only update, and + // corresponds to Windows 10 (Creators Update). + RS2 = 15063 + // V1703 (version 1703, codename "Redstone 2") is an alias for [RS2]. + V1703 = RS2 + + // RS3 (version 1709, codename "Redstone 3") corresponds to Windows Server + // 1709 (Semi-Annual Channel (SAC)), and Windows 10 (Fall Creators Update). + RS3 = 16299 + // V1709 (version 1709, codename "Redstone 3") is an alias for [RS3]. + V1709 = RS3 + + // RS4 (version 1803, codename "Redstone 4") corresponds to Windows Server + // 1803 (Semi-Annual Channel (SAC)), and Windows 10 (April 2018 Update). + RS4 = 17134 + // V1803 (version 1803, codename "Redstone 4") is an alias for [RS4]. + V1803 = RS4 + + // RS5 (version 1809, codename "Redstone 5") corresponds to Windows Server + // 2019 (ltsc2019), and Windows 10 (October 2018 Update). + RS5 = 17763 + // V1809 (version 1809, codename "Redstone 5") is an alias for [RS5]. + V1809 = RS5 + // LTSC2019 (Windows Server 2019) is an alias for [RS5]. + LTSC2019 = RS5 + + // V19H1 (version 1903, codename 19H1) corresponds to Windows Server 1903 (semi-annual + // channel). + V19H1 = 18362 + // V1903 (version 1903) is an alias for [V19H1]. + V1903 = V19H1 + + // V19H2 (version 1909, codename 19H2) corresponds to Windows Server 1909 (semi-annual + // channel). + V19H2 = 18363 + // V1909 (version 1909) is an alias for [V19H2]. + V1909 = V19H2 + + // V20H1 (version 2004, codename 20H1) corresponds to Windows Server 2004 (semi-annual + // channel). + V20H1 = 19041 + // V2004 (version 2004) is an alias for [V20H1]. + V2004 = V20H1 + + // V20H2 corresponds to Windows Server 20H2 (semi-annual channel). + V20H2 = 19042 + + // V21H1 corresponds to Windows Server 21H1 (semi-annual channel). + V21H1 = 19043 + + // V21H2Win10 corresponds to Windows 10 (November 2021 Update). + V21H2Win10 = 19044 + + // V21H2Server corresponds to Windows Server 2022 (ltsc2022). + V21H2Server = 20348 + // LTSC2022 (Windows Server 2022) is an alias for [V21H2Server] + LTSC2022 = V21H2Server + + // V21H2Win11 corresponds to Windows 11 (original release). + V21H2Win11 = 22000 + + // V22H2Win10 corresponds to Windows 10 (2022 Update). + V22H2Win10 = 19045 + + // V22H2Win11 corresponds to Windows 11 (2022 Update). + V22H2Win11 = 22621 +) diff --git a/internal/pdh/pdh.go b/internal/pdh/pdh.go index 2ef8341e2..bdc68dcf7 100644 --- a/internal/pdh/pdh.go +++ b/internal/pdh/pdh.go @@ -38,7 +38,7 @@ import ( "time" "unsafe" - "github.com/prometheus-community/windows_exporter/internal/headers/kernel32" + "github.com/prometheus-community/windows_exporter/internal/win32/kernel32" "golang.org/x/sys/windows" ) diff --git a/internal/headers/dhcpsapi/dhcpsapi.go b/internal/win32/dhcpsapi/dhcpsapi.go similarity index 100% rename from internal/headers/dhcpsapi/dhcpsapi.go rename to internal/win32/dhcpsapi/dhcpsapi.go diff --git a/internal/headers/dhcpsapi/dhcpsapi_test.go b/internal/win32/dhcpsapi/dhcpsapi_test.go similarity index 100% rename from internal/headers/dhcpsapi/dhcpsapi_test.go rename to internal/win32/dhcpsapi/dhcpsapi_test.go diff --git a/internal/headers/dhcpsapi/types.go b/internal/win32/dhcpsapi/types.go similarity index 97% rename from internal/headers/dhcpsapi/types.go rename to internal/win32/dhcpsapi/types.go index 45004b1ab..b78689692 100644 --- a/internal/headers/dhcpsapi/types.go +++ b/internal/win32/dhcpsapi/types.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "net" - "github.com/prometheus-community/windows_exporter/internal/headers/win32api" + "github.com/prometheus-community/windows_exporter/internal/win32/win32api" "golang.org/x/sys/windows" ) diff --git a/internal/win32/guid/guid.go b/internal/win32/guid/guid.go new file mode 100644 index 000000000..e1286096d --- /dev/null +++ b/internal/win32/guid/guid.go @@ -0,0 +1,79 @@ +package guid + +import ( + "fmt" + "strconv" + "strings" + + "golang.org/x/sys/windows" +) + +type GUID windows.GUID + +// FromString parses a string containing a GUID and returns the GUID. The only +// format currently supported is the `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` +// format. +func FromString(s string) (GUID, error) { + if len(s) != 36 { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + var g GUID + + data1, err := strconv.ParseUint(s[0:8], 16, 32) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + g.Data1 = uint32(data1) + + data2, err := strconv.ParseUint(s[9:13], 16, 16) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + g.Data2 = uint16(data2) + + data3, err := strconv.ParseUint(s[14:18], 16, 16) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + g.Data3 = uint16(data3) + + for i, x := range []int{19, 21, 24, 26, 28, 30, 32, 34} { + v, err := strconv.ParseUint(s[x:x+2], 16, 8) + if err != nil { + return GUID{}, fmt.Errorf("invalid GUID %q", s) + } + + g.Data4[i] = uint8(v) + } + + return g, nil +} + +func (g *GUID) UnmarshalJSON(b []byte) error { + guid, err := FromString(strings.Trim(strings.Trim(string(b), `"`), `{}`)) + if err != nil { + return err + } + + *g = guid + + return nil +} + +func (g *GUID) String() string { + return fmt.Sprintf( + "%08x-%04x-%04x-%04x-%012x", + g.Data1, + g.Data2, + g.Data3, + g.Data4[:2], + g.Data4[2:]) +} diff --git a/internal/win32/hcn/hcn.go b/internal/win32/hcn/hcn.go new file mode 100644 index 000000000..4a07b3d1f --- /dev/null +++ b/internal/win32/hcn/hcn.go @@ -0,0 +1,30 @@ +package hcn + +import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/utils" + "github.com/prometheus-community/windows_exporter/internal/win32/guid" + "golang.org/x/sys/windows" +) + +//nolint:gochecknoglobals +var ( + defaultQuery = utils.Must(windows.UTF16PtrFromString(`{"SchemaVersion":{"Major": 2,"Minor": 0},"Flags":"None"}`)) +) + +func GetEndpointProperties(endpointID guid.GUID) (EndpointProperties, error) { + endpoint, err := OpenEndpoint(endpointID) + if err != nil { + return EndpointProperties{}, fmt.Errorf("failed to open endpoint: %w", err) + } + + defer CloseEndpoint(endpoint) + + result, err := QueryEndpointProperties(endpoint, defaultQuery) + if err != nil { + return EndpointProperties{}, fmt.Errorf("failed to query endpoint properties: %w", err) + } + + return result, nil +} diff --git a/internal/win32/hcn/hcn_test.go b/internal/win32/hcn/hcn_test.go new file mode 100644 index 000000000..323484297 --- /dev/null +++ b/internal/win32/hcn/hcn_test.go @@ -0,0 +1,30 @@ +package hcn_test + +import ( + "testing" + + "github.com/prometheus-community/windows_exporter/internal/win32/hcn" + "github.com/stretchr/testify/require" +) + +func TestEnumerateEndpoints(t *testing.T) { + t.Parallel() + + endpoints, err := hcn.EnumerateEndpoints() + require.NoError(t, err) + require.NotNil(t, endpoints) +} + +func TestQueryEndpointProperties(t *testing.T) { + t.Parallel() + + endpoints, err := hcn.EnumerateEndpoints() + require.NoError(t, err) + + if len(endpoints) == 0 { + t.Skip("No endpoints found") + } + + _, err = hcn.GetEndpointProperties(endpoints[0]) + require.NoError(t, err) +} diff --git a/internal/win32/hcn/syscall.go b/internal/win32/hcn/syscall.go new file mode 100644 index 000000000..864b0e7bc --- /dev/null +++ b/internal/win32/hcn/syscall.go @@ -0,0 +1,117 @@ +package hcn + +import ( + "encoding/json" + "fmt" + "unsafe" + + "github.com/prometheus-community/windows_exporter/internal/win32/guid" + "github.com/prometheus-community/windows_exporter/internal/win32/hcs" + "golang.org/x/sys/windows" +) + +//nolint:gochecknoglobals +var ( + modComputeNetwork = windows.NewLazySystemDLL("computenetwork.dll") + + procHcnEnumerateEndpoints = modComputeNetwork.NewProc("HcnEnumerateEndpoints") + procHcnOpenEndpoint = modComputeNetwork.NewProc("HcnOpenEndpoint") + procHcnQueryEndpointProperties = modComputeNetwork.NewProc("HcnQueryEndpointProperties") + procHcnCloseEndpoint = modComputeNetwork.NewProc("HcnCloseEndpoint") +) + +// EnumerateEndpoints enumerates the endpoints. +// +// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnenumerateendpoints +func EnumerateEndpoints() ([]guid.GUID, error) { + var ( + endpointsJSON *uint16 + errorRecord *uint16 + ) + + r1, _, _ := procHcnEnumerateEndpoints.Call( + 0, + uintptr(unsafe.Pointer(&endpointsJSON)), + uintptr(unsafe.Pointer(&errorRecord)), + ) + + windows.CoTaskMemFree(unsafe.Pointer(errorRecord)) + result := windows.UTF16PtrToString(endpointsJSON) + + if r1 != 0 { + return nil, fmt.Errorf("HcnEnumerateEndpoints failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1)) + } + + var endpoints []guid.GUID + + if err := json.Unmarshal([]byte(result), &endpoints); err != nil { + return nil, fmt.Errorf("failed to unmarshal JSON: %w", err) + } + + return endpoints, nil +} + +// OpenEndpoint opens an endpoint. +// +// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnopenendpoint +func OpenEndpoint(id guid.GUID) (Endpoint, error) { + var ( + endpoint Endpoint + errorRecord *uint16 + ) + + r1, _, _ := procHcnOpenEndpoint.Call( + uintptr(unsafe.Pointer(&id)), + uintptr(unsafe.Pointer(&endpoint)), + uintptr(unsafe.Pointer(&errorRecord)), + ) + + windows.CoTaskMemFree(unsafe.Pointer(errorRecord)) + + if r1 != 0 { + return 0, fmt.Errorf("HcnOpenEndpoint failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1)) + } + + return endpoint, nil +} + +// QueryEndpointProperties queries the properties of an endpoint. +// +// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnqueryendpointproperties +func QueryEndpointProperties(endpoint Endpoint, propertyQuery *uint16) (EndpointProperties, error) { + var ( + resultDocument *uint16 + errorRecord *uint16 + ) + + r1, _, _ := procHcnQueryEndpointProperties.Call( + uintptr(endpoint), + uintptr(unsafe.Pointer(&propertyQuery)), + uintptr(unsafe.Pointer(&resultDocument)), + uintptr(unsafe.Pointer(&errorRecord)), + ) + + windows.CoTaskMemFree(unsafe.Pointer(errorRecord)) + + result := windows.UTF16PtrToString(resultDocument) + windows.CoTaskMemFree(unsafe.Pointer(resultDocument)) + + if r1 != 0 { + return EndpointProperties{}, fmt.Errorf("HcsGetComputeSystemProperties failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1)) + } + + var properties EndpointProperties + + if err := json.Unmarshal([]byte(result), &properties); err != nil { + return EndpointProperties{}, fmt.Errorf("failed to unmarshal JSON: %w", err) + } + + return properties, nil +} + +// CloseEndpoint close a handle to an Endpoint. +// +// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcncloseendpoint +func CloseEndpoint(endpoint Endpoint) { + _, _, _ = procHcnCloseEndpoint.Call(uintptr(endpoint)) +} diff --git a/internal/win32/hcn/types.go b/internal/win32/hcn/types.go new file mode 100644 index 000000000..c36913c50 --- /dev/null +++ b/internal/win32/hcn/types.go @@ -0,0 +1,36 @@ +package hcn + +import ( + "github.com/prometheus-community/windows_exporter/internal/win32/guid" + "golang.org/x/sys/windows" +) + +type Endpoint = windows.Handle + +// EndpointProperties contains the properties of an HCN endpoint. +// +// https://learn.microsoft.com/en-us/virtualization/api/hcn/hns_schema#HostComputeEndpoint +type EndpointProperties struct { + ID string `json:"ID"` + State int `json:"State"` + SharedContainers []string `json:"SharedContainers"` + Resources EndpointPropertiesResources `json:"Resources"` +} + +type EndpointPropertiesResources struct { + Allocators []EndpointPropertiesAllocators `json:"Allocators"` +} +type EndpointPropertiesAllocators struct { + AdapterNetCfgInstanceId *guid.GUID `json:"AdapterNetCfgInstanceId"` +} + +type EndpointStats struct { + BytesReceived uint64 `json:"BytesReceived"` + BytesSent uint64 `json:"BytesSent"` + DroppedPacketsIncoming uint64 `json:"DroppedPacketsIncoming"` + DroppedPacketsOutgoing uint64 `json:"DroppedPacketsOutgoing"` + EndpointID string `json:"EndpointId"` + InstanceID string `json:"InstanceId"` + PacketsReceived uint64 `json:"PacketsReceived"` + PacketsSent uint64 `json:"PacketsSent"` +} diff --git a/internal/win32/hcs/hcs.go b/internal/win32/hcs/hcs.go new file mode 100644 index 000000000..e03a84db8 --- /dev/null +++ b/internal/win32/hcs/hcs.go @@ -0,0 +1,80 @@ +package hcs + +import ( + "encoding/json" + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/utils" + "golang.org/x/sys/windows" +) + +//nolint:gochecknoglobals +var ( + ContainerQuery = utils.Must(windows.UTF16PtrFromString(`{"Types":["Container"]}`)) + StatisticsQuery = utils.Must(windows.UTF16PtrFromString(`{"PropertyTypes":["Statistics"]}`)) +) + +func GetContainers() ([]Properties, error) { + operation, err := CreateOperation() + if err != nil { + return nil, fmt.Errorf("failed to create operation: %w", err) + } + + defer CloseOperation(operation) + + if err := EnumerateComputeSystems(ContainerQuery, operation); err != nil { + return nil, fmt.Errorf("failed to enumerate compute systems: %w", err) + } + + resultDocument, err := WaitForOperationResult(operation, 1000) + if err != nil { + return nil, fmt.Errorf("failed to wait and get for operation result: %w - %s", err, resultDocument) + } else if resultDocument == "" { + return nil, ErrEmptyResultDocument + } + + var computeSystems []Properties + if err := json.Unmarshal([]byte(resultDocument), &computeSystems); err != nil { + return nil, fmt.Errorf("failed to unmarshal compute systems: %w", err) + } + + return computeSystems, nil +} + +func GetContainerStatistics(containerID string) (Statistics, error) { + computeSystem, err := OpenComputeSystem(containerID) + if err != nil { + return Statistics{}, fmt.Errorf("failed to open compute system: %w", err) + } + + defer CloseComputeSystem(computeSystem) + + operation, err := CreateOperation() + if err != nil { + return Statistics{}, fmt.Errorf("failed to create operation: %w", err) + } + + defer CloseOperation(operation) + + if err := GetComputeSystemProperties(computeSystem, operation, StatisticsQuery); err != nil { + return Statistics{}, fmt.Errorf("failed to enumerate compute systems: %w", err) + } + + resultDocument, err := WaitForOperationResult(operation, 1000) + if err != nil { + return Statistics{}, fmt.Errorf("failed to get compute system properties: %w", err) + } else if resultDocument == "" { + return Statistics{}, ErrEmptyResultDocument + } + + var properties Properties + if err := json.Unmarshal([]byte(resultDocument), &properties); err != nil { + return Statistics{}, fmt.Errorf("failed to unmarshal system properties: %w", err) + } + + if properties.Statistics == nil { + return Statistics{}, fmt.Errorf("no statistics found for container %s", containerID) + } + + return *properties.Statistics, nil +} diff --git a/internal/win32/hcs/hcs_test.go b/internal/win32/hcs/hcs_test.go new file mode 100644 index 000000000..6e676238c --- /dev/null +++ b/internal/win32/hcs/hcs_test.go @@ -0,0 +1,38 @@ +package hcs_test + +import ( + "testing" + + "github.com/prometheus-community/windows_exporter/internal/win32/hcs" + "github.com/stretchr/testify/require" +) + +func TestGetContainers(t *testing.T) { + t.Parallel() + + containers, err := hcs.GetContainers() + require.NoError(t, err) + require.NotNil(t, containers) +} + +func TestOpenContainer(t *testing.T) { + t.Parallel() + + containers, err := hcs.GetContainers() + require.NoError(t, err) + + if len(containers) == 0 { + t.Skip("No containers found") + } + + statistics, err := hcs.GetContainerStatistics(containers[0].ID) + require.NoError(t, err) + require.NotNil(t, statistics) +} + +func TestOpenContainerNotFound(t *testing.T) { + t.Parallel() + + _, err := hcs.GetContainerStatistics("f3056b79b36ddfe203376473e2aeb4922a8ca7c5d8100764e5829eb5552fe09b") + require.ErrorIs(t, err, hcs.ErrIDNotFound) +} diff --git a/internal/win32/hcs/syscall.go b/internal/win32/hcs/syscall.go new file mode 100644 index 000000000..a4897cfee --- /dev/null +++ b/internal/win32/hcs/syscall.go @@ -0,0 +1,113 @@ +package hcs + +import ( + "fmt" + "unsafe" + + "golang.org/x/sys/windows" +) + +//nolint:gochecknoglobals +var ( + modComputeCore = windows.NewLazySystemDLL("computecore.dll") + + procHcsCreateOperation = modComputeCore.NewProc("HcsCreateOperation") + procHcsWaitForOperationResult = modComputeCore.NewProc("HcsWaitForOperationResult") + procHcsCloseOperation = modComputeCore.NewProc("HcsCloseOperation") + procHcsEnumerateComputeSystems = modComputeCore.NewProc("HcsEnumerateComputeSystems") + procHcsOpenComputeSystem = modComputeCore.NewProc("HcsOpenComputeSystem") + procHcsGetComputeSystemProperties = modComputeCore.NewProc("HcsGetComputeSystemProperties") + procHcsCloseComputeSystem = modComputeCore.NewProc("HcsCloseComputeSystem") +) + +// CreateOperation creates a new operation. +func CreateOperation() (Operation, error) { + r1, r2, _ := procHcsCreateOperation.Call(0, 0) + if r2 != 0 { + return 0, fmt.Errorf("HcsCreateOperation failed: HRESULT 0x%X: %w", r2, Win32FromHResult(r2)) + } + + return Operation(r1), nil +} + +func WaitForOperationResult(operation Operation, timeout uint32) (string, error) { + var resultDocument *uint16 + + r1, _, _ := procHcsWaitForOperationResult.Call(uintptr(operation), uintptr(timeout), uintptr(unsafe.Pointer(&resultDocument))) + if r1 != 0 { + return "", fmt.Errorf("HcsWaitForOperationResult failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1)) + } + + result := windows.UTF16PtrToString(resultDocument) + windows.CoTaskMemFree(unsafe.Pointer(resultDocument)) + + return result, nil +} + +// CloseOperation closes an operation. +func CloseOperation(operation Operation) { + _, _, _ = procHcsCloseOperation.Call(uintptr(operation)) +} + +// EnumerateComputeSystems enumerates compute systems. +// +// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsenumeratecomputesystems +func EnumerateComputeSystems(query *uint16, operation Operation) error { + r1, _, _ := procHcsEnumerateComputeSystems.Call(uintptr(unsafe.Pointer(query)), uintptr(operation)) + if r1 != 0 { + return fmt.Errorf("HcsEnumerateComputeSystems failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1)) + } + + return nil +} + +// OpenComputeSystem opens a handle to an existing compute system. +// +// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsopencomputesystem +func OpenComputeSystem(id string) (ComputeSystem, error) { + idPtr, err := windows.UTF16PtrFromString(id) + if err != nil { + return 0, err + } + + var system ComputeSystem + + r1, _, _ := procHcsOpenComputeSystem.Call( + uintptr(unsafe.Pointer(idPtr)), + uintptr(windows.GENERIC_ALL), + uintptr(unsafe.Pointer(&system)), + ) + if r1 != 0 { + return 0, fmt.Errorf("HcsOpenComputeSystem failed: HRESULT 0x%X: %w", r1, Win32FromHResult(r1)) + } + + return system, nil +} + +func GetComputeSystemProperties(system ComputeSystem, operation Operation, propertyQuery *uint16) error { + r1, _, err := procHcsGetComputeSystemProperties.Call( + uintptr(system), + uintptr(operation), + uintptr(unsafe.Pointer(propertyQuery)), + ) + if r1 != 0 { + return fmt.Errorf("HcsGetComputeSystemProperties failed: HRESULT 0x%X: %w", r1, err) + } + + return nil +} + +// CloseComputeSystem closes a handle to a compute system. +// +// https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsclosecomputesystem +func CloseComputeSystem(system ComputeSystem) { + _, _, _ = procHcsCloseComputeSystem.Call(uintptr(system)) +} + +func Win32FromHResult(hr uintptr) windows.Errno { + if hr&0x1fff0000 == 0x00070000 { + return windows.Errno(hr & 0xffff) + } + + return windows.Errno(hr) +} diff --git a/internal/win32/hcs/types.go b/internal/win32/hcs/types.go new file mode 100644 index 000000000..dd78a90e7 --- /dev/null +++ b/internal/win32/hcs/types.go @@ -0,0 +1,64 @@ +package hcs + +import ( + "errors" + "time" + + "golang.org/x/sys/windows" +) + +var ( + ErrEmptyResultDocument = errors.New("empty result document") + ErrIDNotFound = windows.Errno(2151088398) +) + +type Operation = windows.Handle +type ComputeSystem = windows.Handle + +type Properties struct { + ID string `json:"Id,omitempty"` + SystemType string `json:"SystemType,omitempty"` + Owner string `json:"Owner,omitempty"` + State string `json:"State,omitempty"` + Statistics *Statistics `json:"Statistics,omitempty"` + ProcessList []ProcessDetails `json:"ProcessList,omitempty"` +} + +type ProcessDetails struct { + ProcessId int32 `json:"ProcessId,omitempty"` + ImageName string `json:"ImageName,omitempty"` + CreateTimestamp time.Time `json:"CreateTimestamp,omitempty"` + UserTime100ns int32 `json:"UserTime100ns,omitempty"` + KernelTime100ns int32 `json:"KernelTime100ns,omitempty"` + MemoryCommitBytes int32 `json:"MemoryCommitBytes,omitempty"` + MemoryWorkingSetPrivateBytes int32 `json:"MemoryWorkingSetPrivateBytes,omitempty"` + MemoryWorkingSetSharedBytes int32 `json:"MemoryWorkingSetSharedBytes,omitempty"` +} + +type Statistics struct { + Timestamp time.Time `json:"Timestamp,omitempty"` + ContainerStartTime time.Time `json:"ContainerStartTime,omitempty"` + Uptime100ns uint64 `json:"Uptime100ns,omitempty"` + Processor *ProcessorStats `json:"Processor,omitempty"` + Memory *MemoryStats `json:"Memory,omitempty"` + Storage *StorageStats `json:"Storage,omitempty"` +} + +type ProcessorStats struct { + TotalRuntime100ns uint64 `json:"TotalRuntime100ns,omitempty"` + RuntimeUser100ns uint64 `json:"RuntimeUser100ns,omitempty"` + RuntimeKernel100ns uint64 `json:"RuntimeKernel100ns,omitempty"` +} + +type MemoryStats struct { + MemoryUsageCommitBytes uint64 `json:"MemoryUsageCommitBytes,omitempty"` + MemoryUsageCommitPeakBytes uint64 `json:"MemoryUsageCommitPeakBytes,omitempty"` + MemoryUsagePrivateWorkingSetBytes uint64 `json:"MemoryUsagePrivateWorkingSetBytes,omitempty"` +} + +type StorageStats struct { + ReadCountNormalized uint64 `json:"ReadCountNormalized,omitempty"` + ReadSizeBytes uint64 `json:"ReadSizeBytes,omitempty"` + WriteCountNormalized uint64 `json:"WriteCountNormalized,omitempty"` + WriteSizeBytes uint64 `json:"WriteSizeBytes,omitempty"` +} diff --git a/internal/headers/iphlpapi/const.go b/internal/win32/iphlpapi/const.go similarity index 100% rename from internal/headers/iphlpapi/const.go rename to internal/win32/iphlpapi/const.go diff --git a/internal/headers/iphlpapi/iphlpapi.go b/internal/win32/iphlpapi/iphlpapi.go similarity index 67% rename from internal/headers/iphlpapi/iphlpapi.go rename to internal/win32/iphlpapi/iphlpapi.go index 6674f4a51..953fbd08c 100644 --- a/internal/headers/iphlpapi/iphlpapi.go +++ b/internal/win32/iphlpapi/iphlpapi.go @@ -20,13 +20,16 @@ import ( "fmt" "unsafe" + "github.com/prometheus-community/windows_exporter/internal/win32/guid" "golang.org/x/sys/windows" ) //nolint:gochecknoglobals var ( - modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") - procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable") + modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") + procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable") + procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex") + procConvertInterfaceGuidToLuid = modiphlpapi.NewProc("ConvertInterfaceGuidToLuid") ) func GetTCPConnectionStates(family uint32) (map[MIB_TCP_STATE]uint32, error) { @@ -126,3 +129,37 @@ func getExtendedTcpTable[T any](ulAf uint32, tableClass uint32) ([]T, error) { return unsafe.Slice((*T)(unsafe.Pointer(&buf[4])), binary.LittleEndian.Uint32(buf)), nil } + +// GetIfEntry2Ex function retrieves the specified level of information for the specified interface on the local computer. +// +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-getifentry2ex +func GetIfEntry2Ex(row *MIB_IF_ROW2) error { + ret, _, _ := procGetIfEntry2Ex.Call( + uintptr(0), + uintptr(unsafe.Pointer(row)), + ) + + if ret != 0 { + return fmt.Errorf("GetIfEntry2Ex failed with code %d: %w", ret, windows.Errno(ret)) + } + + return nil +} + +// ConvertInterfaceGUIDToLUID function converts a globally unique identifier (GUID) for a network interface to the +// locally unique identifier (LUID) for the interface. +// +// https://learn.microsoft.com/en-us/windows/win32/api/netioapi/nf-netioapi-convertinterfaceguidtoluid +func ConvertInterfaceGUIDToLUID(guid guid.GUID) (uint64, error) { + var luid uint64 + ret, _, _ := procConvertInterfaceGuidToLuid.Call( + uintptr(unsafe.Pointer(&guid)), + uintptr(unsafe.Pointer(&luid)), + ) + + if ret != 0 { + return 0, fmt.Errorf("ConvertInterfaceGUIDToLUID failed with code %d: %w", ret, windows.Errno(ret)) + } + + return luid, nil +} diff --git a/internal/headers/iphlpapi/iphlpapi_test.go b/internal/win32/iphlpapi/iphlpapi_test.go similarity index 94% rename from internal/headers/iphlpapi/iphlpapi_test.go rename to internal/win32/iphlpapi/iphlpapi_test.go index 6a73cf905..21c17c871 100644 --- a/internal/headers/iphlpapi/iphlpapi_test.go +++ b/internal/win32/iphlpapi/iphlpapi_test.go @@ -20,7 +20,7 @@ import ( "os" "testing" - "github.com/prometheus-community/windows_exporter/internal/headers/iphlpapi" + "github.com/prometheus-community/windows_exporter/internal/win32/iphlpapi" "github.com/stretchr/testify/require" "golang.org/x/sys/windows" ) diff --git a/internal/headers/iphlpapi/types.go b/internal/win32/iphlpapi/types.go similarity index 58% rename from internal/headers/iphlpapi/types.go rename to internal/win32/iphlpapi/types.go index 0d717f714..ae30d13b9 100644 --- a/internal/headers/iphlpapi/types.go +++ b/internal/win32/iphlpapi/types.go @@ -18,6 +18,8 @@ package iphlpapi import ( "encoding/binary" "fmt" + + "github.com/prometheus-community/windows_exporter/internal/win32/guid" ) // MIB_TCPROW_OWNER_PID structure for IPv4. @@ -105,3 +107,54 @@ func (b BigEndianUint32) uint16() uint16 { return binary.LittleEndian.Uint16(data) } + +// Constants from Windows headers +const ( + IF_MAX_STRING_SIZE = 256 + IF_MAX_PHYS_ADDRESS_LENGTH = 32 +) + +// MIB_IF_ROW2 represents network interface statistics +type MIB_IF_ROW2 struct { + InterfaceLuid uint64 + InterfaceIndex uint32 + InterfaceGuid guid.GUID + Alias [IF_MAX_STRING_SIZE + 1]uint16 + Description [IF_MAX_STRING_SIZE + 1]uint16 + PhysicalAddressLength uint32 + PhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]byte + PermanentPhysicalAddress [IF_MAX_PHYS_ADDRESS_LENGTH]byte + Mtu uint32 + Type uint32 + TunnelType uint32 + MediaType uint32 + PhysicalMediumType uint32 + AccessType uint32 + DirectionType uint32 + InterfaceAndOperStatusFlags uint8 + OperStatus uint32 + AdminStatus uint32 + MediaConnectState uint32 + NetworkGuid [16]byte + ConnectionType uint32 + TransmitLinkSpeed uint64 + ReceiveLinkSpeed uint64 + InOctets uint64 + InUcastPkts uint64 + InNUcastPkts uint64 + InDiscards uint64 + InErrors uint64 + InUnknownProtos uint64 + InUcastOctets uint64 + InMulticastOctets uint64 + InBroadcastOctets uint64 + OutOctets uint64 + OutUcastPkts uint64 + OutNUcastPkts uint64 + OutDiscards uint64 + OutErrors uint64 + OutUcastOctets uint64 + OutMulticastOctets uint64 + OutBroadcastOctets uint64 + OutQLen uint64 +} diff --git a/internal/win32/kernel32/job.go b/internal/win32/kernel32/job.go new file mode 100644 index 000000000..943c3d783 --- /dev/null +++ b/internal/win32/kernel32/job.go @@ -0,0 +1,54 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kernel32 + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + // JOB_OBJECT_QUERY is required to retrieve certain information about a job object, + // such as attributes and accounting information (see QueryInformationJobObject and IsProcessInJob). + // https://learn.microsoft.com/en-us/windows/win32/procthread/job-object-security-and-access-rights + JOB_OBJECT_QUERY = 0x0004 + + ClassJobObjectBasicProcessIdList uint16 = 3 + // ClassJobObjectExtendedLimitInformation + // https://learn.microsoft.com/en-us/windows/win32/api/jobapi2/nf-jobapi2-queryinformationjobobject + ClassJobObjectExtendedLimitInformation uint16 = 9 +) + +func OpenJobObject(name string) (windows.Handle, error) { + handle, _, err := procOpenJobObject.Call(JOB_OBJECT_QUERY, 0, uintptr(unsafe.Pointer(&name))) + if handle == 0 { + return 0, err + } + + return windows.Handle(handle), nil +} + +func IsProcessInJob(process windows.Handle, job windows.Handle, result *bool) error { + ret, _, err := procIsProcessInJob.Call( + uintptr(process), + uintptr(job), + uintptr(unsafe.Pointer(&result)), + ) + if ret == 0 { + return err + } + + return nil +} diff --git a/internal/headers/kernel32/kernel32.go b/internal/win32/kernel32/kernel32.go similarity index 94% rename from internal/headers/kernel32/kernel32.go rename to internal/win32/kernel32/kernel32.go index 092f98782..735aec83e 100644 --- a/internal/headers/kernel32/kernel32.go +++ b/internal/win32/kernel32/kernel32.go @@ -27,6 +27,8 @@ var ( procGetDynamicTimeZoneInformationSys = kernel32.NewProc("GetDynamicTimeZoneInformation") kernelLocalFileTimeToFileTime = kernel32.NewProc("LocalFileTimeToFileTime") + procOpenJobObject = kernel32.NewProc("OpenJobObjectW") + procIsProcessInJob = kernel32.NewProc("IsProcessInJob") ) // SYSTEMTIME contains a date and time. diff --git a/internal/win32/kernel32/types.go b/internal/win32/kernel32/types.go new file mode 100644 index 000000000..f000712b9 --- /dev/null +++ b/internal/win32/kernel32/types.go @@ -0,0 +1,58 @@ +package kernel32 + +import "unsafe" + +type JobObjectBasicAccountingInformation struct { + TotalUserTime uint64 + TotalKernelTime uint64 + ThisPeriodTotalUserTime uint64 + ThisPeriodTotalKernelTime uint64 + TotalPageFaultCount uint32 + TotalProcesses uint32 + ActiveProcesses uint32 + TotalTerminatedProcesses uint32 +} + +type IOCounters struct { + ReadOperationCount uint64 + WriteOperationCount uint64 + OtherOperationCount uint64 + ReadTransferCount uint64 + WriteTransferCount uint64 + OtherTransferCount uint64 +} + +type JobObjectExtendedLimitInformation struct { + BasicInfo JobObjectBasicAccountingInformation + IoInfo IOCounters + ProcessMemoryLimit uint64 + JobMemoryLimit uint64 + PeakProcessMemoryUsed uint64 + PeakJobMemoryUsed uint64 +} + +type JobObjectBasicProcessIDList struct { + NumberOfAssignedProcesses uint32 + NumberOfProcessIdsInList uint32 + ProcessIdList [1]uintptr +} + +// PIDs returns all the process Ids in the job object. +func (p *JobObjectBasicProcessIDList) PIDs() []uint32 { + return unsafe.Slice((*uint32)(unsafe.Pointer(&p.ProcessIdList[0])), int(p.NumberOfProcessIdsInList)) +} + +type PROCESS_VM_COUNTERS struct { + PeakVirtualSize uintptr + VirtualSize uintptr + PageFaultCount uint32 + PeakWorkingSetSize uintptr + WorkingSetSize uintptr + QuotaPeakPagedPoolUsage uintptr + QuotaPagedPoolUsage uintptr + QuotaPeakNonPagedPoolUsage uintptr + QuotaNonPagedPoolUsage uintptr + PagefileUsage uintptr + PeakPagefileUsage uintptr + PrivateWorkingSetSize uintptr +} diff --git a/internal/headers/netapi32/netapi32.go b/internal/win32/netapi32/netapi32.go similarity index 100% rename from internal/headers/netapi32/netapi32.go rename to internal/win32/netapi32/netapi32.go diff --git a/internal/headers/psapi/psapi.go b/internal/win32/psapi/psapi.go similarity index 100% rename from internal/headers/psapi/psapi.go rename to internal/win32/psapi/psapi.go diff --git a/internal/headers/schedule_service/schedule_service.go b/internal/win32/schedule_service/schedule_service.go similarity index 100% rename from internal/headers/schedule_service/schedule_service.go rename to internal/win32/schedule_service/schedule_service.go diff --git a/internal/headers/secur32/secur32.go b/internal/win32/secur32/secur32.go similarity index 100% rename from internal/headers/secur32/secur32.go rename to internal/win32/secur32/secur32.go diff --git a/internal/headers/secur32/secur32_test.go b/internal/win32/secur32/secur32_test.go similarity index 91% rename from internal/headers/secur32/secur32_test.go rename to internal/win32/secur32/secur32_test.go index 167f244d6..282033a79 100644 --- a/internal/headers/secur32/secur32_test.go +++ b/internal/win32/secur32/secur32_test.go @@ -18,7 +18,7 @@ package secur32_test import ( "testing" - "github.com/prometheus-community/windows_exporter/internal/headers/secur32" + "github.com/prometheus-community/windows_exporter/internal/win32/secur32" "github.com/stretchr/testify/require" ) diff --git a/internal/headers/secur32/types.go b/internal/win32/secur32/types.go similarity index 100% rename from internal/headers/secur32/types.go rename to internal/win32/secur32/types.go diff --git a/internal/headers/slc/slc.go b/internal/win32/slc/slc.go similarity index 100% rename from internal/headers/slc/slc.go rename to internal/win32/slc/slc.go diff --git a/internal/headers/sysinfoapi/sysinfoapi.go b/internal/win32/sysinfoapi/sysinfoapi.go similarity index 100% rename from internal/headers/sysinfoapi/sysinfoapi.go rename to internal/win32/sysinfoapi/sysinfoapi.go diff --git a/internal/headers/win32api/types.go b/internal/win32/win32api/types.go similarity index 100% rename from internal/headers/win32api/types.go rename to internal/win32/win32api/types.go diff --git a/internal/headers/wtsapi32/wtsapi32.go b/internal/win32/wtsapi32/wtsapi32.go similarity index 100% rename from internal/headers/wtsapi32/wtsapi32.go rename to internal/win32/wtsapi32/wtsapi32.go