Browse code

Added a new method cgroups.GetStats() which will return a cgroups.Stats object which will contain all the available cgroup Stats. Remove old Stats interface in libcontainers cgroups package. Changed Stats to use unit64 instead of int64 to prevent integer overflow issues. Updated unit tests.

Docker-DCO-1.1-Signed-off-by: Vishnu Kannan <vishnuk@google.com> (github: vishh)

Vishnu Kannan authored on 2014/05/28 09:01:08
Showing 16 changed files
... ...
@@ -26,7 +26,7 @@ var (
26 26
 type subsystem interface {
27 27
 	Set(*data) error
28 28
 	Remove(*data) error
29
-	Stats(*data) (map[string]int64, error)
29
+	GetStats(*data, *cgroups.Stats) error
30 30
 }
31 31
 
32 32
 type data struct {
... ...
@@ -74,7 +74,8 @@ func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
74 74
 	return d, nil
75 75
 }
76 76
 
77
-func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]int64, error) {
77
+func GetStats(c *cgroups.Cgroup) (*cgroups.Stats, error) {
78
+	stats := cgroups.NewStats()
78 79
 	cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu")
79 80
 	if err != nil {
80 81
 		return nil, err
... ...
@@ -94,13 +95,15 @@ func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]int64, e
94 94
 		root:   cgroupRoot,
95 95
 		cgroup: cgroup,
96 96
 		c:      c,
97
-		pid:    pid,
98 97
 	}
99
-	sys, exists := subsystems[subsystem]
100
-	if !exists {
101
-		return nil, fmt.Errorf("subsystem %s does not exist", subsystem)
98
+
99
+	for _, sys := range subsystems {
100
+		if err := sys.GetStats(d, stats); err != nil {
101
+			return nil, err
102
+		}
102 103
 	}
103
-	return sys.Stats(d)
104
+
105
+	return stats, nil
104 106
 }
105 107
 
106 108
 func GetPids(c *cgroups.Cgroup) ([]int, error) {
... ...
@@ -3,7 +3,6 @@ package fs
3 3
 import (
4 4
 	"bufio"
5 5
 	"fmt"
6
-	"io/ioutil"
7 6
 	"os"
8 7
 	"path/filepath"
9 8
 	"strconv"
... ...
@@ -57,65 +56,87 @@ examples:
57 57
     8:0 Total 0
58 58
     Total 0
59 59
 */
60
-func (s *blkioGroup) Stats(d *data) (map[string]int64, error) {
61
-	var (
62
-		paramData = make(map[string]int64)
63
-		params    = []string{
64
-			"io_service_bytes_recursive",
65
-			"io_serviced_recursive",
66
-			"io_queued_recursive",
67
-		}
68
-	)
69 60
 
70
-	path, err := d.path("blkio")
71
-	if err != nil {
72
-		return nil, err
73
-	}
61
+func splitBlkioStatLine(r rune) bool {
62
+	return r == ' ' || r == ':'
63
+}
74 64
 
75
-	k, v, err := s.getSectors(path)
65
+func getBlkioStat(path string) ([]cgroups.BlkioStatEntry, error) {
66
+	var blkioStats []cgroups.BlkioStatEntry
67
+	f, err := os.Open(path)
76 68
 	if err != nil {
77 69
 		return nil, err
78 70
 	}
79
-	paramData[fmt.Sprintf("blkio.sectors_recursive:%s", k)] = v
71
+	defer f.Close()
72
+
73
+	sc := bufio.NewScanner(f)
74
+	for sc.Scan() {
75
+		// format: dev type amount
76
+		fields := strings.FieldsFunc(sc.Text(), splitBlkioStatLine)
77
+		if len(fields) < 3 {
78
+			if len(fields) == 2 && fields[0] == "Total" {
79
+				// skip total line
80
+				continue
81
+			} else {
82
+				return nil, fmt.Errorf("Invalid line found while parsing %s: %s", path, sc.Text())
83
+			}
84
+		}
80 85
 
81
-	for _, param := range params {
82
-		f, err := os.Open(filepath.Join(path, fmt.Sprintf("blkio.%s", param)))
86
+		v, err := strconv.ParseUint(fields[0], 10, 64)
83 87
 		if err != nil {
84 88
 			return nil, err
85 89
 		}
86
-		defer f.Close()
87
-
88
-		sc := bufio.NewScanner(f)
89
-		for sc.Scan() {
90
-			// format: dev type amount
91
-			fields := strings.Fields(sc.Text())
92
-			switch len(fields) {
93
-			case 3:
94
-				v, err := strconv.ParseInt(fields[2], 10, 64)
95
-				if err != nil {
96
-					return nil, err
97
-				}
98
-				paramData[fmt.Sprintf("%s:%s:%s", param, fields[0], fields[1])] = v
99
-			case 2:
100
-				// this is the total line, skip
101
-			default:
102
-				return nil, ErrNotValidFormat
103
-			}
90
+		major := v
91
+
92
+		v, err = strconv.ParseUint(fields[1], 10, 64)
93
+		if err != nil {
94
+			return nil, err
95
+		}
96
+		minor := v
97
+
98
+		op := ""
99
+		valueField := 2
100
+		if len(fields) == 4 {
101
+			op = fields[2]
102
+			valueField = 3
103
+		}
104
+		v, err = strconv.ParseUint(fields[valueField], 10, 64)
105
+		if err != nil {
106
+			return nil, err
104 107
 		}
108
+		blkioStats = append(blkioStats, cgroups.BlkioStatEntry{Major: major, Minor: minor, Op: op, Value: v})
105 109
 	}
106
-	return paramData, nil
110
+
111
+	return blkioStats, nil
107 112
 }
108 113
 
109
-func (s *blkioGroup) getSectors(path string) (string, int64, error) {
110
-	f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive"))
114
+func (s *blkioGroup) GetStats(d *data, stats *cgroups.Stats) error {
115
+	var blkioStats []cgroups.BlkioStatEntry
116
+	var err error
117
+	path, err := d.path("blkio")
111 118
 	if err != nil {
112
-		return "", 0, err
119
+		return err
113 120
 	}
114
-	defer f.Close()
115 121
 
116
-	data, err := ioutil.ReadAll(f)
117
-	if err != nil {
118
-		return "", 0, err
122
+	if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.sectors_recursive")); err != nil {
123
+		return err
124
+	}
125
+	stats.BlkioStats.SectorsRecursive = blkioStats
126
+
127
+	if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_service_bytes_recursive")); err != nil {
128
+		return err
119 129
 	}
120
-	return getCgroupParamKeyValue(string(data))
130
+	stats.BlkioStats.IoServiceBytesRecursive = blkioStats
131
+
132
+	if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_serviced_recursive")); err != nil {
133
+		return err
134
+	}
135
+	stats.BlkioStats.IoServicedRecursive = blkioStats
136
+
137
+	if blkioStats, err = getBlkioStat(filepath.Join(path, "blkio.io_queued_recursive")); err != nil {
138
+		return err
139
+	}
140
+	stats.BlkioStats.IoQueuedRecursive = blkioStats
141
+
142
+	return nil
121 143
 }
... ...
@@ -2,14 +2,16 @@ package fs
2 2
 
3 3
 import (
4 4
 	"testing"
5
+
6
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
5 7
 )
6 8
 
7 9
 const (
8 10
 	sectorsRecursiveContents      = `8:0 1024`
9 11
 	serviceBytesRecursiveContents = `8:0 Read 100
10
-8:0 Write 400
11
-8:0 Sync 200
12
-8:0 Async 300
12
+8:0 Write 200
13
+8:0 Sync 300
14
+8:0 Async 500
13 15
 8:0 Total 500
14 16
 Total 500`
15 17
 	servicedRecursiveContents = `8:0 Read 10
... ...
@@ -26,6 +28,12 @@ Total 50`
26 26
 Total 5`
27 27
 )
28 28
 
29
+var actualStats = *cgroups.NewStats()
30
+
31
+func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) {
32
+	*blkioStatEntries = append(*blkioStatEntries, cgroups.BlkioStatEntry{Major: major, Minor: minor, Value: value, Op: op})
33
+}
34
+
29 35
 func TestBlkioStats(t *testing.T) {
30 36
 	helper := NewCgroupTestUtil("blkio", t)
31 37
 	defer helper.cleanup()
... ...
@@ -37,37 +45,34 @@ func TestBlkioStats(t *testing.T) {
37 37
 	})
38 38
 
39 39
 	blkio := &blkioGroup{}
40
-	stats, err := blkio.Stats(helper.CgroupData)
40
+	err := blkio.GetStats(helper.CgroupData, &actualStats)
41 41
 	if err != nil {
42 42
 		t.Fatal(err)
43 43
 	}
44 44
 
45 45
 	// Verify expected stats.
46
-	expectedStats := map[string]int64{
47
-		"blkio.sectors_recursive:8:0": 1024,
48
-
49
-		// Serviced bytes.
50
-		"io_service_bytes_recursive:8:0:Read":  100,
51
-		"io_service_bytes_recursive:8:0:Write": 400,
52
-		"io_service_bytes_recursive:8:0:Sync":  200,
53
-		"io_service_bytes_recursive:8:0:Async": 300,
54
-		"io_service_bytes_recursive:8:0:Total": 500,
55
-
56
-		// Serviced requests.
57
-		"io_serviced_recursive:8:0:Read":  10,
58
-		"io_serviced_recursive:8:0:Write": 40,
59
-		"io_serviced_recursive:8:0:Sync":  20,
60
-		"io_serviced_recursive:8:0:Async": 30,
61
-		"io_serviced_recursive:8:0:Total": 50,
62
-
63
-		// Queued requests.
64
-		"io_queued_recursive:8:0:Read":  1,
65
-		"io_queued_recursive:8:0:Write": 4,
66
-		"io_queued_recursive:8:0:Sync":  2,
67
-		"io_queued_recursive:8:0:Async": 3,
68
-		"io_queued_recursive:8:0:Total": 5,
69
-	}
70
-	expectStats(t, expectedStats, stats)
46
+	expectedStats := cgroups.BlkioStats{}
47
+	appendBlkioStatEntry(&expectedStats.SectorsRecursive, 8, 0, 1024, "")
48
+
49
+	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 100, "Read")
50
+	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 200, "Write")
51
+	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 300, "Sync")
52
+	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Async")
53
+	appendBlkioStatEntry(&expectedStats.IoServiceBytesRecursive, 8, 0, 500, "Total")
54
+
55
+	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 10, "Read")
56
+	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 40, "Write")
57
+	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 20, "Sync")
58
+	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 30, "Async")
59
+	appendBlkioStatEntry(&expectedStats.IoServicedRecursive, 8, 0, 50, "Total")
60
+
61
+	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 1, "Read")
62
+	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 4, "Write")
63
+	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 2, "Sync")
64
+	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 3, "Async")
65
+	appendBlkioStatEntry(&expectedStats.IoQueuedRecursive, 8, 0, 5, "Total")
66
+
67
+	expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats)
71 68
 }
72 69
 
73 70
 func TestBlkioStatsNoSectorsFile(t *testing.T) {
... ...
@@ -80,7 +85,7 @@ func TestBlkioStatsNoSectorsFile(t *testing.T) {
80 80
 	})
81 81
 
82 82
 	blkio := &blkioGroup{}
83
-	_, err := blkio.Stats(helper.CgroupData)
83
+	err := blkio.GetStats(helper.CgroupData, &actualStats)
84 84
 	if err == nil {
85 85
 		t.Fatal("Expected to fail, but did not")
86 86
 	}
... ...
@@ -96,7 +101,7 @@ func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
96 96
 	})
97 97
 
98 98
 	blkio := &blkioGroup{}
99
-	_, err := blkio.Stats(helper.CgroupData)
99
+	err := blkio.GetStats(helper.CgroupData, &actualStats)
100 100
 	if err == nil {
101 101
 		t.Fatal("Expected to fail, but did not")
102 102
 	}
... ...
@@ -112,7 +117,7 @@ func TestBlkioStatsNoServicedFile(t *testing.T) {
112 112
 	})
113 113
 
114 114
 	blkio := &blkioGroup{}
115
-	_, err := blkio.Stats(helper.CgroupData)
115
+	err := blkio.GetStats(helper.CgroupData, &actualStats)
116 116
 	if err == nil {
117 117
 		t.Fatal("Expected to fail, but did not")
118 118
 	}
... ...
@@ -128,7 +133,7 @@ func TestBlkioStatsNoQueuedFile(t *testing.T) {
128 128
 	})
129 129
 
130 130
 	blkio := &blkioGroup{}
131
-	_, err := blkio.Stats(helper.CgroupData)
131
+	err := blkio.GetStats(helper.CgroupData, &actualStats)
132 132
 	if err == nil {
133 133
 		t.Fatal("Expected to fail, but did not")
134 134
 	}
... ...
@@ -145,7 +150,7 @@ func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
145 145
 	})
146 146
 
147 147
 	blkio := &blkioGroup{}
148
-	_, err := blkio.Stats(helper.CgroupData)
148
+	err := blkio.GetStats(helper.CgroupData, &actualStats)
149 149
 	if err == nil {
150 150
 		t.Fatal("Expected to fail, but did not")
151 151
 	}
... ...
@@ -162,7 +167,7 @@ func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
162 162
 	})
163 163
 
164 164
 	blkio := &blkioGroup{}
165
-	_, err := blkio.Stats(helper.CgroupData)
165
+	err := blkio.GetStats(helper.CgroupData, &actualStats)
166 166
 	if err == nil {
167 167
 		t.Fatal("Expected to fail, but did not")
168 168
 	}
... ...
@@ -5,6 +5,8 @@ import (
5 5
 	"os"
6 6
 	"path/filepath"
7 7
 	"strconv"
8
+
9
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
8 10
 )
9 11
 
10 12
 type cpuGroup struct {
... ...
@@ -39,16 +41,15 @@ func (s *cpuGroup) Remove(d *data) error {
39 39
 	return removePath(d.path("cpu"))
40 40
 }
41 41
 
42
-func (s *cpuGroup) Stats(d *data) (map[string]int64, error) {
43
-	paramData := make(map[string]int64)
42
+func (s *cpuGroup) GetStats(d *data, stats *cgroups.Stats) error {
44 43
 	path, err := d.path("cpu")
45 44
 	if err != nil {
46
-		return nil, err
45
+		return err
47 46
 	}
48 47
 
49 48
 	f, err := os.Open(filepath.Join(path, "cpu.stat"))
50 49
 	if err != nil {
51
-		return nil, err
50
+		return err
52 51
 	}
53 52
 	defer f.Close()
54 53
 
... ...
@@ -56,9 +57,18 @@ func (s *cpuGroup) Stats(d *data) (map[string]int64, error) {
56 56
 	for sc.Scan() {
57 57
 		t, v, err := getCgroupParamKeyValue(sc.Text())
58 58
 		if err != nil {
59
-			return nil, err
59
+			return err
60
+		}
61
+		switch t {
62
+		case "nr_periods":
63
+			stats.CpuStats.ThrottlingData.Periods = v
64
+
65
+		case "nr_throttled":
66
+			stats.CpuStats.ThrottlingData.ThrottledPeriods = v
67
+
68
+		case "throttled_time":
69
+			stats.CpuStats.ThrottlingData.ThrottledTime = v
60 70
 		}
61
-		paramData[t] = v
62 71
 	}
63
-	return paramData, nil
72
+	return nil
64 73
 }
... ...
@@ -1,31 +1,40 @@
1 1
 package fs
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"testing"
6
+
7
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
5 8
 )
6 9
 
7 10
 func TestCpuStats(t *testing.T) {
8 11
 	helper := NewCgroupTestUtil("cpu", t)
9 12
 	defer helper.cleanup()
10
-	cpuStatContent := `nr_periods 2000
11
-	nr_throttled 200
12
-	throttled_time 42424242424`
13
+
14
+	const (
15
+		kNrPeriods     = 2000
16
+		kNrThrottled   = 200
17
+		kThrottledTime = uint64(18446744073709551615)
18
+	)
19
+
20
+	cpuStatContent := fmt.Sprintf("nr_periods %d\n nr_throttled %d\n throttled_time %d\n",
21
+		kNrPeriods, kNrThrottled, kThrottledTime)
13 22
 	helper.writeFileContents(map[string]string{
14 23
 		"cpu.stat": cpuStatContent,
15 24
 	})
16 25
 
17 26
 	cpu := &cpuGroup{}
18
-	stats, err := cpu.Stats(helper.CgroupData)
27
+	err := cpu.GetStats(helper.CgroupData, &actualStats)
19 28
 	if err != nil {
20 29
 		t.Fatal(err)
21 30
 	}
22 31
 
23
-	expected_stats := map[string]int64{
24
-		"nr_periods":     2000,
25
-		"nr_throttled":   200,
26
-		"throttled_time": 42424242424,
27
-	}
28
-	expectStats(t, expected_stats, stats)
32
+	expectedStats := cgroups.ThrottlingData{
33
+		Periods:          kNrPeriods,
34
+		ThrottledPeriods: kNrThrottled,
35
+		ThrottledTime:    kThrottledTime}
36
+
37
+	expectThrottlingDataEquals(t, expectedStats, actualStats.CpuStats.ThrottlingData)
29 38
 }
30 39
 
31 40
 func TestNoCpuStatFile(t *testing.T) {
... ...
@@ -33,7 +42,7 @@ func TestNoCpuStatFile(t *testing.T) {
33 33
 	defer helper.cleanup()
34 34
 
35 35
 	cpu := &cpuGroup{}
36
-	_, err := cpu.Stats(helper.CgroupData)
36
+	err := cpu.GetStats(helper.CgroupData, &actualStats)
37 37
 	if err == nil {
38 38
 		t.Fatal("Expected to fail, but did not.")
39 39
 	}
... ...
@@ -50,7 +59,7 @@ func TestInvalidCpuStat(t *testing.T) {
50 50
 	})
51 51
 
52 52
 	cpu := &cpuGroup{}
53
-	_, err := cpu.Stats(helper.CgroupData)
53
+	err := cpu.GetStats(helper.CgroupData, &actualStats)
54 54
 	if err == nil {
55 55
 		t.Fatal("Expected failed stat parsing.")
56 56
 	}
... ...
@@ -15,8 +15,8 @@ import (
15 15
 )
16 16
 
17 17
 var (
18
-	cpuCount   = int64(runtime.NumCPU())
19
-	clockTicks = int64(system.GetClockTicks())
18
+	cpuCount   = uint64(runtime.NumCPU())
19
+	clockTicks = uint64(system.GetClockTicks())
20 20
 )
21 21
 
22 22
 type cpuacctGroup struct {
... ...
@@ -34,34 +34,33 @@ func (s *cpuacctGroup) Remove(d *data) error {
34 34
 	return removePath(d.path("cpuacct"))
35 35
 }
36 36
 
37
-func (s *cpuacctGroup) Stats(d *data) (map[string]int64, error) {
37
+func (s *cpuacctGroup) GetStats(d *data, stats *cgroups.Stats) error {
38 38
 	var (
39
-		startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage int64
40
-		percentage                                                        int64
41
-		paramData                                                         = make(map[string]int64)
39
+		startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage uint64
40
+		percentage                                                        uint64
42 41
 	)
43 42
 	path, err := d.path("cpuacct")
44 43
 	if startCpu, err = s.getCpuUsage(d, path); err != nil {
45
-		return nil, err
44
+		return err
46 45
 	}
47 46
 	if startSystem, err = s.getSystemCpuUsage(d); err != nil {
48
-		return nil, err
47
+		return err
49 48
 	}
50 49
 	startUsageTime := time.Now()
51 50
 	if startUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil {
52
-		return nil, err
51
+		return err
53 52
 	}
54 53
 	// sample for 100ms
55 54
 	time.Sleep(100 * time.Millisecond)
56 55
 	if lastCpu, err = s.getCpuUsage(d, path); err != nil {
57
-		return nil, err
56
+		return err
58 57
 	}
59 58
 	if lastSystem, err = s.getSystemCpuUsage(d); err != nil {
60
-		return nil, err
59
+		return err
61 60
 	}
62 61
 	usageSampleDuration := time.Since(startUsageTime)
63 62
 	if lastUsage, err = getCgroupParamInt(path, "cpuacct.usage"); err != nil {
64
-		return nil, err
63
+		return err
65 64
 	}
66 65
 
67 66
 	var (
... ...
@@ -74,15 +73,14 @@ func (s *cpuacctGroup) Stats(d *data) (map[string]int64, error) {
74 74
 	}
75 75
 	// NOTE: a percentage over 100% is valid for POSIX because that means the
76 76
 	// processes is using multiple cores
77
-	paramData["percentage"] = percentage
78
-
77
+	stats.CpuStats.CpuUsage.PercentUsage = percentage
79 78
 	// Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time.
80
-	paramData["usage"] = deltaUsage / usageSampleDuration.Nanoseconds()
81
-	return paramData, nil
79
+	stats.CpuStats.CpuUsage.CurrentUsage = deltaUsage / uint64(usageSampleDuration.Nanoseconds())
80
+	return nil
82 81
 }
83 82
 
84 83
 // TODO(vmarmol): Use cgroups stats.
85
-func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) {
84
+func (s *cpuacctGroup) getSystemCpuUsage(d *data) (uint64, error) {
86 85
 
87 86
 	f, err := os.Open("/proc/stat")
88 87
 	if err != nil {
... ...
@@ -99,9 +97,9 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) {
99 99
 				return 0, fmt.Errorf("invalid number of cpu fields")
100 100
 			}
101 101
 
102
-			var total int64
102
+			var total uint64
103 103
 			for _, i := range parts[1:8] {
104
-				v, err := strconv.ParseInt(i, 10, 64)
104
+				v, err := strconv.ParseUint(i, 10, 64)
105 105
 				if err != nil {
106 106
 					return 0.0, fmt.Errorf("Unable to convert value %s to int: %s", i, err)
107 107
 				}
... ...
@@ -115,8 +113,8 @@ func (s *cpuacctGroup) getSystemCpuUsage(d *data) (int64, error) {
115 115
 	return 0, fmt.Errorf("invalid stat format")
116 116
 }
117 117
 
118
-func (s *cpuacctGroup) getCpuUsage(d *data, path string) (int64, error) {
119
-	cpuTotal := int64(0)
118
+func (s *cpuacctGroup) getCpuUsage(d *data, path string) (uint64, error) {
119
+	cpuTotal := uint64(0)
120 120
 	f, err := os.Open(filepath.Join(path, "cpuacct.stat"))
121 121
 	if err != nil {
122 122
 		return 0, err
... ...
@@ -6,6 +6,8 @@ import (
6 6
 	"os"
7 7
 	"path/filepath"
8 8
 	"strconv"
9
+
10
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
9 11
 )
10 12
 
11 13
 type cpusetGroup struct {
... ...
@@ -38,8 +40,8 @@ func (s *cpusetGroup) Remove(d *data) error {
38 38
 	return removePath(d.path("cpuset"))
39 39
 }
40 40
 
41
-func (s *cpusetGroup) Stats(d *data) (map[string]int64, error) {
42
-	return nil, ErrNotSupportStat
41
+func (s *cpusetGroup) GetStats(d *data, stats *cgroups.Stats) error {
42
+	return nil
43 43
 }
44 44
 
45 45
 func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
... ...
@@ -1,5 +1,7 @@
1 1
 package fs
2 2
 
3
+import "github.com/dotcloud/docker/pkg/libcontainer/cgroups"
4
+
3 5
 type devicesGroup struct {
4 6
 }
5 7
 
... ...
@@ -55,6 +57,6 @@ func (s *devicesGroup) Remove(d *data) error {
55 55
 	return removePath(d.path("devices"))
56 56
 }
57 57
 
58
-func (s *devicesGroup) Stats(d *data) (map[string]int64, error) {
59
-	return nil, ErrNotSupportStat
58
+func (s *devicesGroup) GetStats(d *data, stats *cgroups.Stats) error {
59
+	return nil
60 60
 }
... ...
@@ -1,11 +1,8 @@
1 1
 package fs
2 2
 
3 3
 import (
4
-	"fmt"
5 4
 	"io/ioutil"
6
-	"os"
7 5
 	"path/filepath"
8
-	"strconv"
9 6
 	"strings"
10 7
 
11 8
 	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
... ...
@@ -35,39 +32,25 @@ func (s *freezerGroup) Remove(d *data) error {
35 35
 	return removePath(d.path("freezer"))
36 36
 }
37 37
 
38
-func (s *freezerGroup) Stats(d *data) (map[string]int64, error) {
39
-	var (
40
-		paramData = make(map[string]int64)
41
-		params    = []string{
42
-			"parent_freezing",
43
-			"self_freezing",
44
-			// comment out right now because this is string "state",
45
-		}
46
-	)
38
+func getFreezerFileData(path string) (string, error) {
39
+	data, err := ioutil.ReadFile(path)
40
+	return strings.TrimSuffix(string(data), "\n"), err
41
+}
47 42
 
43
+func (s *freezerGroup) GetStats(d *data, stats *cgroups.Stats) error {
48 44
 	path, err := d.path("freezer")
49 45
 	if err != nil {
50
-		return nil, err
46
+		return err
51 47
 	}
52
-
53
-	// TODO(vmarmol): This currently outputs nothing since the output is a string, fix.
54
-	for _, param := range params {
55
-		f, err := os.Open(filepath.Join(path, fmt.Sprintf("freezer.%s", param)))
56
-		if err != nil {
57
-			return nil, err
58
-		}
59
-		defer f.Close()
60
-
61
-		data, err := ioutil.ReadAll(f)
62
-		if err != nil {
63
-			return nil, err
64
-		}
65
-
66
-		v, err := strconv.ParseInt(strings.TrimSuffix(string(data), "\n"), 10, 64)
67
-		if err != nil {
68
-			return nil, err
69
-		}
70
-		paramData[param] = v
48
+	var data string
49
+	if data, err = getFreezerFileData(filepath.Join(path, "freezer.parent_freezing")); err != nil {
50
+		return err
51
+	}
52
+	stats.FreezerStats.ParentState = data
53
+	if data, err = getFreezerFileData(filepath.Join(path, "freezer.self_freezing")); err != nil {
54
+		return err
71 55
 	}
72
-	return paramData, nil
56
+	stats.FreezerStats.SelfState = data
57
+
58
+	return nil
73 59
 }
... ...
@@ -2,10 +2,11 @@ package fs
2 2
 
3 3
 import (
4 4
 	"bufio"
5
-	"fmt"
6 5
 	"os"
7 6
 	"path/filepath"
8 7
 	"strconv"
8
+
9
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
9 10
 )
10 11
 
11 12
 type memoryGroup struct {
... ...
@@ -50,17 +51,16 @@ func (s *memoryGroup) Remove(d *data) error {
50 50
 	return removePath(d.path("memory"))
51 51
 }
52 52
 
53
-func (s *memoryGroup) Stats(d *data) (map[string]int64, error) {
54
-	paramData := make(map[string]int64)
53
+func (s *memoryGroup) GetStats(d *data, stats *cgroups.Stats) error {
55 54
 	path, err := d.path("memory")
56 55
 	if err != nil {
57
-		return nil, err
56
+		return err
58 57
 	}
59 58
 
60 59
 	// Set stats from memory.stat.
61 60
 	statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
62 61
 	if err != nil {
63
-		return nil, err
62
+		return err
64 63
 	}
65 64
 	defer statsFile.Close()
66 65
 
... ...
@@ -68,23 +68,22 @@ func (s *memoryGroup) Stats(d *data) (map[string]int64, error) {
68 68
 	for sc.Scan() {
69 69
 		t, v, err := getCgroupParamKeyValue(sc.Text())
70 70
 		if err != nil {
71
-			return nil, err
71
+			return err
72 72
 		}
73
-		paramData[t] = v
73
+		stats.MemoryStats.Stats[t] = v
74 74
 	}
75 75
 
76 76
 	// Set memory usage and max historical usage.
77
-	params := []string{
78
-		"usage_in_bytes",
79
-		"max_usage_in_bytes",
77
+	value, err := getCgroupParamInt(path, "memory.usage_in_bytes")
78
+	if err != nil {
79
+		return err
80 80
 	}
81
-	for _, param := range params {
82
-		value, err := getCgroupParamInt(path, fmt.Sprintf("memory.%s", param))
83
-		if err != nil {
84
-			return nil, err
85
-		}
86
-		paramData[param] = value
81
+	stats.MemoryStats.Usage = value
82
+	value, err = getCgroupParamInt(path, "memory.max_usage_in_bytes")
83
+	if err != nil {
84
+		return err
87 85
 	}
86
+	stats.MemoryStats.MaxUsage = value
88 87
 
89
-	return paramData, nil
88
+	return nil
90 89
 }
... ...
@@ -2,6 +2,8 @@ package fs
2 2
 
3 3
 import (
4 4
 	"testing"
5
+
6
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
5 7
 )
6 8
 
7 9
 const (
... ...
@@ -21,12 +23,12 @@ func TestMemoryStats(t *testing.T) {
21 21
 	})
22 22
 
23 23
 	memory := &memoryGroup{}
24
-	stats, err := memory.Stats(helper.CgroupData)
24
+	err := memory.GetStats(helper.CgroupData, &actualStats)
25 25
 	if err != nil {
26 26
 		t.Fatal(err)
27 27
 	}
28
-	expectedStats := map[string]int64{"cache": 512, "rss": 1024, "usage_in_bytes": 2048, "max_usage_in_bytes": 4096}
29
-	expectStats(t, expectedStats, stats)
28
+	expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Stats: map[string]uint64{"cache": 512, "rss": 1024}}
29
+	expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats)
30 30
 }
31 31
 
32 32
 func TestMemoryStatsNoStatFile(t *testing.T) {
... ...
@@ -38,7 +40,7 @@ func TestMemoryStatsNoStatFile(t *testing.T) {
38 38
 	})
39 39
 
40 40
 	memory := &memoryGroup{}
41
-	_, err := memory.Stats(helper.CgroupData)
41
+	err := memory.GetStats(helper.CgroupData, &actualStats)
42 42
 	if err == nil {
43 43
 		t.Fatal("Expected failure")
44 44
 	}
... ...
@@ -53,7 +55,7 @@ func TestMemoryStatsNoUsageFile(t *testing.T) {
53 53
 	})
54 54
 
55 55
 	memory := &memoryGroup{}
56
-	_, err := memory.Stats(helper.CgroupData)
56
+	err := memory.GetStats(helper.CgroupData, &actualStats)
57 57
 	if err == nil {
58 58
 		t.Fatal("Expected failure")
59 59
 	}
... ...
@@ -68,7 +70,7 @@ func TestMemoryStatsNoMaxUsageFile(t *testing.T) {
68 68
 	})
69 69
 
70 70
 	memory := &memoryGroup{}
71
-	_, err := memory.Stats(helper.CgroupData)
71
+	err := memory.GetStats(helper.CgroupData, &actualStats)
72 72
 	if err == nil {
73 73
 		t.Fatal("Expected failure")
74 74
 	}
... ...
@@ -84,7 +86,7 @@ func TestMemoryStatsBadStatFile(t *testing.T) {
84 84
 	})
85 85
 
86 86
 	memory := &memoryGroup{}
87
-	_, err := memory.Stats(helper.CgroupData)
87
+	err := memory.GetStats(helper.CgroupData, &actualStats)
88 88
 	if err == nil {
89 89
 		t.Fatal("Expected failure")
90 90
 	}
... ...
@@ -100,7 +102,7 @@ func TestMemoryStatsBadUsageFile(t *testing.T) {
100 100
 	})
101 101
 
102 102
 	memory := &memoryGroup{}
103
-	_, err := memory.Stats(helper.CgroupData)
103
+	err := memory.GetStats(helper.CgroupData, &actualStats)
104 104
 	if err == nil {
105 105
 		t.Fatal("Expected failure")
106 106
 	}
... ...
@@ -116,7 +118,7 @@ func TestMemoryStatsBadMaxUsageFile(t *testing.T) {
116 116
 	})
117 117
 
118 118
 	memory := &memoryGroup{}
119
-	_, err := memory.Stats(helper.CgroupData)
119
+	err := memory.GetStats(helper.CgroupData, &actualStats)
120 120
 	if err == nil {
121 121
 		t.Fatal("Expected failure")
122 122
 	}
... ...
@@ -19,6 +19,6 @@ func (s *perfEventGroup) Remove(d *data) error {
19 19
 	return removePath(d.path("perf_event"))
20 20
 }
21 21
 
22
-func (s *perfEventGroup) Stats(d *data) (map[string]int64, error) {
23
-	return nil, ErrNotSupportStat
22
+func (s *perfEventGroup) GetStats(d *data, stats *cgroups.Stats) error {
23
+	return nil
24 24
 }
25 25
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package fs
1
+
2
+import (
3
+	"fmt"
4
+	"log"
5
+	"testing"
6
+
7
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
8
+)
9
+
10
+func blkioStatEntryEquals(expected, actual []cgroups.BlkioStatEntry) error {
11
+	if len(expected) != len(actual) {
12
+		return fmt.Errorf("blkioStatEntries length do not match")
13
+	}
14
+	for i, expValue := range expected {
15
+		actValue := actual[i]
16
+		if expValue != actValue {
17
+			return fmt.Errorf("Expected blkio stat entry %v but found %v", expValue, actValue)
18
+		}
19
+	}
20
+	return nil
21
+}
22
+
23
+func expectBlkioStatsEquals(t *testing.T, expected, actual cgroups.BlkioStats) {
24
+	if err := blkioStatEntryEquals(expected.IoServiceBytesRecursive, actual.IoServiceBytesRecursive); err != nil {
25
+		log.Printf("blkio IoServiceBytesRecursive do not match - %s\n", err)
26
+		t.Fail()
27
+	}
28
+
29
+	if err := blkioStatEntryEquals(expected.IoServicedRecursive, actual.IoServicedRecursive); err != nil {
30
+		log.Printf("blkio IoServicedRecursive do not match - %s\n", err)
31
+		t.Fail()
32
+	}
33
+
34
+	if err := blkioStatEntryEquals(expected.IoQueuedRecursive, actual.IoQueuedRecursive); err != nil {
35
+		log.Printf("blkio IoQueuedRecursive do not match - %s\n", err)
36
+		t.Fail()
37
+	}
38
+
39
+	if err := blkioStatEntryEquals(expected.SectorsRecursive, actual.SectorsRecursive); err != nil {
40
+		log.Printf("blkio SectorsRecursive do not match - %s\n", err)
41
+		t.Fail()
42
+	}
43
+}
44
+
45
+func expectThrottlingDataEquals(t *testing.T, expected, actual cgroups.ThrottlingData) {
46
+	if expected != actual {
47
+		log.Printf("Expected throttling data %v but found %v\n", expected, actual)
48
+		t.Fail()
49
+	}
50
+}
51
+
52
+func expectMemoryStatEquals(t *testing.T, expected, actual cgroups.MemoryStats) {
53
+	if expected.Usage != actual.Usage {
54
+		log.Printf("Expected memory usage %d but found %d\n", expected.Usage, actual.Usage)
55
+		t.Fail()
56
+	}
57
+	if expected.MaxUsage != actual.MaxUsage {
58
+		log.Printf("Expected memory max usage %d but found %d\n", expected.MaxUsage, actual.MaxUsage)
59
+		t.Fail()
60
+	}
61
+	for key, expValue := range expected.Stats {
62
+		actValue, ok := actual.Stats[key]
63
+		if !ok {
64
+			log.Printf("Expected memory stat key %s not found\n", key)
65
+			t.Fail()
66
+		}
67
+		if expValue != actValue {
68
+			log.Printf("Expected memory stat value %d but found %d\n", expValue, actValue)
69
+			t.Fail()
70
+		}
71
+	}
72
+}
... ...
@@ -8,7 +8,6 @@ package fs
8 8
 import (
9 9
 	"fmt"
10 10
 	"io/ioutil"
11
-	"log"
12 11
 	"os"
13 12
 	"testing"
14 13
 )
... ...
@@ -59,17 +58,3 @@ func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) {
59 59
 		}
60 60
 	}
61 61
 }
62
-
63
-// Expect the specified stats.
64
-func expectStats(t *testing.T, expected, actual map[string]int64) {
65
-	for stat, expectedValue := range expected {
66
-		actualValue, ok := actual[stat]
67
-		if !ok {
68
-			log.Printf("Expected stat %s to exist: %s", stat, actual)
69
-			t.Fail()
70
-		} else if actualValue != expectedValue {
71
-			log.Printf("Expected stats %s to have value %f but had %f instead", stat, expectedValue, actualValue)
72
-			t.Fail()
73
-		}
74
-	}
75
-}
... ...
@@ -16,11 +16,11 @@ var (
16 16
 
17 17
 // Parses a cgroup param and returns as name, value
18 18
 //  i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234
19
-func getCgroupParamKeyValue(t string) (string, int64, error) {
19
+func getCgroupParamKeyValue(t string) (string, uint64, error) {
20 20
 	parts := strings.Fields(t)
21 21
 	switch len(parts) {
22 22
 	case 2:
23
-		value, err := strconv.ParseInt(parts[1], 10, 64)
23
+		value, err := strconv.ParseUint(parts[1], 10, 64)
24 24
 		if err != nil {
25 25
 			return "", 0, fmt.Errorf("Unable to convert param value to int: %s", err)
26 26
 		}
... ...
@@ -31,10 +31,10 @@ func getCgroupParamKeyValue(t string) (string, int64, error) {
31 31
 }
32 32
 
33 33
 // Gets a single int64 value from the specified cgroup file.
34
-func getCgroupParamInt(cgroupPath, cgroupFile string) (int64, error) {
34
+func getCgroupParamInt(cgroupPath, cgroupFile string) (uint64, error) {
35 35
 	contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
36 36
 	if err != nil {
37
-		return -1, err
37
+		return 0, err
38 38
 	}
39
-	return strconv.ParseInt(strings.TrimSpace(string(contents)), 10, 64)
39
+	return strconv.ParseUint(strings.TrimSpace(string(contents)), 10, 64)
40 40
 }
... ...
@@ -2,18 +2,18 @@ package cgroups
2 2
 
3 3
 type ThrottlingData struct {
4 4
 	// Number of periods with throttling active
5
-	Periods int64 `json:"periods,omitempty"`
5
+	Periods uint64 `json:"periods,omitempty"`
6 6
 	// Number of periods when the container hit its throttling limit.
7
-	ThrottledPeriods int64 `json:"throttled_periods,omitempty"`
7
+	ThrottledPeriods uint64 `json:"throttled_periods,omitempty"`
8 8
 	// Aggregate time the container was throttled for in nanoseconds.
9
-	ThrottledTime int64 `json:"throttled_time,omitempty"`
9
+	ThrottledTime uint64 `json:"throttled_time,omitempty"`
10 10
 }
11 11
 
12 12
 type CpuUsage struct {
13 13
 	// percentage of available CPUs currently being used.
14
-	PercentUsage int64 `json:"percent_usage,omitempty"`
14
+	PercentUsage uint64 `json:"percent_usage,omitempty"`
15 15
 	// nanoseconds of cpu time consumed over the last 100 ms.
16
-	CurrentUsage int64 `json:"current_usage,omitempty"`
16
+	CurrentUsage uint64 `json:"current_usage,omitempty"`
17 17
 }
18 18
 
19 19
 type CpuStats struct {
... ...
@@ -23,26 +23,27 @@ type CpuStats struct {
23 23
 
24 24
 type MemoryStats struct {
25 25
 	// current res_counter usage for memory
26
-	Usage int64 `json:"usage,omitempty"`
26
+	Usage uint64 `json:"usage,omitempty"`
27 27
 	// maximum usage ever recorded.
28
-	MaxUsage int64 `json:"max_usage,omitempty"`
28
+	MaxUsage uint64 `json:"max_usage,omitempty"`
29 29
 	// TODO(vishh): Export these as stronger types.
30 30
 	// all the stats exported via memory.stat.
31
-	Stats map[string]int64 `json:"stats,omitempty"`
31
+	Stats map[string]uint64 `json:"stats,omitempty"`
32 32
 }
33 33
 
34 34
 type BlkioStatEntry struct {
35
-	Major int64  `json:"major,omitempty"`
36
-	Minor int64  `json:"minor,omitempty"`
35
+	Major uint64 `json:"major,omitempty"`
36
+	Minor uint64 `json:"minor,omitempty"`
37 37
 	Op    string `json:"op,omitempty"`
38
-	Value int64  `json:"value,omitempty"`
38
+	Value uint64 `json:"value,omitempty"`
39 39
 }
40 40
 
41
-type BlockioStats struct {
41
+type BlkioStats struct {
42 42
 	// number of bytes tranferred to and from the block device
43 43
 	IoServiceBytesRecursive []BlkioStatEntry `json:"io_service_bytes_recursive,omitempty"`
44 44
 	IoServicedRecursive     []BlkioStatEntry `json:"io_serviced_recusrive,omitempty"`
45 45
 	IoQueuedRecursive       []BlkioStatEntry `json:"io_queue_recursive,omitempty"`
46
+	SectorsRecursive        []BlkioStatEntry `json:"sectors_recursive,omitempty"`
46 47
 }
47 48
 
48 49
 // TODO(Vishh): Remove freezer from stats since it does not logically belong in stats.
... ...
@@ -54,6 +55,11 @@ type FreezerStats struct {
54 54
 type Stats struct {
55 55
 	CpuStats     CpuStats     `json:"cpu_stats,omitempty"`
56 56
 	MemoryStats  MemoryStats  `json:"memory_stats,omitempty"`
57
-	BlockioStats BlockioStats `json:"blockio_stats,omitempty"`
57
+	BlkioStats   BlkioStats   `json:"blkio_stats,omitempty"`
58 58
 	FreezerStats FreezerStats `json:"freezer_stats,omitempty"`
59 59
 }
60
+
61
+func NewStats() *Stats {
62
+	memoryStats := MemoryStats{Stats: make(map[string]uint64)}
63
+	return &Stats{MemoryStats: memoryStats}
64
+}