Browse code

Implement container stats collection in daemon

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>

Michael Crosby authored on 2015/01/08 07:43:04
Showing 8 changed files
... ...
@@ -411,6 +411,19 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo
411 411
 	return nil
412 412
 }
413 413
 
414
+func getContainersStats(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
415
+	if err := parseForm(r); err != nil {
416
+		return err
417
+	}
418
+	if vars == nil {
419
+		return fmt.Errorf("Missing parameter")
420
+	}
421
+	name := vars["name"]
422
+	job := eng.Job("container_stats", name)
423
+	streamJSON(job, w, true)
424
+	return job.Run()
425
+}
426
+
414 427
 func getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
415 428
 	if err := parseForm(r); err != nil {
416 429
 		return err
... ...
@@ -1323,6 +1336,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, dockerVersion st
1323 1323
 			"/containers/{name:.*}/json":      getContainersByName,
1324 1324
 			"/containers/{name:.*}/top":       getContainersTop,
1325 1325
 			"/containers/{name:.*}/logs":      getContainersLogs,
1326
+			"/containers/{name:.*}/stats":     getContainersStats,
1326 1327
 			"/containers/{name:.*}/attach/ws": wsContainersAttach,
1327 1328
 			"/exec/{id:.*}/json":              getExecByID,
1328 1329
 		},
... ...
@@ -1414,3 +1414,10 @@ func (container *Container) getNetworkedContainer() (*Container, error) {
1414 1414
 		return nil, fmt.Errorf("network mode not set to container")
1415 1415
 	}
1416 1416
 }
1417
+
1418
+func (container *Container) Stats() (*execdriver.ResourceStats, error) {
1419
+	if !container.IsRunning() {
1420
+		return nil, fmt.Errorf("cannot collect stats on a non running container")
1421
+	}
1422
+	return container.daemon.Stats(container)
1423
+}
... ...
@@ -104,6 +104,7 @@ type Daemon struct {
104 104
 	driver         graphdriver.Driver
105 105
 	execDriver     execdriver.Driver
106 106
 	trustStore     *trust.TrustStore
107
+	statsCollector *statsCollector
107 108
 }
108 109
 
109 110
 // Install installs daemon capabilities to eng.
... ...
@@ -116,6 +117,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error {
116 116
 		"container_copy":    daemon.ContainerCopy,
117 117
 		"container_rename":  daemon.ContainerRename,
118 118
 		"container_inspect": daemon.ContainerInspect,
119
+		"container_stats":   daemon.ContainerStats,
119 120
 		"containers":        daemon.Containers,
120 121
 		"create":            daemon.ContainerCreate,
121 122
 		"rm":                daemon.ContainerRm,
... ...
@@ -982,6 +984,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
982 982
 		execDriver:     ed,
983 983
 		eng:            eng,
984 984
 		trustStore:     t,
985
+		statsCollector: newStatsCollector(1 * time.Second),
985 986
 	}
