Skip to content

Commit

Permalink
udp: Added UDP collector (#1725)
Browse files Browse the repository at this point in the history
  • Loading branch information
jkroepke authored Nov 11, 2024
1 parent 55181f5 commit eeb7955
Show file tree
Hide file tree
Showing 17 changed files with 376 additions and 58 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ lint:

.PHONY: e2e-test
e2e-test: windows_exporter.exe
pwsh -NonInteractive -ExecutionPolicy Bypass -File .\tools\end-to-end-test.ps1
powershell -NonInteractive -ExecutionPolicy Bypass -File .\tools\end-to-end-test.ps1

.PHONY: promtool
promtool: windows_exporter.exe
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Name | Description | Enabled by default
[textfile](docs/collector.textfile.md) | Read prometheus metrics from a text file |
[thermalzone](docs/collector.thermalzone.md) | Thermal information |
[time](docs/collector.time.md) | Windows Time Service |
[udp](docs/collector.udp.md) | UDP connections |
[update](docs/collector.update.md) | Windows Update Service |
[vmware](docs/collector.vmware.md) | Performance counters installed by the Vmware Guest agent |

Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,6 @@ This directory contains documentation of the collectors in the windows_exporter,
- [`textfile`](collector.textfile.md)
- [`thermalzone`](collector.thermalzone.md)
- [`time`](collector.time.md)
- [`udp`](collector.udp.md)
- [`update`](collector.update.md)
- [`vmware`](collector.vmware.md)
4 changes: 2 additions & 2 deletions docs/collector.process.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ metrics.

Enables IIS process name queries. IIS process names are combined with their app pool name to form the `process` label.

Disabled by default, and can be enabled with `--collector.process.iis=true`.
Disabled by default, and can be enabled with `--collector.process.iis`. NOTE: Just plain parameter without `true`.


### Example
Expand All @@ -44,7 +44,7 @@ Note that multiple processes with the same name will be disambiguated by
Windows by adding a number suffix, such as `firefox#2`. Your [regexp](https://en.wikipedia.org/wiki/Regular_expression) must take
these suffixes into consideration.

:warning: The regexp is case-sensitive, so `--collector.process.include="FIREFOX.*"` will **NOT** match a process named `firefox` .
:warning: The regexp is case-sensitive, so `--collector.process.include="FIREFOX.*"` will **NOT** match a process named `firefox` .

To specify multiple names, use the pipe `|` character:
```
Expand Down
27 changes: 13 additions & 14 deletions docs/collector.tcp.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
# tcp collector

The tcp collector exposes metrics about the TCP/IPv4 network stack.
The tcp collector exposes metrics about the TCP network stack.

|||
-|-
Metric name prefix | `tcp`
Data source | Perflib
Classes | [`Win32_PerfRawData_Tcpip_TCPv4`](https://msdn.microsoft.com/en-us/library/aa394341(v=vs.85).aspx), Win32_PerfRawData_Tcpip_TCPv6
Enabled by default? | No

## Flags
Expand All @@ -15,18 +14,18 @@ None

## Metrics

Name | Description | Type | Labels
-----|-------------|------|-------
`windows_tcp_connection_failures_total` | Number of times TCP connections have made a direct transition to the CLOSED state from the SYN-SENT state or the SYN-RCVD state, plus the number of times TCP connections have made a direct transition from the SYN-RCVD state to the LISTEN state | counter | af
`windows_tcp_connections_active_total` | Number of times TCP connections have made a direct transition from the CLOSED state to the SYN-SENT state.| counter | af
`windows_tcp_connections_established` | Number of TCP connections for which the current state is either ESTABLISHED or CLOSE-WAIT. | gauge | af
`windows_tcp_connections_passive_total` | Number of times TCP connections have made a direct transition from the LISTEN state to the SYN-RCVD state. | counter | af
`windows_tcp_connections_reset_total` | Connections Reset is the number of times TCP connections have made a direct transition to the CLOSED state from either the ESTABLISHED state or the CLOSE-WAIT state. | counter | af
`windows_tcp_segments_total` | Total segments sent or received using the TCP protocol | counter | af
`windows_tcp_segments_received_total` | Total segments received, including those received in error. This count includes segments received on currently established connections | counter | af
`windows_tcp_segments_retransmitted_total` | Total segments retransmitted. That is, segments transmitted that contain one or more previously transmitted bytes | counter | af
`windows_tcp_segments_sent_total` | Total segments sent, including those on current connections, but excluding those containing *only* retransmitted bytes | counter | af
`windows_tcp_connections_state_count` | Number of TCP connections by state among: CLOSED, LISTENING, SYN_SENT, SYN_RECEIVED, ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSE_WAIT, CLOSING, LAST_ACK, TIME_WAIT, DELETE_TCB | gauge | af
| Name | Description | Type | Labels |
|--------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------|--------|
| `windows_tcp_connection_failures_total` | Number of times TCP connections have made a direct transition to the CLOSED state from the SYN-SENT state or the SYN-RCVD state, plus the number of times TCP connections have made a direct transition from the SYN-RCVD state to the LISTEN state | counter | af |
| `windows_tcp_connections_active_total` | Number of times TCP connections have made a direct transition from the CLOSED state to the SYN-SENT state. | counter | af |
| `windows_tcp_connections_established` | Number of TCP connections for which the current state is either ESTABLISHED or CLOSE-WAIT. | gauge | af |
| `windows_tcp_connections_passive_total` | Number of times TCP connections have made a direct transition from the LISTEN state to the SYN-RCVD state. | counter | af |
| `windows_tcp_connections_reset_total` | Connections Reset is the number of times TCP connections have made a direct transition to the CLOSED state from either the ESTABLISHED state or the CLOSE-WAIT state. | counter | af |
| `windows_tcp_segments_total` | Total segments sent or received using the TCP protocol | counter | af |
| `windows_tcp_segments_received_total` | Total segments received, including those received in error. This count includes segments received on currently established connections | counter | af |
| `windows_tcp_segments_retransmitted_total` | Total segments retransmitted. That is, segments transmitted that contain one or more previously transmitted bytes | counter | af |
| `windows_tcp_segments_sent_total` | Total segments sent, including those on current connections, but excluding those containing *only* retransmitted bytes | counter | af |
| `windows_tcp_connections_state_count` | Number of TCP connections by state among: CLOSED, LISTENING, SYN_SENT, SYN_RECEIVED, ESTABLISHED, FIN_WAIT1, FIN_WAIT2, CLOSE_WAIT, CLOSING, LAST_ACK, TIME_WAIT, DELETE_TCB | gauge | af |

### Example metric
_This collector does not yet have explained examples, we would appreciate your help adding them!_
Expand Down
31 changes: 31 additions & 0 deletions docs/collector.udp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# udp collector

The udp collector exposes metrics about the UDP network stack.

|||
-|-
Metric name prefix | `udp`
Data source | Perflib
Enabled by default? | No

## Flags

None

## Metrics

| Name | Description | Type | Labels |
|-----------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------|---------|--------|
| `windows_udp_datagram_datagram_no_port_total` | Number of received UDP datagrams for which there was no application at the destination port | counter | af |
| `windows_udp_datagram_received_errors_total` | Number of received UDP datagrams that could not be delivered for reasons other than the lack of an application at the destination port | counter | af |
| `windows_udp_datagram_received_total` | Number of UDP datagrams segments received | counter | af |
| `windows_udp_datagram_sent_total` | Number of UDP datagrams segments sent | counter | af |

### Example metric
_This collector does not yet have explained examples, we would appreciate your help adding them!_

## Useful queries
_This collector does not yet have any useful queries added, we would appreciate your help adding them!_

## Alerting examples
_This collector does not yet have alerting examples, we would appreciate your help adding them!_
28 changes: 13 additions & 15 deletions internal/collector/tcp/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package tcp

import (
"errors"
"fmt"
"log/slog"
"slices"
Expand Down Expand Up @@ -97,6 +98,11 @@ func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) {
}

func (c *Collector) Close(_ *slog.Logger) error {
if slices.Contains(c.config.CollectorsEnabled, "metrics") {
c.perfDataCollector4.Close()
c.perfDataCollector6.Close()
}

return nil
}

Expand All @@ -115,12 +121,12 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {

var err error

c.perfDataCollector4, err = perfdata.NewCollector(perfdata.V1, "TCPv4", nil, counters)
c.perfDataCollector4, err = perfdata.NewCollector(perfdata.V2, "TCPv4", nil, counters)
if err != nil {
return fmt.Errorf("failed to create TCPv4 collector: %w", err)
}

c.perfDataCollector6, err = perfdata.NewCollector(perfdata.V1, "TCPv6", nil, counters)
c.perfDataCollector6, err = perfdata.NewCollector(perfdata.V2, "TCPv6", nil, counters)
if err != nil {
return fmt.Errorf("failed to create TCPv6 collector: %w", err)
}
Expand Down Expand Up @@ -190,30 +196,22 @@ func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {

// Collect sends the metric values for each metric
// 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))
func (c *Collector) Collect(_ *types.ScrapeContext, _ *slog.Logger, ch chan<- prometheus.Metric) error {
errs := make([]error, 0, 2)

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
errs = append(errs, fmt.Errorf("failed collecting tcp metrics: %w", 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
errs = append(errs, fmt.Errorf("failed collecting tcp connection state metrics: %w", err))
}
}

return nil
return errors.Join(errs...)
}

func (c *Collector) collect(ch chan<- prometheus.Metric) error {
Expand Down
2 changes: 1 addition & 1 deletion internal/collector/time/time.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func (c *Collector) Collect(ctx *types.ScrapeContext, logger *slog.Logger, ch ch

// Perflib "Windows Time Service".
type windowsTime struct {
ClockFrequencyAdjustmentPPBTotal float64 `perflib:"Clock Frequency Adjustment (ppb)"`
ClockFrequencyAdjustmentPPBTotal float64 `perflib:"Clock Frequency Adjustment (PPB)"`
ComputedTimeOffset float64 `perflib:"Computed Time Offset"`
NTPClientTimeSourceCount float64 `perflib:"NTP Client Time Source Count"`
NTPRoundTripDelay float64 `perflib:"NTP Roundtrip Delay"`
Expand Down
15 changes: 15 additions & 0 deletions internal/collector/udp/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package udp

// The TCPv6 performance object uses the same fields.
// https://learn.microsoft.com/en-us/dotnet/api/system.net.networkinformation.tcpstate?view=net-8.0.
const (
datagramsNoPortPerSec = "Datagrams No Port/sec"
datagramsReceivedPerSec = "Datagrams Received/sec"
datagramsReceivedErrors = "Datagrams Received Errors"
datagramsSentPerSec = "Datagrams Sent/sec"
)

// Datagrams No Port/sec is the rate of received UDP datagrams for which there was no application at the destination port.
// Datagrams Received Errors is the number of received UDP datagrams that could not be delivered for reasons other than the lack of an application at the destination port.
// Datagrams Received/sec is the rate at which UDP datagrams are delivered to UDP users.
// Datagrams Sent/sec is the rate at which UDP datagrams are sent from the entity.
168 changes: 168 additions & 0 deletions internal/collector/udp/udp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
//go:build windows

package udp

import (
"fmt"
"log/slog"

"github.com/alecthomas/kingpin/v2"
"github.com/prometheus-community/windows_exporter/internal/mi"
"github.com/prometheus-community/windows_exporter/internal/perfdata"
"github.com/prometheus-community/windows_exporter/internal/perfdata/perftypes"
"github.com/prometheus-community/windows_exporter/internal/types"
"github.com/prometheus/client_golang/prometheus"
)

const Name = "udp"

type Config struct{}

var ConfigDefaults = Config{}

// A Collector is a Prometheus Collector for WMI Win32_PerfRawData_Tcpip_TCPv{4,6} metrics.
type Collector struct {
config Config

perfDataCollector4 perfdata.Collector
perfDataCollector6 perfdata.Collector

datagramsNoPortTotal *prometheus.Desc
datagramsReceivedTotal *prometheus.Desc
datagramsReceivedErrorsTotal *prometheus.Desc
datagramsSentTotal *prometheus.Desc
}

func New(config *Config) *Collector {
if config == nil {
config = &ConfigDefaults
}

c := &Collector{
config: *config,
}

return c
}

func NewWithFlags(_ *kingpin.Application) *Collector {
c := &Collector{
config: ConfigDefaults,
}

return c
}

func (c *Collector) GetName() string {
return Name
}

func (c *Collector) GetPerfCounter(_ *slog.Logger) ([]string, error) {
return []string{}, nil
}

func (c *Collector) Close(_ *slog.Logger) error {
c.perfDataCollector4.Close()
c.perfDataCollector6.Close()

return nil
}

func (c *Collector) Build(_ *slog.Logger, _ *mi.Session) error {
counters := []string{
datagramsNoPortPerSec,
datagramsReceivedPerSec,
datagramsReceivedErrors,
datagramsSentPerSec,
}

var err error

c.perfDataCollector4, err = perfdata.NewCollector(perfdata.V2, "UDPv4", nil, counters)
if err != nil {
return fmt.Errorf("failed to create UDPv4 collector: %w", err)
}

c.perfDataCollector6, err = perfdata.NewCollector(perfdata.V2, "UDPv6", nil, counters)
if err != nil {
return fmt.Errorf("failed to create UDPv6 collector: %w", err)
}

c.datagramsNoPortTotal = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "datagram_no_port_total"),
"Number of received UDP datagrams for which there was no application at the destination port",
[]string{"af"},
nil,
)
c.datagramsReceivedTotal = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "datagram_received_total"),
"UDP datagrams are delivered to UDP users",
[]string{"af"},
nil,
)
c.datagramsReceivedErrorsTotal = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "datagram_received_errors_total"),
"Number of received UDP datagrams that could not be delivered for reasons other than the lack of an application at the destination port",
[]string{"af"},
nil,
)
c.datagramsSentTotal = prometheus.NewDesc(
prometheus.BuildFQName(types.Namespace, Name, "datagram_sent_total"),
"UDP datagrams are sent from the entity",
[]string{"af"},
nil,
)

return nil
}

// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *Collector) Collect(_ *types.ScrapeContext, _ *slog.Logger, ch chan<- prometheus.Metric) error {
return c.collect(ch)
}

func (c *Collector) collect(ch chan<- prometheus.Metric) error {
data, err := c.perfDataCollector4.Collect()
if err != nil {
return fmt.Errorf("failed to collect UDPv4 metrics: %w", err)
}

c.writeUDPCounters(ch, data[perftypes.EmptyInstance], []string{"ipv4"})

data, err = c.perfDataCollector6.Collect()
if err != nil {
return fmt.Errorf("failed to collect UDPv6 metrics: %w", err)
}

c.writeUDPCounters(ch, data[perftypes.EmptyInstance], []string{"ipv6"})

return nil
}

func (c *Collector) writeUDPCounters(ch chan<- prometheus.Metric, metrics map[string]perftypes.CounterValues, labels []string) {
ch <- prometheus.MustNewConstMetric(
c.datagramsNoPortTotal,
prometheus.CounterValue,
metrics[datagramsNoPortPerSec].FirstValue,
labels...,
)
ch <- prometheus.MustNewConstMetric(
c.datagramsReceivedErrorsTotal,
prometheus.CounterValue,
metrics[datagramsReceivedErrors].FirstValue,
labels...,
)
ch <- prometheus.MustNewConstMetric(
c.datagramsReceivedTotal,
prometheus.GaugeValue,
metrics[datagramsReceivedPerSec].FirstValue,
labels...,
)
ch <- prometheus.MustNewConstMetric(
c.datagramsSentTotal,
prometheus.CounterValue,
metrics[datagramsSentPerSec].FirstValue,
labels...,
)
}
Loading

0 comments on commit eeb7955

Please sign in to comment.