Browse code

Move cgroups package into libcontainer Docker-DCO-1.1-Signed-off-by: Michael Crosby <michael@crosbymichael.com> (github: crosbymichael)

Michael Crosby authored on 2014/05/15 07:21:44
Showing 49 changed files
... ...
@@ -15,8 +15,8 @@ import (
15 15
 	"time"
16 16
 
17 17
 	"github.com/dotcloud/docker/daemon/execdriver"
18
-	"github.com/dotcloud/docker/pkg/cgroups"
19 18
 	"github.com/dotcloud/docker/pkg/label"
19
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
20 20
 	"github.com/dotcloud/docker/pkg/system"
21 21
 	"github.com/dotcloud/docker/utils"
22 22
 )
... ...
@@ -13,8 +13,8 @@ import (
13 13
 
14 14
 	"github.com/dotcloud/docker/daemon/execdriver"
15 15
 	"github.com/dotcloud/docker/pkg/apparmor"
16
-	"github.com/dotcloud/docker/pkg/cgroups"
17 16
 	"github.com/dotcloud/docker/pkg/libcontainer"
17
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
18 18
 	"github.com/dotcloud/docker/pkg/libcontainer/nsinit"
19 19
 	"github.com/dotcloud/docker/pkg/system"
20 20
 )
... ...
@@ -2,8 +2,8 @@ package template
2 2
 
3 3
 import (
4 4
 	"github.com/dotcloud/docker/pkg/apparmor"
5
-	"github.com/dotcloud/docker/pkg/cgroups"
6 5
 	"github.com/dotcloud/docker/pkg/libcontainer"
6
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
7 7
 )
8 8
 
9 9
 // New returns the docker default configuration for libcontainer