986 987
 	if err := daemon.restore(); err != nil {
987 988
 		return nil, err
... ...
@@ -1092,6 +1095,19 @@ func (daemon *Daemon) Kill(c *Container, sig int) error {
1092 1092
 	return daemon.execDriver.Kill(c.command, sig)
1093 1093
 }
1094 1094
 
1095
+func (daemon *Daemon) Stats(c *Container) (*execdriver.ResourceStats, error) {
1096
+	return daemon.execDriver.Stats(c.ID)
1097
+}
1098
+
1099
+func (daemon *Daemon) SubscribeToContainerStats(name string) (<-chan *execdriver.ResourceStats, error) {
1100
+	c := daemon.Get(name)
1101
+	if c == nil {
1102
+		return nil, fmt.Errorf("no such container")
1103
+	}
1104
+	ch := daemon.statsCollector.collect(c)
1105
+	return ch, nil
1106
+}
1107
+
1095 1108
 // Nuke kills all containers then removes all content
1096 1109
 // from the content root, including images, volumes and
1097 1110
 // container filesystems.
... ...
@@ -5,7 +5,9 @@ import (
5 5
 	"io"
6 6
 	"os"
7 7
 	"os/exec"
8
+	"time"
8 9
 
10
+	"github.com/docker/libcontainer"
9 11
 	"github.com/docker/libcontainer/devices"
10 12
 )
11 13
 
... ...
@@ -61,6 +63,7 @@ type Driver interface {
61 61
 	GetPidsForContainer(id string) ([]int, error) // Returns a list of pids for the given container.
62 62
 	Terminate(c *Command) error                   // kill it with fire
63 63
 	Clean(id string) error                        // clean all traces of container exec
64
+	Stats(id string) (*ResourceStats, error)      // Get resource stats for a running container
64 65
 }
65 66
 
66 67
 // Network settings of the container
... ...
@@ -101,6 +104,12 @@ type Resources struct {
101 101
 	Cpuset     string `json:"cpuset"`
102 102
 }
103 103
 
104
+type ResourceStats struct {
105
+	*libcontainer.ContainerStats
106
+	Read       time.Time `json:"read"`
107
+	ClockTicks int       `json:"clock_ticks"`
108
+}
109
+
104 110
 type Mount struct {
105 111
 	Source      string `json:"source"`
106 112
 	Destination string `json:"destination"`
... ...
@@ -524,3 +524,8 @@ func (t *TtyConsole) Close() error {
524 524
 func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) {
525 525
 	return -1, ErrExec
526 526
 }
527
+
528
+func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
529
+	return nil, fmt.Errorf("container stats are not support with LXC")
530
+
531
+}
... ...
@@ -13,6 +13,7 @@ import (
13 13
 	"strings"
14 14
 	"sync"
15 15
 	"syscall"
16
+	"time"
16 17
 
17 18
 	log "github.com/Sirupsen/logrus"
18 19
 	"github.com/docker/docker/daemon/execdriver"
... ...
@@ -279,6 +280,23 @@ func (d *driver) Clean(id string) error {
279 279
 	return os.RemoveAll(filepath.Join(d.root, id))
280 280
 }
281 281
 
282
+func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
283
+	state, err := libcontainer.GetState(filepath.Join(d.root, id))
284
+	if err != nil {
285
+		return nil, err
286
+	}
287
+	now := time.Now()
288
+	stats, err := libcontainer.GetStats(nil, state)
289
+	if err != nil {
290
+		return nil, err
291
+	}
292
+	return &execdriver.ResourceStats{
293
+		ContainerStats: stats,
294
+		ClockTicks:     system.GetClockTicks(),
295
+		Read:           now,
296
+	}, nil
297
+}
298
+
282 299
 func getEnv(key string, env []string) string {
283 300
 	for _, pair := range env {
284 301
 		parts := strings.Split(pair, "=")
... ...
@@ -1,6 +1,7 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"encoding/json"
4 5
 	"fmt"
5 6
 	"os"
6 7
 	"strings"
... ...
@@ -77,3 +78,17 @@ func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.
77 77
 
78 78
 	return nil
79 79
 }
80
+
81
+func (daemon *Daemon) ContainerStats(job *engine.Job) engine.Status {
82
+	stats, err := daemon.SubscribeToContainerStats(job.Args[0])
83
+	if err != nil {
84
+		return job.Error(err)
85
+	}
86
+	enc := json.NewEncoder(job.Stdout)
87
+	for update := range stats {
88
+		if err := enc.Encode(update); err != nil {
89
+			return job.Error(err)
90
+		}
91
+	}
92
+	return engine.StatusOK
93
+}
80 94
new file mode 100644
... ...
@@ -0,0 +1,71 @@
0
+package daemon
1
+
2
+import (
3
+	"sync"
4
+	"time"
5
+
6
+	log "github.com/Sirupsen/logrus"
7
+	"github.com/docker/docker/daemon/execdriver"
8
+)
9
+
10
+func newStatsCollector(interval time.Duration) *statsCollector {
11
+	s := &statsCollector{
12
+		interval:   interval,
13
+		containers: make(map[string]*statsCollectorData),
14
+	}
15
+	s.start()
16
+	return s
17
+}
18
+
19
+type statsCollectorData struct {
20
+	c         *Container
21
+	lastStats *execdriver.ResourceStats
22
+	subs      []chan *execdriver.ResourceStats
23
+}
24
+
25
+// statsCollector manages and provides container resource stats
26
+type statsCollector struct {
27
+	m          sync.Mutex
28
+	interval   time.Duration
29
+	containers map[string]*statsCollectorData
30
+}
31
+
32
+func (s *statsCollector) collect(c *Container) <-chan *execdriver.ResourceStats {
33
+	s.m.Lock()
34
+	ch := make(chan *execdriver.ResourceStats, 1024)
35
+	s.containers[c.ID] = &statsCollectorData{
36
+		c: c,
37
+		subs: []chan *execdriver.ResourceStats{
38
+			ch,
39
+		},
40
+	}
41
+	s.m.Unlock()
42
+	return ch
43
+}
44
+
45
+func (s *statsCollector) stopCollection(c *Container) {
46
+	s.m.Lock()
47
+	delete(s.containers, c.ID)
48
+	s.m.Unlock()
49
+}
50
+
51
+func (s *statsCollector) start() {
52
+	go func() {
53
+		for _ = range time.Tick(s.interval) {
54
+			log.Debugf("starting collection of container stats")
55
+			s.m.Lock()
56
+			for id, d := range s.containers {
57
+				stats, err := d.c.Stats()
58
+				if err != nil {
59
+					// TODO: @crosbymichael evict container depending on error
60
+					log.Errorf("collecting stats for %s: %v", id, err)
61
+					continue
62
+				}
63
+				for _, sub := range s.containers[id].subs {
64
+					sub <- stats
65
+				}
66
+			}
67
+			s.m.Unlock()
68
+		}
69
+	}()
70
+}