Skip to content

Commit 304a3b0

Browse files
committed
netstat_freebsd: add support for TCP state metrics
Metric added (gauge): node_netstat_tcp_connections{state="CLOSED"} node_netstat_tcp_connections{state="CLOSE_WAIT"} node_netstat_tcp_connections{state="CLOSING"} node_netstat_tcp_connections{state="ESTABLISHED"} node_netstat_tcp_connections{state="FIN_WAIT_1"} node_netstat_tcp_connections{state="FIN_WAIT_2"} node_netstat_tcp_connections{state="LAST_ACK"} node_netstat_tcp_connections{state="LISTEN"} node_netstat_tcp_connections{state="SYN_RCVD"} node_netstat_tcp_connections{state="SYN_SENT"} node_netstat_tcp_connections{state="TIME_WAIT"}
1 parent c093708 commit 304a3b0

File tree

2 files changed

+80
-4
lines changed

2 files changed

+80
-4
lines changed

collector/netstat_freebsd.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package collector
1818

1919
import (
20+
"encoding/binary"
2021
"errors"
2122
"fmt"
2223
"log/slog"
@@ -59,6 +60,16 @@ var (
5960
ipv6ForwardTotal = "bsdNetstatIPv6ForwardTotal"
6061
ipv6DeliveredTotal = "bsdNetstatIPv6DeliveredTotal"
6162

63+
tcpStates = []string{
64+
"CLOSED", "LISTEN", "SYN_SENT", "SYN_RCVD",
65+
"ESTABLISHED", "CLOSE_WAIT", "FIN_WAIT_1", "CLOSING",
66+
"LAST_ACK", "FIN_WAIT_2", "TIME_WAIT",
67+
}
68+
69+
tcpStatesMetric = prometheus.NewDesc(
70+
prometheus.BuildFQName(namespace, "netstat", "tcp_connections"),
71+
"Number of TCP connections per state", []string{"state"}, nil)
72+
6273
counterMetrics = map[string]*prometheus.Desc{
6374
// TCP stats
6475
tcpSendTotal: prometheus.NewDesc(
@@ -242,6 +253,30 @@ func getData(queryString string, expectedSize int) ([]byte, error) {
242253
return data, nil
243254
}
244255

256+
func getTCPStates() ([]uint64, error) {
257+
258+
// This sysctl returns an array of uint64
259+
data, err := sysctlRaw("net.inet.tcp.states")
260+
261+
if err != nil {
262+
return nil, err
263+
}
264+
265+
if len(data)/8 != len(tcpStates) {
266+
return nil, fmt.Errorf("invalid TCP states data: expected %d entries, found %d", len(tcpStates), len(data)/8)
267+
}
268+
269+
states := make([]uint64, 0)
270+
271+
offset := 0
272+
for range len(tcpStates) {
273+
s := data[offset : offset+8]
274+
offset += 8
275+
states = append(states, binary.NativeEndian.Uint64(s))
276+
}
277+
return states, nil
278+
}
279+
245280
type netStatCollector struct {
246281
netStatMetric *prometheus.Desc
247282
}
@@ -309,6 +344,15 @@ func (c *netStatCollector) Update(ch chan<- prometheus.Metric) error {
309344
)
310345
}
311346

347+
tcpConnsPerStates, err := getTCPStates()
348+
349+
if err != nil {
350+
return err
351+
}
352+
353+
for i, value := range tcpConnsPerStates {
354+
ch <- prometheus.MustNewConstMetric(tcpStatesMetric, prometheus.GaugeValue, float64(value), tcpStates[i])
355+
}
312356
return nil
313357
}
314358

@@ -323,6 +367,16 @@ func getFreeBSDDataMock(sysctl string) []byte {
323367
size := int(unsafe.Sizeof(C.struct_tcpstat{}))
324368

325369
return unsafe.Slice((*byte)(unsafe.Pointer(&tcpStats)), size)
370+
} else if sysctl == "net.inet.tcp.states" {
371+
tcpStatesSlice := make([]byte, 0, len(tcpStates)*8)
372+
tcpStatesValues := []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
373+
374+
for _, value := range tcpStatesValues {
375+
tcpStatesSlice = binary.NativeEndian.AppendUint64(tcpStatesSlice, value)
376+
}
377+
378+
return tcpStatesSlice
379+
326380
} else if sysctl == "net.inet.udp.stats" {
327381
udpStats := C.struct_udpstat{
328382
udps_opackets: 1234,

collector/netstat_freebsd_test.go

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,24 @@ func TestGetTCPMetrics(t *testing.T) {
6262
}
6363
}
6464

65+
func TestGetTCPStatesMetrics(t *testing.T) {
66+
testSetup()
67+
68+
tcpData, err := getTCPStates()
69+
if err != nil {
70+
t.Fatal("unexpected error:", err)
71+
}
72+
73+
expected := []uint64{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11}
74+
75+
for i, value := range tcpData {
76+
if got, want := float64(value), float64(expected[i]); got != want {
77+
t.Errorf("unexpected %s value: want %f, got %f", tcpStates[i], want, got)
78+
}
79+
}
80+
81+
}
82+
6583
func TestGetUDPMetrics(t *testing.T) {
6684
testSetup()
6785

@@ -143,20 +161,24 @@ func TestGetIPv6Metrics(t *testing.T) {
143161
}
144162

145163
func TestNetStatCollectorUpdate(t *testing.T) {
146-
ch := make(chan prometheus.Metric, len(counterMetrics))
147164
collector := &netStatCollector{
148165
netStatMetric: prometheus.NewDesc("netstat_metric", "NetStat Metric", nil, nil),
149166
}
167+
168+
totalMetrics := len(counterMetrics) + len(tcpStates)
169+
170+
ch := make(chan prometheus.Metric, totalMetrics)
171+
150172
err := collector.Update(ch)
151173
if err != nil {
152174
t.Fatal("unexpected error:", err)
153175
}
154176

155-
if got, want := len(ch), len(counterMetrics); got != want {
156-
t.Errorf("metric count mismatch: want %d, got %d", want, got)
177+
if got, want := len(ch), totalMetrics; got != want {
178+
t.Fatalf("metric count mismatch: want %d, got %d", want, got)
157179
}
158180

159-
for range len(counterMetrics) {
181+
for range totalMetrics {
160182
<-ch
161183
}
162184
}

0 commit comments

Comments
 (0)