1717package collector
1818
1919import (
20+ "encoding/binary"
2021 "errors"
2122 "fmt"
2223 "log/slog"
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+
245280type 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 ,
0 commit comments