2015-09-26 17:36:40 +02:00
// 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.
2015-05-12 13:06:41 +02:00
// +build !nonetstat
2014-11-11 16:24:35 +01:00
package collector
import (
"bufio"
"fmt"
"io"
"os"
2018-03-30 20:28:08 +02:00
"regexp"
2014-11-11 16:24:35 +01:00
"strconv"
"strings"
"github.com/prometheus/client_golang/prometheus"
2018-03-30 20:28:08 +02:00
"gopkg.in/alecthomas/kingpin.v2"
2014-11-11 16:24:35 +01:00
)
const (
netStatsSubsystem = "netstat"
)
2018-03-30 20:28:08 +02:00
var (
2018-11-07 17:21:18 +01:00
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|PassiveOpens|RetransSegs|CurrEstab)|Udp6?_(InDatagrams|OutDatagrams|NoPorts))$" ) . String ( )
2018-03-30 20:28:08 +02:00
)
type netStatCollector struct {
fieldPattern * regexp . Regexp
}
2014-11-11 16:24:35 +01:00
func init ( ) {
2017-09-28 15:06:26 +02:00
registerCollector ( "netstat" , defaultEnabled , NewNetStatCollector )
2014-11-11 16:24:35 +01:00
}
2016-08-12 00:34:00 +02:00
// NewNetStatCollector takes and returns
2014-11-11 16:24:35 +01:00
// a new Collector exposing network stats.
2015-05-20 20:04:49 +02:00
func NewNetStatCollector ( ) ( Collector , error ) {
2018-03-30 20:28:08 +02:00
pattern := regexp . MustCompile ( * netStatFields )
return & netStatCollector {
fieldPattern : pattern ,
} , nil
2014-11-11 16:24:35 +01:00
}
2017-02-28 19:47:20 +01:00
func ( c * netStatCollector ) Update ( ch chan <- prometheus . Metric ) error {
2015-09-26 14:53:46 +02:00
netStats , err := getNetStats ( procFilePath ( "net/netstat" ) )
2014-11-11 16:24:35 +01:00
if err != nil {
return fmt . Errorf ( "couldn't get netstats: %s" , err )
}
2015-09-26 14:53:46 +02:00
snmpStats , err := getNetStats ( procFilePath ( "net/snmp" ) )
2015-06-17 22:05:09 +02:00
if err != nil {
return fmt . Errorf ( "couldn't get SNMP stats: %s" , err )
}
2017-07-08 20:16:35 +02:00
snmp6Stats , err := getSNMP6Stats ( procFilePath ( "net/snmp6" ) )
if err != nil {
return fmt . Errorf ( "couldn't get SNMP6 stats: %s" , err )
}
2015-06-17 22:05:09 +02:00
// Merge the results of snmpStats into netStats (collisions are possible, but
2016-08-12 00:34:00 +02:00
// we know that the keys are always unique for the given use case).
2015-06-17 22:05:09 +02:00
for k , v := range snmpStats {
netStats [ k ] = v
}
2017-07-08 20:16:35 +02:00
for k , v := range snmp6Stats {
netStats [ k ] = v
}
2014-11-11 16:24:35 +01:00
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 )
}
2018-03-30 20:28:08 +02:00
if ! c . fieldPattern . MatchString ( key ) {
continue
}
2016-08-12 00:34:00 +02:00
ch <- prometheus . MustNewConstMetric (
prometheus . NewDesc (
2017-09-28 15:06:26 +02:00
prometheus . BuildFQName ( namespace , netStatsSubsystem , key ) ,
2017-07-08 20:16:35 +02:00
fmt . Sprintf ( "Statistic %s." , protocol + name ) ,
2016-08-12 00:34:00 +02:00
nil , nil ,
) ,
prometheus . UntypedValue , v ,
)
2014-11-11 16:24:35 +01:00
}
}
2016-08-12 00:34:00 +02:00
return nil
2014-11-11 16:24:35 +01:00
}
2015-06-17 22:05:09 +02:00
func getNetStats ( fileName string ) ( map [ string ] map [ string ] string , error ) {
file , err := os . Open ( fileName )
2014-11-11 16:24:35 +01:00
if err != nil {
return nil , err
}
2014-11-25 00:30:07 +01:00
defer file . Close ( )
2015-06-17 22:05:09 +02:00
return parseNetStats ( file , fileName )
2014-11-11 16:24:35 +01:00
}
2015-06-17 22:05:09 +02:00
func parseNetStats ( r io . Reader , fileName string ) ( map [ string ] map [ string ] string , error ) {
2014-11-25 00:30:07 +01:00
var (
netStats = map [ string ] map [ string ] string { }
scanner = bufio . NewScanner ( r )
)
2014-11-11 16:24:35 +01:00
for scanner . Scan ( ) {
2017-02-28 18:42:43 +01:00
nameParts := strings . Split ( scanner . Text ( ) , " " )
2014-11-11 16:24:35 +01:00
scanner . Scan ( )
2017-02-28 18:42:43 +01:00
valueParts := strings . Split ( scanner . Text ( ) , " " )
2014-11-11 16:24:35 +01:00
// 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" ,
2015-06-17 22:05:09 +02:00
fileName , protocol )
2014-11-11 16:24:35 +01:00
}
for i := 1 ; i < len ( nameParts ) ; i ++ {
netStats [ protocol ] [ nameParts [ i ] ] = valueParts [ i ]
}
}
2014-11-25 00:30:07 +01:00
2017-02-28 19:31:35 +01:00
return netStats , scanner . Err ( )
2014-11-11 16:24:35 +01:00
}
2017-07-08 20:16:35 +02:00
func getSNMP6Stats ( fileName string ) ( map [ string ] map [ string ] string , error ) {
file , err := os . Open ( fileName )
if err != nil {
2017-10-31 20:26:32 +01:00
// On systems with IPv6 disabled, this file won't exist.
// Do nothing.
if os . IsNotExist ( err ) {
return nil , nil
}
2017-07-08 20:16:35 +02:00
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 ( )
}