From 356e1bb866f8f4288cfd101eb7e4f1e73801e494 Mon Sep 17 00:00:00 2001 From: Ken Herner Date: Fri, 4 Sep 2015 15:34:10 -0400 Subject: [PATCH] Added sockstat test file initial work on sockstat work Fixed package name Finished implementation of the sockstat plugin missed a return value Added sockstat to default plugins to start Fixed scanner read on sockstat fixed sockstat linux test for TCP alloc update sockstat test case Updated sockstat to return TCP and UDP memory in bytes instead of page count --- collector/fixtures/sockstat | 6 ++ collector/sockstat_linux.go | 114 +++++++++++++++++++++++++++++++ collector/sockstat_linux_test.go | 41 +++++++++++ node_exporter.go | 2 +- 4 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 collector/fixtures/sockstat create mode 100644 collector/sockstat_linux.go create mode 100644 collector/sockstat_linux_test.go diff --git a/collector/fixtures/sockstat b/collector/fixtures/sockstat new file mode 100644 index 00000000..97ea5d1a --- /dev/null +++ b/collector/fixtures/sockstat @@ -0,0 +1,6 @@ +sockets: used 229 +TCP: inuse 4 orphan 0 tw 4 alloc 17 mem 1 +UDP: inuse 0 mem 0 +UDPLITE: inuse 0 +RAW: inuse 0 +FRAG: inuse 0 memory 0 diff --git a/collector/sockstat_linux.go b/collector/sockstat_linux.go new file mode 100644 index 00000000..e415b12d --- /dev/null +++ b/collector/sockstat_linux.go @@ -0,0 +1,114 @@ +// +build !nonetstat + +package collector + +import ( + "bufio" + "fmt" + "github.com/prometheus/client_golang/prometheus" + "io" + "os" + "strconv" + "strings" +) + +const ( + procSockStat = "/proc/net/sockstat" + sockStatSubsystem = "sockstat" +) + +// Used for calculating the total memory bytes on TCP and UDP +var pageSize = os.Getpagesize() + +type sockStatCollector struct { + metrics map[string]prometheus.Gauge +} + +func init() { + Factories["sockstat"] = NewSockStatCollector +} + +// NewSockStatCollector returns a new Collector exposing socket stats +func NewSockStatCollector() (Collector, error) { + return &sockStatCollector{ + metrics: map[string]prometheus.Gauge{}, + }, nil +} + +func (c *sockStatCollector) Update(ch chan<- prometheus.Metric) (err error) { + sockStats, err := getSockStats(procSockStat) + if err != nil { + return fmt.Errorf("couldn't get sockstats: %s", err) + } + for protocol, protocolStats := range sockStats { + for name, value := range protocolStats { + key := protocol + "_" + name + if _, ok := c.metrics[key]; !ok { + c.metrics[key] = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: Namespace, + Subsystem: sockStatSubsystem, + Name: key, + Help: fmt.Sprintf("%s %s from /proc/net/sockstat.", protocol, name), + }, + ) + } + v, err := strconv.ParseFloat(value, 64) + if err != nil { + return fmt.Errorf("invalid value %s in sockstats: %s", value, err) + } + c.metrics[key].Set(v) + } + } + for _, m := range c.metrics { + m.Collect(ch) + } + return err +} + +func getSockStats(fileName string) (map[string]map[string]string, error) { + file, err := os.Open(fileName) + if err != nil { + return nil, err + } + defer file.Close() + + return parseSockStats(file, fileName) +} + +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(string(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++ + } + } + + // The mem options are reported in pages + // Multiply them by the pagesize to get bytes + // Update TCP Mem + pageCount, err := strconv.Atoi(sockStat["TCP"]["mem"]) + if err != nil { + return nil, fmt.Errorf("invalid value %s in sockstats: %s", sockStat["TCP"]["mem"], err) + } + sockStat["TCP"]["mem"] = strconv.Itoa(pageCount * pageSize) + + // Update UDP Mem + pageCount, err = strconv.Atoi(sockStat["UDP"]["mem"]) + if err != nil { + return nil, fmt.Errorf("invalid value %s in sockstats: %s", sockStat["UDP"]["mem"], err) + } + sockStat["UDP"]["mem"] = strconv.Itoa(pageCount * pageSize) + + return sockStat, nil +} diff --git a/collector/sockstat_linux_test.go b/collector/sockstat_linux_test.go new file mode 100644 index 00000000..36b1aec9 --- /dev/null +++ b/collector/sockstat_linux_test.go @@ -0,0 +1,41 @@ +package collector + +import ( + "os" + "strconv" + "testing" +) + +func TestSockStats(t *testing.T) { + file, err := os.Open("fixtures/sockstat") + if err != nil { + t.Fatal(err) + } + + defer file.Close() + + sockStats, err := parseSockStats(file, fileName) + 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 sockets used %s, got %s", want, got) + } + + if want, got := "17", sockStats["TCP"]["alloc"]; want != got { + t.Errorf("want sockstat sockets used %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"]; want != got { + t.Errorf("want sockstat sockets used %s, got %s", want, got) + } +} diff --git a/node_exporter.go b/node_exporter.go index c15b4643..56f2d2d2 100644 --- a/node_exporter.go +++ b/node_exporter.go @@ -28,7 +28,7 @@ var ( memProfile = flag.String("debug.memprofile-file", "", "Write memory profile to this file upon receipt of SIGUSR1.") listenAddress = flag.String("web.listen-address", ":9100", "Address on which to expose metrics and web interface.") metricsPath = flag.String("web.telemetry-path", "/metrics", "Path under which to expose metrics.") - enabledCollectors = flag.String("collectors.enabled", "diskstats,filesystem,loadavg,meminfo,stat,textfile,time,netdev,netstat", "Comma-separated list of collectors to use.") + enabledCollectors = flag.String("collectors.enabled", "diskstats,filesystem,loadavg,meminfo,stat,textfile,time,netdev,netstat,sockstat", "Comma-separated list of collectors to use.") printCollectors = flag.Bool("collectors.print", false, "If true, print available collectors and exit.") authUser = flag.String("auth.user", "", "Username for basic auth.") authPass = flag.String("auth.pass", "", "Password for basic auth.")