diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml index e191b893..282eacc3 100644 --- a/.idea/dictionaries/project.xml +++ b/.idea/dictionaries/project.xml @@ -2,9 +2,12 @@ containerd + endpointstats + gochecknoglobals luid setupapi spdx + vmcompute \ No newline at end of file diff --git a/internal/collector/container/container.go b/internal/collector/container/container.go index 0f496557..5b612979 100644 --- a/internal/collector/container/container.go +++ b/internal/collector/container/container.go @@ -29,10 +29,8 @@ import ( "unsafe" "github.com/alecthomas/kingpin/v2" - "github.com/go-ole/go-ole" "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" @@ -523,7 +521,7 @@ func (c *Collector) collectHCSContainer(ch chan<- prometheus.Metric, containerDe // collectNetworkMetrics collects network metrics for containers. func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error { - endpoints, err := hcn.EnumerateEndpoints() + endpoints, err := hcn.ListEndpoints() if err != nil { return fmt.Errorf("error in fetching HCN endpoints: %w", err) } @@ -533,57 +531,24 @@ func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error { } for _, endpoint := range endpoints { - properties, err := hcn.GetEndpointProperties(endpoint) + if len(endpoint.SharedContainers) == 0 { + continue + } + + endpointStats, err := hcn.GetHNSEndpointStats(endpoint.ID) if err != nil { - c.logger.Warn("Failed to collect properties for interface "+endpoint.String(), + c.logger.Warn("Failed to collect network stats for interface "+endpoint.ID, slog.Any("err", err), ) continue } - if len(properties.SharedContainers) == 0 { - continue - } - - var nicGUID *ole.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 { + for _, containerId := range endpoint.SharedContainers { containerInfo, ok := c.annotationsCacheHCS[containerId] if !ok { - c.logger.Debug("Unknown container " + containerId + " for endpoint " + endpoint.String()) + c.logger.Debug("Unknown container " + containerId + " for endpoint " + endpoint.ID) continue } @@ -593,47 +558,47 @@ func (c *Collector) collectNetworkMetrics(ch chan<- prometheus.Metric) error { continue } - endpointId := strings.ToUpper(endpoint.String()) + endpointId := strings.ToUpper(endpoint.ID) ch <- prometheus.MustNewConstMetric( c.bytesReceived, prometheus.CounterValue, - float64(endpointStats.InOctets), + float64(endpointStats.BytesReceived), containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.bytesSent, prometheus.CounterValue, - float64(endpointStats.OutOctets), + float64(endpointStats.BytesSent), containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.packetsReceived, prometheus.CounterValue, - float64(endpointStats.InUcastPkts+endpointStats.InNUcastPkts), + float64(endpointStats.PacketsReceived), containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.packetsSent, prometheus.CounterValue, - float64(endpointStats.OutUcastPkts+endpointStats.OutNUcastPkts), + float64(endpointStats.PacketsSent), containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.droppedPacketsIncoming, prometheus.CounterValue, - float64(endpointStats.InDiscards+endpointStats.InErrors), + float64(endpointStats.DroppedPacketsIncoming), containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) ch <- prometheus.MustNewConstMetric( c.droppedPacketsOutgoing, prometheus.CounterValue, - float64(endpointStats.OutDiscards+endpointStats.OutErrors), + float64(endpointStats.DroppedPacketsOutgoing), containerInfo.id, containerInfo.namespace, containerInfo.pod, containerInfo.container, endpointId, ) } diff --git a/internal/headers/hcn/hcn.go b/internal/headers/hcn/hcn.go index fabf742b..7e122d7a 100644 --- a/internal/headers/hcn/hcn.go +++ b/internal/headers/hcn/hcn.go @@ -18,30 +18,88 @@ package hcn import ( + "encoding/json" "fmt" + "unsafe" - "github.com/go-ole/go-ole" + "github.com/prometheus-community/windows_exporter/internal/headers/hcs" "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"}`)) + modvmcompute = windows.NewLazySystemDLL("vmcompute.dll") + procHNSCall = modvmcompute.NewProc("HNSCall") + + hcnBodyEmpty = utils.Must(windows.UTF16PtrFromString("")) + hcnMethodGet = utils.Must(windows.UTF16PtrFromString("GET")) + hcnPathEndpoints = utils.Must(windows.UTF16PtrFromString("/endpoints/")) + hcnPathEndpointStats = utils.Must(windows.UTF16FromString("/endpointstats/")) ) -func GetEndpointProperties(endpointID ole.GUID) (EndpointProperties, error) { - endpoint, err := OpenEndpoint(endpointID) +func ListEndpoints() ([]EndpointProperties, error) { + result, err := hnsCall(hcnMethodGet, hcnPathEndpoints, hcnBodyEmpty) if err != nil { - return EndpointProperties{}, fmt.Errorf("failed to open endpoint: %w", err) + return nil, err } - defer CloseEndpoint(endpoint) - - result, err := QueryEndpointProperties(endpoint, defaultQuery) - if err != nil { - return EndpointProperties{}, fmt.Errorf("failed to query endpoint properties: %w", err) + var endpoints struct { + Success bool `json:"success"` + Error string `json:"error"` + Output []EndpointProperties `json:"output"` } - return result, nil + if err := json.Unmarshal([]byte(result), &endpoints); err != nil { + return nil, fmt.Errorf("failed to unmarshal JSON %s: %w", result, err) + } + + if !endpoints.Success { + return nil, fmt.Errorf("HNSCall failed: %s", endpoints.Error) + } + + return endpoints.Output, nil +} + +func GetHNSEndpointStats(endpointID string) (EndpointStats, error) { + endpointIDUTF16, err := windows.UTF16FromString(endpointID) + if err != nil { + return EndpointStats{}, fmt.Errorf("failed to convert endpoint ID to UTF16: %w", err) + } + + path := hcnPathEndpointStats[:len(hcnPathEndpointStats)-1] + path = append(path, endpointIDUTF16...) + + result, err := hnsCall(hcnMethodGet, &path[0], hcnBodyEmpty) + if err != nil { + return EndpointStats{}, err + } + + var stats EndpointStats + + if err := json.Unmarshal([]byte(result), &stats); err != nil { + return EndpointStats{}, fmt.Errorf("failed to unmarshal JSON %s: %w", result, err) + } + + return stats, nil +} + +func hnsCall(method, path, body *uint16) (string, error) { + var responseJSON *uint16 + + r1, _, _ := procHNSCall.Call( + uintptr(unsafe.Pointer(method)), + uintptr(unsafe.Pointer(path)), + uintptr(unsafe.Pointer(body)), + uintptr(unsafe.Pointer(&responseJSON)), + ) + + response := windows.UTF16PtrToString(responseJSON) + windows.CoTaskMemFree(unsafe.Pointer(responseJSON)) + + if r1 != 0 { + return "", fmt.Errorf("HNSCall failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1)) + } + + return response, nil } diff --git a/internal/headers/hcn/hcn_test.go b/internal/headers/hcn/hcn_test.go deleted file mode 100644 index 77941541..00000000 --- a/internal/headers/hcn/hcn_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// 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 deleted file mode 100644 index 7a38c73b..00000000 --- a/internal/headers/hcn/syscall.go +++ /dev/null @@ -1,134 +0,0 @@ -// 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/go-ole/go-ole" - "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() ([]ole.GUID, error) { - var ( - endpointsJSON *uint16 - errorRecord *uint16 - ) - - r1, _, _ := procHcnEnumerateEndpoints.Call( - 0, - uintptr(unsafe.Pointer(&endpointsJSON)), - uintptr(unsafe.Pointer(&errorRecord)), - ) - - result := windows.UTF16PtrToString(endpointsJSON) - windows.CoTaskMemFree(unsafe.Pointer(endpointsJSON)) - windows.CoTaskMemFree(unsafe.Pointer(errorRecord)) - - if r1 != 0 { - return nil, fmt.Errorf("HcnEnumerateEndpoints failed: HRESULT 0x%X: %w", r1, hcs.Win32FromHResult(r1)) - } - - var endpoints []ole.GUID - - if err := json.Unmarshal([]byte(result), &endpoints); err != nil { - return nil, fmt.Errorf("failed to unmarshal JSON %s: %w", result, err) - } - - return endpoints, nil -} - -// OpenEndpoint opens an endpoint. -// -// https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnopenendpoint -func OpenEndpoint(id ole.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)), - ) - - result := windows.UTF16PtrToString(resultDocument) - windows.CoTaskMemFree(unsafe.Pointer(resultDocument)) - windows.CoTaskMemFree(unsafe.Pointer(errorRecord)) - - 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 index 083d4d90..08d1e7f7 100644 --- a/internal/headers/hcn/types.go +++ b/internal/headers/hcn/types.go @@ -18,7 +18,6 @@ package hcn import ( - "github.com/go-ole/go-ole" "golang.org/x/sys/windows" ) @@ -28,17 +27,9 @@ type Endpoint = windows.Handle // // 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 *ole.GUID `json:"AdapterNetCfgInstanceId"` + ID string `json:"ID"` + State int `json:"State"` + SharedContainers []string `json:"SharedContainers"` } type EndpointStats struct { diff --git a/internal/headers/iphlpapi/iphlpapi.go b/internal/headers/iphlpapi/iphlpapi.go index 24734be0..d6a05483 100644 --- a/internal/headers/iphlpapi/iphlpapi.go +++ b/internal/headers/iphlpapi/iphlpapi.go @@ -22,16 +22,13 @@ import ( "fmt" "unsafe" - "github.com/go-ole/go-ole" "golang.org/x/sys/windows" ) //nolint:gochecknoglobals var ( - modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") - procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable") - procGetIfEntry2Ex = modiphlpapi.NewProc("GetIfEntry2Ex") - procConvertInterfaceGuidToLuid = modiphlpapi.NewProc("ConvertInterfaceGuidToLuid") + modiphlpapi = windows.NewLazySystemDLL("iphlpapi.dll") + procGetExtendedTcpTable = modiphlpapi.NewProc("GetExtendedTcpTable") ) func GetTCPConnectionStates(family uint32) (map[MIB_TCP_STATE]uint32, error) { @@ -131,38 +128,3 @@ 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 ole.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 -}