Repository: elastic/go-sysinfo
Pull request #75 (2020-01-10 20:37:08)

Move to uint64 for procnet, deal with MaxConn
So, in order to deal with the fact that `TCP.MaxConn` can be a `-1`, we originally stored `ProcNet` values in `map[string]int64`.

However, after talking with @urso and looking it over, chances are there's some `uint64` sized values floating around in here. Looking at the kernel source, it appears that `MaxConn` is the only signed value in here, and it's stored in a unsigned long before being cast to an int in order to print.


I'm not entirely happy with this, but we're in a situation where we have one oddball value that we want to handle differently. This PR just requires the user that there's a value they need to print as an `int` or hex value in order to make it usable. 

There's a few other solutions here:

Make a struct like this:
```
type SNMP struct {
	IP      map[string]uint64 `json:"ip" netstat:"Ip"`
	ICMP    map[string]uint64 `json:"icmp" netstat:"Icmp"`
	ICMPMsg map[string]uint64 `json:"icmp_msg" netstat:"IcmpMsg"`
	TCP     map[string]uint64 `json:"tcp" netstat:"Tcp"`
	UDP     map[string]uint64 `json:"udp" netstat:"Udp"`
	UDPLite map[string]uint64 `json:"udp_lite" netstat:"UdpLite"`
        TCPMaxConn int32 `json:"max_conn"`
}
```


Or like this:


```
type SNMP struct {
	IP      map[string]uint64 `json:"ip" netstat:"Ip"`
	ICMP    map[string]uint64 `json:"icmp" netstat:"Icmp"`
	ICMPMsg map[string]uint64 `json:"icmp_msg" netstat:"IcmpMsg"`
	TCP     SNMPTCP `json:"tcp" netstat:"Tcp"`
	UDP     map[string]uint64 `json:"udp" netstat:"Udp"`
	UDPLite map[string]uint64 `json:"udp_lite" netstat:"UdpLite"`
}

type SNMPTCP{
    MaxConn int32 
    Values map[string]uint64

}

```


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e98996..ddf1289 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ### Added
 
 ### Changed
+- Convert NetworkCountersInfo maps to uint64 [#75](https://github.com/elastic/go-sysinfo/pull/75)
 
 ### Deprecated
 
diff --git a/providers/linux/procnet.go b/providers/linux/procnet.go
index 685d780..a2cc286 100644
--- a/providers/linux/procnet.go
+++ b/providers/linux/procnet.go
@@ -29,7 +29,7 @@ import (
 )
 
 // 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]int64) {
+func fillStruct(str interface{}, data map[string]map[string]uint64) {
 	val := reflect.ValueOf(str).Elem()
 	typ := reflect.TypeOf(str).Elem()
 
@@ -44,7 +44,7 @@ func fillStruct(str interface{}, data map[string]map[string]int64) {
 }
 
 // parseEntry parses two lines from the net files, the first line being keys, the second being values
-func parseEntry(line1, line2 string) (map[string]int64, error) {
+func parseEntry(line1, line2 string) (map[string]uint64, error) {
 	keyArr := strings.Split(strings.TrimSpace(line1), " ")
 	valueArr := strings.Split(strings.TrimSpace(line2), " ")
 
@@ -52,20 +52,36 @@ func parseEntry(line1, line2 string) (map[string]int64, error) {
 		return nil, errors.New("key and value lines are mismatched")
 	}
 
-	counters := make(map[string]int64, len(valueArr))
+	counters := make(map[string]uint64, len(valueArr))
 	for iter, value := range valueArr {
-		parsed, err := strconv.ParseInt(value, 10, 64)
-		if err != nil {
-			return nil, errors.Wrapf(err, "error parsing string to int in line: %#v", 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, errors.Wrapf(err, "error parsing string to int in line: %#v", valueArr)
+			}
+			parsed = uint64(signedParsed)
+		} else {
+			parsed, err = strconv.ParseUint(value, 10, 64)
+			if err != nil {
+				return nil, errors.Wrapf(err, "error parsing string to int in line: %#v", valueArr)
+			}
 		}
+
 		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]int64, error) {
-	fileMetrics := make(map[string]map[string]int64)
+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 {
diff --git a/types/host.go b/types/host.go
index 80db2c8..b7472fe 100644
--- a/types/host.go
+++ b/types/host.go
@@ -32,19 +32,20 @@ type NetworkCounters interface {
 }
 
 // SNMP represents the data from /proc/net/snmp
+// Note that according to RFC 2012,TCP.MaxConn, if present, is a signed value and should be cast to int64
 type SNMP struct {
-	IP      map[string]int64 `json:"ip" netstat:"Ip"`
-	ICMP    map[string]int64 `json:"icmp" netstat:"Icmp"`
-	ICMPMsg map[string]int64 `json:"icmp_msg" netstat:"IcmpMsg"`
-	TCP     map[string]int64 `json:"tcp" netstat:"Tcp"`
-	UDP     map[string]int64 `json:"udp" netstat:"Udp"`
-	UDPLite map[string]int64 `json:"udp_lite" netstat:"UdpLite"`
+	IP      map[string]uint64 `json:"ip" netstat:"Ip"`
+	ICMP    map[string]uint64 `json:"icmp" netstat:"Icmp"`
+	ICMPMsg map[string]uint64 `json:"icmp_msg" netstat:"IcmpMsg"`
+	TCP     map[string]uint64 `json:"tcp" netstat:"Tcp"`
+	UDP     map[string]uint64 `json:"udp" netstat:"Udp"`
+	UDPLite map[string]uint64 `json:"udp_lite" netstat:"UdpLite"`
 }
 
 // Netstat represents the data from /proc/net/netstat
 type Netstat struct {
-	TCPExt map[string]int64 `json:"tcp_ext" netstat:"TcpExt"`
-	IPExt  map[string]int64 `json:"ip_ext" netstat:"IpExt"`
+	TCPExt map[string]uint64 `json:"tcp_ext" netstat:"TcpExt"`
+	IPExt  map[string]uint64 `json:"ip_ext" netstat:"IpExt"`
 }
 
 // NetworkCountersInfo represents available network counters from /proc/net


Test patch:
diff --git a/providers/linux/procnet_test.go b/providers/linux/procnet_test.go
index d09a189..4773a3b 100644
--- a/providers/linux/procnet_test.go
+++ b/providers/linux/procnet_test.go
@@ -46,5 +46,6 @@ UdpLite: 0 0 0 0 0 0 0 0`
 	fillStruct(&testOut, mapStr)
 
 	assert.NotEmpty(t, testOut.IP)
-	assert.Equal(t, int64(16755), testOut.UDP["InDatagrams"])
+	assert.Equal(t, uint64(16755), testOut.UDP["InDatagrams"])
+	assert.Equal(t, uint64(0xffffffffffffffff), testOut.TCP["MaxConn"])
 }