providers/linux/procnet.go (85 lines of code) (raw):
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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 linux
import (
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"github.com/elastic/go-sysinfo/types"
)
// fillStruct is some reflection work that can dynamically fill one of our tagged `netstat` structs with netstat data
func fillStruct(str interface{}, data map[string]map[string]uint64) {
val := reflect.ValueOf(str).Elem()
typ := reflect.TypeOf(str).Elem()
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if tag := field.Tag.Get("netstat"); tag != "" {
if values, ok := data[tag]; ok {
val.Field(i).Set(reflect.ValueOf(values))
}
}
}
}
// parseEntry parses two lines from the net files, the first line being keys, the second being values
func parseEntry(line1, line2 string) (map[string]uint64, error) {
keyArr := strings.Split(strings.TrimSpace(line1), " ")
valueArr := strings.Split(strings.TrimSpace(line2), " ")
if len(keyArr) != len(valueArr) {
return nil, errors.New("key and value lines are mismatched")
}
counters := make(map[string]uint64, len(valueArr))
for iter, value := range valueArr {
// This if-else block is to deal with the MaxConn value in SNMP,
// which is a signed value according to RFC2012.
// This library emulates the behavior of the kernel: store all values as a uint, then cast to a signed value for printing
// Users of this library need to be aware that this value should be printed as a signed int or hex value to make it useful.
var parsed uint64
var err error
if strings.Contains(value, "-") {
signedParsed, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing string to int in line: %#v: %w", valueArr, err)
}
parsed = uint64(signedParsed)
} else {
parsed, err = strconv.ParseUint(value, 10, 64)
if err != nil {
return nil, fmt.Errorf("error parsing string to int in line: %#v: %w", valueArr, err)
}
}
counters[keyArr[iter]] = parsed
}
return counters, nil
}
// parseNetFile parses an entire file, and returns a 2D map, representing how files are sorted by protocol
func parseNetFile(body string) (map[string]map[string]uint64, error) {
fileMetrics := make(map[string]map[string]uint64)
bodySplit := strings.Split(strings.TrimSpace(body), "\n")
// There should be an even number of lines. If not, something is wrong.
if len(bodySplit)%2 != 0 {
return nil, fmt.Errorf("badly parsed body: %s", body)
}
// in the network counters, data is divided into two-line sections: a line of keys, and a line of values
// With each line
for index := 0; index < len(bodySplit); index += 2 {
keysSplit := strings.Split(bodySplit[index], ":")
valuesSplit := strings.Split(bodySplit[index+1], ":")
if len(keysSplit) != 2 || len(valuesSplit) != 2 {
return nil, fmt.Errorf("wrong number of keys: %#v", keysSplit)
}
valMap, err := parseEntry(keysSplit[1], valuesSplit[1])
if err != nil {
return nil, fmt.Errorf("error parsing lines: %w", err)
}
fileMetrics[valuesSplit[0]] = valMap
}
return fileMetrics, nil
}
// getNetSnmpStats pulls snmp stats from /proc/net
func getNetSnmpStats(raw []byte) (types.SNMP, error) {
snmpData, err := parseNetFile(string(raw))
if err != nil {
return types.SNMP{}, fmt.Errorf("error parsing SNMP: %w", err)
}
output := types.SNMP{}
fillStruct(&output, snmpData)
return output, nil
}
// getNetstatStats pulls netstat stats from /proc/net
func getNetstatStats(raw []byte) (types.Netstat, error) {
netstatData, err := parseNetFile(string(raw))
if err != nil {
return types.Netstat{}, fmt.Errorf("error parsing netstat: %w", err)
}
output := types.Netstat{}
fillStruct(&output, netstatData)
return output, nil
}