Repository: prometheus/procfs (go)

Issue:
Implement parsing of /proc/loadavg
Looks like "/proc/loadavg" file isn't parsed at now. 
It's useful from this to get current system load.

Pull request:
Add support for loadavg
Signed-off-by: tukeJonny <ne250143@yahoo.co.jp>.

This is my first contribution to this project.
This PR adds support for parsing /proc/loadavg.

fixes #239 



Patch:
diff --git a/fixtures.ttar b/fixtures.ttar
index eeb3d00..e340b9b 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -1527,6 +1527,11 @@ blocksize    : 16
 min keysize  : 16
 max keysize  : 32
 
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/proc/loadavg
+Lines: 1
+0.02 0.04 0.05 1/497 11947
 Mode: 444
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Path: fixtures/proc/diskstats
diff --git a/loadavg.go b/loadavg.go
new file mode 100644
index 0000000..00bbe14
--- /dev/null
+++ b/loadavg.go
@@ -0,0 +1,62 @@
+// Copyright 2019 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.
+
+package procfs
+
+import (
+	"fmt"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+// LoadAvg represents an entry in /proc/loadavg
+type LoadAvg struct {
+	Load1  float64
+	Load5  float64
+	Load15 float64
+}
+
+// LoadAvg returns loadavg from /proc.
+func (fs FS) LoadAvg() (*LoadAvg, error) {
+	path := fs.proc.Path("loadavg")
+
+	data, err := util.ReadFileNoStat(path)
+	if err != nil {
+		return nil, err
+	}
+	return parseLoad(data)
+}
+
+// Parse /proc loadavg and return 1m, 5m and 15m.
+func parseLoad(loadavgBytes []byte) (*LoadAvg, error) {
+	loads := make([]float64, 3)
+	parts := strings.Fields(string(loadavgBytes))
+	if len(parts) < 3 {
+		return nil, fmt.Errorf("malformed loadavg line: too few fields in loadavg string: %s", string(loadavgBytes))
+	}
+
+	var err error
+	for i, load := range parts[0:3] {
+		loads[i], err = strconv.ParseFloat(load, 64)
+		if err != nil {
+			return nil, fmt.Errorf("could not parse load '%s': %s", load, err)
+		}
+	}
+	return &LoadAvg{
+		Load1:  loads[0],
+		Load5:  loads[1],
+		Load15: loads[2],
+	}, nil
+}


Test patch:
diff --git a/loadavg_test.go b/loadavg_test.go
new file mode 100644
index 0000000..924ba0c
--- /dev/null
+++ b/loadavg_test.go
@@ -0,0 +1,93 @@
+// Copyright 2019 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.
+
+package procfs
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestLoadAvg(t *testing.T) {
+	fs, err := NewFS(procTestFixtures)
+	if err != nil {
+		t.Fatalf("failed to open procfs: %v", err)
+	}
+
+	loadavg, err := fs.LoadAvg()
+	if err != nil {
+		t.Fatalf("failed to get loadavg: %v", err)
+	}
+
+	if diff := cmp.Diff(0.02, loadavg.Load1); diff != "" {
+		t.Fatalf("unexpected LoadAvg Per a minute:\n%s", diff)
+	}
+	if diff := cmp.Diff(0.04, loadavg.Load5); diff != "" {
+		t.Fatalf("unexpected LoadAvg Per five minutes:\n%s", diff)
+	}
+	if diff := cmp.Diff(0.05, loadavg.Load15); diff != "" {
+		t.Fatalf("unexpected LoadAvg Per fifteen minutes:\n%s", diff)
+	}
+}
+
+func Test_parseLoad(t *testing.T) {
+	tests := []struct {
+		name    string
+		s       string
+		ok      bool
+		loadavg *LoadAvg
+	}{
+		{
+			name: "empty",
+			ok:   false,
+		},
+		{
+			name: "not enough fields",
+			s:    `0.00 0.03`,
+			ok:   false,
+		},
+		{
+			name: "invalid line",
+			s:    `malformed line`,
+			ok:   false,
+		},
+		{
+			name:    "valid line",
+			s:       `0.00 0.03 0.05 1/502 33634`,
+			ok:      true,
+			loadavg: &LoadAvg{Load1: 0, Load5: 0.03, Load15: 0.05},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			loadavg, err := parseLoad([]byte(tt.s))
+			if err != nil {
+				if tt.ok {
+					t.Fatalf("failed to parse loadavg: %v", err)
+				}
+
+				t.Logf("OK error: %v", err)
+				return
+			}
+			if !tt.ok {
+				t.Fatal("expected an error, but none occurred")
+			}
+
+			if diff := cmp.Diff(tt.loadavg, loadavg); diff != "" {
+				t.Errorf("unexpected loadavg(-want +got):\n%s", diff)
+			}
+		})
+	}
+}