From 5ac4381a62ff84a7b6e942269c06bdf71132bac4 Mon Sep 17 00:00:00 2001 From: Peter Bueschel Date: Mon, 18 Nov 2019 22:09:01 +0100 Subject: [PATCH 1/3] Add /proc/net/udp parsing especially for the tx_queue and rx_queue lengths. @pgier this belongs to the requested of @discordianfish in https://github.com/prometheus/node_exporter/pull/1503. Signed-off-by: Peter Bueschel --- fixtures.ttar | 14 ++++++ net_udp.go | 94 ++++++++++++++++++++++++++++++++++ net_udp_test.go | 131 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) create mode 100644 net_udp.go create mode 100644 net_udp_test.go diff --git a/fixtures.ttar b/fixtures.ttar index c50a18ace..6828389a2 100644 --- a/fixtures.ttar +++ b/fixtures.ttar @@ -1825,6 +1825,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 diff --git a/net_udp.go b/net_udp.go new file mode 100644 index 000000000..4cd1f441c --- /dev/null +++ b/net_udp.go @@ -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 +} diff --git a/net_udp_test.go b/net_udp_test.go new file mode 100644 index 000000000..49e04b2e8 --- /dev/null +++ b/net_udp_test.go @@ -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) + } + }) + } +} From 1632cd9efe3627166e00af4ffd92fffecf39c167 Mon Sep 17 00:00:00 2001 From: Peter Bueschel Date: Tue, 26 Nov 2019 17:55:29 +0100 Subject: [PATCH 2/3] Add extra summary struct for /proc/net/upd{,6}. Add UDPv6 support. Limit file read to 4GiB. Signed-off-by: Peter Bueschel --- fixtures.ttar | 7 ++ net_udp.go | 175 +++++++++++++++++++++++++++++++++++++++++------- net_udp_test.go | 174 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 293 insertions(+), 63 deletions(-) diff --git a/fixtures.ttar b/fixtures.ttar index 6828389a2..3ff38528b 100644 --- a/fixtures.ttar +++ b/fixtures.ttar @@ -1833,6 +1833,13 @@ Lines: 4 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/udp6 +Lines: 3 + sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops + 1315: 00000000000000000000000000000000:14EB 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 981 0 21040 2 0000000013726323 0 + 6073: 00000000000000000000000000000000:C781 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 1000 0 11337031 2 00000000b9256fdd 0 +Mode: 444 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/proc/net/udp_broken Lines: 2 sl local_address rem_address st diff --git a/net_udp.go b/net_udp.go index 4cd1f441c..8bfb30f86 100644 --- a/net_udp.go +++ b/net_udp.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright 2019 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 @@ -16,41 +16,85 @@ package procfs import ( "bufio" "fmt" + "io" "os" "strconv" "strings" ) +const ( + readLimit = 4294967296 // Bytes +) + 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 represents the contents of /proc/net/udp{,6} file without the header. + NetUDP []*netUDPLine - NetUDP struct { + // NetUDPSummary provides already computed values like the total queue lengths or + // the total number of used sockets. In contrast to NetUDP it does not collect + // the parsed lines into a slice. + NetUDPSummary struct { + // TxQueueLength shows the total queue length of all parsed tx_queue lengths. TxQueueLength uint64 + // RxQueueLength shows the total queue length of all parsed rx_queue lengths. RxQueueLength uint64 - UsedSockets uint64 + // UsedSockets shows the total number of parsed lines representing the + // number of used sockets. + UsedSockets uint64 + } + + // netUDPLine represents the fields parsed from a single line + // in /proc/net/udp{,6}. Fields which are not used by UDP are skipped. + // For the proc file format details, see https://linux.die.net/man/5/proc. + netUDPLine struct { + Sl uint64 + LocalAddr uint64 + LocalPort uint64 + RemAddr uint64 + RemPort uint64 + St uint64 + TxQueue uint64 + RxQueue uint64 + UID uint64 } ) -// NetUDP returns kernel/networking statistics for udp datagrams read from /proc/net/udp. -func (fs FS) NetUDP() (*NetUDP, error) { +// NetUDP returns the IPv4 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) { +// NetUDP6 returns the IPv6 kernel/networking statistics for UDP datagrams +// read from /proc/net/udp6. +func (fs FS) NetUDP6() (NetUDP, error) { + return newNetUDP(fs.proc.Path("net/udp6")) +} + +// NetUDPSummary returns already computed statistics like the total queue lengths +// for UDP datagrams read from /proc/net/udp. +func (fs FS) NetUDPSummary() (*NetUDPSummary, error) { + return newNetUDPSummary(fs.proc.Path("net/udp")) +} + +// NetUDP6Summary returns already computed statistics like the total queue lengths +// for UDP datagrams read from /proc/net/udp6. +func (fs FS) NetUDP6Summary() (*NetUDPSummary, error) { + return newNetUDPSummary(fs.proc.Path("net/udp6")) +} + +// newNetUDP creates a new NetUDP{,6} 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) + netUDP := NetUDP{} + + lr := io.LimitReader(f, readLimit) + s := bufio.NewScanner(lr) s.Scan() // skip first line with headers for s.Scan() { fields := strings.Fields(s.Text()) @@ -58,9 +102,7 @@ func newNetUDP(file string) (*NetUDP, error) { if err != nil { return nil, err } - netUDP.TxQueueLength += line.TxQueue - netUDP.RxQueueLength += line.RxQueue - netUDP.UsedSockets++ + netUDP = append(netUDP, line) } if err := s.Err(); err != nil { return nil, err @@ -68,27 +110,112 @@ func newNetUDP(file string) (*NetUDP, error) { return netUDP, nil } -func parseNetUDPLine(fields []string) (*NetUDPLine, error) { - line := &NetUDPLine{} - if len(fields) < 5 { +// newNetUDP creates a new NetUDP{,6} from the contents of the given file. +func newNetUDPSummary(file string) (*NetUDPSummary, error) { + f, err := os.Open(file) + if err != nil { + return nil, err + } + defer f.Close() + + netUDPSummary := &NetUDPSummary{} + + lr := io.LimitReader(f, readLimit) + s := bufio.NewScanner(lr) + 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 + } + netUDPSummary.TxQueueLength += line.TxQueue + netUDPSummary.RxQueueLength += line.RxQueue + netUDPSummary.UsedSockets++ + } + if err := s.Err(); err != nil { + return nil, err + } + return netUDPSummary, nil +} + +// parseNetUDPLine parses a single line, represented by a list of fields. +func parseNetUDPLine(fields []string) (*netUDPLine, error) { + line := &netUDPLine{} + if len(fields) < 8 { return nil, fmt.Errorf( - "cannot parse net udp socket line as it has less then 5 columns: %s", + "cannot parse net udp socket line as it has less then 8 columns: %s", strings.Join(fields, " "), ) } + var err error // parse error + + // sl + s := strings.Split(fields[0], ":") + if len(s) != 2 { + return nil, fmt.Errorf( + "cannot parse sl field in udp socket line: %s", fields[0]) + } + + if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil { + return nil, fmt.Errorf("cannot parse sl value in udp socket line: %s", err) + } + // local_address + l := strings.Split(fields[1], ":") + if len(l) != 2 { + return nil, fmt.Errorf( + "cannot parse local_address field in udp socket line: %s", fields[1]) + } + if line.LocalAddr, err = strconv.ParseUint(l[0], 16, 64); err != nil { + return nil, fmt.Errorf( + "cannot parse local_address value in udp socket line: %s", err) + } + if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil { + return nil, fmt.Errorf( + "cannot parse local_address port value in udp socket line: %s", err) + } + + // remote_address + r := strings.Split(fields[2], ":") + if len(r) != 2 { + return nil, fmt.Errorf( + "cannot parse rem_address field in udp socket line: %s", fields[1]) + } + if line.RemAddr, err = strconv.ParseUint(r[0], 16, 64); err != nil { + return nil, fmt.Errorf( + "cannot parse rem_address value in udp socket line: %s", err) + } + if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil { + return nil, fmt.Errorf( + "cannot parse rem_address port value in udp socket line: %s", err) + } + + // st + if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil { + return nil, fmt.Errorf( + "cannot parse st value in udp socket line: %s", err) + } + + // tx_queue and rx_queue q := strings.Split(fields[4], ":") - if len(q) < 2 { + 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) } + + // uid + if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil { + return nil, fmt.Errorf( + "cannot parse uid value in udp socket line: %s", err) + } + return line, nil } diff --git a/net_udp_test.go b/net_udp_test.go index 49e04b2e8..563530c78 100644 --- a/net_udp_test.go +++ b/net_udp_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 The Prometheus Authors +// Copyright 2019 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 @@ -19,58 +19,79 @@ import ( ) func Test_parseNetUDPLine(t *testing.T) { - type args struct { - fields []string - } tests := []struct { + fields []string name string - args args - want *NetUDPLine + want *netUDPLine wantErr bool }{ { - name: "reading valid lines, no issue should happened", - args: args{ - fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "00000017:0000002A"}, + name: "reading valid lines, no issue should happened", + fields: []string{"11:", "00000000:0000", "00000000:0000", "0A", "00000017:0000002A", "0:0", "0", "1000"}, + want: &netUDPLine{ + Sl: 11, + LocalAddr: 0, + LocalPort: 0, + RemAddr: 0, + RemPort: 0, + St: 10, + TxQueue: 23, + RxQueue: 42, + UID: 1000, }, - 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"}, - }, + name: "error case - invalid line - number of fields/columns < 8", + fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "0:0", "0"}, want: nil, wantErr: true, }, { - name: "error case - cannot parse line - missing colon", - args: args{ - fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "0000000000000001"}, - }, + name: "error case - parse sl - not a valid uint", + fields: []string{"a:", "00000000:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "0"}, 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"}, - }, + name: "error case - parse local_address - not a valid hex", + fields: []string{"1:", "0000000O:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "0"}, 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"}, - }, + name: "error case - parse rem_address - not a valid hex", + fields: []string{"1:", "00000000:0000", "0000000O:0000", "07", "00000000:00000001", "0:0", "0", "0"}, + want: nil, + wantErr: true, + }, + { + name: "error case - cannot parse line - missing colon", + fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "0000000000000001", "0:0", "0", "0"}, + want: nil, + wantErr: true, + }, + { + name: "error case - parse tx_queue - not a valid hex", + fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "DEADCODE:00000001", "0:0", "0", "0"}, + want: nil, + wantErr: true, + }, + { + name: "error case - parse rx_queue - not a valid hex", + fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:FEEDCODE", "0:0", "0", "0"}, + want: nil, + wantErr: true, + }, + { + name: "error case - parse UID - not a valid uint", + fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "-10"}, want: nil, wantErr: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := parseNetUDPLine(tt.args.fields) + got, err := parseNetUDPLine(tt.fields) if (err != nil) != tt.wantErr { t.Errorf("parseNetUDPLine() error = %v, wantErr %v", err, tt.wantErr) return @@ -79,7 +100,16 @@ func Test_parseNetUDPLine(t *testing.T) { t.Errorf("parseNetUDPLine() = %v, want %v", got, tt.want) } if got != nil { - if (got.RxQueue != tt.want.RxQueue) || (got.TxQueue != tt.want.TxQueue) { + if (got.Sl != tt.want.Sl) || + (got.LocalAddr != tt.want.LocalAddr) || + (got.LocalPort != tt.want.LocalPort) || + (got.RemAddr != tt.want.RemAddr) || + (got.RemPort != tt.want.RemPort) || + (got.St != tt.want.St) || + (got.TxQueue != tt.want.TxQueue) || + (got.RxQueue != tt.want.RxQueue) || + (got.UID != tt.want.UID) { + t.Errorf("parseNetUDPLine() = %#v, want %#v", got, tt.want) } } @@ -88,37 +118,57 @@ func Test_parseNetUDPLine(t *testing.T) { } func Test_newNetUDP(t *testing.T) { - type args struct { - file string - } tests := []struct { name string - args args - want *NetUDP + file string + 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}, + name: "udp file found, no error should come up", + file: "fixtures/proc/net/udp", + want: []*netUDPLine{ + &netUDPLine{ + Sl: 0, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 0, RxQueue: 1, UID: 0, + }, + &netUDPLine{ + Sl: 1, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 1, RxQueue: 0, UID: 0, + }, + &netUDPLine{ + Sl: 2, LocalAddr: 0, LocalPort: 22, RemAddr: 0, RemPort: 0, St: 10, TxQueue: 1, RxQueue: 1, UID: 0, + }, + }, + wantErr: false, + }, + { + name: "udp6 file found, no error should come up", + file: "fixtures/proc/net/udp6", + want: []*netUDPLine{ + &netUDPLine{ + Sl: 1315, LocalAddr: 0, LocalPort: 5355, RemAddr: 0, RemPort: 0, St: 7, TxQueue: 0, RxQueue: 0, UID: 981, + }, + &netUDPLine{ + Sl: 6073, LocalAddr: 0, LocalPort: 51073, RemAddr: 0, RemPort: 0, St: 7, TxQueue: 0, RxQueue: 0, UID: 1000, + }, + }, wantErr: false, }, { name: "error case - file not found", - args: args{file: "somewhere over the rainbow"}, + file: "somewhere over the rainbow", want: nil, wantErr: true, }, { name: "error case - parse error", - args: args{file: "fixtures/proc/net/udp_broken"}, + 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) + got, err := newNetUDP(tt.file) if (err != nil) != tt.wantErr { t.Errorf("newNetUDP() error = %v, wantErr %v", err, tt.wantErr) return @@ -129,3 +179,49 @@ func Test_newNetUDP(t *testing.T) { }) } } + +func Test_newNetUDPSummary(t *testing.T) { + tests := []struct { + name string + file string + want *NetUDPSummary + wantErr bool + }{ + { + name: "udp file found, no error should come up", + file: "fixtures/proc/net/udp", + want: &NetUDPSummary{TxQueueLength: 2, RxQueueLength: 2, UsedSockets: 3}, + wantErr: false, + }, + { + name: "udp6 file found, no error should come up", + file: "fixtures/proc/net/udp6", + want: &NetUDPSummary{TxQueueLength: 0, RxQueueLength: 0, UsedSockets: 2}, + wantErr: false, + }, + { + name: "error case - file not found", + file: "somewhere over the rainbow", + want: nil, + wantErr: true, + }, + { + name: "error case - parse error", + file: "fixtures/proc/net/udp_broken", + want: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := newNetUDPSummary(tt.file) + if (err != nil) != tt.wantErr { + t.Errorf("newNetUDPSummary() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("newNetUDPSummary() = %v, want %v", got, tt.want) + } + }) + } +} From dee3ff5ffec4be4cc67be41f033d0dd46ecadcf3 Mon Sep 17 00:00:00 2001 From: Peter Bueschel Date: Thu, 5 Dec 2019 10:40:54 +0100 Subject: [PATCH 3/3] Add explanation to the readLimit. Fix comment on newNetUDPSummary. Signed-off-by: Peter Bueschel --- net_udp.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/net_udp.go b/net_udp.go index 8bfb30f86..17991197a 100644 --- a/net_udp.go +++ b/net_udp.go @@ -23,7 +23,13 @@ import ( ) const ( - readLimit = 4294967296 // Bytes + // readLimit is used by io.LimitReader while reading the content of the + // /proc/net/udp{,6} files. The number of lines inside such a file is dynamic + // as each line represents a single used socket. + // In theory, the number of available sockets is 65535 (2^16 - 1) per IP. + // With e.g. 150 Byte per line and the maximum number of 65535, + // the reader needs to handle 150 Byte * 65535 =~ 10 MB for a single IP. + readLimit = 4294967296 // Byte -> 4 GiB ) type ( @@ -110,7 +116,7 @@ func newNetUDP(file string) (NetUDP, error) { return netUDP, nil } -// newNetUDP creates a new NetUDP{,6} from the contents of the given file. +// newNetUDPSummary creates a new NetUDP{,6} from the contents of the given file. func newNetUDPSummary(file string) (*NetUDPSummary, error) { f, err := os.Open(file) if err != nil {