Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
| ... | ... |
@@ -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 |
+} |