node_exporter/collector/netstat_linux.go
Ben Kochie 204164e4e4
Include TCP OutRsts in netstat metrics
TCP "OutRsts" is the number of TCP Resets sent by the node. This can be
useful for monitoring connection failures and flooding.

Signed-off-by: Ben Kochie <superq@gmail.com>
2020-06-04 08:51:39 +02:00

178 lines
4.8 KiB
Go

// 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.
// +build !nonetstat
package collector
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strconv"
"strings"
"github.com/go-kit/kit/log"
"github.com/prometheus/client_golang/prometheus"
"gopkg.in/alecthomas/kingpin.v2"
)
const (
netStatsSubsystem = "netstat"
)
var (
netStatFields = kingpin.Flag("collector.netstat.fields", "Regexp of fields to return for netstat collector.").Default("^(.*_(InErrors|InErrs)|Ip_Forwarding|Ip(6|Ext)_(InOctets|OutOctets)|Icmp6?_(InMsgs|OutMsgs)|TcpExt_(Listen.*|Syncookies.*|TCPSynRetrans)|Tcp_(ActiveOpens|InSegs|OutSegs|OutRsts|PassiveOpens|RetransSegs|CurrEstab)|Udp6?_(InDatagrams|OutDatagrams|NoPorts|RcvbufErrors|SndbufErrors))$").String()
)
type netStatCollector struct {
fieldPattern *regexp.Regexp
logger log.Logger
}
func init() {
registerCollector("netstat", defaultEnabled, NewNetStatCollector)
}
// NewNetStatCollector takes and returns
// a new Collector exposing network stats.
func NewNetStatCollector(logger log.Logger) (Collector, error) {
pattern := regexp.MustCompile(*netStatFields)
return &netStatCollector{
fieldPattern: pattern,
logger: logger,
}, nil
}
func (c *netStatCollector) Update(ch chan<- prometheus.Metric) error {
netStats, err := getNetStats(procFilePath("net/netstat"))
if err != nil {
return fmt.Errorf("couldn't get netstats: %s", err)
}
snmpStats, err := getNetStats(procFilePath("net/snmp"))
if err != nil {
return fmt.Errorf("couldn't get SNMP stats: %s", err)
}
snmp6Stats, err := getSNMP6Stats(procFilePath("net/snmp6"))
if err != nil {
return fmt.Errorf("couldn't get SNMP6 stats: %s", err)
}
// Merge the results of snmpStats into netStats (collisions are possible, but
// we know that the keys are always unique for the given use case).
for k, v := range snmpStats {
netStats[k] = v
}
for k, v := range snmp6Stats {
netStats[k] = v
}
for protocol, protocolStats := range netStats {
for name, value := range protocolStats {
key := protocol + "_" + name
v, err := strconv.ParseFloat(value, 64)
if err != nil {
return fmt.Errorf("invalid value %s in netstats: %s", value, err)
}
if !c.fieldPattern.MatchString(key) {
continue
}
ch <- prometheus.MustNewConstMetric(
prometheus.NewDesc(
prometheus.BuildFQName(namespace, netStatsSubsystem, key),
fmt.Sprintf("Statistic %s.", protocol+name),
nil, nil,
),
prometheus.UntypedValue, v,
)
}
}
return nil
}
func getNetStats(fileName string) (map[string]map[string]string, error) {
file, err := os.Open(fileName)
if err != nil {
return nil, err
}
defer file.Close()
return parseNetStats(file, fileName)
}
func parseNetStats(r io.Reader, fileName string) (map[string]map[string]string, error) {
var (
netStats = map[string]map[string]string{}
scanner = bufio.NewScanner(r)
)
for scanner.Scan() {
nameParts := strings.Split(scanner.Text(), " ")
scanner.Scan()
valueParts := strings.Split(scanner.Text(), " ")
// Remove trailing :.
protocol := nameParts[0][:len(nameParts[0])-1]
netStats[protocol] = map[string]string{}
if len(nameParts) != len(valueParts) {
return nil, fmt.Errorf("mismatch field count mismatch in %s: %s",
fileName, protocol)
}
for i := 1; i < len(nameParts); i++ {
netStats[protocol][nameParts[i]] = valueParts[i]
}
}
return netStats, scanner.Err()
}
func getSNMP6Stats(fileName string) (map[string]map[string]string, error) {
file, err := os.Open(fileName)
if err != nil {
// On systems with IPv6 disabled, this file won't exist.
// Do nothing.
if os.IsNotExist(err) {
return nil, nil
}
return nil, err
}
defer file.Close()
return parseSNMP6Stats(file)
}
func parseSNMP6Stats(r io.Reader) (map[string]map[string]string, error) {
var (
netStats = map[string]map[string]string{}
scanner = bufio.NewScanner(r)
)
for scanner.Scan() {
stat := strings.Fields(scanner.Text())
if len(stat) < 2 {
continue
}
// Expect to have "6" in metric name, skip line otherwise
if sixIndex := strings.Index(stat[0], "6"); sixIndex != -1 {
protocol := stat[0][:sixIndex+1]
name := stat[0][sixIndex+1:]
if _, present := netStats[protocol]; !present {
netStats[protocol] = map[string]string{}
}
netStats[protocol][name] = stat[1]
}
}
return netStats, scanner.Err()
}