From d0fbb44090acfcbea8689ebd96b3e038b79c0923 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Sat, 23 Nov 2019 01:13:15 -0500 Subject: [PATCH 1/3] collector: reimplement sockstat collector with procfs Signed-off-by: Matt Layher --- collector/fixtures/e2e-output.txt | 20 +- collector/fixtures/proc/net/sockstat6 | 5 + collector/fixtures/proc/net/sockstat_rhe4 | 5 - collector/sockstat_linux.go | 175 +++++++++----- collector/sockstat_linux_test.go | 59 ----- go.mod | 2 +- go.sum | 4 +- .../prometheus/procfs/.golangci.yml | 2 - .../prometheus/procfs/fixtures.ttar | 19 ++ .../procfs/internal/util/valueparser.go | 18 +- .../github.com/prometheus/procfs/meminfo.go | 228 ++---------------- .../prometheus/procfs/net_sockstat.go | 163 +++++++++++++ .../github.com/prometheus/procfs/net_unix.go | 4 - vendor/modules.txt | 2 +- 14 files changed, 364 insertions(+), 342 deletions(-) create mode 100644 collector/fixtures/proc/net/sockstat6 delete mode 100644 collector/fixtures/proc/net/sockstat_rhe4 delete mode 100644 collector/sockstat_linux_test.go create mode 100644 vendor/github.com/prometheus/procfs/net_sockstat.go diff --git a/collector/fixtures/e2e-output.txt b/collector/fixtures/e2e-output.txt index c163346963..48284c52fd 100644 --- a/collector/fixtures/e2e-output.txt +++ b/collector/fixtures/e2e-output.txt @@ -2554,15 +2554,27 @@ node_scrape_collector_success{collector="vmstat"} 1 node_scrape_collector_success{collector="wifi"} 1 node_scrape_collector_success{collector="xfs"} 1 node_scrape_collector_success{collector="zfs"} 1 +# HELP node_sockstat_FRAG6_inuse Number of FRAG6 sockets in state inuse. +# TYPE node_sockstat_FRAG6_inuse gauge +node_sockstat_FRAG6_inuse 0 +# HELP node_sockstat_FRAG6_memory Number of FRAG6 sockets in state memory. +# TYPE node_sockstat_FRAG6_memory gauge +node_sockstat_FRAG6_memory 0 # HELP node_sockstat_FRAG_inuse Number of FRAG sockets in state inuse. # TYPE node_sockstat_FRAG_inuse gauge node_sockstat_FRAG_inuse 0 # HELP node_sockstat_FRAG_memory Number of FRAG sockets in state memory. # TYPE node_sockstat_FRAG_memory gauge node_sockstat_FRAG_memory 0 +# HELP node_sockstat_RAW6_inuse Number of RAW6 sockets in state inuse. +# TYPE node_sockstat_RAW6_inuse gauge +node_sockstat_RAW6_inuse 1 # HELP node_sockstat_RAW_inuse Number of RAW sockets in state inuse. # TYPE node_sockstat_RAW_inuse gauge node_sockstat_RAW_inuse 0 +# HELP node_sockstat_TCP6_inuse Number of TCP6 sockets in state inuse. +# TYPE node_sockstat_TCP6_inuse gauge +node_sockstat_TCP6_inuse 17 # HELP node_sockstat_TCP_alloc Number of TCP sockets in state alloc. # TYPE node_sockstat_TCP_alloc gauge node_sockstat_TCP_alloc 17 @@ -2581,6 +2593,12 @@ node_sockstat_TCP_orphan 0 # HELP node_sockstat_TCP_tw Number of TCP sockets in state tw. # TYPE node_sockstat_TCP_tw gauge node_sockstat_TCP_tw 4 +# HELP node_sockstat_UDP6_inuse Number of UDP6 sockets in state inuse. +# TYPE node_sockstat_UDP6_inuse gauge +node_sockstat_UDP6_inuse 9 +# HELP node_sockstat_UDPLITE6_inuse Number of UDPLITE6 sockets in state inuse. +# TYPE node_sockstat_UDPLITE6_inuse gauge +node_sockstat_UDPLITE6_inuse 0 # HELP node_sockstat_UDPLITE_inuse Number of UDPLITE sockets in state inuse. # TYPE node_sockstat_UDPLITE_inuse gauge node_sockstat_UDPLITE_inuse 0 @@ -2593,7 +2611,7 @@ node_sockstat_UDP_mem 0 # HELP node_sockstat_UDP_mem_bytes Number of UDP sockets in state mem_bytes. # TYPE node_sockstat_UDP_mem_bytes gauge node_sockstat_UDP_mem_bytes 0 -# HELP node_sockstat_sockets_used Number of sockets sockets in state used. +# HELP node_sockstat_sockets_used Number of IPv4 sockets in use. # TYPE node_sockstat_sockets_used gauge node_sockstat_sockets_used 229 # HELP node_textfile_mtime_seconds Unixtime mtime of textfiles successfully read. diff --git a/collector/fixtures/proc/net/sockstat6 b/collector/fixtures/proc/net/sockstat6 new file mode 100644 index 0000000000..de5806d8d1 --- /dev/null +++ b/collector/fixtures/proc/net/sockstat6 @@ -0,0 +1,5 @@ +TCP6: inuse 17 +UDP6: inuse 9 +UDPLITE6: inuse 0 +RAW6: inuse 1 +FRAG6: inuse 0 memory 0 diff --git a/collector/fixtures/proc/net/sockstat_rhe4 b/collector/fixtures/proc/net/sockstat_rhe4 deleted file mode 100644 index 1e178f366c..0000000000 --- a/collector/fixtures/proc/net/sockstat_rhe4 +++ /dev/null @@ -1,5 +0,0 @@ -sockets: used 229 -TCP: inuse 4 orphan 0 tw 4 alloc 17 mem 1 -UDP: inuse 0 -RAW: inuse 0 -FRAG: inuse 0 memory 0 diff --git a/collector/sockstat_linux.go b/collector/sockstat_linux.go index 92847be4ec..1a67f9908e 100644 --- a/collector/sockstat_linux.go +++ b/collector/sockstat_linux.go @@ -16,14 +16,11 @@ package collector import ( - "bufio" "fmt" - "io" "os" - "strconv" - "strings" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/procfs" ) const ( @@ -45,78 +42,130 @@ func NewSockStatCollector() (Collector, error) { } func (c *sockStatCollector) Update(ch chan<- prometheus.Metric) error { - sockStats, err := getSockStats(procFilePath("net/sockstat")) + fs, err := procfs.NewFS(*procPath) if err != nil { - return fmt.Errorf("couldn't get sockstats: %s", err) + return fmt.Errorf("failed to open procfs: %v", err) } - for protocol, protocolStats := range sockStats { - for name, value := range protocolStats { - v, err := strconv.ParseFloat(value, 64) - if err != nil { - return fmt.Errorf("invalid value %s in sockstats: %s", value, err) - } - ch <- prometheus.MustNewConstMetric( - prometheus.NewDesc( - prometheus.BuildFQName(namespace, sockStatSubsystem, protocol+"_"+name), - fmt.Sprintf("Number of %s sockets in state %s.", protocol, name), - nil, nil, - ), - prometheus.GaugeValue, v, - ) - } - } - return err -} -func getSockStats(fileName string) (map[string]map[string]string, error) { - file, err := os.Open(fileName) + stat4, err := fs.NetSockstat() if err != nil { - return nil, err + return fmt.Errorf("failed to get IPv4 sockstat data: %v", err) + } + + // If IPv6 is disabled on this kernel, handle it gracefully. + stat6, err := fs.NetSockstat6() + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to get IPv6 sockstat data: %v", err) + } + + stats := []struct { + isIPv6 bool + stat *procfs.NetSockstat + }{ + { + stat: stat4, + }, + { + isIPv6: true, + stat: stat6, + }, + } + + for _, s := range stats { + c.update(ch, s.isIPv6, s.stat) } - defer file.Close() - return parseSockStats(file, fileName) + return nil } -func parseSockStats(r io.Reader, fileName string) (map[string]map[string]string, error) { - var ( - sockStat = map[string]map[string]string{} - scanner = bufio.NewScanner(r) - ) - - for scanner.Scan() { - line := strings.Split(scanner.Text(), " ") - // Remove trailing ':'. - protocol := line[0][:len(line[0])-1] - sockStat[protocol] = map[string]string{} - - for i := 1; i < len(line) && i+1 < len(line); i++ { - sockStat[protocol][line[i]] = line[i+1] - i++ - } +func (c *sockStatCollector) update(ch chan<- prometheus.Metric, isIPv6 bool, s *procfs.NetSockstat) { + if s == nil { + // IPv6 disabled or similar; nothing to do. + return } - if err := scanner.Err(); err != nil { - return nil, err + + // If sockstat contains the number of used sockets, export it. + if !isIPv6 && s.Used != nil { + // TODO: this must be updated if sockstat6 ever exports this data. + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName(namespace, sockStatSubsystem, "sockets_used"), + "Number of IPv4 sockets in use.", + nil, + nil, + ), + prometheus.GaugeValue, + float64(*s.Used), + ) } - // The mem metrics is the count of pages used. Multiply the mem metrics by - // the page size from the kernel to get the number of bytes used. - // - // Update the TCP mem from page count to bytes. - pageCount, err := strconv.Atoi(sockStat["TCP"]["mem"]) - if err != nil { - return nil, fmt.Errorf("invalid value %s in sockstats: %s", sockStat["TCP"]["mem"], err) + // A name and optional value for a sockstat metric. + type ssPair struct { + name string + v *int } - sockStat["TCP"]["mem_bytes"] = strconv.Itoa(pageCount * pageSize) - // Update the UDP mem from page count to bytes. - if udpMem := sockStat["UDP"]["mem"]; udpMem != "" { - pageCount, err = strconv.Atoi(udpMem) - if err != nil { - return nil, fmt.Errorf("invalid value %s in sockstats: %s", sockStat["UDP"]["mem"], err) + // Previously these metric names were generated directly from the file output. + // In order to keep the same level of compatibility, we must map the fields + // to their correct names. + for _, p := range s.Protocols { + pairs := []ssPair{ + { + name: "inuse", + v: &p.InUse, + }, + { + name: "orphan", + v: p.Orphan, + }, + { + name: "tw", + v: p.TW, + }, + { + name: "alloc", + v: p.Alloc, + }, + { + name: "mem", + v: p.Mem, + }, + { + name: "memory", + v: p.Memory, + }, } - sockStat["UDP"]["mem_bytes"] = strconv.Itoa(pageCount * pageSize) - } - return sockStat, nil + // Also export mem_bytes values for sockets which have a mem value + // stored in pages. + if p.Mem != nil { + v := *p.Mem * pageSize + pairs = append(pairs, ssPair{ + name: "mem_bytes", + v: &v, + }) + } + + for _, pair := range pairs { + if pair.v == nil { + // This value is not set for this protocol; nothing to do. + continue + } + + ch <- prometheus.MustNewConstMetric( + prometheus.NewDesc( + prometheus.BuildFQName( + namespace, + sockStatSubsystem, + fmt.Sprintf("%s_%s", p.Protocol, pair.name), + ), + fmt.Sprintf("Number of %s sockets in state %s.", p.Protocol, pair.name), + nil, + nil, + ), + prometheus.GaugeValue, + float64(*pair.v), + ) + } + } } diff --git a/collector/sockstat_linux_test.go b/collector/sockstat_linux_test.go deleted file mode 100644 index 70bedba900..0000000000 --- a/collector/sockstat_linux_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2015 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 collector - -import ( - "os" - "strconv" - "testing" -) - -func TestSockStats(t *testing.T) { - testSockStats(t, "fixtures/proc/net/sockstat") - testSockStats(t, "fixtures/proc/net/sockstat_rhe4") -} - -func testSockStats(t *testing.T, fixture string) { - file, err := os.Open(fixture) - if err != nil { - t.Fatal(err) - } - - defer file.Close() - - sockStats, err := parseSockStats(file, fixture) - if err != nil { - t.Fatal(err) - } - - if want, got := "229", sockStats["sockets"]["used"]; want != got { - t.Errorf("want sockstat sockets used %s, got %s", want, got) - } - - if want, got := "4", sockStats["TCP"]["tw"]; want != got { - t.Errorf("want sockstat TCP tw %s, got %s", want, got) - } - - if want, got := "17", sockStats["TCP"]["alloc"]; want != got { - t.Errorf("want sockstat TCP alloc %s, got %s", want, got) - } - - // The test file has 1 for TCP mem, which is one page. So we should get the - // page size in bytes back from sockstat_linux. We get the page size from - // os here because this value can change from system to system. The value is - // 4096 by default from linux 2.4 onward. - if want, got := strconv.Itoa(os.Getpagesize()), sockStats["TCP"]["mem_bytes"]; want != got { - t.Errorf("want sockstat TCP mem_bytes %s, got %s", want, got) - } -} diff --git a/go.mod b/go.mod index c0ca32283e..078e3bbf06 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/prometheus/client_golang v1.0.0 github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 github.com/prometheus/common v0.7.0 - github.com/prometheus/procfs v0.0.7 + github.com/prometheus/procfs v0.0.8 github.com/siebenmann/go-kstat v0.0.0-20160321171754-d34789b79745 github.com/soundcloud/go-runit v0.0.0-20150630195641-06ad41a06c4a go.uber.org/atomic v1.3.2 // indirect diff --git a/go.sum b/go.sum index e8a8f943a5..230ca21e15 100644 --- a/go.sum +++ b/go.sum @@ -85,8 +85,8 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNGfs= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.7 h1:RS5GAlMbnkWkhs4+bPocMTmGjYkuCY5djjqEDdXOhcQ= -github.com/prometheus/procfs v0.0.7/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/siebenmann/go-kstat v0.0.0-20160321171754-d34789b79745 h1:IuH7WumZNax0D+rEqmy2TyhKCzrtMGqbZO0b8rO00JA= github.com/siebenmann/go-kstat v0.0.0-20160321171754-d34789b79745/go.mod h1:G81aIFAMS9ECrwBYR9YxhlPjWgrItd+Kje78O6+uqm8= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= diff --git a/vendor/github.com/prometheus/procfs/.golangci.yml b/vendor/github.com/prometheus/procfs/.golangci.yml index 438ca92eca..7c4ce1fa84 100644 --- a/vendor/github.com/prometheus/procfs/.golangci.yml +++ b/vendor/github.com/prometheus/procfs/.golangci.yml @@ -1,6 +1,4 @@ -# Run only staticcheck for now. Additional linters will be enabled one-by-one. linters: enable: - staticcheck - govet - disable-all: true diff --git a/vendor/github.com/prometheus/procfs/fixtures.ttar b/vendor/github.com/prometheus/procfs/fixtures.ttar index 38b71fe324..c50a18ace4 100644 --- a/vendor/github.com/prometheus/procfs/fixtures.ttar +++ b/vendor/github.com/prometheus/procfs/fixtures.ttar @@ -1801,6 +1801,25 @@ proc4 2 2 10853 proc4ops 72 0 0 0 1098 2 0 0 0 0 8179 5896 0 0 0 0 5900 0 0 2 0 2 0 9609 0 2 150 1272 0 0 0 1236 0 0 0 0 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/net/sockstat +Lines: 6 +sockets: used 1602 +TCP: inuse 35 orphan 0 tw 4 alloc 59 mem 22 +UDP: inuse 12 mem 62 +UDPLITE: inuse 0 +RAW: inuse 0 +FRAG: inuse 0 memory 0 +Mode: 444 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/net/sockstat6 +Lines: 5 +TCP6: inuse 17 +UDP6: inuse 9 +UDPLITE6: inuse 0 +RAW6: inuse 1 +FRAG6: inuse 0 memory 0 +Mode: 444 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/proc/net/softnet_stat Lines: 1 00015c73 00020e76 F0000769 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 diff --git a/vendor/github.com/prometheus/procfs/internal/util/valueparser.go b/vendor/github.com/prometheus/procfs/internal/util/valueparser.go index ac93cb42d2..fe2355d3c6 100644 --- a/vendor/github.com/prometheus/procfs/internal/util/valueparser.go +++ b/vendor/github.com/prometheus/procfs/internal/util/valueparser.go @@ -33,6 +33,9 @@ func NewValueParser(v string) *ValueParser { return &ValueParser{v: v} } +// Int interprets the underlying value as an int and returns that value. +func (vp *ValueParser) Int() int { return int(vp.int64()) } + // PInt64 interprets the underlying value as an int64 and returns a pointer to // that value. func (vp *ValueParser) PInt64() *int64 { @@ -40,16 +43,27 @@ func (vp *ValueParser) PInt64() *int64 { return nil } + v := vp.int64() + return &v +} + +// int64 interprets the underlying value as an int64 and returns that value. +// TODO: export if/when necessary. +func (vp *ValueParser) int64() int64 { + if vp.err != nil { + return 0 + } + // A base value of zero makes ParseInt infer the correct base using the // string's prefix, if any. const base = 0 v, err := strconv.ParseInt(vp.v, base, 64) if err != nil { vp.err = err - return nil + return 0 } - return &v + return v } // PUInt64 interprets the underlying value as an uint64 and returns a pointer to diff --git a/vendor/github.com/prometheus/procfs/meminfo.go b/vendor/github.com/prometheus/procfs/meminfo.go index dd630c0480..50dab4bcd5 100644 --- a/vendor/github.com/prometheus/procfs/meminfo.go +++ b/vendor/github.com/prometheus/procfs/meminfo.go @@ -16,6 +16,8 @@ package procfs import ( "bufio" "bytes" + "fmt" + "io" "strconv" "strings" @@ -143,311 +145,133 @@ type Meminfo struct { // Meminfo returns an information about current kernel/system memory statistics. // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt func (fs FS) Meminfo() (Meminfo, error) { - data, err := util.ReadFileNoStat(fs.proc.Path("meminfo")) + b, err := util.ReadFileNoStat(fs.proc.Path("meminfo")) if err != nil { return Meminfo{}, err } - return parseMemInfo(data) + + m, err := parseMemInfo(bytes.NewReader(b)) + if err != nil { + return Meminfo{}, fmt.Errorf("failed to parse meminfo: %v", err) + } + + return *m, nil } -func parseMemInfo(info []byte) (m Meminfo, err error) { - scanner := bufio.NewScanner(bytes.NewReader(info)) +func parseMemInfo(r io.Reader) (*Meminfo, error) { + var m Meminfo + s := bufio.NewScanner(r) + for s.Scan() { + // Each line has at least a name and value; we ignore the unit. + fields := strings.Fields(s.Text()) + if len(fields) < 2 { + return nil, fmt.Errorf("malformed meminfo line: %q", s.Text()) + } - var line string - for scanner.Scan() { - line = scanner.Text() + v, err := strconv.ParseUint(fields[1], 0, 64) + if err != nil { + return nil, err + } - field := strings.Fields(line) - switch field[0] { + switch fields[0] { case "MemTotal:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.MemTotal = v case "MemFree:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.MemFree = v case "MemAvailable:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.MemAvailable = v case "Buffers:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Buffers = v case "Cached:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Cached = v case "SwapCached:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.SwapCached = v case "Active:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Active = v case "Inactive:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Inactive = v case "Active(anon):": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.ActiveAnon = v case "Inactive(anon):": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.InactiveAnon = v case "Active(file):": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.ActiveFile = v case "Inactive(file):": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.InactiveFile = v case "Unevictable:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Unevictable = v case "Mlocked:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Mlocked = v case "SwapTotal:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.SwapTotal = v case "SwapFree:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.SwapFree = v case "Dirty:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Dirty = v case "Writeback:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Writeback = v case "AnonPages:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.AnonPages = v case "Mapped:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Mapped = v case "Shmem:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Shmem = v case "Slab:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Slab = v case "SReclaimable:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.SReclaimable = v case "SUnreclaim:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.SUnreclaim = v case "KernelStack:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.KernelStack = v case "PageTables:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.PageTables = v case "NFS_Unstable:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.NFSUnstable = v case "Bounce:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Bounce = v case "WritebackTmp:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.WritebackTmp = v case "CommitLimit:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.CommitLimit = v case "Committed_AS:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.CommittedAS = v case "VmallocTotal:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.VmallocTotal = v case "VmallocUsed:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.VmallocUsed = v case "VmallocChunk:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.VmallocChunk = v case "HardwareCorrupted:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.HardwareCorrupted = v case "AnonHugePages:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.AnonHugePages = v case "ShmemHugePages:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.ShmemHugePages = v case "ShmemPmdMapped:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.ShmemPmdMapped = v case "CmaTotal:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.CmaTotal = v case "CmaFree:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.CmaFree = v case "HugePages_Total:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.HugePagesTotal = v case "HugePages_Free:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.HugePagesFree = v case "HugePages_Rsvd:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.HugePagesRsvd = v case "HugePages_Surp:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.HugePagesSurp = v case "Hugepagesize:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.Hugepagesize = v case "DirectMap4k:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.DirectMap4k = v case "DirectMap2M:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.DirectMap2M = v case "DirectMap1G:": - v, err := strconv.ParseUint(field[1], 0, 64) - if err != nil { - return Meminfo{}, err - } m.DirectMap1G = v } } - return m, nil + + return &m, nil } diff --git a/vendor/github.com/prometheus/procfs/net_sockstat.go b/vendor/github.com/prometheus/procfs/net_sockstat.go new file mode 100644 index 0000000000..f91ef55237 --- /dev/null +++ b/vendor/github.com/prometheus/procfs/net_sockstat.go @@ -0,0 +1,163 @@ +// 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 +// +// 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" + "bytes" + "errors" + "fmt" + "io" + "strings" + + "github.com/prometheus/procfs/internal/util" +) + +// A NetSockstat contains the output of /proc/net/sockstat{,6} for IPv4 or IPv6, +// respectively. +type NetSockstat struct { + // Used is non-nil for IPv4 sockstat results, but nil for IPv6. + Used *int + Protocols []NetSockstatProtocol +} + +// A NetSockstatProtocol contains statistics about a given socket protocol. +// Pointer fields indicate that the value may or may not be present on any +// given protocol. +type NetSockstatProtocol struct { + Protocol string + InUse int + Orphan *int + TW *int + Alloc *int + Mem *int + Memory *int +} + +// NetSockstat retrieves IPv4 socket statistics. +func (fs FS) NetSockstat() (*NetSockstat, error) { + return readSockstat(fs.proc.Path("net", "sockstat")) +} + +// NetSockstat6 retrieves IPv6 socket statistics. +// +// If IPv6 is disabled on this kernel, the returned error can be checked with +// os.IsNotExist. +func (fs FS) NetSockstat6() (*NetSockstat, error) { + return readSockstat(fs.proc.Path("net", "sockstat6")) +} + +// readSockstat opens and parses a NetSockstat from the input file. +func readSockstat(name string) (*NetSockstat, error) { + // This file is small and can be read with one syscall. + b, err := util.ReadFileNoStat(name) + if err != nil { + // Do not wrap this error so the caller can detect os.IsNotExist and + // similar conditions. + return nil, err + } + + stat, err := parseSockstat(bytes.NewReader(b)) + if err != nil { + return nil, fmt.Errorf("failed to read sockstats from %q: %v", name, err) + } + + return stat, nil +} + +// parseSockstat reads the contents of a sockstat file and parses a NetSockstat. +func parseSockstat(r io.Reader) (*NetSockstat, error) { + var stat NetSockstat + s := bufio.NewScanner(r) + for s.Scan() { + // Expect a minimum of a protocol and one key/value pair. + fields := strings.Split(s.Text(), " ") + if len(fields) < 3 { + return nil, fmt.Errorf("malformed sockstat line: %q", s.Text()) + } + + // The remaining fields are key/value pairs. + kvs, err := parseSockstatKVs(fields[1:]) + if err != nil { + return nil, fmt.Errorf("error parsing sockstat key/value pairs from %q: %v", s.Text(), err) + } + + // The first field is the protocol. We must trim its colon suffix. + proto := strings.TrimSuffix(fields[0], ":") + switch proto { + case "sockets": + // Special case: IPv4 has a sockets "used" key/value pair that we + // embed at the top level of the structure. + used := kvs["used"] + stat.Used = &used + default: + // Parse all other lines as individual protocols. + nsp := parseSockstatProtocol(kvs) + nsp.Protocol = proto + stat.Protocols = append(stat.Protocols, nsp) + } + } + + if err := s.Err(); err != nil { + return nil, err + } + + return &stat, nil +} + +// parseSockstatKVs parses a string slice into a map of key/value pairs. +func parseSockstatKVs(kvs []string) (map[string]int, error) { + if len(kvs)%2 != 0 { + return nil, errors.New("odd number of fields in key/value pairs") + } + + // Iterate two values at a time to gather key/value pairs. + out := make(map[string]int, len(kvs)/2) + for i := 0; i < len(kvs); i += 2 { + vp := util.NewValueParser(kvs[i+1]) + out[kvs[i]] = vp.Int() + + if err := vp.Err(); err != nil { + return nil, err + } + } + + return out, nil +} + +// parseSockstatProtocol parses a NetSockstatProtocol from the input kvs map. +func parseSockstatProtocol(kvs map[string]int) NetSockstatProtocol { + var nsp NetSockstatProtocol + for k, v := range kvs { + // Capture the range variable to ensure we get unique pointers for + // each of the optional fields. + v := v + switch k { + case "inuse": + nsp.InUse = v + case "orphan": + nsp.Orphan = &v + case "tw": + nsp.TW = &v + case "alloc": + nsp.Alloc = &v + case "mem": + nsp.Mem = &v + case "memory": + nsp.Memory = &v + } + } + + return nsp +} diff --git a/vendor/github.com/prometheus/procfs/net_unix.go b/vendor/github.com/prometheus/procfs/net_unix.go index 240340a83a..93bd58f809 100644 --- a/vendor/github.com/prometheus/procfs/net_unix.go +++ b/vendor/github.com/prometheus/procfs/net_unix.go @@ -207,10 +207,6 @@ func (u NetUnix) parseUsers(hexStr string) (uint64, error) { return strconv.ParseUint(hexStr, 16, 32) } -func (u NetUnix) parseProtocol(hexStr string) (uint64, error) { - return strconv.ParseUint(hexStr, 16, 32) -} - func (u NetUnix) parseType(hexStr string) (NetUnixType, error) { typ, err := strconv.ParseUint(hexStr, 16, 16) if err != nil { diff --git a/vendor/modules.txt b/vendor/modules.txt index 484d28b4a7..4d02fdae6b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -47,7 +47,7 @@ github.com/prometheus/common/internal/bitbucket.org/ww/goautoneg github.com/prometheus/common/log github.com/prometheus/common/model github.com/prometheus/common/version -# github.com/prometheus/procfs v0.0.7 +# github.com/prometheus/procfs v0.0.8 github.com/prometheus/procfs github.com/prometheus/procfs/bcache github.com/prometheus/procfs/internal/fs From eacada73f737679245864f2034f81e677881f607 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Mon, 25 Nov 2019 13:17:45 -0500 Subject: [PATCH 2/3] CHANGELOG: note sockstat collector IPv6 support Signed-off-by: Matt Layher --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8eaf77306a..bfc0c2d08e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ * [ENHANCEMENT] Report non-fatal collection errors in the exporter metric. #1439 * [ENHANCEMENT] Expose IPVS firewall mark as a label #1455 * [ENHANCEMENT] Add check for systemd version before attempting to query certain metrics. #1413 +* [ENHANCEMENT] The sockstat collector now exposes IPv6 statistics in addition to the existing IPv4 support. * [BUGFIX] Renamed label `state` to `name` on `node_systemd_service_restart_total`. #1393 * [BUGFIX] Fix netdev nil reference on Darwin #1414 * [BUGFIX] Strip path.rootfs from mountpoint labels #1421 From d95f070960009725944d7498ee6497375ad27161 Mon Sep 17 00:00:00 2001 From: Matt Layher Date: Mon, 25 Nov 2019 13:33:24 -0500 Subject: [PATCH 3/3] collector: handle sockstat IPv4 disabled, debug logging Signed-off-by: Matt Layher --- collector/sockstat_linux.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/collector/sockstat_linux.go b/collector/sockstat_linux.go index 1a67f9908e..d6c4c72a93 100644 --- a/collector/sockstat_linux.go +++ b/collector/sockstat_linux.go @@ -20,6 +20,7 @@ import ( "os" "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" "github.com/prometheus/procfs" ) @@ -47,14 +48,22 @@ func (c *sockStatCollector) Update(ch chan<- prometheus.Metric) error { return fmt.Errorf("failed to open procfs: %v", err) } + // If IPv4 and/or IPv6 are disabled on this kernel, handle it gracefully. stat4, err := fs.NetSockstat() - if err != nil { + switch { + case err == nil: + case os.IsNotExist(err): + log.Debug("IPv4 sockstat statistics not found, skipping") + default: return fmt.Errorf("failed to get IPv4 sockstat data: %v", err) } - // If IPv6 is disabled on this kernel, handle it gracefully. stat6, err := fs.NetSockstat6() - if err != nil && !os.IsNotExist(err) { + switch { + case err == nil: + case os.IsNotExist(err): + log.Debug("IPv6 sockstat statistics not found, skipping") + default: return fmt.Errorf("failed to get IPv6 sockstat data: %v", err) }