Merge pull request #111 from chosenken/sockstat

Implementation of /proc/net/sockstat collector
This commit is contained in:
Julius Volz 2015-09-10 12:19:47 +02:00
commit dd5fc6a3ca
4 changed files with 166 additions and 1 deletions

View File

@ -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

118
collector/sockstat_linux.go Normal file
View File

@ -0,0 +1,118 @@
// +build !nosockstat
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[sockStatSubsystem] = 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 metrics is the count of pages used.
Multiply the mem metrics by the page size from the kernal 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)
}
sockStat["TCP"]["mem_bytes"] = strconv.Itoa(pageCount * pageSize)
// Update the UDP mem from page count to bytes.
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_bytes"] = strconv.Itoa(pageCount * pageSize)
return sockStat, nil
}

View File

@ -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_bytes"]; want != got {
t.Errorf("want sockstat sockets used %s, got %s", want, got)
}
}

View File

@ -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.")