10 10
deleted file mode 100644
... ...
@@ -1,3 +0,0 @@
1
-Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
2
-Rohit Jnagal <jnagal@google.com> (@rjnagal)
3
-Victor Marmol <vmarmol@google.com> (@vmarmol)
4 1
deleted file mode 100644
... ...
@@ -1,30 +0,0 @@
1
-package cgroups
2
-
3
-import (
4
-	"errors"
5
-)
6
-
7
-var (
8
-	ErrNotFound = errors.New("mountpoint not found")
9
-)
10
-
11
-type Cgroup struct {
12
-	Name   string `json:"name,omitempty"`
13
-	Parent string `json:"parent,omitempty"`
14
-
15
-	DeviceAccess      bool   `json:"device_access,omitempty"`      // name of parent cgroup or slice
16
-	Memory            int64  `json:"memory,omitempty"`             // Memory limit (in bytes)
17
-	MemoryReservation int64  `json:"memory_reservation,omitempty"` // Memory reservation or soft_limit (in bytes)
18
-	MemorySwap        int64  `json:"memory_swap,omitempty"`        // Total memory usage (memory + swap); set `-1' to disable swap
19
-	CpuShares         int64  `json:"cpu_shares,omitempty"`         // CPU shares (relative weight vs. other containers)
20
-	CpuQuota          int64  `json:"cpu_quota,omitempty"`          // CPU hardcap limit (in usecs). Allowed cpu time in a given period.
21
-	CpuPeriod         int64  `json:"cpu_period,omitempty"`         // CPU period to be used for hardcapping (in usecs). 0 to use system default.
22
-	CpusetCpus        string `json:"cpuset_cpus,omitempty"`        // CPU to use
23
-	Freezer           string `json:"freezer,omitempty"`            // set the freeze value for the process
24
-
25
-	Slice string `json:"slice,omitempty"` // Parent slice to use for systemd
26
-}
27
-
28
-type ActiveCgroup interface {
29
-	Cleanup() error
30
-}
31 1
deleted file mode 100644
... ...
@@ -1,27 +0,0 @@
1
-package cgroups
2
-
3
-import (
4
-	"bytes"
5
-	"testing"
6
-)
7
-
8
-const (
9
-	cgroupsContents = `11:hugetlb:/
10
-10:perf_event:/
11
-9:blkio:/
12
-8:net_cls:/
13
-7:freezer:/
14
-6:devices:/
15
-5:memory:/
16
-4:cpuacct,cpu:/
17
-3:cpuset:/
18
-2:name=systemd:/user.slice/user-1000.slice/session-16.scope`
19
-)
20
-
21
-func TestParseCgroups(t *testing.T) {
22
-	r := bytes.NewBuffer([]byte(cgroupsContents))
23
-	_, err := parseCgroupFile("blkio", r)
24
-	if err != nil {
25
-		t.Fatal(err)
26
-	}
27
-}
28 1
deleted file mode 100644
... ...
@@ -1,155 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"fmt"
5
-	"io/ioutil"
6
-	"os"
7
-	"path/filepath"
8
-	"strconv"
9
-
10
-	"github.com/dotcloud/docker/pkg/cgroups"
11
-)
12
-
13
-var (
14
-	subsystems = map[string]subsystem{
15
-		"devices":    &devicesGroup{},
16
-		"memory":     &memoryGroup{},
17
-		"cpu":        &cpuGroup{},
18
-		"cpuset":     &cpusetGroup{},
19
-		"cpuacct":    &cpuacctGroup{},
20
-		"blkio":      &blkioGroup{},
21
-		"perf_event": &perfEventGroup{},
22
-		"freezer":    &freezerGroup{},
23
-	}
24
-)
25
-
26
-type subsystem interface {
27
-	Set(*data) error
28
-	Remove(*data) error
29
-	Stats(*data) (map[string]float64, error)
30
-}
31
-
32
-type data struct {
33
-	root   string
34
-	cgroup string
35
-	c      *cgroups.Cgroup
36
-	pid    int
37
-}
38
-
39
-func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
40
-	// We have two implementation of cgroups support, one is based on
41
-	// systemd and the dbus api, and one is based on raw cgroup fs operations
42
-	// following the pre-single-writer model docs at:
43
-	// http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/
44
-	//
45
-	// we can pick any subsystem to find the root
46
-
47
-	cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu")
48
-	if err != nil {
49
-		return nil, err
50
-	}
51
-	cgroupRoot = filepath.Dir(cgroupRoot)
52
-
53
-	if _, err := os.Stat(cgroupRoot); err != nil {
54
-		return nil, fmt.Errorf("cgroups fs not found")
55
-	}
56
-
57
-	cgroup := c.Name
58
-	if c.Parent != "" {
59
-		cgroup = filepath.Join(c.Parent, cgroup)
60
-	}
61
-
62
-	d := &data{
63
-		root:   cgroupRoot,
64
-		cgroup: cgroup,
65
-		c:      c,
66
-		pid:    pid,
67
-	}
68
-	for _, sys := range subsystems {
69
-		if err := sys.Set(d); err != nil {
70
-			d.Cleanup()
71
-			return nil, err
72
-		}
73
-	}
74
-	return d, nil
75
-}
76
-
77
-func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]float64, error) {
78
-	cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu")
79
-	if err != nil {
80
-		return nil, err
81
-	}
82
-	cgroupRoot = filepath.Dir(cgroupRoot)
83
-
84
-	if _, err := os.Stat(cgroupRoot); err != nil {
85
-		return nil, fmt.Errorf("cgroups fs not found")
86
-	}
87
-
88
-	cgroup := c.Name
89
-	if c.Parent != "" {
90
-		cgroup = filepath.Join(c.Parent, cgroup)
91
-	}
92
-
93
-	d := &data{
94
-		root:   cgroupRoot,
95
-		cgroup: cgroup,
96
-		c:      c,
97
-		pid:    pid,
98
-	}
99
-	sys, exists := subsystems[subsystem]
100
-	if !exists {
101
-		return nil, fmt.Errorf("subsystem %s does not exist", subsystem)
102
-	}
103
-	return sys.Stats(d)
104
-}
105
-
106
-func (raw *data) parent(subsystem string) (string, error) {
107
-	initPath, err := cgroups.GetInitCgroupDir(subsystem)
108
-	if err != nil {
109
-		return "", err
110
-	}
111
-	return filepath.Join(raw.root, subsystem, initPath), nil
112
-}
113
-
114
-func (raw *data) path(subsystem string) (string, error) {
115
-	parent, err := raw.parent(subsystem)
116
-	if err != nil {
117
-		return "", err
118
-	}
119
-	return filepath.Join(parent, raw.cgroup), nil
120
-}
121
-
122
-func (raw *data) join(subsystem string) (string, error) {
123
-	path, err := raw.path(subsystem)
124
-	if err != nil {
125
-		return "", err
126
-	}
127
-	if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
128
-		return "", err
129
-	}
130
-	if err := writeFile(path, "cgroup.procs", strconv.Itoa(raw.pid)); err != nil {
131
-		return "", err
132
-	}
133
-	return path, nil
134
-}
135
-
136
-func (raw *data) Cleanup() error {
137
-	for _, sys := range subsystems {
138
-		sys.Remove(raw)
139
-	}
140
-	return nil
141
-}
142
-
143
-func writeFile(dir, file, data string) error {
144
-	return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
145
-}
146
-
147
-func removePath(p string, err error) error {
148
-	if err != nil {
149
-		return err
150
-	}
151
-	if p != "" {
152
-		return os.RemoveAll(p)
153
-	}
154
-	return nil
155
-}
156 1
deleted file mode 100644
... ...
@@ -1,121 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"bufio"
5
-	"fmt"
6
-	"io/ioutil"
7
-	"os"
8
-	"path/filepath"
9
-	"strconv"
10
-	"strings"
11
-
12
-	"github.com/dotcloud/docker/pkg/cgroups"
13
-)
14
-
15
-type blkioGroup struct {
16
-}
17
-
18
-func (s *blkioGroup) Set(d *data) error {
19
-	// we just want to join this group even though we don't set anything
20
-	if _, err := d.join("blkio"); err != nil && err != cgroups.ErrNotFound {
21
-		return err
22
-	}
23
-	return nil
24
-}
25
-
26
-func (s *blkioGroup) Remove(d *data) error {
27
-	return removePath(d.path("blkio"))
28
-}
29
-
30
-/*
31
-examples:
32
-
33
-    blkio.sectors
34
-    8:0 6792
35
-
36
-    blkio.io_service_bytes
37
-    8:0 Read 1282048
38
-    8:0 Write 2195456
39
-    8:0 Sync 2195456
40
-    8:0 Async 1282048
41
-    8:0 Total 3477504
42
-    Total 3477504
43
-
44
-    blkio.io_serviced
45
-    8:0 Read 124
46
-    8:0 Write 104
47
-    8:0 Sync 104
48
-    8:0 Async 124
49
-    8:0 Total 228
50
-    Total 228
51
-
52
-    blkio.io_queued
53
-    8:0 Read 0
54
-    8:0 Write 0
55
-    8:0 Sync 0
56
-    8:0 Async 0
57
-    8:0 Total 0
58
-    Total 0
59
-*/
60
-func (s *blkioGroup) Stats(d *data) (map[string]float64, error) {
61
-	var (
62
-		paramData = make(map[string]float64)
63
-		params    = []string{
64
-			"io_service_bytes_recursive",
65
-			"io_serviced_recursive",
66
-			"io_queued_recursive",
67
-		}
68
-	)
69
-
70
-	path, err := d.path("blkio")
71
-	if err != nil {
72
-		return nil, err
73
-	}
74
-
75
-	k, v, err := s.getSectors(path)
76
-	if err != nil {
77
-		return nil, err
78
-	}
79
-	paramData[fmt.Sprintf("blkio.sectors_recursive:%s", k)] = v
80
-
81
-	for _, param := range params {
82
-		f, err := os.Open(filepath.Join(path, fmt.Sprintf("blkio.%s", param)))
83
-		if err != nil {
84
-			return nil, err
85
-		}
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.ParseFloat(fields[2], 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
-			}
104
-		}
105
-	}
106
-	return paramData, nil
107
-}
108
-
109
-func (s *blkioGroup) getSectors(path string) (string, float64, error) {
110
-	f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive"))
111
-	if err != nil {
112
-		return "", 0, err
113
-	}
114
-	defer f.Close()
115
-
116
-	data, err := ioutil.ReadAll(f)
117
-	if err != nil {
118
-		return "", 0, err
119
-	}
120
-	return getCgroupParamKeyValue(string(data))
121
-}
122 1
deleted file mode 100644
... ...
@@ -1,169 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"testing"
5
-)
6
-
7
-const (
8
-	sectorsRecursiveContents      = `8:0 1024`
9
-	serviceBytesRecursiveContents = `8:0 Read 100
10
-8:0 Write 400
11
-8:0 Sync 200
12
-8:0 Async 300
13
-8:0 Total 500
14
-Total 500`
15
-	servicedRecursiveContents = `8:0 Read 10
16
-8:0 Write 40
17
-8:0 Sync 20
18
-8:0 Async 30
19
-8:0 Total 50
20
-Total 50`
21
-	queuedRecursiveContents = `8:0 Read 1
22
-8:0 Write 4
23
-8:0 Sync 2
24
-8:0 Async 3
25
-8:0 Total 5
26
-Total 5`
27
-)
28
-
29
-func TestBlkioStats(t *testing.T) {
30
-	helper := NewCgroupTestUtil("blkio", t)
31
-	defer helper.cleanup()
32
-	helper.writeFileContents(map[string]string{
33
-		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
34
-		"blkio.io_serviced_recursive":      servicedRecursiveContents,
35
-		"blkio.io_queued_recursive":        queuedRecursiveContents,
36
-		"blkio.sectors_recursive":          sectorsRecursiveContents,
37
-	})
38
-
39
-	blkio := &blkioGroup{}
40
-	stats, err := blkio.Stats(helper.CgroupData)
41
-	if err != nil {
42
-		t.Fatal(err)
43
-	}
44
-
45
-	// Verify expected stats.
46
-	expectedStats := map[string]float64{
47
-		"blkio.sectors_recursive:8:0": 1024.0,
48
-
49
-		// Serviced bytes.
50
-		"io_service_bytes_recursive:8:0:Read":  100.0,
51
-		"io_service_bytes_recursive:8:0:Write": 400.0,
52
-		"io_service_bytes_recursive:8:0:Sync":  200.0,
53
-		"io_service_bytes_recursive:8:0:Async": 300.0,
54
-		"io_service_bytes_recursive:8:0:Total": 500.0,
55
-
56
-		// Serviced requests.
57
-		"io_serviced_recursive:8:0:Read":  10.0,
58
-		"io_serviced_recursive:8:0:Write": 40.0,
59
-		"io_serviced_recursive:8:0:Sync":  20.0,
60
-		"io_serviced_recursive:8:0:Async": 30.0,
61
-		"io_serviced_recursive:8:0:Total": 50.0,
62
-
63
-		// Queued requests.
64
-		"io_queued_recursive:8:0:Read":  1.0,
65
-		"io_queued_recursive:8:0:Write": 4.0,
66
-		"io_queued_recursive:8:0:Sync":  2.0,
67
-		"io_queued_recursive:8:0:Async": 3.0,
68
-		"io_queued_recursive:8:0:Total": 5.0,
69
-	}
70
-	expectStats(t, expectedStats, stats)
71
-}
72
-
73
-func TestBlkioStatsNoSectorsFile(t *testing.T) {
74
-	helper := NewCgroupTestUtil("blkio", t)
75
-	defer helper.cleanup()
76
-	helper.writeFileContents(map[string]string{
77
-		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
78
-		"blkio.io_serviced_recursive":      servicedRecursiveContents,
79
-		"blkio.io_queued_recursive":        queuedRecursiveContents,
80
-	})
81
-
82
-	blkio := &blkioGroup{}
83
-	_, err := blkio.Stats(helper.CgroupData)
84
-	if err == nil {
85
-		t.Fatal("Expected to fail, but did not")
86
-	}
87
-}
88
-
89
-func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
90
-	helper := NewCgroupTestUtil("blkio", t)
91
-	defer helper.cleanup()
92
-	helper.writeFileContents(map[string]string{
93
-		"blkio.io_serviced_recursive": servicedRecursiveContents,
94
-		"blkio.io_queued_recursive":   queuedRecursiveContents,
95
-		"blkio.sectors_recursive":     sectorsRecursiveContents,
96
-	})
97
-
98
-	blkio := &blkioGroup{}
99
-	_, err := blkio.Stats(helper.CgroupData)
100
-	if err == nil {
101
-		t.Fatal("Expected to fail, but did not")
102
-	}
103
-}
104
-
105
-func TestBlkioStatsNoServicedFile(t *testing.T) {
106
-	helper := NewCgroupTestUtil("blkio", t)
107
-	defer helper.cleanup()
108
-	helper.writeFileContents(map[string]string{
109
-		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
110
-		"blkio.io_queued_recursive":        queuedRecursiveContents,
111
-		"blkio.sectors_recursive":          sectorsRecursiveContents,
112
-	})
113
-
114
-	blkio := &blkioGroup{}
115
-	_, err := blkio.Stats(helper.CgroupData)
116
-	if err == nil {
117
-		t.Fatal("Expected to fail, but did not")
118
-	}
119
-}
120
-
121
-func TestBlkioStatsNoQueuedFile(t *testing.T) {
122
-	helper := NewCgroupTestUtil("blkio", t)
123
-	defer helper.cleanup()
124
-	helper.writeFileContents(map[string]string{
125
-		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
126
-		"blkio.io_serviced_recursive":      servicedRecursiveContents,
127
-		"blkio.sectors_recursive":          sectorsRecursiveContents,
128
-	})
129
-
130
-	blkio := &blkioGroup{}
131
-	_, err := blkio.Stats(helper.CgroupData)
132
-	if err == nil {
133
-		t.Fatal("Expected to fail, but did not")
134
-	}
135
-}
136
-
137
-func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
138
-	helper := NewCgroupTestUtil("blkio", t)
139
-	defer helper.cleanup()
140
-	helper.writeFileContents(map[string]string{
141
-		"blkio.io_service_bytes_recursive": "8:0 Read 100 100",
142
-		"blkio.io_serviced_recursive":      servicedRecursiveContents,
143
-		"blkio.io_queued_recursive":        queuedRecursiveContents,
144
-		"blkio.sectors_recursive":          sectorsRecursiveContents,
145
-	})
146
-
147
-	blkio := &blkioGroup{}
148
-	_, err := blkio.Stats(helper.CgroupData)
149
-	if err == nil {
150
-		t.Fatal("Expected to fail, but did not")
151
-	}
152
-}
153
-
154
-func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
155
-	helper := NewCgroupTestUtil("blkio", t)
156
-	defer helper.cleanup()
157
-	helper.writeFileContents(map[string]string{
158
-		"blkio.io_service_bytes_recursive": "8:0 Read Write",
159
-		"blkio.io_serviced_recursive":      servicedRecursiveContents,
160
-		"blkio.io_queued_recursive":        queuedRecursiveContents,
161
-		"blkio.sectors_recursive":          sectorsRecursiveContents,
162
-	})
163
-
164
-	blkio := &blkioGroup{}
165
-	_, err := blkio.Stats(helper.CgroupData)
166
-	if err == nil {
167
-		t.Fatal("Expected to fail, but did not")
168
-	}
169
-}
170 1
deleted file mode 100644
... ...
@@ -1,64 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"bufio"
5
-	"os"
6
-	"path/filepath"
7
-	"strconv"
8
-)
9
-
10
-type cpuGroup struct {
11
-}
12
-
13
-func (s *cpuGroup) Set(d *data) error {
14
-	// We always want to join the cpu group, to allow fair cpu scheduling
15
-	// on a container basis
16
-	dir, err := d.join("cpu")
17
-	if err != nil {
18
-		return err
19
-	}
20
-	if d.c.CpuShares != 0 {
21
-		if err := writeFile(dir, "cpu.shares", strconv.FormatInt(d.c.CpuShares, 10)); err != nil {
22
-			return err
23
-		}
24
-	}
25
-	if d.c.CpuPeriod != 0 {
26
-		if err := writeFile(dir, "cpu.cfs_period_us", strconv.FormatInt(d.c.CpuPeriod, 10)); err != nil {
27
-			return err
28
-		}
29
-	}
30
-	if d.c.CpuQuota != 0 {
31
-		if err := writeFile(dir, "cpu.cfs_quota_us", strconv.FormatInt(d.c.CpuQuota, 10)); err != nil {
32
-			return err
33
-		}
34
-	}
35
-	return nil
36
-}
37
-
38
-func (s *cpuGroup) Remove(d *data) error {
39
-	return removePath(d.path("cpu"))
40
-}
41
-
42
-func (s *cpuGroup) Stats(d *data) (map[string]float64, error) {
43
-	paramData := make(map[string]float64)
44
-	path, err := d.path("cpu")
45
-	if err != nil {
46
-		return nil, err
47
-	}
48
-
49
-	f, err := os.Open(filepath.Join(path, "cpu.stat"))
50
-	if err != nil {
51
-		return nil, err
52
-	}
53
-	defer f.Close()
54
-
55
-	sc := bufio.NewScanner(f)
56
-	for sc.Scan() {
57
-		t, v, err := getCgroupParamKeyValue(sc.Text())
58
-		if err != nil {
59
-			return nil, err
60
-		}
61
-		paramData[t] = v
62
-	}
63
-	return paramData, nil
64
-}
65 1
deleted file mode 100644
... ...
@@ -1,57 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"testing"
5
-)
6
-
7
-func TestCpuStats(t *testing.T) {
8
-	helper := NewCgroupTestUtil("cpu", t)
9
-	defer helper.cleanup()
10
-	cpuStatContent := `nr_periods 2000
11
-	nr_throttled 200
12
-	throttled_time 42424242424`
13
-	helper.writeFileContents(map[string]string{
14
-		"cpu.stat": cpuStatContent,
15
-	})
16
-
17
-	cpu := &cpuGroup{}
18
-	stats, err := cpu.Stats(helper.CgroupData)
19
-	if err != nil {
20
-		t.Fatal(err)
21
-	}
22
-
23
-	expected_stats := map[string]float64{
24
-		"nr_periods":     2000.0,
25
-		"nr_throttled":   200.0,
26
-		"throttled_time": 42424242424.0,
27
-	}
28
-	expectStats(t, expected_stats, stats)
29
-}
30
-
31
-func TestNoCpuStatFile(t *testing.T) {
32
-	helper := NewCgroupTestUtil("cpu", t)
33
-	defer helper.cleanup()
34
-
35
-	cpu := &cpuGroup{}
36
-	_, err := cpu.Stats(helper.CgroupData)
37
-	if err == nil {
38
-		t.Fatal("Expected to fail, but did not.")
39
-	}
40
-}
41
-
42
-func TestInvalidCpuStat(t *testing.T) {
43
-	helper := NewCgroupTestUtil("cpu", t)
44
-	defer helper.cleanup()
45
-	cpuStatContent := `nr_periods 2000
46
-	nr_throttled 200
47
-	throttled_time fortytwo`
48
-	helper.writeFileContents(map[string]string{
49
-		"cpu.stat": cpuStatContent,
50
-	})
51
-
52
-	cpu := &cpuGroup{}
53
-	_, err := cpu.Stats(helper.CgroupData)
54
-	if err == nil {
55
-		t.Fatal("Expected failed stat parsing.")
56
-	}
57
-}
58 1
deleted file mode 100644
... ...
@@ -1,143 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"bufio"
5
-	"fmt"
6
-	"os"
7
-	"path/filepath"
8
-	"runtime"
9
-	"strconv"
10
-	"strings"
11
-	"time"
12
-
13
-	"github.com/dotcloud/docker/pkg/cgroups"
14
-	"github.com/dotcloud/docker/pkg/system"
15
-)
16
-
17
-var (
18
-	cpuCount   = float64(runtime.NumCPU())
19
-	clockTicks = float64(system.GetClockTicks())
20
-)
21
-
22
-type cpuacctGroup struct {
23
-}
24
-
25
-func (s *cpuacctGroup) Set(d *data) error {
26
-	// we just want to join this group even though we don't set anything
27
-	if _, err := d.join("cpuacct"); err != nil && err != cgroups.ErrNotFound {
28
-		return err
29
-	}
30
-	return nil
31
-}
32
-
33
-func (s *cpuacctGroup) Remove(d *data) error {
34
-	return removePath(d.path("cpuacct"))
35
-}
36
-
37
-func (s *cpuacctGroup) Stats(d *data) (map[string]float64, error) {
38
-	var (
39
-		startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage float64
40
-		percentage                                                        float64
41
-		paramData                                                         = make(map[string]float64)
42
-	)
43
-	path, err := d.path("cpuacct")
44
-	if startCpu, err = s.getCpuUsage(d, path); err != nil {
45
-		return nil, err
46
-	}
47
-	if startSystem, err = s.getSystemCpuUsage(d); err != nil {
48
-		return nil, err
49
-	}
50
-	startUsageTime := time.Now()
51
-	if startUsage, err = getCgroupParamFloat64(path, "cpuacct.usage"); err != nil {
52
-		return nil, err
53
-	}
54
-	// sample for 100ms
55
-	time.Sleep(100 * time.Millisecond)
56
-	if lastCpu, err = s.getCpuUsage(d, path); err != nil {
57
-		return nil, err
58
-	}
59
-	if lastSystem, err = s.getSystemCpuUsage(d); err != nil {
60
-		return nil, err
61
-	}
62
-	usageSampleDuration := time.Since(startUsageTime)
63
-	if lastUsage, err = getCgroupParamFloat64(path, "cpuacct.usage"); err != nil {
64
-		return nil, err
65
-	}
66
-
67
-	var (
68
-		deltaProc   = lastCpu - startCpu
69
-		deltaSystem = lastSystem - startSystem
70
-		deltaUsage  = lastUsage - startUsage
71
-	)
72
-	if deltaSystem > 0.0 {
73
-		percentage = ((deltaProc / deltaSystem) * clockTicks) * cpuCount
74
-	}
75
-	// NOTE: a percentage over 100% is valid for POSIX because that means the
76
-	// processes is using multiple cores
77
-	paramData["percentage"] = percentage
78
-
79
-	// Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time.
80
-	paramData["usage"] = deltaUsage / float64(usageSampleDuration.Nanoseconds())
81
-	return paramData, nil
82
-}
83
-
84
-func (s *cpuacctGroup) getProcStarttime(d *data) (float64, error) {
85
-	rawStart, err := system.GetProcessStartTime(d.pid)
86
-	if err != nil {
87
-		return 0, err
88
-	}
89
-	return strconv.ParseFloat(rawStart, 64)
90
-}
91
-
92
-func (s *cpuacctGroup) getSystemCpuUsage(d *data) (float64, error) {
93
-
94
-	f, err := os.Open("/proc/stat")
95
-	if err != nil {
96
-		return 0, err
97
-	}
98
-	defer f.Close()
99
-
100
-	sc := bufio.NewScanner(f)
101
-	for sc.Scan() {
102
-		parts := strings.Fields(sc.Text())
103
-		switch parts[0] {
104
-		case "cpu":
105
-			if len(parts) < 8 {
106
-				return 0, fmt.Errorf("invalid number of cpu fields")
107
-			}
108
-
109
-			var total float64
110
-			for _, i := range parts[1:8] {
111
-				v, err := strconv.ParseFloat(i, 64)
112
-				if err != nil {
113
-					return 0.0, fmt.Errorf("Unable to convert value %s to float: %s", i, err)
114
-				}
115
-				total += v
116
-			}
117
-			return total, nil
118
-		default:
119
-			continue
120
-		}
121
-	}
122
-	return 0, fmt.Errorf("invalid stat format")
123
-}
124
-
125
-func (s *cpuacctGroup) getCpuUsage(d *data, path string) (float64, error) {
126
-	cpuTotal := 0.0
127
-	f, err := os.Open(filepath.Join(path, "cpuacct.stat"))
128
-	if err != nil {
129
-		return 0.0, err
130
-	}
131
-	defer f.Close()
132
-
133
-	sc := bufio.NewScanner(f)
134
-	for sc.Scan() {
135
-		_, v, err := getCgroupParamKeyValue(sc.Text())
136
-		if err != nil {
137
-			return 0.0, err
138
-		}
139
-		// set the raw data in map
140
-		cpuTotal += v
141
-	}
142
-	return cpuTotal, nil
143
-}
144 1
deleted file mode 100644
... ...
@@ -1,108 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"bytes"
5
-	"io/ioutil"
6
-	"os"
7
-	"path/filepath"
8
-	"strconv"
9
-)
10
-
11
-type cpusetGroup struct {
12
-}
13
-
14
-func (s *cpusetGroup) Set(d *data) error {
15
-	// we don't want to join this cgroup unless it is specified
16
-	if d.c.CpusetCpus != "" {
17
-		dir, err := d.path("cpuset")
18
-		if err != nil {
19
-			return err
20
-		}
21
-		if err := s.ensureParent(dir); err != nil {
22
-			return err
23
-		}
24
-
25
-		// because we are not using d.join we need to place the pid into the procs file
26
-		// unlike the other subsystems
27
-		if err := writeFile(dir, "cgroup.procs", strconv.Itoa(d.pid)); err != nil {
28
-			return err
29
-		}
30
-		if err := writeFile(dir, "cpuset.cpus", d.c.CpusetCpus); err != nil {
31
-			return err
32
-		}
33
-	}
34
-	return nil
35
-}
36
-
37
-func (s *cpusetGroup) Remove(d *data) error {
38
-	return removePath(d.path("cpuset"))
39
-}
40
-
41
-func (s *cpusetGroup) Stats(d *data) (map[string]float64, error) {
42
-	return nil, ErrNotSupportStat
43
-}
44
-
45
-func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
46
-	if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
47
-		return
48
-	}
49
-	if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil {
50
-		return
51
-	}
52
-	return cpus, mems, nil
53
-}
54
-
55
-// ensureParent ensures that the parent directory of current is created
56
-// with the proper cpus and mems files copied from it's parent if the values
57
-// are a file with a new line char
58
-func (s *cpusetGroup) ensureParent(current string) error {
59
-	parent := filepath.Dir(current)
60
-
61
-	if _, err := os.Stat(parent); err != nil {
62
-		if !os.IsNotExist(err) {
63
-			return err
64
-		}
65
-
66
-		if err := s.ensureParent(parent); err != nil {
67
-			return err
68
-		}
69
-	}
70
-
71
-	if err := os.MkdirAll(current, 0755); err != nil && !os.IsExist(err) {
72
-		return err
73
-	}
74
-	return s.copyIfNeeded(current, parent)
75
-}
76
-
77
-// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
78
-// directory to the current directory if the file's contents are 0
79
-func (s *cpusetGroup) copyIfNeeded(current, parent string) error {
80
-	var (
81
-		err                      error
82
-		currentCpus, currentMems []byte
83
-		parentCpus, parentMems   []byte
84
-	)
85
-
86
-	if currentCpus, currentMems, err = s.getSubsystemSettings(current); err != nil {
87
-		return err
88
-	}
89
-	if parentCpus, parentMems, err = s.getSubsystemSettings(parent); err != nil {
90
-		return err
91
-	}
92
-
93
-	if s.isEmpty(currentCpus) {
94
-		if err := writeFile(current, "cpuset.cpus", string(parentCpus)); err != nil {
95
-			return err
96
-		}
97
-	}
98
-	if s.isEmpty(currentMems) {
99
-		if err := writeFile(current, "cpuset.mems", string(parentMems)); err != nil {
100
-			return err
101
-		}
102
-	}
103
-	return nil
104
-}
105
-
106
-func (s *cpusetGroup) isEmpty(b []byte) bool {
107
-	return len(bytes.Trim(b, "\n")) == 0
108
-}
109 1
deleted file mode 100644
... ...
@@ -1,69 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"os"
5
-)
6
-
7
-type devicesGroup struct {
8
-}
9
-
10
-func (s *devicesGroup) Set(d *data) error {
11
-	dir, err := d.join("devices")
12
-	if err != nil {
13
-		return err
14
-	}
15
-	defer func() {
16
-		if err != nil {
17
-			os.RemoveAll(dir)
18
-		}
19
-	}()
20
-
21
-	if !d.c.DeviceAccess {
22
-		if err := writeFile(dir, "devices.deny", "a"); err != nil {
23
-			return err
24
-		}
25
-
26
-		allow := []string{
27
-			// allow mknod for any device
28
-			"c *:* m",
29
-			"b *:* m",
30
-
31
-			// /dev/null, zero, full
32
-			"c 1:3 rwm",
33
-			"c 1:5 rwm",
34
-			"c 1:7 rwm",
35
-
36
-			// consoles
37
-			"c 5:1 rwm",
38
-			"c 5:0 rwm",
39
-			"c 4:0 rwm",
40
-			"c 4:1 rwm",
41
-
42
-			// /dev/urandom,/dev/random
43
-			"c 1:9 rwm",
44
-			"c 1:8 rwm",
45
-
46
-			// /dev/pts/ - pts namespaces are "coming soon"
47
-			"c 136:* rwm",
48
-			"c 5:2 rwm",
49
-
50
-			// tuntap
51
-			"c 10:200 rwm",
52
-		}
53
-
54
-		for _, val := range allow {
55
-			if err := writeFile(dir, "devices.allow", val); err != nil {
56
-				return err
57
-			}
58
-		}
59
-	}
60
-	return nil
61
-}
62
-
63
-func (s *devicesGroup) Remove(d *data) error {
64
-	return removePath(d.path("devices"))
65
-}
66
-
67
-func (s *devicesGroup) Stats(d *data) (map[string]float64, error) {
68
-	return nil, ErrNotSupportStat
69
-}
70 1
deleted file mode 100644
... ...
@@ -1,72 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"fmt"
5
-	"io/ioutil"
6
-	"os"
7
-	"path/filepath"
8
-	"strconv"
9
-	"strings"
10
-
11
-	"github.com/dotcloud/docker/pkg/cgroups"
12
-)
13
-
14
-type freezerGroup struct {
15
-}
16
-
17
-func (s *freezerGroup) Set(d *data) error {
18
-	dir, err := d.join("freezer")
19
-	if err != nil {
20
-		if err != cgroups.ErrNotFound {
21
-			return err
22
-		}
23
-		return nil
24
-	}
25
-
26
-	if d.c.Freezer != "" {
27
-		if err := writeFile(dir, "freezer.state", d.c.Freezer); err != nil {
28
-			return err
29
-		}
30
-	}
31
-	return nil
32
-}
33
-
34
-func (s *freezerGroup) Remove(d *data) error {
35
-	return removePath(d.path("freezer"))
36
-}
37
-
38
-func (s *freezerGroup) Stats(d *data) (map[string]float64, error) {
39
-	var (
40
-		paramData = make(map[string]float64)
41
-		params    = []string{
42
-			"parent_freezing",
43
-			"self_freezing",
44
-			// comment out right now because this is string "state",
45
-		}
46
-	)
47
-
48
-	path, err := d.path("freezer")
49
-	if err != nil {
50
-		return nil, err
51
-	}
52
-
53
-	for _, param := range params {
54
-		f, err := os.Open(filepath.Join(path, fmt.Sprintf("freezer.%s", param)))
55
-		if err != nil {
56
-			return nil, err
57
-		}
58
-		defer f.Close()
59
-
60
-		data, err := ioutil.ReadAll(f)
61
-		if err != nil {
62
-			return nil, err
63
-		}
64
-
65
-		v, err := strconv.ParseFloat(strings.TrimSuffix(string(data), "\n"), 64)
66
-		if err != nil {
67
-			return nil, err
68
-		}
69
-		paramData[param] = v
70
-	}
71
-	return paramData, nil
72
-}
73 1
deleted file mode 100644
... ...
@@ -1,90 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"bufio"
5
-	"fmt"
6
-	"os"
7
-	"path/filepath"
8
-	"strconv"
9
-)
10
-
11
-type memoryGroup struct {
12
-}
13
-
14
-func (s *memoryGroup) Set(d *data) error {
15
-	dir, err := d.join("memory")
16
-	// only return an error for memory if it was not specified
17
-	if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
18
-		return err
19
-	}
20
-	defer func() {
21
-		if err != nil {
22
-			os.RemoveAll(dir)
23
-		}
24
-	}()
25
-
26
-	// Only set values if some config was specified.
27
-	if d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0 {
28
-		if d.c.Memory != 0 {
29
-			if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(d.c.Memory, 10)); err != nil {
30
-				return err
31
-			}
32
-		}
33
-		if d.c.MemoryReservation != 0 {
34
-			if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(d.c.MemoryReservation, 10)); err != nil {
35
-				return err
36
-			}
37
-		}
38
-		// By default, MemorySwap is set to twice the size of RAM.
39
-		// If you want to omit MemorySwap, set it to `-1'.
40
-		if d.c.MemorySwap != -1 {
41
-			if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.Memory*2, 10)); err != nil {
42
-				return err
43
-			}
44
-		}
45
-	}
46
-	return nil
47
-}
48
-
49
-func (s *memoryGroup) Remove(d *data) error {
50
-	return removePath(d.path("memory"))
51
-}
52
-
53
-func (s *memoryGroup) Stats(d *data) (map[string]float64, error) {
54
-	paramData := make(map[string]float64)
55
-	path, err := d.path("memory")
56
-	if err != nil {
57
-		return nil, err
58
-	}
59
-
60
-	// Set stats from memory.stat.
61
-	statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
62
-	if err != nil {
63
-		return nil, err
64
-	}
65
-	defer statsFile.Close()
66
-
67
-	sc := bufio.NewScanner(statsFile)
68
-	for sc.Scan() {
69
-		t, v, err := getCgroupParamKeyValue(sc.Text())
70
-		if err != nil {
71
-			return nil, err
72
-		}
73
-		paramData[t] = v
74
-	}
75
-
76
-	// Set memory usage and max historical usage.
77
-	params := []string{
78
-		"usage_in_bytes",
79
-		"max_usage_in_bytes",
80
-	}
81
-	for _, param := range params {
82
-		value, err := getCgroupParamFloat64(path, fmt.Sprintf("memory.%s", param))
83
-		if err != nil {
84
-			return nil, err
85
-		}
86
-		paramData[param] = value
87
-	}
88
-
89
-	return paramData, nil
90
-}
91 1
deleted file mode 100644
... ...
@@ -1,123 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"testing"
5
-)
6
-
7
-const (
8
-	memoryStatContents = `cache 512
9
-rss 1024`
10
-	memoryUsageContents    = "2048\n"
11
-	memoryMaxUsageContents = "4096\n"
12
-)
13
-
14
-func TestMemoryStats(t *testing.T) {
15
-	helper := NewCgroupTestUtil("memory", t)
16
-	defer helper.cleanup()
17
-	helper.writeFileContents(map[string]string{
18
-		"memory.stat":               memoryStatContents,
19
-		"memory.usage_in_bytes":     memoryUsageContents,
20
-		"memory.max_usage_in_bytes": memoryMaxUsageContents,
21
-	})
22
-
23
-	memory := &memoryGroup{}
24
-	stats, err := memory.Stats(helper.CgroupData)
25
-	if err != nil {
26
-		t.Fatal(err)
27
-	}
28
-	expectedStats := map[string]float64{"cache": 512.0, "rss": 1024.0, "usage_in_bytes": 2048.0, "max_usage_in_bytes": 4096.0}
29
-	expectStats(t, expectedStats, stats)
30
-}
31
-
32
-func TestMemoryStatsNoStatFile(t *testing.T) {
33
-	helper := NewCgroupTestUtil("memory", t)
34
-	defer helper.cleanup()
35
-	helper.writeFileContents(map[string]string{
36
-		"memory.usage_in_bytes":     memoryUsageContents,
37
-		"memory.max_usage_in_bytes": memoryMaxUsageContents,
38
-	})
39
-
40
-	memory := &memoryGroup{}
41
-	_, err := memory.Stats(helper.CgroupData)
42
-	if err == nil {
43
-		t.Fatal("Expected failure")
44
-	}
45
-}
46
-
47
-func TestMemoryStatsNoUsageFile(t *testing.T) {
48
-	helper := NewCgroupTestUtil("memory", t)
49
-	defer helper.cleanup()
50
-	helper.writeFileContents(map[string]string{
51
-		"memory.stat":               memoryStatContents,
52
-		"memory.max_usage_in_bytes": memoryMaxUsageContents,
53
-	})
54
-
55
-	memory := &memoryGroup{}
56
-	_, err := memory.Stats(helper.CgroupData)
57
-	if err == nil {
58
-		t.Fatal("Expected failure")
59
-	}
60
-}
61
-
62
-func TestMemoryStatsNoMaxUsageFile(t *testing.T) {
63
-	helper := NewCgroupTestUtil("memory", t)
64
-	defer helper.cleanup()
65
-	helper.writeFileContents(map[string]string{
66
-		"memory.stat":           memoryStatContents,
67
-		"memory.usage_in_bytes": memoryUsageContents,
68
-	})
69
-
70
-	memory := &memoryGroup{}
71
-	_, err := memory.Stats(helper.CgroupData)
72
-	if err == nil {
73
-		t.Fatal("Expected failure")
74
-	}
75
-}
76
-
77
-func TestMemoryStatsBadStatFile(t *testing.T) {
78
-	helper := NewCgroupTestUtil("memory", t)
79
-	defer helper.cleanup()
80
-	helper.writeFileContents(map[string]string{
81
-		"memory.stat":               "rss rss",
82
-		"memory.usage_in_bytes":     memoryUsageContents,
83
-		"memory.max_usage_in_bytes": memoryMaxUsageContents,
84
-	})
85
-
86
-	memory := &memoryGroup{}
87
-	_, err := memory.Stats(helper.CgroupData)
88
-	if err == nil {
89
-		t.Fatal("Expected failure")
90
-	}
91
-}
92
-
93
-func TestMemoryStatsBadUsageFile(t *testing.T) {
94
-	helper := NewCgroupTestUtil("memory", t)
95
-	defer helper.cleanup()
96
-	helper.writeFileContents(map[string]string{
97
-		"memory.stat":               memoryStatContents,
98
-		"memory.usage_in_bytes":     "bad",
99
-		"memory.max_usage_in_bytes": memoryMaxUsageContents,
100
-	})
101
-
102
-	memory := &memoryGroup{}
103
-	_, err := memory.Stats(helper.CgroupData)
104
-	if err == nil {
105
-		t.Fatal("Expected failure")
106
-	}
107
-}
108
-
109
-func TestMemoryStatsBadMaxUsageFile(t *testing.T) {
110
-	helper := NewCgroupTestUtil("memory", t)
111
-	defer helper.cleanup()
112
-	helper.writeFileContents(map[string]string{
113
-		"memory.stat":               memoryStatContents,
114
-		"memory.usage_in_bytes":     memoryUsageContents,
115
-		"memory.max_usage_in_bytes": "bad",
116
-	})
117
-
118
-	memory := &memoryGroup{}
119
-	_, err := memory.Stats(helper.CgroupData)
120
-	if err == nil {
121
-		t.Fatal("Expected failure")
122
-	}
123
-}
124 1
deleted file mode 100644
... ...
@@ -1,24 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"github.com/dotcloud/docker/pkg/cgroups"
5
-)
6
-
7
-type perfEventGroup struct {
8
-}
9
-
10
-func (s *perfEventGroup) Set(d *data) error {
11
-	// we just want to join this group even though we don't set anything
12
-	if _, err := d.join("perf_event"); err != nil && err != cgroups.ErrNotFound {
13
-		return err
14
-	}
15
-	return nil
16
-}
17
-
18
-func (s *perfEventGroup) Remove(d *data) error {
19
-	return removePath(d.path("perf_event"))
20
-}
21
-
22
-func (s *perfEventGroup) Stats(d *data) (map[string]float64, error) {
23
-	return nil, ErrNotSupportStat
24
-}
25 1
deleted file mode 100644
... ...
@@ -1,75 +0,0 @@
1
-/*
2
-Utility for testing cgroup operations.
3
-
4
-Creates a mock of the cgroup filesystem for the duration of the test.
5
-*/
6
-package fs
7
-
8
-import (
9
-	"fmt"
10
-	"io/ioutil"
11
-	"log"
12
-	"os"
13
-	"testing"
14
-)
15
-
16
-type cgroupTestUtil struct {
17
-	// data to use in tests.
18
-	CgroupData *data
19
-
20
-	// Path to the mock cgroup directory.
21
-	CgroupPath string
22
-
23
-	// Temporary directory to store mock cgroup filesystem.
24
-	tempDir string
25
-	t       *testing.T
26
-}
27
-
28
-// Creates a new test util for the specified subsystem
29
-func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil {
30
-	d := &data{}
31
-	tempDir, err := ioutil.TempDir("", fmt.Sprintf("%s_cgroup_test", subsystem))
32
-	if err != nil {
33
-		t.Fatal(err)
34
-	}
35
-	d.root = tempDir
36
-	testCgroupPath, err := d.path(subsystem)
37
-	if err != nil {
38
-		t.Fatal(err)
39
-	}
40
-
41
-	// Ensure the full mock cgroup path exists.
42
-	err = os.MkdirAll(testCgroupPath, 0755)
43
-	if err != nil {
44
-		t.Fatal(err)
45
-	}
46
-	return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t}
47
-}
48
-
49
-func (c *cgroupTestUtil) cleanup() {
50
-	os.RemoveAll(c.tempDir)
51
-}
52
-
53
-// Write the specified contents on the mock of the specified cgroup files.
54
-func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) {
55
-	for file, contents := range fileContents {
56
-		err := writeFile(c.CgroupPath, file, contents)
57
-		if err != nil {
58
-			c.t.Fatal(err)
59
-		}
60
-	}
61
-}
62
-
63
-// Expect the specified stats.
64
-func expectStats(t *testing.T, expected, actual map[string]float64) {
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
-}
76 1
deleted file mode 100644
... ...
@@ -1,40 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"errors"
5
-	"fmt"
6
-	"io/ioutil"
7
-	"path/filepath"
8
-	"strconv"
9
-	"strings"
10
-)
11
-
12
-var (
13
-	ErrNotSupportStat = errors.New("stats are not supported for subsystem")
14
-	ErrNotValidFormat = errors.New("line is not a valid key value format")
15
-)
16
-
17
-// Parses a cgroup param and returns as name, value
18
-//  i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234
19
-func getCgroupParamKeyValue(t string) (string, float64, error) {
20
-	parts := strings.Fields(t)
21
-	switch len(parts) {
22
-	case 2:
23
-		value, err := strconv.ParseFloat(parts[1], 64)
24
-		if err != nil {
25
-			return "", 0.0, fmt.Errorf("Unable to convert param value to float: %s", err)
26
-		}
27
-		return parts[0], value, nil
28
-	default:
29
-		return "", 0.0, ErrNotValidFormat
30
-	}
31
-}
32
-
33
-// Gets a single float64 value from the specified cgroup file.
34
-func getCgroupParamFloat64(cgroupPath, cgroupFile string) (float64, error) {
35
-	contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
36
-	if err != nil {
37
-		return -1.0, err
38
-	}
39
-	return strconv.ParseFloat(strings.TrimSpace(string(contents)), 64)
40
-}
41 1
deleted file mode 100644
... ...
@@ -1,68 +0,0 @@
1
-package fs
2
-
3
-import (
4
-	"io/ioutil"
5
-	"os"
6
-	"path/filepath"
7
-	"testing"
8
-)
9
-
10
-const (
11
-	cgroupFile  = "cgroup.file"
12
-	floatValue  = 2048.0
13
-	floatString = "2048"
14
-)
15
-
16
-func TestGetCgroupParamsFloat64(t *testing.T) {
17
-	// Setup tempdir.
18
-	tempDir, err := ioutil.TempDir("", "cgroup_utils_test")
19
-	if err != nil {
20
-		t.Fatal(err)
21
-	}
22
-	defer os.RemoveAll(tempDir)
23
-	tempFile := filepath.Join(tempDir, cgroupFile)
24
-
25
-	// Success.
26
-	err = ioutil.WriteFile(tempFile, []byte(floatString), 0755)
27
-	if err != nil {
28
-		t.Fatal(err)
29
-	}
30
-	value, err := getCgroupParamFloat64(tempDir, cgroupFile)
31
-	if err != nil {
32
-		t.Fatal(err)
33
-	} else if value != floatValue {
34
-		t.Fatalf("Expected %f to equal %f", value, floatValue)
35
-	}
36
-
37
-	// Success with new line.
38
-	err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755)
39
-	if err != nil {
40
-		t.Fatal(err)
41
-	}
42
-	value, err = getCgroupParamFloat64(tempDir, cgroupFile)
43
-	if err != nil {
44
-		t.Fatal(err)
45
-	} else if value != floatValue {
46
-		t.Fatalf("Expected %f to equal %f", value, floatValue)
47
-	}
48
-
49
-	// Not a float.
50
-	err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755)
51
-	if err != nil {
52
-		t.Fatal(err)
53
-	}
54
-	_, err = getCgroupParamFloat64(tempDir, cgroupFile)
55
-	if err == nil {
56
-		t.Fatal("Expecting error, got none")
57
-	}
58
-
59
-	// Unknown file.
60
-	err = os.Remove(tempFile)
61
-	if err != nil {
62
-		t.Fatal(err)
63
-	}
64
-	_, err = getCgroupParamFloat64(tempDir, cgroupFile)
65
-	if err == nil {
66
-		t.Fatal("Expecting error, got none")
67
-	}
68
-}
69 1
deleted file mode 100644
... ...
@@ -1,16 +0,0 @@
1
-// +build !linux
2
-
3
-package systemd
4
-
5
-import (
6
-	"fmt"
7
-	"github.com/dotcloud/docker/pkg/cgroups"
8
-)
9
-
10
-func UseSystemd() bool {
11
-	return false
12
-}
13
-
14
-func Apply(c *Cgroup, pid int) (cgroups.ActiveCgroup, error) {
15
-	return nil, fmt.Errorf("Systemd not supported")
16
-}
17 1
deleted file mode 100644
... ...
@@ -1,296 +0,0 @@
1
-// +build linux
2
-
3
-package systemd
4
-
5
-import (
6
-	"io/ioutil"
7
-	"os"
8
-	"path/filepath"
9
-	"strconv"
10
-	"strings"
11
-	"sync"
12
-
13
-	systemd1 "github.com/coreos/go-systemd/dbus"
14
-	"github.com/dotcloud/docker/pkg/cgroups"
15
-	"github.com/dotcloud/docker/pkg/systemd"
16
-	"github.com/godbus/dbus"
17
-)
18
-
19
-type systemdCgroup struct {
20
-	cleanupDirs []string
21
-}
22
-
23
-type DeviceAllow struct {
24
-	Node        string
25
-	Permissions string
26
-}
27
-
28
-var (
29
-	connLock              sync.Mutex
30
-	theConn               *systemd1.Conn
31
-	hasStartTransientUnit bool
32
-)
33
-
34
-func UseSystemd() bool {
35
-	if !systemd.SdBooted() {
36
-		return false
37
-	}
38
-
39
-	connLock.Lock()
40
-	defer connLock.Unlock()
41
-
42
-	if theConn == nil {
43
-		var err error
44
-		theConn, err = systemd1.New()
45
-		if err != nil {
46
-			return false
47
-		}
48
-
49
-		// Assume we have StartTransientUnit
50
-		hasStartTransientUnit = true
51
-
52
-		// But if we get UnknownMethod error we don't
53
-		if _, err := theConn.StartTransientUnit("test.scope", "invalid"); err != nil {
54
-			if dbusError, ok := err.(dbus.Error); ok {
55
-				if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
56
-					hasStartTransientUnit = false
57
-				}
58
-			}
59
-		}
60
-	}
61
-	return hasStartTransientUnit
62
-}
63
-
64
-func getIfaceForUnit(unitName string) string {
65
-	if strings.HasSuffix(unitName, ".scope") {
66
-		return "Scope"
67
-	}
68
-	if strings.HasSuffix(unitName, ".service") {
69
-		return "Service"
70
-	}
71
-	return "Unit"
72
-}
73
-
74
-type cgroupArg struct {
75
-	File  string
76
-	Value string
77
-}
78
-
79
-func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
80
-	var (
81
-		unitName   = c.Parent + "-" + c.Name + ".scope"
82
-		slice      = "system.slice"
83
-		properties []systemd1.Property
84
-		cpuArgs    []cgroupArg
85
-		cpusetArgs []cgroupArg
86
-		memoryArgs []cgroupArg
87
-		res        systemdCgroup
88
-	)
89
-
90
-	// First set up things not supported by systemd
91
-
92
-	// -1 disables memorySwap
93
-	if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) {
94
-		memorySwap := c.MemorySwap
95
-
96
-		if memorySwap == 0 {
97
-			// By default, MemorySwap is set to twice the size of RAM.
98
-			memorySwap = c.Memory * 2
99
-		}
100
-
101
-		memoryArgs = append(memoryArgs, cgroupArg{"memory.memsw.limit_in_bytes", strconv.FormatInt(memorySwap, 10)})
102
-	}
103
-
104
-	if c.CpusetCpus != "" {
105
-		cpusetArgs = append(cpusetArgs, cgroupArg{"cpuset.cpus", c.CpusetCpus})
106
-	}
107
-
108
-	if c.Slice != "" {
109
-		slice = c.Slice
110
-	}
111
-
112
-	properties = append(properties,
113
-		systemd1.Property{"Slice", dbus.MakeVariant(slice)},
114
-		systemd1.Property{"Description", dbus.MakeVariant("docker container " + c.Name)},
115
-		systemd1.Property{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})},
116
-	)
117
-
118
-	if !c.DeviceAccess {
119
-		properties = append(properties,
120
-			systemd1.Property{"DevicePolicy", dbus.MakeVariant("strict")},
121
-			systemd1.Property{"DeviceAllow", dbus.MakeVariant([]DeviceAllow{
122
-				{"/dev/null", "rwm"},
123
-				{"/dev/zero", "rwm"},
124
-				{"/dev/full", "rwm"},
125
-				{"/dev/random", "rwm"},
126
-				{"/dev/urandom", "rwm"},
127
-				{"/dev/tty", "rwm"},
128
-				{"/dev/console", "rwm"},
129
-				{"/dev/tty0", "rwm"},
130
-				{"/dev/tty1", "rwm"},
131
-				{"/dev/pts/ptmx", "rwm"},
132
-				// There is no way to add /dev/pts/* here atm, so we hack this manually below
133
-				// /dev/pts/* (how to add this?)
134
-				// Same with tuntap, which doesn't exist as a node most of the time
135
-			})})
136
-	}
137
-
138
-	// Always enable accounting, this gets us the same behaviour as the fs implementation,
139
-	// plus the kernel has some problems with joining the memory cgroup at a later time.
140
-	properties = append(properties,
141
-		systemd1.Property{"MemoryAccounting", dbus.MakeVariant(true)},
142
-		systemd1.Property{"CPUAccounting", dbus.MakeVariant(true)},
143
-		systemd1.Property{"BlockIOAccounting", dbus.MakeVariant(true)})
144
-
145
-	if c.Memory != 0 {
146
-		properties = append(properties,
147
-			systemd1.Property{"MemoryLimit", dbus.MakeVariant(uint64(c.Memory))})
148
-	}
149
-	// TODO: MemoryReservation and MemorySwap not available in systemd
150
-
151
-	if c.CpuShares != 0 {
152
-		properties = append(properties,
153
-			systemd1.Property{"CPUShares", dbus.MakeVariant(uint64(c.CpuShares))})
154
-	}
155
-
156
-	if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil {
157
-		return nil, err
158
-	}
159
-
160
-	// To work around the lack of /dev/pts/* support above we need to manually add these
161
-	// so, ask systemd for the cgroup used
162
-	props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName))
163
-	if err != nil {
164
-		return nil, err
165
-	}
166
-
167
-	cgroup := props["ControlGroup"].(string)
168
-
169
-	if !c.DeviceAccess {
170
-		mountpoint, err := cgroups.FindCgroupMountpoint("devices")
171
-		if err != nil {
172
-			return nil, err
173
-		}
174
-
175
-		path := filepath.Join(mountpoint, cgroup)
176
-
177
-		// /dev/pts/*
178
-		if err := ioutil.WriteFile(filepath.Join(path, "devices.allow"), []byte("c 136:* rwm"), 0700); err != nil {
179
-			return nil, err
180
-		}
181
-		// tuntap
182
-		if err := ioutil.WriteFile(filepath.Join(path, "devices.allow"), []byte("c 10:200 rwm"), 0700); err != nil {
183
-			return nil, err
184
-		}
185
-	}
186
-
187
-	if len(cpuArgs) != 0 {
188
-		mountpoint, err := cgroups.FindCgroupMountpoint("cpu")
189
-		if err != nil {
190
-			return nil, err
191
-		}
192
-
193
-		path := filepath.Join(mountpoint, cgroup)
194
-
195
-		for _, arg := range cpuArgs {
196
-			if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
197
-				return nil, err
198
-			}
199
-		}
200
-	}
201
-
202
-	if len(memoryArgs) != 0 {
203
-		mountpoint, err := cgroups.FindCgroupMountpoint("memory")
204
-		if err != nil {
205
-			return nil, err
206
-		}
207
-
208
-		path := filepath.Join(mountpoint, cgroup)
209
-
210
-		for _, arg := range memoryArgs {
211
-			if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
212
-				return nil, err
213
-			}
214
-		}
215
-	}
216
-
217
-	if len(cpusetArgs) != 0 {
218
-		// systemd does not atm set up the cpuset controller, so we must manually
219
-		// join it. Additionally that is a very finicky controller where each
220
-		// level must have a full setup as the default for a new directory is "no cpus",
221
-		// so we avoid using any hierarchies here, creating a toplevel directory.
222
-		mountpoint, err := cgroups.FindCgroupMountpoint("cpuset")
223
-		if err != nil {
224
-			return nil, err
225
-		}
226
-		initPath, err := cgroups.GetInitCgroupDir("cpuset")
227
-		if err != nil {
228
-			return nil, err
229
-		}
230
-
231
-		rootPath := filepath.Join(mountpoint, initPath)
232
-
233
-		path := filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name)
234
-
235
-		res.cleanupDirs = append(res.cleanupDirs, path)
236
-
237
-		if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
238
-			return nil, err
239
-		}
240
-
241
-		foundCpus := false
242
-		foundMems := false
243
-
244
-		for _, arg := range cpusetArgs {
245
-			if arg.File == "cpuset.cpus" {
246
-				foundCpus = true
247
-			}
248
-			if arg.File == "cpuset.mems" {
249
-				foundMems = true
250
-			}
251
-			if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
252
-				return nil, err
253
-			}
254
-		}
255
-
256
-		// These are required, if not specified inherit from parent
257
-		if !foundCpus {
258
-			s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.cpus"))
259
-			if err != nil {
260
-				return nil, err
261
-			}
262
-
263
-			if err := ioutil.WriteFile(filepath.Join(path, "cpuset.cpus"), s, 0700); err != nil {
264
-				return nil, err
265
-			}
266
-		}
267
-
268
-		// These are required, if not specified inherit from parent
269
-		if !foundMems {
270
-			s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.mems"))
271
-			if err != nil {
272
-				return nil, err
273
-			}
274
-
275
-			if err := ioutil.WriteFile(filepath.Join(path, "cpuset.mems"), s, 0700); err != nil {
276
-				return nil, err
277
-			}
278
-		}
279
-
280
-		if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
281
-			return nil, err
282
-		}
283
-	}
284
-
285
-	return &res, nil
286
-}
287
-
288
-func (c *systemdCgroup) Cleanup() error {
289
-	// systemd cleans up, we don't need to do much
290
-
291
-	for _, path := range c.cleanupDirs {
292
-		os.RemoveAll(path)
293
-	}
294
-
295
-	return nil
296
-}
297 1
deleted file mode 100644
... ...
@@ -1,67 +0,0 @@
1
-package cgroups
2
-
3
-import (
4
-	"bufio"
5
-	"io"
6
-	"os"
7
-	"strings"
8
-
9
-	"github.com/dotcloud/docker/pkg/mount"
10
-)
11
-
12
-// https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt
13
-func FindCgroupMountpoint(subsystem string) (string, error) {
14
-	mounts, err := mount.GetMounts()
15
-	if err != nil {
16
-		return "", err
17
-	}
18
-
19
-	for _, mount := range mounts {
20
-		if mount.Fstype == "cgroup" {
21
-			for _, opt := range strings.Split(mount.VfsOpts, ",") {
22
-				if opt == subsystem {
23
-					return mount.Mountpoint, nil
24
-				}
25
-			}
26
-		}
27
-	}
28
-	return "", ErrNotFound
29
-}
30
-
31
-// Returns the relative path to the cgroup docker is running in.
32
-func GetThisCgroupDir(subsystem string) (string, error) {
33
-	f, err := os.Open("/proc/self/cgroup")
34
-	if err != nil {
35
-		return "", err
36
-	}
37
-	defer f.Close()
38
-
39
-	return parseCgroupFile(subsystem, f)
40
-}
41
-
42
-func GetInitCgroupDir(subsystem string) (string, error) {
43
-	f, err := os.Open("/proc/1/cgroup")
44
-	if err != nil {
45
-		return "", err
46
-	}
47
-	defer f.Close()
48
-
49
-	return parseCgroupFile(subsystem, f)
50
-}
51
-
52
-func parseCgroupFile(subsystem string, r io.Reader) (string, error) {
53
-	s := bufio.NewScanner(r)
54
-	for s.Scan() {
55
-		if err := s.Err(); err != nil {
56
-			return "", err
57
-		}
58
-		text := s.Text()
59
-		parts := strings.Split(text, ":")
60
-		for _, subs := range strings.Split(parts[1], ",") {
61
-			if subs == subsystem {
62
-				return parts[2], nil
63
-			}
64
-		}
65
-	}
66
-	return "", ErrNotFound
67
-}
68 1
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+Michael Crosby <michael@crosbymichael.com> (@crosbymichael)
1
+Rohit Jnagal <jnagal@google.com> (@rjnagal)
2
+Victor Marmol <vmarmol@google.com> (@vmarmol)
0 3
new file mode 100644
... ...
@@ -0,0 +1,30 @@
0
+package cgroups
1
+
2
+import (
3
+	"errors"
4
+)
5
+
6
+var (
7
+	ErrNotFound = errors.New("mountpoint not found")
8
+)
9
+
10
+type Cgroup struct {
11
+	Name   string `json:"name,omitempty"`
12
+	Parent string `json:"parent,omitempty"`
13
+
14
+	DeviceAccess      bool   `json:"device_access,omitempty"`      // name of parent cgroup or slice
15
+	Memory            int64  `json:"memory,omitempty"`             // Memory limit (in bytes)
16
+	MemoryReservation int64  `json:"memory_reservation,omitempty"` // Memory reservation or soft_limit (in bytes)
17
+	MemorySwap        int64  `json:"memory_swap,omitempty"`        // Total memory usage (memory + swap); set `-1' to disable swap
18
+	CpuShares         int64  `json:"cpu_shares,omitempty"`         // CPU shares (relative weight vs. other containers)
19
+	CpuQuota          int64  `json:"cpu_quota,omitempty"`          // CPU hardcap limit (in usecs). Allowed cpu time in a given period.
20
+	CpuPeriod         int64  `json:"cpu_period,omitempty"`         // CPU period to be used for hardcapping (in usecs). 0 to use system default.
21
+	CpusetCpus        string `json:"cpuset_cpus,omitempty"`        // CPU to use
22
+	Freezer           string `json:"freezer,omitempty"`            // set the freeze value for the process
23
+
24
+	Slice string `json:"slice,omitempty"` // Parent slice to use for systemd
25
+}
26
+
27
+type ActiveCgroup interface {
28
+	Cleanup() error
29
+}
0 30
new file mode 100644
... ...
@@ -0,0 +1,27 @@
0
+package cgroups
1
+
2
+import (
3
+	"bytes"
4
+	"testing"
5
+)
6
+
7
+const (
8
+	cgroupsContents = `11:hugetlb:/
9
+10:perf_event:/
10
+9:blkio:/
11
+8:net_cls:/
12
+7:freezer:/
13
+6:devices:/
14
+5:memory:/
15
+4:cpuacct,cpu:/
16
+3:cpuset:/
17
+2:name=systemd:/user.slice/user-1000.slice/session-16.scope`
18
+)
19
+
20
+func TestParseCgroups(t *testing.T) {
21
+	r := bytes.NewBuffer([]byte(cgroupsContents))
22
+	_, err := parseCgroupFile("blkio", r)
23
+	if err != nil {
24
+		t.Fatal(err)
25
+	}
26
+}
0 27
new file mode 100644
... ...
@@ -0,0 +1,155 @@
0
+package fs
1
+
2
+import (
3
+	"fmt"
4
+	"io/ioutil"
5
+	"os"
6
+	"path/filepath"
7
+	"strconv"
8
+
9
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
10
+)
11
+
12
+var (
13
+	subsystems = map[string]subsystem{
14
+		"devices":    &devicesGroup{},
15
+		"memory":     &memoryGroup{},
16
+		"cpu":        &cpuGroup{},
17
+		"cpuset":     &cpusetGroup{},
18
+		"cpuacct":    &cpuacctGroup{},
19
+		"blkio":      &blkioGroup{},
20
+		"perf_event": &perfEventGroup{},
21
+		"freezer":    &freezerGroup{},
22
+	}
23
+)
24
+
25
+type subsystem interface {
26
+	Set(*data) error
27
+	Remove(*data) error
28
+	Stats(*data) (map[string]float64, error)
29
+}
30
+
31
+type data struct {
32
+	root   string
33
+	cgroup string
34
+	c      *cgroups.Cgroup
35
+	pid    int
36
+}
37
+
38
+func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
39
+	// We have two implementation of cgroups support, one is based on
40
+	// systemd and the dbus api, and one is based on raw cgroup fs operations
41
+	// following the pre-single-writer model docs at:
42
+	// http://www.freedesktop.org/wiki/Software/systemd/PaxControlGroups/
43
+	//
44
+	// we can pick any subsystem to find the root
45
+
46
+	cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu")
47
+	if err != nil {
48
+		return nil, err
49
+	}
50
+	cgroupRoot = filepath.Dir(cgroupRoot)
51
+
52
+	if _, err := os.Stat(cgroupRoot); err != nil {
53
+		return nil, fmt.Errorf("cgroups fs not found")
54
+	}
55
+
56
+	cgroup := c.Name
57
+	if c.Parent != "" {
58
+		cgroup = filepath.Join(c.Parent, cgroup)
59
+	}
60
+
61
+	d := &data{
62
+		root:   cgroupRoot,
63
+		cgroup: cgroup,
64
+		c:      c,
65
+		pid:    pid,
66
+	}
67
+	for _, sys := range subsystems {
68
+		if err := sys.Set(d); err != nil {
69
+			d.Cleanup()
70
+			return nil, err
71
+		}
72
+	}
73
+	return d, nil
74
+}
75
+
76
+func GetStats(c *cgroups.Cgroup, subsystem string, pid int) (map[string]float64, error) {
77
+	cgroupRoot, err := cgroups.FindCgroupMountpoint("cpu")
78
+	if err != nil {
79
+		return nil, err
80
+	}
81
+	cgroupRoot = filepath.Dir(cgroupRoot)
82
+
83
+	if _, err := os.Stat(cgroupRoot); err != nil {
84
+		return nil, fmt.Errorf("cgroups fs not found")
85
+	}
86
+
87
+	cgroup := c.Name
88
+	if c.Parent != "" {
89
+		cgroup = filepath.Join(c.Parent, cgroup)
90
+	}
91
+
92
+	d := &data{
93
+		root:   cgroupRoot,
94
+		cgroup: cgroup,
95
+		c:      c,
96
+		pid:    pid,
97
+	}
98
+	sys, exists := subsystems[subsystem]
99
+	if !exists {
100
+		return nil, fmt.Errorf("subsystem %s does not exist", subsystem)
101
+	}
102
+	return sys.Stats(d)
103
+}
104
+
105
+func (raw *data) parent(subsystem string) (string, error) {
106
+	initPath, err := cgroups.GetInitCgroupDir(subsystem)
107
+	if err != nil {
108
+		return "", err
109
+	}
110
+	return filepath.Join(raw.root, subsystem, initPath), nil
111
+}
112
+
113
+func (raw *data) path(subsystem string) (string, error) {
114
+	parent, err := raw.parent(subsystem)
115
+	if err != nil {
116
+		return "", err
117
+	}
118
+	return filepath.Join(parent, raw.cgroup), nil
119
+}
120
+
121
+func (raw *data) join(subsystem string) (string, error) {
122
+	path, err := raw.path(subsystem)
123
+	if err != nil {
124
+		return "", err
125
+	}
126
+	if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
127
+		return "", err
128
+	}
129
+	if err := writeFile(path, "cgroup.procs", strconv.Itoa(raw.pid)); err != nil {
130
+		return "", err
131
+	}
132
+	return path, nil
133
+}
134
+
135
+func (raw *data) Cleanup() error {
136
+	for _, sys := range subsystems {
137
+		sys.Remove(raw)
138
+	}
139
+	return nil
140
+}
141
+
142
+func writeFile(dir, file, data string) error {
143
+	return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700)
144
+}
145
+
146
+func removePath(p string, err error) error {
147
+	if err != nil {
148
+		return err
149
+	}
150
+	if p != "" {
151
+		return os.RemoveAll(p)
152
+	}
153
+	return nil
154
+}
0 155
new file mode 100644
... ...
@@ -0,0 +1,121 @@
0
+package fs
1
+
2
+import (
3
+	"bufio"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"os"
7
+	"path/filepath"
8
+	"strconv"
9
+	"strings"
10
+
11
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
12
+)
13
+
14
+type blkioGroup struct {
15
+}
16
+
17
+func (s *blkioGroup) Set(d *data) error {
18
+	// we just want to join this group even though we don't set anything
19
+	if _, err := d.join("blkio"); err != nil && err != cgroups.ErrNotFound {
20
+		return err
21
+	}
22
+	return nil
23
+}
24
+
25
+func (s *blkioGroup) Remove(d *data) error {
26
+	return removePath(d.path("blkio"))
27
+}
28
+
29
+/*
30
+examples:
31
+
32
+    blkio.sectors
33
+    8:0 6792
34
+
35
+    blkio.io_service_bytes
36
+    8:0 Read 1282048
37
+    8:0 Write 2195456
38
+    8:0 Sync 2195456
39
+    8:0 Async 1282048
40
+    8:0 Total 3477504
41
+    Total 3477504
42
+
43
+    blkio.io_serviced
44
+    8:0 Read 124
45
+    8:0 Write 104
46
+    8:0 Sync 104
47
+    8:0 Async 124
48
+    8:0 Total 228
49
+    Total 228
50
+
51
+    blkio.io_queued
52
+    8:0 Read 0
53
+    8:0 Write 0
54
+    8:0 Sync 0
55
+    8:0 Async 0
56
+    8:0 Total 0
57
+    Total 0
58
+*/
59
+func (s *blkioGroup) Stats(d *data) (map[string]float64, error) {
60
+	var (
61
+		paramData = make(map[string]float64)
62
+		params    = []string{
63
+			"io_service_bytes_recursive",
64
+			"io_serviced_recursive",
65
+			"io_queued_recursive",
66
+		}
67
+	)
68
+
69
+	path, err := d.path("blkio")
70
+	if err != nil {
71
+		return nil, err
72
+	}
73
+
74
+	k, v, err := s.getSectors(path)
75
+	if err != nil {
76
+		return nil, err
77
+	}
78
+	paramData[fmt.Sprintf("blkio.sectors_recursive:%s", k)] = v
79
+
80
+	for _, param := range params {
81
+		f, err := os.Open(filepath.Join(path, fmt.Sprintf("blkio.%s", param)))
82
+		if err != nil {
83
+			return nil, err
84
+		}
85
+		defer f.Close()
86
+
87
+		sc := bufio.NewScanner(f)
88
+		for sc.Scan() {
89
+			// format: dev type amount
90
+			fields := strings.Fields(sc.Text())
91
+			switch len(fields) {
92
+			case 3:
93
+				v, err := strconv.ParseFloat(fields[2], 64)
94
+				if err != nil {
95
+					return nil, err
96
+				}
97
+				paramData[fmt.Sprintf("%s:%s:%s", param, fields[0], fields[1])] = v
98
+			case 2:
99
+				// this is the total line, skip
100
+			default:
101
+				return nil, ErrNotValidFormat
102
+			}
103
+		}
104
+	}
105
+	return paramData, nil
106
+}
107
+
108
+func (s *blkioGroup) getSectors(path string) (string, float64, error) {
109
+	f, err := os.Open(filepath.Join(path, "blkio.sectors_recursive"))
110
+	if err != nil {
111
+		return "", 0, err
112
+	}
113
+	defer f.Close()
114
+
115
+	data, err := ioutil.ReadAll(f)
116
+	if err != nil {
117
+		return "", 0, err
118
+	}
119
+	return getCgroupParamKeyValue(string(data))
120
+}
0 121
new file mode 100644
... ...
@@ -0,0 +1,169 @@
0
+package fs
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+const (
7
+	sectorsRecursiveContents      = `8:0 1024`
8
+	serviceBytesRecursiveContents = `8:0 Read 100
9
+8:0 Write 400
10
+8:0 Sync 200
11
+8:0 Async 300
12
+8:0 Total 500
13
+Total 500`
14
+	servicedRecursiveContents = `8:0 Read 10
15
+8:0 Write 40
16
+8:0 Sync 20
17
+8:0 Async 30
18
+8:0 Total 50
19
+Total 50`
20
+	queuedRecursiveContents = `8:0 Read 1
21
+8:0 Write 4
22
+8:0 Sync 2
23
+8:0 Async 3
24
+8:0 Total 5
25
+Total 5`
26
+)
27
+
28
+func TestBlkioStats(t *testing.T) {
29
+	helper := NewCgroupTestUtil("blkio", t)
30
+	defer helper.cleanup()
31
+	helper.writeFileContents(map[string]string{
32
+		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
33
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
34
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
35
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
36
+	})
37
+
38
+	blkio := &blkioGroup{}
39
+	stats, err := blkio.Stats(helper.CgroupData)
40
+	if err != nil {
41
+		t.Fatal(err)
42
+	}
43
+
44
+	// Verify expected stats.
45
+	expectedStats := map[string]float64{
46
+		"blkio.sectors_recursive:8:0": 1024.0,
47
+
48
+		// Serviced bytes.
49
+		"io_service_bytes_recursive:8:0:Read":  100.0,
50
+		"io_service_bytes_recursive:8:0:Write": 400.0,
51
+		"io_service_bytes_recursive:8:0:Sync":  200.0,
52
+		"io_service_bytes_recursive:8:0:Async": 300.0,
53
+		"io_service_bytes_recursive:8:0:Total": 500.0,
54
+
55
+		// Serviced requests.
56
+		"io_serviced_recursive:8:0:Read":  10.0,
57
+		"io_serviced_recursive:8:0:Write": 40.0,
58
+		"io_serviced_recursive:8:0:Sync":  20.0,
59
+		"io_serviced_recursive:8:0:Async": 30.0,
60
+		"io_serviced_recursive:8:0:Total": 50.0,
61
+
62
+		// Queued requests.
63
+		"io_queued_recursive:8:0:Read":  1.0,
64
+		"io_queued_recursive:8:0:Write": 4.0,
65
+		"io_queued_recursive:8:0:Sync":  2.0,
66
+		"io_queued_recursive:8:0:Async": 3.0,
67
+		"io_queued_recursive:8:0:Total": 5.0,
68
+	}
69
+	expectStats(t, expectedStats, stats)
70
+}
71
+
72
+func TestBlkioStatsNoSectorsFile(t *testing.T) {
73
+	helper := NewCgroupTestUtil("blkio", t)
74
+	defer helper.cleanup()
75
+	helper.writeFileContents(map[string]string{
76
+		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
77
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
78
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
79
+	})
80
+
81
+	blkio := &blkioGroup{}
82
+	_, err := blkio.Stats(helper.CgroupData)
83
+	if err == nil {
84
+		t.Fatal("Expected to fail, but did not")
85
+	}
86
+}
87
+
88
+func TestBlkioStatsNoServiceBytesFile(t *testing.T) {
89
+	helper := NewCgroupTestUtil("blkio", t)
90
+	defer helper.cleanup()
91
+	helper.writeFileContents(map[string]string{
92
+		"blkio.io_serviced_recursive": servicedRecursiveContents,
93
+		"blkio.io_queued_recursive":   queuedRecursiveContents,
94
+		"blkio.sectors_recursive":     sectorsRecursiveContents,
95
+	})
96
+
97
+	blkio := &blkioGroup{}
98
+	_, err := blkio.Stats(helper.CgroupData)
99
+	if err == nil {
100
+		t.Fatal("Expected to fail, but did not")
101
+	}
102
+}
103
+
104
+func TestBlkioStatsNoServicedFile(t *testing.T) {
105
+	helper := NewCgroupTestUtil("blkio", t)
106
+	defer helper.cleanup()
107
+	helper.writeFileContents(map[string]string{
108
+		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
109
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
110
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
111
+	})
112
+
113
+	blkio := &blkioGroup{}
114
+	_, err := blkio.Stats(helper.CgroupData)
115
+	if err == nil {
116
+		t.Fatal("Expected to fail, but did not")
117
+	}
118
+}
119
+
120
+func TestBlkioStatsNoQueuedFile(t *testing.T) {
121
+	helper := NewCgroupTestUtil("blkio", t)
122
+	defer helper.cleanup()
123
+	helper.writeFileContents(map[string]string{
124
+		"blkio.io_service_bytes_recursive": serviceBytesRecursiveContents,
125
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
126
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
127
+	})
128
+
129
+	blkio := &blkioGroup{}
130
+	_, err := blkio.Stats(helper.CgroupData)
131
+	if err == nil {
132
+		t.Fatal("Expected to fail, but did not")
133
+	}
134
+}
135
+
136
+func TestBlkioStatsUnexpectedNumberOfFields(t *testing.T) {
137
+	helper := NewCgroupTestUtil("blkio", t)
138
+	defer helper.cleanup()
139
+	helper.writeFileContents(map[string]string{
140
+		"blkio.io_service_bytes_recursive": "8:0 Read 100 100",
141
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
142
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
143
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
144
+	})
145
+
146
+	blkio := &blkioGroup{}
147
+	_, err := blkio.Stats(helper.CgroupData)
148
+	if err == nil {
149
+		t.Fatal("Expected to fail, but did not")
150
+	}
151
+}
152
+
153
+func TestBlkioStatsUnexpectedFieldType(t *testing.T) {
154
+	helper := NewCgroupTestUtil("blkio", t)
155
+	defer helper.cleanup()
156
+	helper.writeFileContents(map[string]string{
157
+		"blkio.io_service_bytes_recursive": "8:0 Read Write",
158
+		"blkio.io_serviced_recursive":      servicedRecursiveContents,
159
+		"blkio.io_queued_recursive":        queuedRecursiveContents,
160
+		"blkio.sectors_recursive":          sectorsRecursiveContents,
161
+	})
162
+
163
+	blkio := &blkioGroup{}
164
+	_, err := blkio.Stats(helper.CgroupData)
165
+	if err == nil {
166
+		t.Fatal("Expected to fail, but did not")
167
+	}
168
+}
0 169
new file mode 100644
... ...
@@ -0,0 +1,64 @@
0
+package fs
1
+
2
+import (
3
+	"bufio"
4
+	"os"
5
+	"path/filepath"
6
+	"strconv"
7
+)
8
+
9
+type cpuGroup struct {
10
+}
11
+
12
+func (s *cpuGroup) Set(d *data) error {
13
+	// We always want to join the cpu group, to allow fair cpu scheduling
14
+	// on a container basis
15
+	dir, err := d.join("cpu")
16
+	if err != nil {
17
+		return err
18
+	}
19
+	if d.c.CpuShares != 0 {
20
+		if err := writeFile(dir, "cpu.shares", strconv.FormatInt(d.c.CpuShares, 10)); err != nil {
21
+			return err
22
+		}
23
+	}
24
+	if d.c.CpuPeriod != 0 {
25
+		if err := writeFile(dir, "cpu.cfs_period_us", strconv.FormatInt(d.c.CpuPeriod, 10)); err != nil {
26
+			return err
27
+		}
28
+	}
29
+	if d.c.CpuQuota != 0 {
30
+		if err := writeFile(dir, "cpu.cfs_quota_us", strconv.FormatInt(d.c.CpuQuota, 10)); err != nil {
31
+			return err
32
+		}
33
+	}
34
+	return nil
35
+}
36
+
37
+func (s *cpuGroup) Remove(d *data) error {
38
+	return removePath(d.path("cpu"))
39
+}
40
+
41
+func (s *cpuGroup) Stats(d *data) (map[string]float64, error) {
42
+	paramData := make(map[string]float64)
43
+	path, err := d.path("cpu")
44
+	if err != nil {
45
+		return nil, err
46
+	}
47
+
48
+	f, err := os.Open(filepath.Join(path, "cpu.stat"))
49
+	if err != nil {
50
+		return nil, err
51
+	}
52
+	defer f.Close()
53
+
54
+	sc := bufio.NewScanner(f)
55
+	for sc.Scan() {
56
+		t, v, err := getCgroupParamKeyValue(sc.Text())
57
+		if err != nil {
58
+			return nil, err
59
+		}
60
+		paramData[t] = v
61
+	}
62
+	return paramData, nil
63
+}
0 64
new file mode 100644
... ...
@@ -0,0 +1,57 @@
0
+package fs
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+func TestCpuStats(t *testing.T) {
7
+	helper := NewCgroupTestUtil("cpu", t)
8
+	defer helper.cleanup()
9
+	cpuStatContent := `nr_periods 2000
10
+	nr_throttled 200
11
+	throttled_time 42424242424`
12
+	helper.writeFileContents(map[string]string{
13
+		"cpu.stat": cpuStatContent,
14
+	})
15
+
16
+	cpu := &cpuGroup{}
17
+	stats, err := cpu.Stats(helper.CgroupData)
18
+	if err != nil {
19
+		t.Fatal(err)
20
+	}
21
+
22
+	expected_stats := map[string]float64{
23
+		"nr_periods":     2000.0,
24
+		"nr_throttled":   200.0,
25
+		"throttled_time": 42424242424.0,
26
+	}
27
+	expectStats(t, expected_stats, stats)
28
+}
29
+
30
+func TestNoCpuStatFile(t *testing.T) {
31
+	helper := NewCgroupTestUtil("cpu", t)
32
+	defer helper.cleanup()
33
+
34
+	cpu := &cpuGroup{}
35
+	_, err := cpu.Stats(helper.CgroupData)
36
+	if err == nil {
37
+		t.Fatal("Expected to fail, but did not.")
38
+	}
39
+}
40
+
41
+func TestInvalidCpuStat(t *testing.T) {
42
+	helper := NewCgroupTestUtil("cpu", t)
43
+	defer helper.cleanup()
44
+	cpuStatContent := `nr_periods 2000
45
+	nr_throttled 200
46
+	throttled_time fortytwo`
47
+	helper.writeFileContents(map[string]string{
48
+		"cpu.stat": cpuStatContent,
49
+	})
50
+
51
+	cpu := &cpuGroup{}
52
+	_, err := cpu.Stats(helper.CgroupData)
53
+	if err == nil {
54
+		t.Fatal("Expected failed stat parsing.")
55
+	}
56
+}
0 57
new file mode 100644
... ...
@@ -0,0 +1,143 @@
0
+package fs
1
+
2
+import (
3
+	"bufio"
4
+	"fmt"
5
+	"os"
6
+	"path/filepath"
7
+	"runtime"
8
+	"strconv"
9
+	"strings"
10
+	"time"
11
+
12
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
13
+	"github.com/dotcloud/docker/pkg/system"
14
+)
15
+
16
+var (
17
+	cpuCount   = float64(runtime.NumCPU())
18
+	clockTicks = float64(system.GetClockTicks())
19
+)
20
+
21
+type cpuacctGroup struct {
22
+}
23
+
24
+func (s *cpuacctGroup) Set(d *data) error {
25
+	// we just want to join this group even though we don't set anything
26
+	if _, err := d.join("cpuacct"); err != nil && err != cgroups.ErrNotFound {
27
+		return err
28
+	}
29
+	return nil
30
+}
31
+
32
+func (s *cpuacctGroup) Remove(d *data) error {
33
+	return removePath(d.path("cpuacct"))
34
+}
35
+
36
+func (s *cpuacctGroup) Stats(d *data) (map[string]float64, error) {
37
+	var (
38
+		startCpu, lastCpu, startSystem, lastSystem, startUsage, lastUsage float64
39
+		percentage                                                        float64
40
+		paramData                                                         = make(map[string]float64)
41
+	)
42
+	path, err := d.path("cpuacct")
43
+	if startCpu, err = s.getCpuUsage(d, path); err != nil {
44
+		return nil, err
45
+	}
46
+	if startSystem, err = s.getSystemCpuUsage(d); err != nil {
47
+		return nil, err
48
+	}
49
+	startUsageTime := time.Now()
50
+	if startUsage, err = getCgroupParamFloat64(path, "cpuacct.usage"); err != nil {
51
+		return nil, err
52
+	}
53
+	// sample for 100ms
54
+	time.Sleep(100 * time.Millisecond)
55
+	if lastCpu, err = s.getCpuUsage(d, path); err != nil {
56
+		return nil, err
57
+	}
58
+	if lastSystem, err = s.getSystemCpuUsage(d); err != nil {
59
+		return nil, err
60
+	}
61
+	usageSampleDuration := time.Since(startUsageTime)
62
+	if lastUsage, err = getCgroupParamFloat64(path, "cpuacct.usage"); err != nil {
63
+		return nil, err
64
+	}
65
+
66
+	var (
67
+		deltaProc   = lastCpu - startCpu
68
+		deltaSystem = lastSystem - startSystem
69
+		deltaUsage  = lastUsage - startUsage
70
+	)
71
+	if deltaSystem > 0.0 {
72
+		percentage = ((deltaProc / deltaSystem) * clockTicks) * cpuCount
73
+	}
74
+	// NOTE: a percentage over 100% is valid for POSIX because that means the
75
+	// processes is using multiple cores
76
+	paramData["percentage"] = percentage
77
+
78
+	// Delta usage is in nanoseconds of CPU time so get the usage (in cores) over the sample time.
79
+	paramData["usage"] = deltaUsage / float64(usageSampleDuration.Nanoseconds())
80
+	return paramData, nil
81
+}
82
+
83
+func (s *cpuacctGroup) getProcStarttime(d *data) (float64, error) {
84
+	rawStart, err := system.GetProcessStartTime(d.pid)
85
+	if err != nil {
86
+		return 0, err
87
+	}
88
+	return strconv.ParseFloat(rawStart, 64)
89
+}
90
+
91
+func (s *cpuacctGroup) getSystemCpuUsage(d *data) (float64, error) {
92
+
93
+	f, err := os.Open("/proc/stat")
94
+	if err != nil {
95
+		return 0, err
96
+	}
97
+	defer f.Close()
98
+
99
+	sc := bufio.NewScanner(f)
100
+	for sc.Scan() {
101
+		parts := strings.Fields(sc.Text())
102
+		switch parts[0] {
103
+		case "cpu":
104
+			if len(parts) < 8 {
105
+				return 0, fmt.Errorf("invalid number of cpu fields")
106
+			}
107
+
108
+			var total float64
109
+			for _, i := range parts[1:8] {
110
+				v, err := strconv.ParseFloat(i, 64)
111
+				if err != nil {
112
+					return 0.0, fmt.Errorf("Unable to convert value %s to float: %s", i, err)
113
+				}
114
+				total += v
115
+			}
116
+			return total, nil
117
+		default:
118
+			continue
119
+		}
120
+	}
121
+	return 0, fmt.Errorf("invalid stat format")
122
+}
123
+
124
+func (s *cpuacctGroup) getCpuUsage(d *data, path string) (float64, error) {
125
+	cpuTotal := 0.0
126
+	f, err := os.Open(filepath.Join(path, "cpuacct.stat"))
127
+	if err != nil {
128
+		return 0.0, err
129
+	}
130
+	defer f.Close()
131
+
132
+	sc := bufio.NewScanner(f)
133
+	for sc.Scan() {
134
+		_, v, err := getCgroupParamKeyValue(sc.Text())
135
+		if err != nil {
136
+			return 0.0, err
137
+		}
138
+		// set the raw data in map
139
+		cpuTotal += v
140
+	}
141
+	return cpuTotal, nil
142
+}
0 143
new file mode 100644
... ...
@@ -0,0 +1,108 @@
0
+package fs
1
+
2
+import (
3
+	"bytes"
4
+	"io/ioutil"
5
+	"os"
6
+	"path/filepath"
7
+	"strconv"
8
+)
9
+
10
+type cpusetGroup struct {
11
+}
12
+
13
+func (s *cpusetGroup) Set(d *data) error {
14
+	// we don't want to join this cgroup unless it is specified
15
+	if d.c.CpusetCpus != "" {
16
+		dir, err := d.path("cpuset")
17
+		if err != nil {
18
+			return err
19
+		}
20
+		if err := s.ensureParent(dir); err != nil {
21
+			return err
22
+		}
23
+
24
+		// because we are not using d.join we need to place the pid into the procs file
25
+		// unlike the other subsystems
26
+		if err := writeFile(dir, "cgroup.procs", strconv.Itoa(d.pid)); err != nil {
27
+			return err
28
+		}
29
+		if err := writeFile(dir, "cpuset.cpus", d.c.CpusetCpus); err != nil {
30
+			return err
31
+		}
32
+	}
33
+	return nil
34
+}
35
+
36
+func (s *cpusetGroup) Remove(d *data) error {
37
+	return removePath(d.path("cpuset"))
38
+}
39
+
40
+func (s *cpusetGroup) Stats(d *data) (map[string]float64, error) {
41
+	return nil, ErrNotSupportStat
42
+}
43
+
44
+func (s *cpusetGroup) getSubsystemSettings(parent string) (cpus []byte, mems []byte, err error) {
45
+	if cpus, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.cpus")); err != nil {
46
+		return
47
+	}
48
+	if mems, err = ioutil.ReadFile(filepath.Join(parent, "cpuset.mems")); err != nil {
49
+		return
50
+	}
51
+	return cpus, mems, nil
52
+}
53
+
54
+// ensureParent ensures that the parent directory of current is created
55
+// with the proper cpus and mems files copied from it's parent if the values
56
+// are a file with a new line char
57
+func (s *cpusetGroup) ensureParent(current string) error {
58
+	parent := filepath.Dir(current)
59
+
60
+	if _, err := os.Stat(parent); err != nil {
61
+		if !os.IsNotExist(err) {
62
+			return err
63
+		}
64
+
65
+		if err := s.ensureParent(parent); err != nil {
66
+			return err
67
+		}
68
+	}
69
+
70
+	if err := os.MkdirAll(current, 0755); err != nil && !os.IsExist(err) {
71
+		return err
72
+	}
73
+	return s.copyIfNeeded(current, parent)
74
+}
75
+
76
+// copyIfNeeded copies the cpuset.cpus and cpuset.mems from the parent
77
+// directory to the current directory if the file's contents are 0
78
+func (s *cpusetGroup) copyIfNeeded(current, parent string) error {
79
+	var (
80
+		err                      error
81
+		currentCpus, currentMems []byte
82
+		parentCpus, parentMems   []byte
83
+	)
84
+
85
+	if currentCpus, currentMems, err = s.getSubsystemSettings(current); err != nil {
86
+		return err
87
+	}
88
+	if parentCpus, parentMems, err = s.getSubsystemSettings(parent); err != nil {
89
+		return err
90
+	}
91
+
92
+	if s.isEmpty(currentCpus) {
93
+		if err := writeFile(current, "cpuset.cpus", string(parentCpus)); err != nil {
94
+			return err
95
+		}
96
+	}
97
+	if s.isEmpty(currentMems) {
98
+		if err := writeFile(current, "cpuset.mems", string(parentMems)); err != nil {
99
+			return err
100
+		}
101
+	}
102
+	return nil
103
+}
104
+
105
+func (s *cpusetGroup) isEmpty(b []byte) bool {
106
+	return len(bytes.Trim(b, "\n")) == 0
107
+}
0 108
new file mode 100644
... ...
@@ -0,0 +1,69 @@
0
+package fs
1
+
2
+import (
3
+	"os"
4
+)
5
+
6
+type devicesGroup struct {
7
+}
8
+
9
+func (s *devicesGroup) Set(d *data) error {
10
+	dir, err := d.join("devices")
11
+	if err != nil {
12
+		return err
13
+	}
14
+	defer func() {
15
+		if err != nil {
16
+			os.RemoveAll(dir)
17
+		}
18
+	}()
19
+
20
+	if !d.c.DeviceAccess {
21
+		if err := writeFile(dir, "devices.deny", "a"); err != nil {
22
+			return err
23
+		}
24
+
25
+		allow := []string{
26
+			// allow mknod for any device
27
+			"c *:* m",
28
+			"b *:* m",
29
+
30
+			// /dev/null, zero, full
31
+			"c 1:3 rwm",
32
+			"c 1:5 rwm",
33
+			"c 1:7 rwm",
34
+
35
+			// consoles
36
+			"c 5:1 rwm",
37
+			"c 5:0 rwm",
38
+			"c 4:0 rwm",
39
+			"c 4:1 rwm",
40
+
41
+			// /dev/urandom,/dev/random
42
+			"c 1:9 rwm",
43
+			"c 1:8 rwm",
44
+
45
+			// /dev/pts/ - pts namespaces are "coming soon"
46
+			"c 136:* rwm",
47
+			"c 5:2 rwm",
48
+
49
+			// tuntap
50
+			"c 10:200 rwm",
51
+		}
52
+
53
+		for _, val := range allow {
54
+			if err := writeFile(dir, "devices.allow", val); err != nil {
55
+				return err
56
+			}
57
+		}
58
+	}
59
+	return nil
60
+}
61
+
62
+func (s *devicesGroup) Remove(d *data) error {
63
+	return removePath(d.path("devices"))
64
+}
65
+
66
+func (s *devicesGroup) Stats(d *data) (map[string]float64, error) {
67
+	return nil, ErrNotSupportStat
68
+}
0 69
new file mode 100644
... ...
@@ -0,0 +1,72 @@
0
+package fs
1
+
2
+import (
3
+	"fmt"
4
+	"io/ioutil"
5
+	"os"
6
+	"path/filepath"
7
+	"strconv"
8
+	"strings"
9
+
10
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
11
+)
12
+
13
+type freezerGroup struct {
14
+}
15
+
16
+func (s *freezerGroup) Set(d *data) error {
17
+	dir, err := d.join("freezer")
18
+	if err != nil {
19
+		if err != cgroups.ErrNotFound {
20
+			return err
21
+		}
22
+		return nil
23
+	}
24
+
25
+	if d.c.Freezer != "" {
26
+		if err := writeFile(dir, "freezer.state", d.c.Freezer); err != nil {
27
+			return err
28
+		}
29
+	}
30
+	return nil
31
+}
32
+
33
+func (s *freezerGroup) Remove(d *data) error {
34
+	return removePath(d.path("freezer"))
35
+}
36
+
37
+func (s *freezerGroup) Stats(d *data) (map[string]float64, error) {
38
+	var (
39
+		paramData = make(map[string]float64)
40
+		params    = []string{
41
+			"parent_freezing",
42
+			"self_freezing",
43
+			// comment out right now because this is string "state",
44
+		}
45
+	)
46
+
47
+	path, err := d.path("freezer")
48
+	if err != nil {
49
+		return nil, err
50
+	}
51
+
52
+	for _, param := range params {
53
+		f, err := os.Open(filepath.Join(path, fmt.Sprintf("freezer.%s", param)))
54
+		if err != nil {
55
+			return nil, err
56
+		}
57
+		defer f.Close()
58
+
59
+		data, err := ioutil.ReadAll(f)
60
+		if err != nil {
61
+			return nil, err
62
+		}
63
+
64
+		v, err := strconv.ParseFloat(strings.TrimSuffix(string(data), "\n"), 64)
65
+		if err != nil {
66
+			return nil, err
67
+		}
68
+		paramData[param] = v
69
+	}
70
+	return paramData, nil
71
+}
0 72
new file mode 100644
... ...
@@ -0,0 +1,90 @@
0
+package fs
1
+
2
+import (
3
+	"bufio"
4
+	"fmt"
5
+	"os"
6
+	"path/filepath"
7
+	"strconv"
8
+)
9
+
10
+type memoryGroup struct {
11
+}
12
+
13
+func (s *memoryGroup) Set(d *data) error {
14
+	dir, err := d.join("memory")
15
+	// only return an error for memory if it was not specified
16
+	if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) {
17
+		return err
18
+	}
19
+	defer func() {
20
+		if err != nil {
21
+			os.RemoveAll(dir)
22
+		}
23
+	}()
24
+
25
+	// Only set values if some config was specified.
26
+	if d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0 {
27
+		if d.c.Memory != 0 {
28
+			if err := writeFile(dir, "memory.limit_in_bytes", strconv.FormatInt(d.c.Memory, 10)); err != nil {
29
+				return err
30
+			}
31
+		}
32
+		if d.c.MemoryReservation != 0 {
33
+			if err := writeFile(dir, "memory.soft_limit_in_bytes", strconv.FormatInt(d.c.MemoryReservation, 10)); err != nil {
34
+				return err
35
+			}
36
+		}
37
+		// By default, MemorySwap is set to twice the size of RAM.
38
+		// If you want to omit MemorySwap, set it to `-1'.
39
+		if d.c.MemorySwap != -1 {
40
+			if err := writeFile(dir, "memory.memsw.limit_in_bytes", strconv.FormatInt(d.c.Memory*2, 10)); err != nil {
41
+				return err
42
+			}
43
+		}
44
+	}
45
+	return nil
46
+}
47
+
48
+func (s *memoryGroup) Remove(d *data) error {
49
+	return removePath(d.path("memory"))
50
+}
51
+
52
+func (s *memoryGroup) Stats(d *data) (map[string]float64, error) {
53
+	paramData := make(map[string]float64)
54
+	path, err := d.path("memory")
55
+	if err != nil {
56
+		return nil, err
57
+	}
58
+
59
+	// Set stats from memory.stat.
60
+	statsFile, err := os.Open(filepath.Join(path, "memory.stat"))
61
+	if err != nil {
62
+		return nil, err
63
+	}
64
+	defer statsFile.Close()
65
+
66
+	sc := bufio.NewScanner(statsFile)
67
+	for sc.Scan() {
68
+		t, v, err := getCgroupParamKeyValue(sc.Text())
69
+		if err != nil {
70
+			return nil, err
71
+		}
72
+		paramData[t] = v
73
+	}
74
+
75
+	// Set memory usage and max historical usage.
76
+	params := []string{
77
+		"usage_in_bytes",
78
+		"max_usage_in_bytes",
79
+	}
80
+	for _, param := range params {
81
+		value, err := getCgroupParamFloat64(path, fmt.Sprintf("memory.%s", param))
82
+		if err != nil {
83
+			return nil, err
84
+		}
85
+		paramData[param] = value
86
+	}
87
+
88
+	return paramData, nil
89
+}
0 90
new file mode 100644
... ...
@@ -0,0 +1,123 @@
0
+package fs
1
+
2
+import (
3
+	"testing"
4
+)
5
+
6
+const (
7
+	memoryStatContents = `cache 512
8
+rss 1024`
9
+	memoryUsageContents    = "2048\n"
10
+	memoryMaxUsageContents = "4096\n"
11
+)
12
+
13
+func TestMemoryStats(t *testing.T) {
14
+	helper := NewCgroupTestUtil("memory", t)
15
+	defer helper.cleanup()
16
+	helper.writeFileContents(map[string]string{
17
+		"memory.stat":               memoryStatContents,
18
+		"memory.usage_in_bytes":     memoryUsageContents,
19
+		"memory.max_usage_in_bytes": memoryMaxUsageContents,
20
+	})
21
+
22
+	memory := &memoryGroup{}
23
+	stats, err := memory.Stats(helper.CgroupData)
24
+	if err != nil {
25
+		t.Fatal(err)
26
+	}
27
+	expectedStats := map[string]float64{"cache": 512.0, "rss": 1024.0, "usage_in_bytes": 2048.0, "max_usage_in_bytes": 4096.0}
28
+	expectStats(t, expectedStats, stats)
29
+}
30
+
31
+func TestMemoryStatsNoStatFile(t *testing.T) {
32
+	helper := NewCgroupTestUtil("memory", t)
33
+	defer helper.cleanup()
34
+	helper.writeFileContents(map[string]string{
35
+		"memory.usage_in_bytes":     memoryUsageContents,
36
+		"memory.max_usage_in_bytes": memoryMaxUsageContents,
37
+	})
38
+
39
+	memory := &memoryGroup{}
40
+	_, err := memory.Stats(helper.CgroupData)
41
+	if err == nil {
42
+		t.Fatal("Expected failure")
43
+	}
44
+}
45
+
46
+func TestMemoryStatsNoUsageFile(t *testing.T) {
47
+	helper := NewCgroupTestUtil("memory", t)
48
+	defer helper.cleanup()
49
+	helper.writeFileContents(map[string]string{
50
+		"memory.stat":               memoryStatContents,
51
+		"memory.max_usage_in_bytes": memoryMaxUsageContents,
52
+	})
53
+
54
+	memory := &memoryGroup{}
55
+	_, err := memory.Stats(helper.CgroupData)
56
+	if err == nil {
57
+		t.Fatal("Expected failure")
58
+	}
59
+}
60
+
61
+func TestMemoryStatsNoMaxUsageFile(t *testing.T) {
62
+	helper := NewCgroupTestUtil("memory", t)
63
+	defer helper.cleanup()
64
+	helper.writeFileContents(map[string]string{
65
+		"memory.stat":           memoryStatContents,
66
+		"memory.usage_in_bytes": memoryUsageContents,
67
+	})
68
+
69
+	memory := &memoryGroup{}
70
+	_, err := memory.Stats(helper.CgroupData)
71
+	if err == nil {
72
+		t.Fatal("Expected failure")
73
+	}
74
+}
75
+
76
+func TestMemoryStatsBadStatFile(t *testing.T) {
77
+	helper := NewCgroupTestUtil("memory", t)
78
+	defer helper.cleanup()
79
+	helper.writeFileContents(map[string]string{
80
+		"memory.stat":               "rss rss",
81
+		"memory.usage_in_bytes":     memoryUsageContents,
82
+		"memory.max_usage_in_bytes": memoryMaxUsageContents,
83
+	})
84
+
85
+	memory := &memoryGroup{}
86
+	_, err := memory.Stats(helper.CgroupData)
87
+	if err == nil {
88
+		t.Fatal("Expected failure")
89
+	}
90
+}
91
+
92
+func TestMemoryStatsBadUsageFile(t *testing.T) {
93
+	helper := NewCgroupTestUtil("memory", t)
94
+	defer helper.cleanup()
95
+	helper.writeFileContents(map[string]string{
96
+		"memory.stat":               memoryStatContents,
97
+		"memory.usage_in_bytes":     "bad",
98
+		"memory.max_usage_in_bytes": memoryMaxUsageContents,
99
+	})
100
+
101
+	memory := &memoryGroup{}
102
+	_, err := memory.Stats(helper.CgroupData)
103
+	if err == nil {
104
+		t.Fatal("Expected failure")
105
+	}
106
+}
107
+
108
+func TestMemoryStatsBadMaxUsageFile(t *testing.T) {
109
+	helper := NewCgroupTestUtil("memory", t)
110
+	defer helper.cleanup()
111
+	helper.writeFileContents(map[string]string{
112
+		"memory.stat":               memoryStatContents,
113
+		"memory.usage_in_bytes":     memoryUsageContents,
114
+		"memory.max_usage_in_bytes": "bad",
115
+	})
116
+
117
+	memory := &memoryGroup{}
118
+	_, err := memory.Stats(helper.CgroupData)
119
+	if err == nil {
120
+		t.Fatal("Expected failure")
121
+	}
122
+}
0 123
new file mode 100644
... ...
@@ -0,0 +1,24 @@
0
+package fs
1
+
2
+import (
3
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
4
+)
5
+
6
+type perfEventGroup struct {
7
+}
8
+
9
+func (s *perfEventGroup) Set(d *data) error {
10
+	// we just want to join this group even though we don't set anything
11
+	if _, err := d.join("perf_event"); err != nil && err != cgroups.ErrNotFound {
12
+		return err
13
+	}
14
+	return nil
15
+}
16
+
17
+func (s *perfEventGroup) Remove(d *data) error {
18
+	return removePath(d.path("perf_event"))
19
+}
20
+
21
+func (s *perfEventGroup) Stats(d *data) (map[string]float64, error) {
22
+	return nil, ErrNotSupportStat
23
+}
0 24
new file mode 100644
... ...
@@ -0,0 +1,75 @@
0
+/*
1
+Utility for testing cgroup operations.
2
+
3
+Creates a mock of the cgroup filesystem for the duration of the test.
4
+*/
5
+package fs
6
+
7
+import (
8
+	"fmt"
9
+	"io/ioutil"
10
+	"log"
11
+	"os"
12
+	"testing"
13
+)
14
+
15
+type cgroupTestUtil struct {
16
+	// data to use in tests.
17
+	CgroupData *data
18
+
19
+	// Path to the mock cgroup directory.
20
+	CgroupPath string
21
+
22
+	// Temporary directory to store mock cgroup filesystem.
23
+	tempDir string
24
+	t       *testing.T
25
+}
26
+
27
+// Creates a new test util for the specified subsystem
28
+func NewCgroupTestUtil(subsystem string, t *testing.T) *cgroupTestUtil {
29
+	d := &data{}
30
+	tempDir, err := ioutil.TempDir("", fmt.Sprintf("%s_cgroup_test", subsystem))
31
+	if err != nil {
32
+		t.Fatal(err)
33
+	}
34
+	d.root = tempDir
35
+	testCgroupPath, err := d.path(subsystem)
36
+	if err != nil {
37
+		t.Fatal(err)
38
+	}
39
+
40
+	// Ensure the full mock cgroup path exists.
41
+	err = os.MkdirAll(testCgroupPath, 0755)
42
+	if err != nil {
43
+		t.Fatal(err)
44
+	}
45
+	return &cgroupTestUtil{CgroupData: d, CgroupPath: testCgroupPath, tempDir: tempDir, t: t}
46
+}
47
+
48
+func (c *cgroupTestUtil) cleanup() {
49
+	os.RemoveAll(c.tempDir)
50
+}
51
+
52
+// Write the specified contents on the mock of the specified cgroup files.
53
+func (c *cgroupTestUtil) writeFileContents(fileContents map[string]string) {
54
+	for file, contents := range fileContents {
55
+		err := writeFile(c.CgroupPath, file, contents)
56
+		if err != nil {
57
+			c.t.Fatal(err)
58
+		}
59
+	}
60
+}
61
+
62
+// Expect the specified stats.
63
+func expectStats(t *testing.T, expected, actual map[string]float64) {
64
+	for stat, expectedValue := range expected {
65
+		actualValue, ok := actual[stat]
66
+		if !ok {
67
+			log.Printf("Expected stat %s to exist: %s", stat, actual)
68
+			t.Fail()
69
+		} else if actualValue != expectedValue {
70
+			log.Printf("Expected stats %s to have value %f but had %f instead", stat, expectedValue, actualValue)
71
+			t.Fail()
72
+		}
73
+	}
74
+}
0 75
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+package fs
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"io/ioutil"
6
+	"path/filepath"
7
+	"strconv"
8
+	"strings"
9
+)
10
+
11
+var (
12
+	ErrNotSupportStat = errors.New("stats are not supported for subsystem")
13
+	ErrNotValidFormat = errors.New("line is not a valid key value format")
14
+)
15
+
16
+// Parses a cgroup param and returns as name, value
17
+//  i.e. "io_service_bytes 1234" will return as io_service_bytes, 1234
18
+func getCgroupParamKeyValue(t string) (string, float64, error) {
19
+	parts := strings.Fields(t)
20
+	switch len(parts) {
21
+	case 2:
22
+		value, err := strconv.ParseFloat(parts[1], 64)
23
+		if err != nil {
24
+			return "", 0.0, fmt.Errorf("Unable to convert param value to float: %s", err)
25
+		}
26
+		return parts[0], value, nil
27
+	default:
28
+		return "", 0.0, ErrNotValidFormat
29
+	}
30
+}
31
+
32
+// Gets a single float64 value from the specified cgroup file.
33
+func getCgroupParamFloat64(cgroupPath, cgroupFile string) (float64, error) {
34
+	contents, err := ioutil.ReadFile(filepath.Join(cgroupPath, cgroupFile))
35
+	if err != nil {
36
+		return -1.0, err
37
+	}
38
+	return strconv.ParseFloat(strings.TrimSpace(string(contents)), 64)
39
+}
0 40
new file mode 100644
... ...
@@ -0,0 +1,68 @@
0
+package fs
1
+
2
+import (
3
+	"io/ioutil"
4
+	"os"
5
+	"path/filepath"
6
+	"testing"
7
+)
8
+
9
+const (
10
+	cgroupFile  = "cgroup.file"
11
+	floatValue  = 2048.0
12
+	floatString = "2048"
13
+)
14
+
15
+func TestGetCgroupParamsFloat64(t *testing.T) {
16
+	// Setup tempdir.
17
+	tempDir, err := ioutil.TempDir("", "cgroup_utils_test")
18
+	if err != nil {
19
+		t.Fatal(err)
20
+	}
21
+	defer os.RemoveAll(tempDir)
22
+	tempFile := filepath.Join(tempDir, cgroupFile)
23
+
24
+	// Success.
25
+	err = ioutil.WriteFile(tempFile, []byte(floatString), 0755)
26
+	if err != nil {
27
+		t.Fatal(err)
28
+	}
29
+	value, err := getCgroupParamFloat64(tempDir, cgroupFile)
30
+	if err != nil {
31
+		t.Fatal(err)
32
+	} else if value != floatValue {
33
+		t.Fatalf("Expected %f to equal %f", value, floatValue)
34
+	}
35
+
36
+	// Success with new line.
37
+	err = ioutil.WriteFile(tempFile, []byte(floatString+"\n"), 0755)
38
+	if err != nil {
39
+		t.Fatal(err)
40
+	}
41
+	value, err = getCgroupParamFloat64(tempDir, cgroupFile)
42
+	if err != nil {
43
+		t.Fatal(err)
44
+	} else if value != floatValue {
45
+		t.Fatalf("Expected %f to equal %f", value, floatValue)
46
+	}
47
+
48
+	// Not a float.
49
+	err = ioutil.WriteFile(tempFile, []byte("not-a-float"), 0755)
50
+	if err != nil {
51
+		t.Fatal(err)
52
+	}
53
+	_, err = getCgroupParamFloat64(tempDir, cgroupFile)
54
+	if err == nil {
55
+		t.Fatal("Expecting error, got none")
56
+	}
57
+
58
+	// Unknown file.
59
+	err = os.Remove(tempFile)
60
+	if err != nil {
61
+		t.Fatal(err)
62
+	}
63
+	_, err = getCgroupParamFloat64(tempDir, cgroupFile)
64
+	if err == nil {
65
+		t.Fatal("Expecting error, got none")
66
+	}
67
+}
0 68
new file mode 100644
... ...
@@ -0,0 +1,17 @@
0
+// +build !linux
1
+
2
+package systemd
3
+
4
+import (
5
+	"fmt"
6
+
7
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
8
+)
9
+
10
+func UseSystemd() bool {
11
+	return false
12
+}
13
+
14
+func Apply(c *Cgroup, pid int) (cgroups.ActiveCgroup, error) {
15
+	return nil, fmt.Errorf("Systemd not supported")
16
+}
0 17
new file mode 100644
... ...
@@ -0,0 +1,296 @@
0
+// +build linux
1
+
2
+package systemd
3
+
4
+import (
5
+	"io/ioutil"
6
+	"os"
7
+	"path/filepath"
8
+	"strconv"
9
+	"strings"
10
+	"sync"
11
+
12
+	systemd1 "github.com/coreos/go-systemd/dbus"
13
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
14
+	"github.com/dotcloud/docker/pkg/systemd"
15
+	"github.com/godbus/dbus"
16
+)
17
+
18
+type systemdCgroup struct {
19
+	cleanupDirs []string
20
+}
21
+
22
+type DeviceAllow struct {
23
+	Node        string
24
+	Permissions string
25
+}
26
+
27
+var (
28
+	connLock              sync.Mutex
29
+	theConn               *systemd1.Conn
30
+	hasStartTransientUnit bool
31
+)
32
+
33
+func UseSystemd() bool {
34
+	if !systemd.SdBooted() {
35
+		return false
36
+	}
37
+
38
+	connLock.Lock()
39
+	defer connLock.Unlock()
40
+
41
+	if theConn == nil {
42
+		var err error
43
+		theConn, err = systemd1.New()
44
+		if err != nil {
45
+			return false
46
+		}
47
+
48
+		// Assume we have StartTransientUnit
49
+		hasStartTransientUnit = true
50
+
51
+		// But if we get UnknownMethod error we don't
52
+		if _, err := theConn.StartTransientUnit("test.scope", "invalid"); err != nil {
53
+			if dbusError, ok := err.(dbus.Error); ok {
54
+				if dbusError.Name == "org.freedesktop.DBus.Error.UnknownMethod" {
55
+					hasStartTransientUnit = false
56
+				}
57
+			}
58
+		}
59
+	}
60
+	return hasStartTransientUnit
61
+}
62
+
63
+func getIfaceForUnit(unitName string) string {
64
+	if strings.HasSuffix(unitName, ".scope") {
65
+		return "Scope"
66
+	}
67
+	if strings.HasSuffix(unitName, ".service") {
68
+		return "Service"
69
+	}
70
+	return "Unit"
71
+}
72
+
73
+type cgroupArg struct {
74
+	File  string
75
+	Value string
76
+}
77
+
78
+func Apply(c *cgroups.Cgroup, pid int) (cgroups.ActiveCgroup, error) {
79
+	var (
80
+		unitName   = c.Parent + "-" + c.Name + ".scope"
81
+		slice      = "system.slice"
82
+		properties []systemd1.Property
83
+		cpuArgs    []cgroupArg
84
+		cpusetArgs []cgroupArg
85
+		memoryArgs []cgroupArg
86
+		res        systemdCgroup
87
+	)
88
+
89
+	// First set up things not supported by systemd
90
+
91
+	// -1 disables memorySwap
92
+	if c.MemorySwap >= 0 && (c.Memory != 0 || c.MemorySwap > 0) {
93
+		memorySwap := c.MemorySwap
94
+
95
+		if memorySwap == 0 {
96
+			// By default, MemorySwap is set to twice the size of RAM.
97
+			memorySwap = c.Memory * 2
98
+		}
99
+
100
+		memoryArgs = append(memoryArgs, cgroupArg{"memory.memsw.limit_in_bytes", strconv.FormatInt(memorySwap, 10)})
101
+	}
102
+
103
+	if c.CpusetCpus != "" {
104
+		cpusetArgs = append(cpusetArgs, cgroupArg{"cpuset.cpus", c.CpusetCpus})
105
+	}
106
+
107
+	if c.Slice != "" {
108
+		slice = c.Slice
109
+	}
110
+
111
+	properties = append(properties,
112
+		systemd1.Property{"Slice", dbus.MakeVariant(slice)},
113
+		systemd1.Property{"Description", dbus.MakeVariant("docker container " + c.Name)},
114
+		systemd1.Property{"PIDs", dbus.MakeVariant([]uint32{uint32(pid)})},
115
+	)
116
+
117
+	if !c.DeviceAccess {
118
+		properties = append(properties,
119
+			systemd1.Property{"DevicePolicy", dbus.MakeVariant("strict")},
120
+			systemd1.Property{"DeviceAllow", dbus.MakeVariant([]DeviceAllow{
121
+				{"/dev/null", "rwm"},
122
+				{"/dev/zero", "rwm"},
123
+				{"/dev/full", "rwm"},
124
+				{"/dev/random", "rwm"},
125
+				{"/dev/urandom", "rwm"},
126
+				{"/dev/tty", "rwm"},
127
+				{"/dev/console", "rwm"},
128
+				{"/dev/tty0", "rwm"},
129
+				{"/dev/tty1", "rwm"},
130
+				{"/dev/pts/ptmx", "rwm"},
131
+				// There is no way to add /dev/pts/* here atm, so we hack this manually below
132
+				// /dev/pts/* (how to add this?)
133
+				// Same with tuntap, which doesn't exist as a node most of the time
134
+			})})
135
+	}
136
+
137
+	// Always enable accounting, this gets us the same behaviour as the fs implementation,
138
+	// plus the kernel has some problems with joining the memory cgroup at a later time.
139
+	properties = append(properties,
140
+		systemd1.Property{"MemoryAccounting", dbus.MakeVariant(true)},
141
+		systemd1.Property{"CPUAccounting", dbus.MakeVariant(true)},
142
+		systemd1.Property{"BlockIOAccounting", dbus.MakeVariant(true)})
143
+
144
+	if c.Memory != 0 {
145
+		properties = append(properties,
146
+			systemd1.Property{"MemoryLimit", dbus.MakeVariant(uint64(c.Memory))})
147
+	}
148
+	// TODO: MemoryReservation and MemorySwap not available in systemd
149
+
150
+	if c.CpuShares != 0 {
151
+		properties = append(properties,
152
+			systemd1.Property{"CPUShares", dbus.MakeVariant(uint64(c.CpuShares))})
153
+	}
154
+
155
+	if _, err := theConn.StartTransientUnit(unitName, "replace", properties...); err != nil {
156
+		return nil, err
157
+	}
158
+
159
+	// To work around the lack of /dev/pts/* support above we need to manually add these
160
+	// so, ask systemd for the cgroup used
161
+	props, err := theConn.GetUnitTypeProperties(unitName, getIfaceForUnit(unitName))
162
+	if err != nil {
163
+		return nil, err
164
+	}
165
+
166
+	cgroup := props["ControlGroup"].(string)
167
+
168
+	if !c.DeviceAccess {
169
+		mountpoint, err := cgroups.FindCgroupMountpoint("devices")
170
+		if err != nil {
171
+			return nil, err
172
+		}
173
+
174
+		path := filepath.Join(mountpoint, cgroup)
175
+
176
+		// /dev/pts/*
177
+		if err := ioutil.WriteFile(filepath.Join(path, "devices.allow"), []byte("c 136:* rwm"), 0700); err != nil {
178
+			return nil, err
179
+		}
180
+		// tuntap
181
+		if err := ioutil.WriteFile(filepath.Join(path, "devices.allow"), []byte("c 10:200 rwm"), 0700); err != nil {
182
+			return nil, err
183
+		}
184
+	}
185
+
186
+	if len(cpuArgs) != 0 {
187
+		mountpoint, err := cgroups.FindCgroupMountpoint("cpu")
188
+		if err != nil {
189
+			return nil, err
190
+		}
191
+
192
+		path := filepath.Join(mountpoint, cgroup)
193
+
194
+		for _, arg := range cpuArgs {
195
+			if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
196
+				return nil, err
197
+			}
198
+		}
199
+	}
200
+
201
+	if len(memoryArgs) != 0 {
202
+		mountpoint, err := cgroups.FindCgroupMountpoint("memory")
203
+		if err != nil {
204
+			return nil, err
205
+		}
206
+
207
+		path := filepath.Join(mountpoint, cgroup)
208
+
209
+		for _, arg := range memoryArgs {
210
+			if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
211
+				return nil, err
212
+			}
213
+		}
214
+	}
215
+
216
+	if len(cpusetArgs) != 0 {
217
+		// systemd does not atm set up the cpuset controller, so we must manually
218
+		// join it. Additionally that is a very finicky controller where each
219
+		// level must have a full setup as the default for a new directory is "no cpus",
220
+		// so we avoid using any hierarchies here, creating a toplevel directory.
221
+		mountpoint, err := cgroups.FindCgroupMountpoint("cpuset")
222
+		if err != nil {
223
+			return nil, err
224
+		}
225
+		initPath, err := cgroups.GetInitCgroupDir("cpuset")
226
+		if err != nil {
227
+			return nil, err
228
+		}
229
+
230
+		rootPath := filepath.Join(mountpoint, initPath)
231
+
232
+		path := filepath.Join(mountpoint, initPath, c.Parent+"-"+c.Name)
233
+
234
+		res.cleanupDirs = append(res.cleanupDirs, path)
235
+
236
+		if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) {
237
+			return nil, err
238
+		}
239
+
240
+		foundCpus := false
241
+		foundMems := false
242
+
243
+		for _, arg := range cpusetArgs {
244
+			if arg.File == "cpuset.cpus" {
245
+				foundCpus = true
246
+			}
247
+			if arg.File == "cpuset.mems" {
248
+				foundMems = true
249
+			}
250
+			if err := ioutil.WriteFile(filepath.Join(path, arg.File), []byte(arg.Value), 0700); err != nil {
251
+				return nil, err
252
+			}
253
+		}
254
+
255
+		// These are required, if not specified inherit from parent
256
+		if !foundCpus {
257
+			s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.cpus"))
258
+			if err != nil {
259
+				return nil, err
260
+			}
261
+
262
+			if err := ioutil.WriteFile(filepath.Join(path, "cpuset.cpus"), s, 0700); err != nil {
263
+				return nil, err
264
+			}
265
+		}
266
+
267
+		// These are required, if not specified inherit from parent
268
+		if !foundMems {
269
+			s, err := ioutil.ReadFile(filepath.Join(rootPath, "cpuset.mems"))
270
+			if err != nil {
271
+				return nil, err
272
+			}
273
+
274
+			if err := ioutil.WriteFile(filepath.Join(path, "cpuset.mems"), s, 0700); err != nil {
275
+				return nil, err
276
+			}
277
+		}
278
+
279
+		if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil {
280
+			return nil, err
281
+		}
282
+	}
283
+
284
+	return &res, nil
285
+}
286
+
287
+func (c *systemdCgroup) Cleanup() error {
288
+	// systemd cleans up, we don't need to do much
289
+
290
+	for _, path := range c.cleanupDirs {
291
+		os.RemoveAll(path)
292
+	}
293
+
294
+	return nil
295
+}
0 296
new file mode 100644
... ...
@@ -0,0 +1,67 @@
0
+package cgroups
1
+
2
+import (
3
+	"bufio"
4
+	"io"
5
+	"os"
6
+	"strings"
7
+
8
+	"github.com/dotcloud/docker/pkg/mount"
9
+)
10
+
11
+// https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt
12
+func FindCgroupMountpoint(subsystem string) (string, error) {
13
+	mounts, err := mount.GetMounts()
14
+	if err != nil {
15
+		return "", err
16
+	}
17
+
18
+	for _, mount := range mounts {
19
+		if mount.Fstype == "cgroup" {
20
+			for _, opt := range strings.Split(mount.VfsOpts, ",") {
21
+				if opt == subsystem {
22
+					return mount.Mountpoint, nil
23
+				}
24
+			}
25
+		}
26
+	}
27
+	return "", ErrNotFound
28
+}
29
+
30
+// Returns the relative path to the cgroup docker is running in.
31
+func GetThisCgroupDir(subsystem string) (string, error) {
32
+	f, err := os.Open("/proc/self/cgroup")
33
+	if err != nil {
34
+		return "", err
35
+	}
36
+	defer f.Close()
37
+
38
+	return parseCgroupFile(subsystem, f)
39
+}
40
+
41
+func GetInitCgroupDir(subsystem string) (string, error) {
42
+	f, err := os.Open("/proc/1/cgroup")
43
+	if err != nil {
44
+		return "", err
45
+	}
46
+	defer f.Close()
47
+
48
+	return parseCgroupFile(subsystem, f)
49
+}
50
+
51
+func parseCgroupFile(subsystem string, r io.Reader) (string, error) {
52
+	s := bufio.NewScanner(r)
53
+	for s.Scan() {
54
+		if err := s.Err(); err != nil {
55
+			return "", err
56
+		}
57
+		text := s.Text()
58
+		parts := strings.Split(text, ":")
59
+		for _, subs := range strings.Split(parts[1], ",") {
60
+			if subs == subsystem {
61
+				return parts[2], nil
62
+			}
63
+		}
64
+	}
65
+	return "", ErrNotFound
66
+}
... ...
@@ -1,7 +1,7 @@
1 1
 package libcontainer
