Skip to content

Commit

Permalink
Add /proc/net/udp parsing especially for the tx_queue and rx_queue le…
Browse files Browse the repository at this point in the history
…ngths.

@pgier this belongs to the requested of @discordianfish in prometheus/node_exporter#1503.

Signed-off-by: Peter Bueschel <peter.bueschel@logmein.com>
  • Loading branch information
Peter Bueschel authored and pgier committed Dec 9, 2019
1 parent a5d9959 commit 2c8d75c
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 0 deletions.
14 changes: 14 additions & 0 deletions fixtures.ttar
Original file line number Diff line number Diff line change
Expand Up @@ -1826,6 +1826,20 @@ Lines: 1
00015c73 00020e76 F0000769 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/proc/net/udp
Lines: 4
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
0: 00000000:0016 00000000:0000 0A 00000000:00000001 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
1: 00000000:0016 00000000:0000 0A 00000001:00000000 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
2: 00000000:0016 00000000:0000 0A 00000001:00000001 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/proc/net/udp_broken
Lines: 2
sl local_address rem_address st
1: 00000000:0016 00000000:0000 0A
Mode: 644
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Path: fixtures/proc/net/unix
Lines: 6
Num RefCount Protocol Flags Type St Inode Path
Expand Down
94 changes: 94 additions & 0 deletions net_udp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package procfs

import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)

type (
// NetUDPLine is a line parsed from /proc/net/udp
// For the proc file format details, see https://linux.die.net/man/5/proc
NetUDPLine struct {
TxQueue uint64
RxQueue uint64
}

NetUDP struct {
TxQueueLength uint64
RxQueueLength uint64
UsedSockets uint64
}
)

// NetUDP returns kernel/networking statistics for udp datagrams read from /proc/net/udp.
func (fs FS) NetUDP() (*NetUDP, error) {
return newNetUDP(fs.proc.Path("net/udp"))
}

// newNetUDP creates a new NetUDP from the contents of the given file.
func newNetUDP(file string) (*NetUDP, error) {
f, err := os.Open(file)
if err != nil {
return nil, err
}
defer f.Close()

netUDP := &NetUDP{}
s := bufio.NewScanner(f)
s.Scan() // skip first line with headers
for s.Scan() {
fields := strings.Fields(s.Text())
line, err := parseNetUDPLine(fields)
if err != nil {
return nil, err
}
netUDP.TxQueueLength += line.TxQueue
netUDP.RxQueueLength += line.RxQueue
netUDP.UsedSockets++
}
if err := s.Err(); err != nil {
return nil, err
}
return netUDP, nil
}

func parseNetUDPLine(fields []string) (*NetUDPLine, error) {
line := &NetUDPLine{}
if len(fields) < 5 {
return nil, fmt.Errorf(
"cannot parse net udp socket line as it has less then 5 columns: %s",
strings.Join(fields, " "),
)
}
q := strings.Split(fields[4], ":")
if len(q) < 2 {
return nil, fmt.Errorf(
"cannot parse tx/rx queues in udp socket line as it has a missing colon: %s",
fields[4],
)
}
var err error // parse error
if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
return nil, fmt.Errorf("cannot parse tx_queue value in udp socket line: %s", err)
}
if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
return nil, fmt.Errorf("cannot parse rx_queue value in udp socket line: %s", err)
}
return line, nil
}
131 changes: 131 additions & 0 deletions net_udp_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// Copyright 2018 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package procfs

import (
"reflect"
"testing"
)

func Test_parseNetUDPLine(t *testing.T) {
type args struct {
fields []string
}
tests := []struct {
name string
args args
want *NetUDPLine
wantErr bool
}{
{
name: "reading valid lines, no issue should happened",
args: args{
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "00000017:0000002A"},
},
want: &NetUDPLine{TxQueue: 23, RxQueue: 42},
},
{
name: "error case - invalid line - number of fields/columns < 5",
args: args{
fields: []string{"1:", "00000000:0000", "00000000:0000", "07"},
},
want: nil,
wantErr: true,
},
{
name: "error case - cannot parse line - missing colon",
args: args{
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "0000000000000001"},
},
want: nil,
wantErr: true,
},
{
name: "error case - parse tx_queue - not an valid hex",
args: args{
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "DEADCODE:00000001"},
},
want: nil,
wantErr: true,
},
{
name: "error case - parse rx_queue - not an valid hex",
args: args{
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:FEEDCODE"},
},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseNetUDPLine(tt.args.fields)
if (err != nil) != tt.wantErr {
t.Errorf("parseNetUDPLine() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.want == nil && got != nil {
t.Errorf("parseNetUDPLine() = %v, want %v", got, tt.want)
}
if got != nil {
if (got.RxQueue != tt.want.RxQueue) || (got.TxQueue != tt.want.TxQueue) {
t.Errorf("parseNetUDPLine() = %#v, want %#v", got, tt.want)
}
}
})
}
}

func Test_newNetUDP(t *testing.T) {
type args struct {
file string
}
tests := []struct {
name string
args args
want *NetUDP
wantErr bool
}{
{
name: "file found, no error should come up",
args: args{file: "fixtures/proc/net/udp"},
want: &NetUDP{TxQueueLength: 2, RxQueueLength: 2, UsedSockets: 3},
wantErr: false,
},
{
name: "error case - file not found",
args: args{file: "somewhere over the rainbow"},
want: nil,
wantErr: true,
},
{
name: "error case - parse error",
args: args{file: "fixtures/proc/net/udp_broken"},
want: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := newNetUDP(tt.args.file)
if (err != nil) != tt.wantErr {
t.Errorf("newNetUDP() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("newNetUDP() = %v, want %v", got, tt.want)
}
})
}
}

0 comments on commit 2c8d75c

Please sign in to comment.