Implement client side display for stats

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
Upstream-commit: 2640a10bca29c4a4199c906a26f658aac8a68dc2
Component: engine
This commit is contained in:
Michael Crosby
2015-01-20 20:21:46 -08:00
parent 947efff180
commit 03d1b8dbe7
7 changed files with 340 additions and 10 deletions
@@ -106,8 +106,10 @@ type Resources struct {
type ResourceStats struct {
*libcontainer.ContainerStats
Read time.Time `json:"read"`
ClockTicks int `json:"clock_ticks"`
Read time.Time `json:"read"`
ClockTicks int `json:"clock_ticks"`
MemoryLimit int64 `json:"memory_limit"`
SystemUsage uint64 `json:"system_usage"`
}
type Mount struct {
@@ -2,14 +2,21 @@ package execdrivers
import (
"fmt"
"path"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/lxc"
"github.com/docker/docker/daemon/execdriver/native"
"github.com/docker/docker/pkg/sysinfo"
"path"
"github.com/docker/docker/pkg/system"
)
func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
meminfo, err := system.ReadMemInfo()
if err != nil {
return nil, err
}
switch name {
case "lxc":
// we want to give the lxc driver the full docker root because it needs
@@ -17,7 +24,7 @@ func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdrive
// to be backwards compatible
return lxc.NewDriver(root, initPath, sysInfo.AppArmor)
case "native":
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath)
return native.NewDriver(path.Join(root, "execdriver", "native"), initPath, meminfo.MemTotal/1000)
}
return nil, fmt.Errorf("unknown exec driver %s", name)
}
@@ -42,23 +42,23 @@ type driver struct {
root string
initPath string
activeContainers map[string]*activeContainer
machineMemory int64
sync.Mutex
}
func NewDriver(root, initPath string) (*driver, error) {
func NewDriver(root, initPath string, machineMemory int64) (*driver, error) {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, err
}
// native driver root is at docker_root/execdriver/native. Put apparmor at docker_root
if err := apparmor.InstallDefaultProfile(); err != nil {
return nil, err
}
return &driver{
root: root,
initPath: initPath,
activeContainers: make(map[string]*activeContainer),
machineMemory: machineMemory,
}, nil
}
@@ -281,6 +281,7 @@ func (d *driver) Clean(id string) error {
}
func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
c := d.activeContainers[id]
state, err := libcontainer.GetState(filepath.Join(d.root, id))
if err != nil {
return nil, err
@@ -290,10 +291,15 @@ func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
if err != nil {
return nil, err
}
memoryLimit := c.container.Cgroups.Memory
if memoryLimit == 0 {
memoryLimit = d.machineMemory
}
return &execdriver.ResourceStats{
ContainerStats: stats,
ClockTicks: system.GetClockTicks(),
Read: now,
MemoryLimit: memoryLimit,
}, nil
}
+13 -3
View File
@@ -8,6 +8,7 @@ import (
"github.com/docker/docker/engine"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/stats"
)
func (daemon *Daemon) ContainerStart(job *engine.Job) engine.Status {
@@ -80,15 +81,24 @@ func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.
}
func (daemon *Daemon) ContainerStats(job *engine.Job) engine.Status {
stats, err := daemon.SubscribeToContainerStats(job.Args[0])
s, err := daemon.SubscribeToContainerStats(job.Args[0])
if err != nil {
return job.Error(err)
}
enc := json.NewEncoder(job.Stdout)
for update := range stats {
if err := enc.Encode(update); err != nil {
for update := range s {
ss := stats.ToStats(update.ContainerStats)
ss.MemoryStats.Limit = uint64(update.MemoryLimit)
ss.Read = update.Read
ss.ClockTicks = update.ClockTicks
ss.CpuStats.SystemUsage = update.SystemUsage
if err := enc.Encode(ss); err != nil {
return job.Error(err)
}
}
return engine.StatusOK
}
func mapToAPIStats() {
}
@@ -1,6 +1,11 @@
package daemon
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
"sync"
"time"
@@ -55,12 +60,18 @@ func (s *statsCollector) start() {
log.Debugf("starting collection of container stats")
s.m.Lock()
for id, d := range s.containers {
systemUsage, err := getSystemCpuUsage()
if err != nil {
log.Errorf("collecting system cpu usage for %s: %v", id, err)
continue
}
stats, err := d.c.Stats()
if err != nil {
// TODO: @crosbymichael evict container depending on error
log.Errorf("collecting stats for %s: %v", id, err)
continue
}
stats.SystemUsage = systemUsage
for _, sub := range s.containers[id].subs {
sub <- stats
}
@@ -69,3 +80,36 @@ func (s *statsCollector) start() {
}
}()
}
// returns value in nanoseconds
func getSystemCpuUsage() (uint64, error) {
f, err := os.Open("/proc/stat")
if err != nil {
return 0, err
}
defer f.Close()
sc := bufio.NewScanner(f)
for sc.Scan() {
parts := strings.Fields(sc.Text())
switch parts[0] {
case "cpu":
if len(parts) < 8 {
return 0, fmt.Errorf("invalid number of cpu fields")
}
var total uint64
for _, i := range parts[1:8] {
v, err := strconv.ParseUint(i, 10, 64)
if err != nil {
return 0.0, fmt.Errorf("Unable to convert value %s to int: %s", i, err)
}
total += v
}
return total * 1000000000, nil
default:
continue
}
}
return 0, fmt.Errorf("invalid stat format")
}