net: expose network interfaces address (#1635)

This commit is contained in:
Jan-Otto Kröpke
2024-10-01 23:44:53 +02:00
committed by GitHub
parent 1caef5cc12
commit 2155d34779
5 changed files with 410 additions and 101 deletions

View File

@@ -0,0 +1,36 @@
package net
const (
BytesReceivedPerSec = "Bytes Received/sec"
BytesSentPerSec = "Bytes Sent/sec"
BytesTotalPerSec = "Bytes Total/sec"
OutputQueueLength = "Output Queue Length"
PacketsOutboundDiscarded = "Packets Outbound Discarded"
PacketsOutboundErrors = "Packets Outbound Errors"
PacketsPerSec = "Packets/sec"
PacketsReceivedDiscarded = "Packets Received Discarded"
PacketsReceivedErrors = "Packets Received Errors"
PacketsReceivedPerSec = "Packets Received/sec"
PacketsReceivedUnknown = "Packets Received Unknown"
PacketsSentPerSec = "Packets Sent/sec"
CurrentBandwidth = "Current Bandwidth"
)
// Win32_PerfRawData_Tcpip_NetworkInterface docs:
// - https://technet.microsoft.com/en-us/security/aa394340(v=vs.80)
type perflibNetworkInterface struct {
BytesReceivedPerSec float64 `perflib:"Bytes Received/sec"`
BytesSentPerSec float64 `perflib:"Bytes Sent/sec"`
BytesTotalPerSec float64 `perflib:"Bytes Total/sec"`
Name string
OutputQueueLength float64 `perflib:"Output Queue Length"`
PacketsOutboundDiscarded float64 `perflib:"Packets Outbound Discarded"`
PacketsOutboundErrors float64 `perflib:"Packets Outbound Errors"`
PacketsPerSec float64 `perflib:"Packets/sec"`
PacketsReceivedDiscarded float64 `perflib:"Packets Received Discarded"`
PacketsReceivedErrors float64 `perflib:"Packets Received Errors"`
PacketsReceivedPerSec float64 `perflib:"Packets Received/sec"`
PacketsReceivedUnknown float64 `perflib:"Packets Received Unknown"`
PacketsSentPerSec float64 `perflib:"Packets Sent/sec"`
CurrentBandwidth float64 `perflib:"Current Bandwidth"`
}

View File

@@ -3,35 +3,48 @@
package net
import (
"errors"
"fmt"
"log/slog"
"os"
"regexp"
"slices"
"strings"
"unsafe"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/pkg/perfdata"
"github.com/prometheus-community/windows_exporter/pkg/perflib"
"github.com/prometheus-community/windows_exporter/pkg/types"
"github.com/prometheus-community/windows_exporter/pkg/utils"
"github.com/prometheus/client_golang/prometheus"
"github.com/yusufpapurcu/wmi"
"golang.org/x/sys/windows"
)
const Name = "net"
type Config struct {
NicExclude *regexp.Regexp `yaml:"nic_exclude"`
NicInclude *regexp.Regexp `yaml:"nic_include"`
NicExclude *regexp.Regexp `yaml:"nic_exclude"`
NicInclude *regexp.Regexp `yaml:"nic_include"`
CollectorsEnabled []string `yaml:"collectors_enabled"`
}
var ConfigDefaults = Config{
NicExclude: types.RegExpEmpty,
NicInclude: types.RegExpAny,
CollectorsEnabled: []string{
"metrics",
"nic_addresses",
},
}
var nicNameToUnderscore = regexp.MustCompile("[^a-zA-Z0-9]")
// A Collector is a Prometheus Collector for Perflib Network Interface metrics.
type Collector struct {
config Config
perfDataCollector *perfdata.Collector
bytesReceivedTotal *prometheus.Desc
bytesSentTotal *prometheus.Desc
bytesTotal *prometheus.Desc
@@ -45,6 +58,9 @@ type Collector struct {
packetsReceivedUnknown *prometheus.Desc
packetsSentTotal *prometheus.Desc
currentBandwidth *prometheus.Desc
nicAddressInfo *prometheus.Desc
routeInfo *prometheus.Desc
}
func New(config *Config) *Collector {
@@ -60,6 +76,10 @@ func New(config *Config) *Collector {
config.NicInclude = ConfigDefaults.NicInclude
}
if config.CollectorsEnabled == nil {
config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled
}
c := &Collector{
config: *config,
}
@@ -71,9 +91,12 @@ func NewWithFlags(app *kingpin.Application) *Collector {
c := &Collector{
config: ConfigDefaults,
}
c.config.CollectorsEnabled = make([]string, 0)
var nicExclude, nicInclude string
var collectorsEnabled string
app.Flag(
"collector.net.nic-exclude",
"Regexp of NIC:s to exclude. NIC name must both match include and not match exclude to be included.",
@@ -84,7 +107,14 @@ func NewWithFlags(app *kingpin.Application) *Collector {
"Regexp of NIC:s to include. NIC name must both match include and not match exclude to be included.",
).Default(c.config.NicInclude.String()).StringVar(&nicInclude)
app.Flag(
"collector.net.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, ",")
var err error
c.config.NicExclude, err = regexp.Compile(fmt.Sprintf("^(?:%s)$", nicExclude))
@@ -108,6 +138,10 @@ func (c *Collector) GetName() string {
}
func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) {
if utils.PDHEnabled() {
return []string{}, nil
}
return []string{"Network Interface"}, nil
}
@@ -115,7 +149,38 @@ func (c *Collector) Close(_ *slog.Logger) error {
return nil
}
func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error {
func (c *Collector) Build(logger *slog.Logger, _ *wmi.Client) error {
if utils.PDHEnabled() {
counters := []string{
BytesReceivedPerSec,
BytesSentPerSec,
BytesTotalPerSec,
OutputQueueLength,
PacketsOutboundDiscarded,
PacketsOutboundErrors,
PacketsPerSec,
PacketsReceivedDiscarded,
PacketsReceivedErrors,
PacketsReceivedPerSec,
PacketsReceivedUnknown,
PacketsSentPerSec,
CurrentBandwidth,
}
var err error
c.perfDataCollector, err = perfdata.NewCollector("Network Interface", []string{"*"}, counters)
if err != nil {
return fmt.Errorf("failed to create Processor Information collector: %w", err)
}
}
if slices.Contains(c.config.CollectorsEnabled, "addresses") {
logger.Info("nic/addresses collector is in an experimental state! The configuration and metrics may change in future. Please report any issues.",
slog.String("collector", Name),
)
}
c.bytesReceivedTotal = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "bytes_received_total"),
"(Network.BytesReceivedPerSec)",
@@ -194,6 +259,18 @@ func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error {
[]string{"nic"},
nil,
)
c.nicAddressInfo = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "nic_address_info"),
"A metric with a constant '1' value labeled with the network interface's address information.",
[]string{"nic", "friendly_name", "address", "family"},
nil,
)
c.routeInfo = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "route_info"),
"A metric with a constant '1' value labeled with the network interface's route information.",
[]string{"nic", "src", "dest", "metric"},
nil,
)
return nil
}
@@ -202,46 +279,32 @@ func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error {
// to the provided prometheus Metric channel.
func (c *Collector) Collect(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error {
logger = logger.With(slog.String("collector", Name))
if err := c.collect(ctx, logger, ch); err != nil {
logger.Error("failed collecting net metrics",
slog.Any("err", err),
)
return err
if slices.Contains(c.config.CollectorsEnabled, "metrics") {
var err error
if utils.PDHEnabled() {
err = c.collectPDH(ch)
} else {
err = c.collect(ctx, logger, ch)
}
if err != nil {
return fmt.Errorf("failed collecting net metrics: %w", err)
}
}
if slices.Contains(c.config.CollectorsEnabled, "nic_addresses") {
if err := c.collectNICAddresses(ch); err != nil {
return fmt.Errorf("failed collecting net addresses: %w", err)
}
}
return nil
}
// mangleNetworkName mangles Network Adapter name (non-alphanumeric to _)
// that is used in networkInterface.
func mangleNetworkName(name string) string {
return nicNameToUnderscore.ReplaceAllString(name, "_")
}
// Win32_PerfRawData_Tcpip_NetworkInterface docs:
// - https://technet.microsoft.com/en-us/security/aa394340(v=vs.80)
type networkInterface struct {
BytesReceivedPerSec float64 `perflib:"Bytes Received/sec"`
BytesSentPerSec float64 `perflib:"Bytes Sent/sec"`
BytesTotalPerSec float64 `perflib:"Bytes Total/sec"`
Name string
OutputQueueLength float64 `perflib:"Output Queue Length"`
PacketsOutboundDiscarded float64 `perflib:"Packets Outbound Discarded"`
PacketsOutboundErrors float64 `perflib:"Packets Outbound Errors"`
PacketsPerSec float64 `perflib:"Packets/sec"`
PacketsReceivedDiscarded float64 `perflib:"Packets Received Discarded"`
PacketsReceivedErrors float64 `perflib:"Packets Received Errors"`
PacketsReceivedPerSec float64 `perflib:"Packets Received/sec"`
PacketsReceivedUnknown float64 `perflib:"Packets Received Unknown"`
PacketsSentPerSec float64 `perflib:"Packets Sent/sec"`
CurrentBandwidth float64 `perflib:"Current Bandwidth"`
}
func (c *Collector) collect(ctx *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error {
logger = logger.With(slog.String("collector", Name))
var dst []networkInterface
var dst []perflibNetworkInterface
if err := perflib.UnmarshalObject(ctx.PerfObjects["Network Interface"], &dst, logger); err != nil {
return err
@@ -253,8 +316,99 @@ func (c *Collector) collect(ctx *types.ScrapeContext, logger *slog.Logger, ch ch
continue
}
name := mangleNetworkName(nic.Name)
if name == "" {
// Counters
ch <- prometheus.MustNewConstMetric(
c.bytesReceivedTotal,
prometheus.CounterValue,
nic.BytesReceivedPerSec,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.bytesSentTotal,
prometheus.CounterValue,
nic.BytesSentPerSec,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.bytesTotal,
prometheus.CounterValue,
nic.BytesTotalPerSec,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.outputQueueLength,
prometheus.GaugeValue,
nic.OutputQueueLength,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.packetsOutboundDiscarded,
prometheus.CounterValue,
nic.PacketsOutboundDiscarded,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.packetsOutboundErrors,
prometheus.CounterValue,
nic.PacketsOutboundErrors,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.packetsTotal,
prometheus.CounterValue,
nic.PacketsPerSec,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceivedDiscarded,
prometheus.CounterValue,
nic.PacketsReceivedDiscarded,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceivedErrors,
prometheus.CounterValue,
nic.PacketsReceivedErrors,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceivedTotal,
prometheus.CounterValue,
nic.PacketsReceivedPerSec,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceivedUnknown,
prometheus.CounterValue,
nic.PacketsReceivedUnknown,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.packetsSentTotal,
prometheus.CounterValue,
nic.PacketsSentPerSec,
nic.Name,
)
ch <- prometheus.MustNewConstMetric(
c.currentBandwidth,
prometheus.GaugeValue,
nic.CurrentBandwidth/8,
nic.Name,
)
}
return nil
}
func (c *Collector) collectPDH(ch chan<- prometheus.Metric) error {
data, err := c.perfDataCollector.Collect()
if err != nil {
return fmt.Errorf("failed to collect Network Information metrics: %w", err)
}
for nicName, nicData := range data {
if c.config.NicExclude.MatchString(nicName) ||
!c.config.NicInclude.MatchString(nicName) {
continue
}
@@ -262,82 +416,184 @@ func (c *Collector) collect(ctx *types.ScrapeContext, logger *slog.Logger, ch ch
ch <- prometheus.MustNewConstMetric(
c.bytesReceivedTotal,
prometheus.CounterValue,
nic.BytesReceivedPerSec,
name,
nicData[BytesReceivedPerSec].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.bytesSentTotal,
prometheus.CounterValue,
nic.BytesSentPerSec,
name,
nicData[BytesSentPerSec].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.bytesTotal,
prometheus.CounterValue,
nic.BytesTotalPerSec,
name,
nicData[BytesTotalPerSec].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.outputQueueLength,
prometheus.GaugeValue,
nic.OutputQueueLength,
name,
nicData[OutputQueueLength].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.packetsOutboundDiscarded,
prometheus.CounterValue,
nic.PacketsOutboundDiscarded,
name,
nicData[PacketsOutboundDiscarded].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.packetsOutboundErrors,
prometheus.CounterValue,
nic.PacketsOutboundErrors,
name,
nicData[PacketsOutboundErrors].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.packetsTotal,
prometheus.CounterValue,
nic.PacketsPerSec,
name,
nicData[PacketsPerSec].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceivedDiscarded,
prometheus.CounterValue,
nic.PacketsReceivedDiscarded,
name,
nicData[PacketsReceivedDiscarded].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceivedErrors,
prometheus.CounterValue,
nic.PacketsReceivedErrors,
name,
nicData[PacketsReceivedErrors].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceivedTotal,
prometheus.CounterValue,
nic.PacketsReceivedPerSec,
name,
nicData[PacketsReceivedPerSec].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.packetsReceivedUnknown,
prometheus.CounterValue,
nic.PacketsReceivedUnknown,
name,
nicData[PacketsReceivedUnknown].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.packetsSentTotal,
prometheus.CounterValue,
nic.PacketsSentPerSec,
name,
nicData[PacketsSentPerSec].FirstValue,
nicName,
)
ch <- prometheus.MustNewConstMetric(
c.currentBandwidth,
prometheus.GaugeValue,
nic.CurrentBandwidth/8,
name,
nicData[CurrentBandwidth].FirstValue/8,
nicName,
)
}
return nil
}
var addressFamily = map[uint16]string{
windows.AF_INET: "ipv4",
windows.AF_INET6: "ipv6",
}
func (c *Collector) collectNICAddresses(ch chan<- prometheus.Metric) error {
nicAdapterAddresses, err := adapterAddresses()
if err != nil {
return err
}
convertNicName := strings.NewReplacer("(", "[", ")", "]")
for _, nicAdapterAddress := range nicAdapterAddresses {
friendlyName := windows.UTF16PtrToString(nicAdapterAddress.FriendlyName)
nicName := windows.UTF16PtrToString(nicAdapterAddress.Description)
if c.config.NicExclude.MatchString(nicName) ||
!c.config.NicInclude.MatchString(nicName) {
continue
}
for address := nicAdapterAddress.FirstUnicastAddress; address != nil; address = address.Next {
ipAddr := address.Address.IP()
if ipAddr == nil || !ipAddr.IsGlobalUnicast() {
continue
}
ch <- prometheus.MustNewConstMetric(
c.nicAddressInfo,
prometheus.GaugeValue,
1,
convertNicName.Replace(nicName),
friendlyName,
ipAddr.String(),
addressFamily[address.Address.Sockaddr.Addr.Family],
)
}
for address := nicAdapterAddress.FirstAnycastAddress; address != nil; address = address.Next {
ipAddr := address.Address.IP()
if ipAddr == nil || !ipAddr.IsGlobalUnicast() {
continue
}
ch <- prometheus.MustNewConstMetric(
c.nicAddressInfo,
prometheus.GaugeValue,
1,
convertNicName.Replace(nicName),
friendlyName,
ipAddr.String(),
addressFamily[address.Address.Sockaddr.Addr.Family],
)
}
}
return nil
}
// adapterAddresses returns a list of IP adapter and address
// structures. The structure contains an IP adapter and flattened
// multiple IP addresses including unicast, anycast and multicast
// addresses.
func adapterAddresses() ([]*windows.IpAdapterAddresses, error) {
var b []byte
l := uint32(15000) // recommended initial size
for {
b = make([]byte, l)
const flags = windows.GAA_FLAG_SKIP_MULTICAST | windows.GAA_FLAG_SKIP_DNS_SERVER
err := windows.GetAdaptersAddresses(windows.AF_UNSPEC, flags, 0, (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])), &l)
if err == nil {
if l == 0 {
return nil, nil
}
break
}
if !errors.Is(err, windows.ERROR_BUFFER_OVERFLOW) {
return nil, os.NewSyscallError("getadaptersaddresses", err)
}
if l <= uint32(len(b)) {
return nil, os.NewSyscallError("getadaptersaddresses", err)
}
}
var addresses []*windows.IpAdapterAddresses
for address := (*windows.IpAdapterAddresses)(unsafe.Pointer(&b[0])); address != nil; address = address.Next {
addresses = append(addresses, address)
}
return addresses, nil
}

View File

@@ -1,21 +0,0 @@
//go:build windows
package net
import (
"testing"
)
func TestNetworkToInstanceName(t *testing.T) {
t.Parallel()
data := map[string]string{
"Intel[R] Dual Band Wireless-AC 8260": "Intel_R__Dual_Band_Wireless_AC_8260",
}
for in, out := range data {
got := mangleNetworkName(in)
if got != out {
t.Error("expected", out, "got", got)
}
}
}

View File

@@ -5,6 +5,8 @@ package tcp
import (
"fmt"
"log/slog"
"slices"
"strings"
"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/pkg/headers/iphlpapi"
@@ -17,9 +19,16 @@ import (
const Name = "tcp"
type Config struct{}
type Config struct {
CollectorsEnabled []string `yaml:"collectors_enabled"`
}
var ConfigDefaults = Config{}
var ConfigDefaults = Config{
CollectorsEnabled: []string{
"metrics",
"connections_state",
},
}
// A Collector is a Prometheus Collector for WMI Win32_PerfRawData_Tcpip_TCPv{4,6} metrics.
type Collector struct {
@@ -45,6 +54,10 @@ func New(config *Config) *Collector {
config = &ConfigDefaults
}
if config.CollectorsEnabled == nil {
config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled
}
c := &Collector{
config: *config,
}
@@ -52,8 +65,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.tcp.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 {
@@ -160,20 +191,25 @@ func (c *Collector) Build(_ *slog.Logger, _ *wmi.Client) error {
// to the provided prometheus Metric channel.
func (c *Collector) Collect(_ *types.ScrapeContext, logger *slog.Logger, ch chan<- prometheus.Metric) error {
logger = logger.With(slog.String("collector", Name))
if err := c.collect(ch); err != nil {
logger.Error("failed collecting tcp metrics",
slog.Any("err", err),
)
return err
if slices.Contains(c.config.CollectorsEnabled, "metrics") {
if err := c.collect(ch); err != nil {
logger.Error("failed collecting tcp metrics",
slog.Any("err", err),
)
return err
}
}
if err := c.collectConnectionsState(ch); err != nil {
logger.Error("failed collecting tcp connection state metrics",
slog.Any("err", err),
)
if slices.Contains(c.config.CollectorsEnabled, "connections_state") {
if err := c.collectConnectionsState(ch); err != nil {
logger.Error("failed collecting tcp connection state metrics",
slog.Any("err", err),
)
return err
return err
}
}
return nil

View File

@@ -29,6 +29,8 @@ test_alpha_total 42
# TYPE windows_cpu_interrupts_total counter
# HELP windows_cpu_logical_processor Total number of logical processors
# TYPE windows_cpu_logical_processor gauge
# HELP windows_net_nic_address_info A metric with a constant '1' value labeled with the network interface's address information.
# TYPE windows_net_nic_address_info gauge
# HELP windows_cpu_parking_status Parking Status represents whether a processor is parked or not
# TYPE windows_cpu_parking_status gauge
# HELP windows_cpu_processor_mperf_total Processor MPerf is the number of TSC ticks incremented while executing instructions