diff --git a/container/docker/handler.go b/container/docker/handler.go index 05f0d0a3..b9d7c1f6 100644 --- a/container/docker/handler.go +++ b/container/docker/handler.go @@ -23,6 +23,7 @@ import ( "os" "path" "strings" + "time" "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" @@ -68,6 +69,9 @@ type dockerContainerHandler struct { usesAufsDriver bool fsInfo fs.FsInfo storageDirs []string + + // Time at which this container was created. + creationTime time.Time } func DockerStateDir() string { @@ -119,6 +123,7 @@ func newDockerContainerHandler( if err != nil { return nil, fmt.Errorf("failed to inspect container %q: %v", id, err) } + handler.creationTime = ctnr.Created // Add the name and bare ID as aliases of the container. handler.aliases = append(handler.aliases, strings.TrimPrefix(ctnr.Name, "/")) @@ -235,23 +240,23 @@ func libcontainerConfigToContainerSpec(config *libcontainer.Config, mi *info.Mac return spec } -func (self *dockerContainerHandler) GetSpec() (spec info.ContainerSpec, err error) { +func (self *dockerContainerHandler) GetSpec() (info.ContainerSpec, error) { mi, err := self.machineInfoFactory.GetMachineInfo() if err != nil { - return + return info.ContainerSpec{}, err } libcontainerConfig, err := self.readLibcontainerConfig() if err != nil { - return + return info.ContainerSpec{}, err } - spec = libcontainerConfigToContainerSpec(libcontainerConfig, mi) - + spec := libcontainerConfigToContainerSpec(libcontainerConfig, mi) + spec.CreationTime = self.creationTime if self.usesAufsDriver { spec.HasFilesystem = true } - return + return spec, err } func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error { diff --git a/container/raw/handler.go b/container/raw/handler.go index 83a6c231..5edfb9b4 100644 --- a/container/raw/handler.go +++ b/container/raw/handler.go @@ -18,9 +18,11 @@ package raw import ( "fmt" "io/ioutil" + "os" "path" "strconv" "strings" + "time" "code.google.com/p/go.exp/inotify" dockerlibcontainer "github.com/docker/libcontainer" @@ -180,6 +182,20 @@ func (self *rawContainerHandler) GetSpec() (info.ContainerSpec, error) { // The raw driver assumes unified hierarchy containers. + // Get the lowest creation time from all hierarchies as the container creation time. + now := time.Now() + lowestTime := now + for _, cgroupPath := range self.cgroupPaths { + // The modified time of the cgroup directory is when the container was created. + fi, err := os.Stat(cgroupPath) + if err == nil && fi.ModTime().Before(lowestTime) { + lowestTime = fi.ModTime() + } + } + if lowestTime != now { + spec.CreationTime = lowestTime + } + // Get machine info. mi, err := self.machineInfoFactory.GetMachineInfo() if err != nil { diff --git a/info/container.go b/info/container.go index b5ef4fcf..b3816336 100644 --- a/info/container.go +++ b/info/container.go @@ -40,6 +40,9 @@ type MemorySpec struct { } type ContainerSpec struct { + // Time at which the container was created. + CreationTime time.Time `json:"creation_time,omitempty"` + HasCpu bool `json:"has_cpu"` Cpu CpuSpec `json:"cpu,omitempty"` @@ -88,6 +91,7 @@ type ContainerInfo struct { Stats []*ContainerStats `json:"stats,omitempty"` } +// TODO(vmarmol): Refactor to not need this equality comparison. // ContainerInfo may be (un)marshaled by json or other en/decoder. In that // case, the Timestamp field in each stats/sample may not be precisely // en/decoded. This will lead to small but acceptable differences between a @@ -111,7 +115,7 @@ func (self *ContainerInfo) Eq(b *ContainerInfo) bool { if !reflect.DeepEqual(self.Subcontainers, b.Subcontainers) { return false } - if !reflect.DeepEqual(self.Spec, b.Spec) { + if !self.Spec.Eq(&b.Spec) { return false } @@ -125,6 +129,37 @@ func (self *ContainerInfo) Eq(b *ContainerInfo) bool { return true } +func (self *ContainerSpec) Eq(b *ContainerSpec) bool { + // Creation within 1s of each other. + diff := self.CreationTime.Sub(b.CreationTime) + if (diff > time.Second) || (diff < -time.Second) { + return false + } + + if self.HasCpu != b.HasCpu { + return false + } + if !reflect.DeepEqual(self.Cpu, b.Cpu) { + return false + } + if self.HasMemory != b.HasMemory { + return false + } + if !reflect.DeepEqual(self.Memory, b.Memory) { + return false + } + if self.HasNetwork != b.HasNetwork { + return false + } + if self.HasFilesystem != b.HasFilesystem { + return false + } + if self.HasDiskIo != b.HasDiskIo { + return false + } + return true +} + func (self *ContainerInfo) StatsAfter(ref time.Time) []*ContainerStats { n := len(self.Stats) + 1 for i, s := range self.Stats { diff --git a/info/test/datagen.go b/info/test/datagen.go index 519e28c1..1be1dd19 100644 --- a/info/test/datagen.go +++ b/info/test/datagen.go @@ -51,8 +51,9 @@ func GenerateRandomStats(numStats, numCores int, duration time.Duration) []*info func GenerateRandomContainerSpec(numCores int) info.ContainerSpec { ret := info.ContainerSpec{ - Cpu: info.CpuSpec{}, - Memory: info.MemorySpec{}, + CreationTime: time.Now(), + Cpu: info.CpuSpec{}, + Memory: info.MemorySpec{}, } ret.Cpu.Limit = uint64(1000 + rand.Int63n(2000)) ret.Cpu.MaxLimit = uint64(1000 + rand.Int63n(2000))