From 898e16bcb1f6bb3ea4a1cb61d56dc694cd84deac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Sun, 18 May 2025 09:39:52 +0200 Subject: [PATCH] container: support hostprocess containers and expose kubernetes labels (#1911) --- .gitignore | 15 +- .golangci.yaml | 6 + .idea/copyright/profiles_settings.xml | 11 + .idea/copyright/windows_exporter.xml | 7 + .idea/dictionaries/project.xml | 7 + .idea/go.imports.xml | 10 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/vcs.xml | 6 + docs/collector.container.md | 42 +- go.mod | 13 - go.sum | 127 ---- internal/collector/container/container.go | 627 +++++++++++++++--- internal/collector/hyperv/hyperv.go | 2 +- .../hyperv/hyperv_dynamic_memory_balancer.go | 2 +- .../hyperv/hyperv_dynamic_memory_vm.go | 2 +- .../collector/mscluster/mscluster_cluster.go | 2 +- .../collector/mscluster/mscluster_node.go | 2 +- internal/collector/os/os.go | 13 +- .../performancecounter_test_test.go | 5 +- .../collector/performancecounter/types.go | 2 +- .../collector/textfile/textfile_test_test.go | 7 +- internal/collector/time/time.go | 2 +- internal/headers/guid/guid.go | 96 +++ internal/headers/hcn/hcn.go | 47 ++ internal/headers/hcn/hcn_test.go | 47 ++ internal/headers/hcn/syscall.go | 134 ++++ internal/headers/hcn/types.go | 53 ++ internal/headers/hcs/hcs.go | 97 +++ internal/headers/hcs/hcs_test.go | 55 ++ internal/headers/hcs/syscall.go | 130 ++++ internal/headers/hcs/types.go | 83 +++ internal/headers/iphlpapi/iphlpapi.go | 41 +- internal/headers/iphlpapi/types.go | 53 ++ internal/headers/kernel32/job.go | 53 ++ internal/headers/kernel32/kernel32.go | 2 + internal/headers/kernel32/types.go | 75 +++ internal/osversion/osversion_windows.go | 65 ++ internal/osversion/osversion_windows_test.go | 36 + internal/osversion/windowsbuilds.go | 101 +++ internal/pdh/collector.go | 2 +- internal/pdh/collector_test.go | 3 +- internal/pdh/registry/nametable.go | 5 - renovate.json | 2 +- 43 files changed, 1800 insertions(+), 296 deletions(-) create mode 100644 .idea/copyright/profiles_settings.xml create mode 100644 .idea/copyright/windows_exporter.xml create mode 100644 .idea/dictionaries/project.xml create mode 100644 .idea/go.imports.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/vcs.xml create mode 100644 internal/headers/guid/guid.go create mode 100644 internal/headers/hcn/hcn.go create mode 100644 internal/headers/hcn/hcn_test.go create mode 100644 internal/headers/hcn/syscall.go create mode 100644 internal/headers/hcn/types.go create mode 100644 internal/headers/hcs/hcs.go create mode 100644 internal/headers/hcs/hcs_test.go create mode 100644 internal/headers/hcs/syscall.go create mode 100644 internal/headers/hcs/types.go create mode 100644 internal/headers/kernel32/job.go create mode 100644 internal/headers/kernel32/types.go create mode 100644 internal/osversion/osversion_windows.go create mode 100644 internal/osversion/osversion_windows_test.go create mode 100644 internal/osversion/windowsbuilds.go diff --git a/.gitignore b/.gitignore index 584f5cfd..7047eaac 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,20 @@ VERSION *.un~ output/ .vscode -.idea *.syso installer/*.msi installer/*.wixpdb local/ -!.idea/inspectionProfiles/Project_Default.xml + +/.idea/* +!/.idea/inspectionProfiles/ +/.idea/inspectionProfiles/* +!/.idea/inspectionProfiles/Project_Default.xml +!/.idea/dictionaries/ +/.idea/dictionaries/* +!/.idea/dictionaries/project.xml +/.idea/copyright/* +!/.idea/copyright/profiles_settings.xml +!/.idea/copyright/windows_exporter.xml +!/.idea/vcs.xml +!/.idea/go.imports.xml diff --git a/.golangci.yaml b/.golangci.yaml index 58be63c0..7f83057b 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -4,6 +4,7 @@ linters: disable: - cyclop - depguard + - dogsled - dupl - err113 - exhaustive @@ -56,6 +57,11 @@ linters: excludes: - G101 - G115 + govet: + enable-all: true + disable: + - fieldalignment + - shadow sloglint: no-mixed-args: true kv-only: false diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..93d50412 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/windows_exporter.xml b/.idea/copyright/windows_exporter.xml new file mode 100644 index 00000000..9c29b697 --- /dev/null +++ b/.idea/copyright/windows_exporter.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 00000000..1ea1cf2a --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,7 @@ + + + + spdx + + + \ No newline at end of file diff --git a/.idea/go.imports.xml b/.idea/go.imports.xml new file mode 100644 index 00000000..8e1cbe42 --- /dev/null +++ b/.idea/go.imports.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..ca3c807f --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/docs/collector.container.md b/docs/collector.container.md index d16ca01c..ae974303 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 98fdc4c0..78ed1b01 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/prometheus-community/windows_exporter go 1.24 require ( - github.com/Microsoft/hcsshim v0.13.0 github.com/alecthomas/kingpin/v2 v2.4.0 github.com/bmatcuk/doublestar/v4 v4.8.1 github.com/dimchansky/utfbom v1.1.1 @@ -18,36 +17,24 @@ 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/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.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/procfs v0.16.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.38.0 // indirect golang.org/x/net v0.40.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.14.0 // indirect golang.org/x/text v0.25.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9 // indirect - google.golang.org/grpc v1.72.1 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect ) diff --git a/go.sum b/go.sum index c8b9cc9f..329804b7 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.13.0 h1:/BcXOiS6Qi7N9XqUcv27vkIuVOkBEcWstd2pMlWSeaA= -github.com/Microsoft/hcsshim v0.13.0/go.mod h1:9KWJ/8DgU+QzYGupX4tzMhRQE8h6w90lH6HAaclpEok= 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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -86,14 +38,11 @@ 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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= @@ -104,105 +53,31 @@ github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzM github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 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.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -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.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= -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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= -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.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -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.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -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-20250512202823-5a2f75b736a9 h1:IkAfh6J/yllPtpYFU0zZN1hUPYdT0ogkBT/9hMxHjvg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250512202823-5a2f75b736a9/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= -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.72.1 h1:HR03wO6eyZ7lknl75XlxABNVLLFc2PAb6mHlYh756mA= -google.golang.org/grpc v1.72.1/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -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.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -213,5 +88,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 b119d71a..90407875 100644 --- a/internal/collector/container/container.go +++ b/internal/collector/container/container.go @@ -18,25 +18,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/headers/guid" + "github.com/prometheus-community/windows_exporter/internal/headers/hcn" + "github.com/prometheus-community/windows_exporter/internal/headers/hcs" + "github.com/prometheus-community/windows_exporter/internal/headers/iphlpapi" + "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/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 { @@ -44,6 +67,9 @@ type Collector struct { logger *slog.Logger + annotationsCacheHCS map[string]containerInfo + annotationsCacheJob map[string]containerInfo + // Presence containerAvailable *prometheus.Desc @@ -75,12 +101,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, } @@ -88,8 +129,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 { @@ -103,10 +162,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( @@ -118,97 +183,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, ) @@ -218,39 +283,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 + } - if err = c.collectContainer(ch, containerDetails, containerIdWithPrefix); err != nil { - if hcsshim.IsNotExist(err) { + containerIDs = append(containerIDs, container.ID) + + countersCount++ + + 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), ) @@ -259,14 +370,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...)) } @@ -274,94 +396,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 @@ -371,73 +486,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 network stats for interface "+endpoint.Id, + c.logger.Warn("Failed to collect properties for interface "+endpoint.String(), slog.Any("err", err), ) continue } - for _, containerId := range endpoint.SharedContainers { - containerIdWithPrefix, ok := containerPrefixes[containerId] + 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 { + 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 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, ) } } @@ -445,12 +592,286 @@ 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(containerDStateDir) + + 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 { + if errors.Is(err, fs.ErrNotExist) { + c.logger.Warn("containerd state directory does not exist", + slog.String("path", containerDStateDir), + slog.Any("err", err), + ) + + return 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 fmt.Errorf("error in walking containerd state directory: %w", 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/hyperv/hyperv.go b/internal/collector/hyperv/hyperv.go index 7ec3611d..56ca36c9 100644 --- a/internal/collector/hyperv/hyperv.go +++ b/internal/collector/hyperv/hyperv.go @@ -25,9 +25,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/hyperv/hyperv_dynamic_memory_balancer.go b/internal/collector/hyperv/hyperv_dynamic_memory_balancer.go index 8849f93d..54aeca46 100644 --- a/internal/collector/hyperv/hyperv_dynamic_memory_balancer.go +++ b/internal/collector/hyperv/hyperv_dynamic_memory_balancer.go @@ -20,7 +20,7 @@ package hyperv import ( "fmt" - "github.com/Microsoft/hcsshim/osversion" + "github.com/prometheus-community/windows_exporter/internal/osversion" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" diff --git a/internal/collector/hyperv/hyperv_dynamic_memory_vm.go b/internal/collector/hyperv/hyperv_dynamic_memory_vm.go index b9e3fc16..b4b06698 100644 --- a/internal/collector/hyperv/hyperv_dynamic_memory_vm.go +++ b/internal/collector/hyperv/hyperv_dynamic_memory_vm.go @@ -20,7 +20,7 @@ package hyperv import ( "fmt" - "github.com/Microsoft/hcsshim/osversion" + "github.com/prometheus-community/windows_exporter/internal/osversion" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus-community/windows_exporter/internal/utils" diff --git a/internal/collector/mscluster/mscluster_cluster.go b/internal/collector/mscluster/mscluster_cluster.go index ca91e322..648b2ea5 100644 --- a/internal/collector/mscluster/mscluster_cluster.go +++ b/internal/collector/mscluster/mscluster_cluster.go @@ -20,8 +20,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 f8ae3a0f..c5de0b89 100644 --- a/internal/collector/mscluster/mscluster_node.go +++ b/internal/collector/mscluster/mscluster_node.go @@ -20,8 +20,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/os/os.go b/internal/collector/os/os.go index 25d4ee4e..d84be008 100644 --- a/internal/collector/os/os.go +++ b/internal/collector/os/os.go @@ -30,6 +30,7 @@ import ( "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/osversion" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/windows" @@ -118,10 +119,10 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { return fmt.Errorf("failed to get Windows version: %w", err) } - version := windows.RtlGetVersion() + version := osversion.Get() // Microsoft has decided to keep the major version as "10" for Windows 11, including the product name. - if version.BuildNumber >= 22000 { + if version.Build >= osversion.V21H2Win11 { productName = strings.Replace(productName, " 10 ", " 11 ", 1) } @@ -131,10 +132,10 @@ func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error { nil, prometheus.Labels{ "product": productName, - "version": fmt.Sprintf("%d.%d.%d", version.MajorVersion, version.MinorVersion, version.BuildNumber), + "version": version.String(), "major_version": strconv.FormatUint(uint64(version.MajorVersion), 10), "minor_version": strconv.FormatUint(uint64(version.MinorVersion), 10), - "build_number": strconv.FormatUint(uint64(version.BuildNumber), 10), + "build_number": strconv.FormatUint(uint64(version.Build), 10), "revision": revision, }, ) @@ -365,7 +366,9 @@ func (c *Collector) getWindowsVersion() (string, string, error) { return "", "", fmt.Errorf("failed to open registry key: %w", err) } - defer ntKey.Close() + defer func(ntKey registry.Key) { + _ = ntKey.Close() + }(ntKey) productName, _, err := ntKey.GetStringValue("ProductName") if err != nil { diff --git a/internal/collector/performancecounter/performancecounter_test_test.go b/internal/collector/performancecounter/performancecounter_test_test.go index bc8c2180..a47038c3 100644 --- a/internal/collector/performancecounter/performancecounter_test_test.go +++ b/internal/collector/performancecounter/performancecounter_test_test.go @@ -30,7 +30,6 @@ import ( "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -208,9 +207,9 @@ windows_performancecounter_processor_information_processor_time\{core="0,0",stat promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError}).ServeHTTP(rw, &http.Request{}) got := rw.Body.String() - assert.NotEmpty(t, got) + require.NotEmpty(t, got) require.NotEmpty(t, tc.expectedMetrics) - assert.Regexp(t, tc.expectedMetrics, got) + require.Regexp(t, tc.expectedMetrics, got) }) } } diff --git a/internal/collector/performancecounter/types.go b/internal/collector/performancecounter/types.go index 6a75bfcc..f5293a4f 100644 --- a/internal/collector/performancecounter/types.go +++ b/internal/collector/performancecounter/types.go @@ -28,7 +28,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/textfile/textfile_test_test.go b/internal/collector/textfile/textfile_test_test.go index b8615bea..cb462139 100644 --- a/internal/collector/textfile/textfile_test_test.go +++ b/internal/collector/textfile/textfile_test_test.go @@ -28,7 +28,6 @@ import ( "github.com/prometheus-community/windows_exporter/pkg/collector" "github.com/prometheus/client_golang/prometheus" dto "github.com/prometheus/client_model/go" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -70,7 +69,7 @@ func TestMultipleDirectories(t *testing.T) { require.NoError(t, <-errCh) for _, f := range []string{"dir1", "dir2", "dir3", "dir3sub"} { - assert.Contains(t, got, f) + require.Contains(t, got, f) } } @@ -106,6 +105,6 @@ func TestDuplicateFileName(t *testing.T) { require.ErrorContains(t, <-errCh, "duplicate filename detected") - assert.Contains(t, got, "file") - assert.NotContains(t, got, "sub_file") + require.Contains(t, got, "file") + require.NotContains(t, got, "sub_file") } diff --git a/internal/collector/time/time.go b/internal/collector/time/time.go index dc10884c..8e2d77fa 100644 --- a/internal/collector/time/time.go +++ b/internal/collector/time/time.go @@ -25,10 +25,10 @@ import ( "strings" "time" - "github.com/Microsoft/hcsshim/osversion" "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/osversion" "github.com/prometheus-community/windows_exporter/internal/pdh" "github.com/prometheus-community/windows_exporter/internal/types" "github.com/prometheus/client_golang/prometheus" diff --git a/internal/headers/guid/guid.go b/internal/headers/guid/guid.go new file mode 100644 index 00000000..76e3e206 --- /dev/null +++ b/internal/headers/guid/guid.go @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +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/headers/hcn/hcn.go b/internal/headers/hcn/hcn.go new file mode 100644 index 00000000..61a7ac21 --- /dev/null +++ b/internal/headers/hcn/hcn.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +package hcn + +import ( + "fmt" + + "github.com/prometheus-community/windows_exporter/internal/headers/guid" + "github.com/prometheus-community/windows_exporter/internal/utils" + "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/headers/hcn/hcn_test.go b/internal/headers/hcn/hcn_test.go new file mode 100644 index 00000000..77941541 --- /dev/null +++ b/internal/headers/hcn/hcn_test.go @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +package hcn_test + +import ( + "testing" + + "github.com/prometheus-community/windows_exporter/internal/headers/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/headers/hcn/syscall.go b/internal/headers/hcn/syscall.go new file mode 100644 index 00000000..2df638c8 --- /dev/null +++ b/internal/headers/hcn/syscall.go @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +package hcn + +import ( + "encoding/json" + "fmt" + "unsafe" + + "github.com/prometheus-community/windows_exporter/internal/headers/guid" + "github.com/prometheus-community/windows_exporter/internal/headers/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/headers/hcn/types.go b/internal/headers/hcn/types.go new file mode 100644 index 00000000..812f4733 --- /dev/null +++ b/internal/headers/hcn/types.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +package hcn + +import ( + "github.com/prometheus-community/windows_exporter/internal/headers/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/headers/hcs/hcs.go b/internal/headers/hcs/hcs.go new file mode 100644 index 00000000..b4d06624 --- /dev/null +++ b/internal/headers/hcs/hcs.go @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +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/headers/hcs/hcs_test.go b/internal/headers/hcs/hcs_test.go new file mode 100644 index 00000000..72715d12 --- /dev/null +++ b/internal/headers/hcs/hcs_test.go @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +package hcs_test + +import ( + "testing" + + "github.com/prometheus-community/windows_exporter/internal/headers/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/headers/hcs/syscall.go b/internal/headers/hcs/syscall.go new file mode 100644 index 00000000..71a95c75 --- /dev/null +++ b/internal/headers/hcs/syscall.go @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +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/headers/hcs/types.go b/internal/headers/hcs/types.go new file mode 100644 index 00000000..f61e9332 --- /dev/null +++ b/internal/headers/hcs/types.go @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +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 + 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/iphlpapi.go b/internal/headers/iphlpapi/iphlpapi.go index 1dad32fc..8bd71167 100644 --- a/internal/headers/iphlpapi/iphlpapi.go +++ b/internal/headers/iphlpapi/iphlpapi.go @@ -22,13 +22,16 @@ import ( "fmt" "unsafe" + "github.com/prometheus-community/windows_exporter/internal/headers/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) { @@ -128,3 +131,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/types.go b/internal/headers/iphlpapi/types.go index cc2509fc..4719af23 100644 --- a/internal/headers/iphlpapi/types.go +++ b/internal/headers/iphlpapi/types.go @@ -20,6 +20,8 @@ package iphlpapi import ( "encoding/binary" "fmt" + + "github.com/prometheus-community/windows_exporter/internal/headers/guid" ) // MIB_TCPROW_OWNER_PID structure for IPv4. @@ -107,3 +109,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/headers/kernel32/job.go b/internal/headers/kernel32/job.go new file mode 100644 index 00000000..fb524a4b --- /dev/null +++ b/internal/headers/kernel32/job.go @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +package kernel32 + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +const ( + // JobObjectQuery 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 + JobObjectQuery = 0x0004 +) + +func OpenJobObject(name string) (windows.Handle, error) { + handle, _, err := procOpenJobObject.Call(JobObjectQuery, 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/headers/kernel32/kernel32.go index a8375d56..17da14c7 100644 --- a/internal/headers/kernel32/kernel32.go +++ b/internal/headers/kernel32/kernel32.go @@ -30,6 +30,8 @@ var ( procGetDynamicTimeZoneInformationSys = modkernel32.NewProc("GetDynamicTimeZoneInformation") procKernelLocalFileTimeToFileTime = modkernel32.NewProc("LocalFileTimeToFileTime") procGetTickCount = modkernel32.NewProc("GetTickCount64") + procOpenJobObject = modkernel32.NewProc("OpenJobObjectW") + procIsProcessInJob = modkernel32.NewProc("IsProcessInJob") ) // SYSTEMTIME contains a date and time. diff --git a/internal/headers/kernel32/types.go b/internal/headers/kernel32/types.go new file mode 100644 index 00000000..2f0d9d85 --- /dev/null +++ b/internal/headers/kernel32/types.go @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +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/osversion/osversion_windows.go b/internal/osversion/osversion_windows.go new file mode 100644 index 00000000..0d719944 --- /dev/null +++ b/internal/osversion/osversion_windows.go @@ -0,0 +1,65 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +package osversion + +import ( + "fmt" + "sync" + + "golang.org/x/sys/windows" +) + +// 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) +} diff --git a/internal/osversion/osversion_windows_test.go b/internal/osversion/osversion_windows_test.go new file mode 100644 index 00000000..ddfe29c7 --- /dev/null +++ b/internal/osversion/osversion_windows_test.go @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +package osversion + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestOSVersionString(t *testing.T) { + v := OSVersion{ + Version: 809042555, + MajorVersion: 123, + MinorVersion: 2, + Build: 12345, + } + + require.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 00000000..4aa4e2ea --- /dev/null +++ b/internal/osversion/windowsbuilds.go @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright 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. + +//go:build windows + +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/collector.go b/internal/pdh/collector.go index 6b48dc42..c2dfe117 100644 --- a/internal/pdh/collector.go +++ b/internal/pdh/collector.go @@ -27,8 +27,8 @@ import ( "sync" "unsafe" - "github.com/Microsoft/hcsshim/osversion" "github.com/prometheus-community/windows_exporter/internal/mi" + "github.com/prometheus-community/windows_exporter/internal/osversion" "github.com/prometheus/client_golang/prometheus" "golang.org/x/sys/windows" ) diff --git a/internal/pdh/collector_test.go b/internal/pdh/collector_test.go index 28505912..904786b9 100644 --- a/internal/pdh/collector_test.go +++ b/internal/pdh/collector_test.go @@ -22,7 +22,6 @@ import ( "time" "github.com/prometheus-community/windows_exporter/internal/pdh" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -66,7 +65,7 @@ func TestCollector(t *testing.T) { continue } - assert.NotZerof(t, instance.ThreadCount, "object: %s, instance: %s, counter: %s", tc.object, instance, instance.ThreadCount) + require.NotZerof(t, instance.ThreadCount, "object: %s, instance: %s, counter: %s", tc.object, instance, instance.ThreadCount) } }) } diff --git a/internal/pdh/registry/nametable.go b/internal/pdh/registry/nametable.go index d09b337b..7532de84 100644 --- a/internal/pdh/registry/nametable.go +++ b/internal/pdh/registry/nametable.go @@ -19,7 +19,6 @@ package registry import ( "bytes" - "fmt" "strconv" "sync" ) @@ -90,10 +89,6 @@ func (t *NameTable) initialize() { break } - if err != nil { - panic(fmt.Sprint("Invalid index ", index)) - } - indexInt, _ := strconv.Atoi(index) t.table.index[uint32(indexInt)] = desc diff --git a/renovate.json b/renovate.json index 985389d8..68226db9 100644 --- a/renovate.json +++ b/renovate.json @@ -57,7 +57,7 @@ ], "ignoreDeps": [ "mcr.microsoft.com/oss/kubernetes/windows-host-process-containers-base-image", - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" ], "customManagers": [ {