From 9b65f1635515fa35ed38f8a76b6d8f73ab3a1d02 Mon Sep 17 00:00:00 2001
From: Michael Crosby <michael@crosbymichael.com>
Date: Sun, 20 Apr 2014 12:02:39 -0700
Subject: [PATCH] Refactor stats and add them to all subsystems
 Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com>
 (github: crosbymichael)

---
 pkg/cgroups/fs/apply_raw.go  |  1 +
 pkg/cgroups/fs/blkio.go      | 28 ++++++------
 pkg/cgroups/fs/cpu.go        |  4 ++
 pkg/cgroups/fs/cpuacct.go    | 84 ++++++++++++++++++------------------
 pkg/cgroups/fs/cpuset.go     |  4 ++
 pkg/cgroups/fs/devices.go    |  4 ++
 pkg/cgroups/fs/freezer.go    |  4 ++
 pkg/cgroups/fs/memory.go     | 10 ++---
 pkg/cgroups/fs/perf_event.go |  4 ++
 pkg/cgroups/fs/utils.go      |  6 ++-
 10 files changed, 88 insertions(+), 61 deletions(-)

diff --git a/pkg/cgroups/fs/apply_raw.go b/pkg/cgroups/fs/apply_raw.go
index cdb2b354c6..ecc4bb62e9 100644
--- a/pkg/cgroups/fs/apply_raw.go
+++ b/pkg/cgroups/fs/apply_raw.go
@@ -26,6 +26,7 @@ var (
 type subsystem interface {
 	Set(*data) error
 	Remove(*data) error
+	Stats(*data) (map[string]float64, error)
 }
 
 type data struct {
diff --git a/pkg/cgroups/fs/blkio.go b/pkg/cgroups/fs/blkio.go
index fae8d5a037..abf8fb6227 100644
--- a/pkg/cgroups/fs/blkio.go
+++ b/pkg/cgroups/fs/blkio.go
@@ -25,29 +25,33 @@ func (s *blkioGroup) Remove(d *data) error {
 }
 
 func (s *blkioGroup) Stats(d *data) (map[string]float64, error) {
-	paramData := make(map[string]float64)
+	var (
+		paramData = make(map[string]float64)
+		params    = []string{
+			"sectors",
+			"io_service_bytes",
+			"io_serviced",
+			"io_queued",
+		}
+	)
+
 	path, err := d.path("blkio")
 	if err != nil {
-		return paramData, fmt.Errorf("Unable to read %s cgroup param: %s", path, err)
-	}
-	params := []string{
-		"sectors",
-		"io_service_bytes",
-		"io_serviced",
-		"io_queued",
+		return nil, err
 	}
+
 	for _, param := range params {
-		p := fmt.Sprintf("blkio.%s", param)
-		f, err := os.Open(filepath.Join(path, p))
+		f, err := os.Open(filepath.Join(path, fmt.Sprintf("blkio.%s", param)))
 		if err != nil {
-			return paramData, err
+			return nil, err
 		}
 		defer f.Close()
+
 		sc := bufio.NewScanner(f)
 		for sc.Scan() {
 			_, v, err := getCgroupParamKeyValue(sc.Text())
 			if err != nil {
-				return paramData, fmt.Errorf("Error parsing param data: %s", err)
+				return nil, err
 			}
 			paramData[param] = v
 		}
diff --git a/pkg/cgroups/fs/cpu.go b/pkg/cgroups/fs/cpu.go
index f458d7915e..5534443412 100644
--- a/pkg/cgroups/fs/cpu.go
+++ b/pkg/cgroups/fs/cpu.go
@@ -25,3 +25,7 @@ func (s *cpuGroup) Set(d *data) error {
 func (s *cpuGroup) Remove(d *data) error {
 	return removePath(d.path("cpu"))
 }
+
+func (s *cpuGroup) Stats(d *data) (map[string]float64, error) {
+	return nil, ErrNotSupportStat
+}
diff --git a/pkg/cgroups/fs/cpuacct.go b/pkg/cgroups/fs/cpuacct.go
index 621592e31f..b0b14a1475 100644
--- a/pkg/cgroups/fs/cpuacct.go
+++ b/pkg/cgroups/fs/cpuacct.go
@@ -2,7 +2,6 @@ package fs
 
 import (
 	"bufio"
-	"fmt"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -10,6 +9,7 @@ import (
 	"strings"
 
 	"github.com/dotcloud/docker/pkg/cgroups"
+	"github.com/dotcloud/docker/pkg/system"
 )
 
 type cpuacctGroup struct {
@@ -28,62 +28,62 @@ func (s *cpuacctGroup) Remove(d *data) error {
 }
 
 func (s *cpuacctGroup) Stats(d *data) (map[string]float64, error) {
-	paramData := make(map[string]float64)
+	var (
+		uptime, startTime float64
+		paramData         = make(map[string]float64)
+		cpuTotal          = 0.0
+	)
+
 	path, err := d.path("cpuacct")
-	if err != nil {
-		return paramData, fmt.Errorf("Unable to read %s cgroup param: %s", path, err)
-	}
-	f, err := os.Open(filepath.Join(path, "cpuacct.stat"))
 	if err != nil {
 		return paramData, err
 	}
+	f, err := os.Open(filepath.Join(path, "cpuacct.stat"))
+	if err != nil {
+		return nil, err
+	}
 	defer f.Close()
+
 	sc := bufio.NewScanner(f)
-	cpuTotal := 0.0
 	for sc.Scan() {
 		t, v, err := getCgroupParamKeyValue(sc.Text())
 		if err != nil {
-			return paramData, fmt.Errorf("Error parsing param data: %s", err)
+			return paramData, err
 		}
 		// set the raw data in map
 		paramData[t] = v
 		cpuTotal += v
 	}
-	// calculate percentage from jiffies
-	// get sys uptime
-	uf, err := os.Open("/proc/uptime")
-	if err != nil {
-		return paramData, fmt.Errorf("Unable to open /proc/uptime")
+
+	if uptime, err = s.getUptime(); err != nil {
+		return nil, err
 	}
-	defer uf.Close()
-	uptimeData, err := ioutil.ReadAll(uf)
-	if err != nil {
-		return paramData, fmt.Errorf("Error reading /proc/uptime: %s", err)
+	if startTime, err = s.getProcStarttime(d); err != nil {
+		return nil, err
 	}
-	uptimeFields := strings.Fields(string(uptimeData))
-	uptime, err := strconv.ParseFloat(uptimeFields[0], 64)
-	if err != nil {
-		return paramData, fmt.Errorf("Error parsing cpu stats: %s", err)
-	}
-	// find starttime of process
-	pf, err := os.Open(filepath.Join(path, "cgroup.procs"))
-	if err != nil {
-		return paramData, fmt.Errorf("Error parsing cpu stats: %s", err)
-	}
-	defer pf.Close()
-	pr := bufio.NewReader(pf)
-	l, _, err := pr.ReadLine()
-	if err != nil {
-		return paramData, fmt.Errorf("Error reading param file: %s", err)
-	}
-	starttime, err := strconv.ParseFloat(string(l), 64)
-	if err != nil {
-		return paramData, fmt.Errorf("Unable to parse starttime: %s", err)
-	}
-	// get total elapsed seconds since proc start
-	seconds := uptime - (starttime / 100)
-	// finally calc percentage
-	cpuPercentage := 100.0 * ((cpuTotal / 100.0) / float64(seconds))
-	paramData["percentage"] = cpuPercentage
+	paramData["percentage"] = 100.0 * ((cpuTotal/100.0)/uptime - (startTime / 100))
+
 	return paramData, nil
 }
+
+func (s *cpuacctGroup) getUptime() (float64, error) {
+	f, err := os.Open("/proc/uptime")
+	if err != nil {
+		return 0, err
+	}
+	defer f.Close()
+
+	data, err := ioutil.ReadAll(f)
+	if err != nil {
+		return 0, err
+	}
+	return strconv.ParseFloat(strings.Fields(string(data))[0], 64)
+}
+
+func (s *cpuacctGroup) getProcStarttime(d *data) (float64, error) {
+	rawStart, err := system.GetProcessStartTime(d.pid)
+	if err != nil {
+		return 0, err
+	}
+	return strconv.ParseFloat(rawStart, 64)
+}
diff --git a/pkg/cgroups/fs/cpuset.go b/pkg/cgroups/fs/cpuset.go
index 3d3b15f113..8a13c56cea 100644
--- a/pkg/cgroups/fs/cpuset.go
+++ b/pkg/cgroups/fs/cpuset.go
@@ -30,3 +30,7 @@ func (s *cpusetGroup) Set(d *data) error {
 func (s *cpusetGroup) Remove(d *data) error {
 	return removePath(d.path("cpuset"))
 }
+
+func (s *cpusetGroup) Stats(d *data) (map[string]float64, error) {
+	return nil, ErrNotSupportStat
+}
diff --git a/pkg/cgroups/fs/devices.go b/pkg/cgroups/fs/devices.go
index fc5d83b9c9..a2f91eda14 100644
--- a/pkg/cgroups/fs/devices.go
+++ b/pkg/cgroups/fs/devices.go
@@ -63,3 +63,7 @@ func (s *devicesGroup) Set(d *data) error {
 func (s *devicesGroup) Remove(d *data) error {
 	return removePath(d.path("devices"))
 }
+
+func (s *devicesGroup) Stats(d *data) (map[string]float64, error) {
+	return nil, ErrNotSupportStat
+}
diff --git a/pkg/cgroups/fs/freezer.go b/pkg/cgroups/fs/freezer.go
index 05bc584cf4..878a3a5fe9 100644
--- a/pkg/cgroups/fs/freezer.go
+++ b/pkg/cgroups/fs/freezer.go
@@ -18,3 +18,7 @@ func (s *freezerGroup) Set(d *data) error {
 func (s *freezerGroup) Remove(d *data) error {
 	return removePath(d.path("freezer"))
 }
+
+func (s *freezerGroup) Stats(d *data) (map[string]float64, error) {
+	return nil, ErrNotSupportStat
+}
diff --git a/pkg/cgroups/fs/memory.go b/pkg/cgroups/fs/memory.go
index 26281c1c75..cf4bf5ab73 100644
--- a/pkg/cgroups/fs/memory.go
+++ b/pkg/cgroups/fs/memory.go
@@ -2,7 +2,6 @@ package fs
 
 import (
 	"bufio"
-	"fmt"
 	"os"
 	"path/filepath"
 	"strconv"
@@ -51,19 +50,20 @@ func (s *memoryGroup) Stats(d *data) (map[string]float64, error) {
 	paramData := make(map[string]float64)
 	path, err := d.path("memory")
 	if err != nil {
-		fmt.Errorf("Unable to read %s cgroup param: %s", path, err)
-		return paramData, err
+		return nil, err
 	}
+
 	f, err := os.Open(filepath.Join(path, "memory.stat"))
 	if err != nil {
-		return paramData, err
+		return nil, err
 	}
 	defer f.Close()
+
 	sc := bufio.NewScanner(f)
 	for sc.Scan() {
 		t, v, err := getCgroupParamKeyValue(sc.Text())
 		if err != nil {
-			return paramData, fmt.Errorf("Error parsing param data: %s", err)
+			return nil, err
 		}
 		paramData[t] = v
 	}
diff --git a/pkg/cgroups/fs/perf_event.go b/pkg/cgroups/fs/perf_event.go
index b5ec6c6fd3..789b3e59ad 100644
--- a/pkg/cgroups/fs/perf_event.go
+++ b/pkg/cgroups/fs/perf_event.go
@@ -18,3 +18,7 @@ func (s *perfEventGroup) Set(d *data) error {
 func (s *perfEventGroup) Remove(d *data) error {
 	return removePath(d.path("perf_event"))
 }
+
+func (s *perfEventGroup) Stats(d *data) (map[string]float64, error) {
+	return nil, ErrNotSupportStat
+}
diff --git a/pkg/cgroups/fs/utils.go b/pkg/cgroups/fs/utils.go
index 260cf13a52..6a0838f3a2 100644
--- a/pkg/cgroups/fs/utils.go
+++ b/pkg/cgroups/fs/utils.go
@@ -1,23 +1,25 @@
 package fs
 
 import (
+	"errors"
 	"fmt"
 	"strconv"
 	"strings"
 )
 
+var ErrNotSupportStat = errors.New("stats are not supported for subsystem")
+
 // Parses a cgroup param and returns as name, value
 //  i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234
 func getCgroupParamKeyValue(t string) (string, float64, error) {
 	parts := strings.Fields(t)
 	switch len(parts) {
 	case 2:
-		name := parts[0]
 		value, err := strconv.ParseFloat(parts[1], 64)
 		if err != nil {
 			return "", 0.0, fmt.Errorf("Unable to convert param value to float: %s", err)
 		}
-		return name, value, nil
+		return parts[0], value, nil
 	default:
 		return "", 0.0, fmt.Errorf("Unable to parse cgroup param: not enough parts; expected 2")
 	}