2 2
 
3 3
 import (
4
-	"github.com/dotcloud/docker/pkg/cgroups"
4
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
5 5
 )
6 6
 
7 7
 // Context is a generic key value pair that allows
... ...
@@ -7,10 +7,10 @@ import (
7 7
 	"os/exec"
8 8
 	"syscall"
9 9
 
10
-	"github.com/dotcloud/docker/pkg/cgroups"
11
-	"github.com/dotcloud/docker/pkg/cgroups/fs"
12
-	"github.com/dotcloud/docker/pkg/cgroups/systemd"
13 10
 	"github.com/dotcloud/docker/pkg/libcontainer"
11
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
12
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups/fs"
13
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups/systemd"
14 14
 	"github.com/dotcloud/docker/pkg/libcontainer/network"
15 15
 	"github.com/dotcloud/docker/pkg/system"
16 16
 )
... ...
@@ -3,8 +3,8 @@
3 3
 package nsinit
4 4
 
5 5
 import (
6
-	"github.com/dotcloud/docker/pkg/cgroups"
7 6
 	"github.com/dotcloud/docker/pkg/libcontainer"
7
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
8 8
 )
9 9
 
10 10
 func Exec(container *libcontainer.Container, term Terminal, rootfs, dataPath string, args []string, createCommand CreateCommand, startCallback func()) (int, error) {
... ...
@@ -1,11 +1,12 @@
1 1
 package sysinfo
2 2
 
3 3
 import (
4
-	"github.com/dotcloud/docker/pkg/cgroups"
5 4
 	"io/ioutil"
6 5
 	"log"
7 6
 	"os"
8 7
 	"path"
8
+
9
+	"github.com/dotcloud/docker/pkg/libcontainer/cgroups"
9 10
 )
10 11
 
11 12
 type SysInfo struct {