Skip to content

Commit

Permalink
Add NetStat to parse /proc/net/stat/... (#316)
Browse files Browse the repository at this point in the history
* Add NetStat to parse /proc/net/stat/...

Signed-off-by: Aleksei Zakharov <zaharov@selectel.ru>
  • Loading branch information
AlexZzz committed Aug 9, 2021
1 parent e979fa4 commit f436cbb
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 0 deletions.
17 changes: 17 additions & 0 deletions fixtures.ttar
Expand Up @@ -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
Expand Down
68 changes: 68 additions & 0 deletions 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)

This comment has been minimized.

Copy link
@fire833

fire833 Aug 22, 2021

Would you want to defer closing each of the files after you are done processing them, or are you keeping them open because they will be queried regularly?

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
}
114 changes: 114 additions & 0 deletions 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])
}
}
}
}
}
}
}

0 comments on commit f436cbb

Please sign in to comment.