mirror of
https://github.com/prometheus-community/windows_exporter.git
synced 2026-02-08 05:56:37 +00:00
container: fix network metrics (#2136)
This commit is contained in:
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user