diff --git a/fixtures.ttar b/fixtures.ttar index e7d35069c..5e7eeef4a 100644 --- a/fixtures.ttar +++ b/fixtures.ttar @@ -2209,6 +2209,23 @@ Lines: 1 00015c73 00020e76 F0000769 00000000 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Directory: fixtures/proc/net/stat +Mode: 755 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/net/stat/arp_cache +Lines: 3 +entries allocs destroys hash_grows lookups hits res_failed rcv_probes_mcast rcv_probes_ucast periodic_gc_runs forced_gc_runs unresolved_discards table_fulls +00000014 00000001 00000002 00000003 00000004 00000005 00000006 00000007 00000008 00000009 0000000a 0000000b 0000000c +00000014 0000000d 0000000e 0000000f 00000010 00000011 00000012 00000013 00000014 00000015 00000016 00000017 00000018 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/net/stat/ndisc_cache +Lines: 3 +entries allocs destroys hash_grows lookups hits res_failed rcv_probes_mcast rcv_probes_ucast periodic_gc_runs forced_gc_runs unresolved_discards table_fulls +00000024 000000f0 000000f1 000000f2 000000f3 000000f4 000000f5 000000f6 000000f7 000000f8 000000f9 000000fa 000000fb +00000024 000000fc 000000fd 000000fe 000000ff 00000100 00000101 00000102 00000103 00000104 00000105 00000106 00000107 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/proc/net/tcp Lines: 4 sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode diff --git a/netstat.go b/netstat.go new file mode 100644 index 000000000..94d892f11 --- /dev/null +++ b/netstat.go @@ -0,0 +1,68 @@ +// Copyright 2020 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" + "os" + "path/filepath" + "strconv" + "strings" +) + +// NetStat contains statistics for all the counters from one file +type NetStat struct { + Filename string + Stats map[string][]uint64 +} + +// NetStat retrieves stats from /proc/net/stat/ +func (fs FS) NetStat() ([]NetStat, error) { + statFiles, err := filepath.Glob(fs.proc.Path("net/stat/*")) + if err != nil { + return nil, err + } + + var netStatsTotal []NetStat + + for _, filePath := range statFiles { + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + + netStatFile := NetStat{ + Filename: filepath.Base(filePath), + Stats: make(map[string][]uint64), + } + scanner := bufio.NewScanner(file) + scanner.Scan() + // First string is always a header for stats + var headers []string + headers = append(headers, strings.Fields(scanner.Text())...) + + // Other strings represent per-CPU counters + for scanner.Scan() { + for num, counter := range strings.Fields(scanner.Text()) { + value, err := strconv.ParseUint(counter, 16, 32) + if err != nil { + return nil, err + } + netStatFile.Stats[headers[num]] = append(netStatFile.Stats[headers[num]], value) + } + } + netStatsTotal = append(netStatsTotal, netStatFile) + } + return netStatsTotal, nil +} diff --git a/netstat_test.go b/netstat_test.go new file mode 100644 index 000000000..1d3936251 --- /dev/null +++ b/netstat_test.go @@ -0,0 +1,114 @@ +// Copyright 2020 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 ( + "testing" +) + +func TestNetStat(t *testing.T) { + const ( + filesCount = 2 + CPUsCount = 2 + arpCacheMetricsCount = 13 + ndiscCacheMetricsCount = 13 + ) + + fs, err := NewFS(procTestFixtures) + if err != nil { + t.Fatalf("failed to open procfs: %v", err) + } + + netStats, err := fs.NetStat() + if err != nil { + t.Fatalf("NetStat() error: %s", err) + } + + if len(netStats) != filesCount { + t.Fatalf("unexpected number of files parsed %d, expected %d", len(netStats), filesCount) + } + + expectedStats := [2]NetStat{ + { + Filename: "arp_cache", + Stats: make(map[string][]uint64), + }, + { + Filename: "ndisc_cache", + Stats: make(map[string][]uint64), + }, + } + + for _, expected := range expectedStats { + if expected.Filename == "arp_cache" { + expected.Stats["entries"] = []uint64{20, 20} + expected.Stats["allocs"] = []uint64{1, 13} + expected.Stats["destroys"] = []uint64{2, 14} + expected.Stats["hash_grows"] = []uint64{3, 15} + expected.Stats["lookups"] = []uint64{4, 16} + expected.Stats["hits"] = []uint64{5, 17} + expected.Stats["res_failed"] = []uint64{6, 18} + expected.Stats["rcv_probes_mcast"] = []uint64{7, 19} + expected.Stats["rcv_probes_ucast"] = []uint64{8, 20} + expected.Stats["periodic_gc_runs"] = []uint64{9, 21} + expected.Stats["forced_gc_runs"] = []uint64{10, 22} + expected.Stats["unresolved_discards"] = []uint64{11, 23} + expected.Stats["table_fulls"] = []uint64{12, 24} + } + if expected.Filename == "ndisc_cache" { + expected.Stats["entries"] = []uint64{36, 36} + expected.Stats["allocs"] = []uint64{240, 252} + expected.Stats["destroys"] = []uint64{241, 253} + expected.Stats["hash_grows"] = []uint64{242, 254} + expected.Stats["lookups"] = []uint64{243, 255} + expected.Stats["hits"] = []uint64{244, 256} + expected.Stats["res_failed"] = []uint64{245, 257} + expected.Stats["rcv_probes_mcast"] = []uint64{246, 258} + expected.Stats["rcv_probes_ucast"] = []uint64{247, 259} + expected.Stats["periodic_gc_runs"] = []uint64{248, 260} + expected.Stats["forced_gc_runs"] = []uint64{249, 261} + expected.Stats["unresolved_discards"] = []uint64{250, 262} + expected.Stats["table_fulls"] = []uint64{251, 263} + } + } + + for _, netStatFile := range netStats { + if netStatFile.Filename == "arp_cache" && len(netStatFile.Stats) != arpCacheMetricsCount { + t.Fatalf("unexpected arp_cache metrics count %d, expected %d", len(netStatFile.Stats), arpCacheMetricsCount) + } + if netStatFile.Filename == "ndisc_cache" && len(netStatFile.Stats) != ndiscCacheMetricsCount { + t.Fatalf("unexpected ndisc_cache metrics count %d, expected %d", len(netStatFile.Stats), ndiscCacheMetricsCount) + } + for _, expected := range expectedStats { + for header, stats := range netStatFile.Stats { + if header == "" { + t.Fatalf("Found empty metric name") + } + if len(stats) != CPUsCount { + t.Fatalf("NetStat() parsed %d lines with metrics, expected %d", len(stats), CPUsCount) + } + if netStatFile.Filename == expected.Filename { + if expected.Stats[header] == nil { + t.Fatalf("unexpected metric header: %s", header) + } + for cpu, value := range netStatFile.Stats[header] { + if expected.Stats[header][cpu] != value { + t.Fatalf("unexpected value for %s for cpu %d in %s: %d, expected %d", header, cpu, netStatFile.Filename, value, expected.Stats[header][cpu]) + } + } + } + } + } + } +}