@@ -18,13 +18,12 @@ package collector
1818
1919import (
2020 "fmt"
21- "io"
22- "io/ioutil"
2321 "os"
24- "strconv "
25- "strings "
22+ "syscall "
23+ "unsafe "
2624
2725 "github.com/go-kit/log"
26+ "github.com/mdlayher/netlink"
2827 "github.com/prometheus/client_golang/prometheus"
2928)
3029
@@ -80,16 +79,64 @@ func NewTCPStatCollector(logger log.Logger) (Collector, error) {
8079 }, nil
8180}
8281
82+ // InetDiagSockID (inet_diag_sockid) contains the socket identity.
83+ // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L13
84+ type InetDiagSockID struct {
85+ SourcePort [2 ]byte
86+ DestPort [2 ]byte
87+ SourceIP [4 ][4 ]byte
88+ DestIP [4 ][4 ]byte
89+ Interface uint32
90+ Cookie [2 ]uint32
91+ }
92+
93+ // InetDiagReqV2 (inet_diag_req_v2) is used to request diagnostic data.
94+ // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L37
95+ type InetDiagReqV2 struct {
96+ Family uint8
97+ Protocol uint8
98+ Ext uint8
99+ Pad uint8
100+ States uint32
101+ ID InetDiagSockID
102+ }
103+
104+ const sizeOfDiagRequest = 0x38
105+
106+ func (req * InetDiagReqV2 ) Serialize () []byte {
107+ return (* (* [sizeOfDiagRequest ]byte )(unsafe .Pointer (req )))[:]
108+ }
109+
110+ func (req * InetDiagReqV2 ) Len () int {
111+ return sizeOfDiagRequest
112+ }
113+
114+ type InetDiagMsg struct {
115+ Family uint8
116+ State uint8
117+ Timer uint8
118+ Retrans uint8
119+ ID InetDiagSockID
120+ Expires uint32
121+ RQueue uint32
122+ WQueue uint32
123+ UID uint32
124+ Inode uint32
125+ }
126+
127+ func parseInetDiagMsg (b []byte ) * InetDiagMsg {
128+ return (* InetDiagMsg )(unsafe .Pointer (& b [0 ]))
129+ }
130+
83131func (c * tcpStatCollector ) Update (ch chan <- prometheus.Metric ) error {
84- tcpStats , err := getTCPStats (procFilePath ( "net/tcp" ) )
132+ tcpStats , err := getTCPStats (syscall . AF_INET )
85133 if err != nil {
86134 return fmt .Errorf ("couldn't get tcpstats: %w" , err )
87135 }
88136
89137 // if enabled ipv6 system
90- tcp6File := procFilePath ("net/tcp6" )
91- if _ , hasIPv6 := os .Stat (tcp6File ); hasIPv6 == nil {
92- tcp6Stats , err := getTCPStats (tcp6File )
138+ if _ , hasIPv6 := os .Stat (procFilePath ("net/tcp6" )); hasIPv6 == nil {
139+ tcp6Stats , err := getTCPStats (syscall .AF_INET6 )
93140 if err != nil {
94141 return fmt .Errorf ("couldn't get tcp6stats: %w" , err )
95142 }
@@ -102,59 +149,51 @@ func (c *tcpStatCollector) Update(ch chan<- prometheus.Metric) error {
102149 for st , value := range tcpStats {
103150 ch <- c .desc .mustNewConstMetric (value , st .String ())
104151 }
152+
105153 return nil
106154}
107155
108- func getTCPStats (statsFile string ) (map [tcpConnectionState ]float64 , error ) {
109- file , err := os .Open (statsFile )
156+ func getTCPStats (family uint8 ) (map [tcpConnectionState ]float64 , error ) {
157+ const TCPFAll = 0xFFF
158+ const InetDiagInfo = 2
159+ const SockDiagByFamily = 20
160+
161+ conn , err := netlink .Dial (syscall .NETLINK_INET_DIAG , nil )
110162 if err != nil {
111- return nil , err
163+ return nil , fmt .Errorf ("couldn't connect netlink: %w" , err )
164+ }
165+ defer conn .Close ()
166+
167+ msg := netlink.Message {
168+ Header : netlink.Header {
169+ Type : SockDiagByFamily ,
170+ Flags : syscall .NLM_F_REQUEST | syscall .NLM_F_DUMP ,
171+ },
172+ Data : (& InetDiagReqV2 {
173+ Family : family ,
174+ Protocol : syscall .IPPROTO_TCP ,
175+ States : TCPFAll ,
176+ Ext : 0 | 1 << (InetDiagInfo - 1 ),
177+ }).Serialize (),
112178 }
113- defer file .Close ()
114-
115- return parseTCPStats (file )
116- }
117179
118- func parseTCPStats (r io.Reader ) (map [tcpConnectionState ]float64 , error ) {
119- tcpStats := map [tcpConnectionState ]float64 {}
120- contents , err := ioutil .ReadAll (r )
180+ messages , err := conn .Execute (msg )
121181 if err != nil {
122182 return nil , err
123183 }
124184
125- for _ , line := range strings .Split (string (contents ), "\n " )[1 :] {
126- parts := strings .Fields (line )
127- if len (parts ) == 0 {
128- continue
129- }
130- if len (parts ) < 5 {
131- return nil , fmt .Errorf ("invalid TCP stats line: %q" , line )
132- }
133-
134- qu := strings .Split (parts [4 ], ":" )
135- if len (qu ) < 2 {
136- return nil , fmt .Errorf ("cannot parse tx_queues and rx_queues: %q" , line )
137- }
138-
139- tx , err := strconv .ParseUint (qu [0 ], 16 , 64 )
140- if err != nil {
141- return nil , err
142- }
143- tcpStats [tcpConnectionState (tcpTxQueuedBytes )] += float64 (tx )
144-
145- rx , err := strconv .ParseUint (qu [1 ], 16 , 64 )
146- if err != nil {
147- return nil , err
148- }
149- tcpStats [tcpConnectionState (tcpRxQueuedBytes )] += float64 (rx )
185+ return parseTCPStats (messages )
186+ }
150187
151- st , err := strconv .ParseInt (parts [3 ], 16 , 8 )
152- if err != nil {
153- return nil , err
154- }
188+ func parseTCPStats (msgs []netlink.Message ) (map [tcpConnectionState ]float64 , error ) {
189+ tcpStats := map [tcpConnectionState ]float64 {}
155190
156- tcpStats [tcpConnectionState (st )]++
191+ for _ , m := range msgs {
192+ msg := parseInetDiagMsg (m .Data )
157193
194+ tcpStats [tcpTxQueuedBytes ] += float64 (msg .WQueue )
195+ tcpStats [tcpRxQueuedBytes ] += float64 (msg .RQueue )
196+ tcpStats [tcpConnectionState (msg .State )]++
158197 }
159198
160199 return tcpStats , nil
0 commit comments