diff --git a/.travis.yml b/.travis.yml index b1276692..d3718380 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,12 @@ language: go go: - - 1.1 + - 1.2 +before_script: + - go get github.com/stretchr/testify/mock + - go get github.com/kr/pretty script: - go test -v -race github.com/google/cadvisor/container - - go test -v -race github.com/google/cadvisor/info - - go test -v -race github.com/google/cadvisor/client - - go test -v -race github.com/google/cadvisor/sampling - - go build -race github.com/google/cadvisor + - go test -v github.com/google/cadvisor/info + - go test -v github.com/google/cadvisor/client + - go test -v github.com/google/cadvisor/sampling + - go build github.com/google/cadvisor diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f5b15170 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM google/golang-runtime +MAINTAINER dengnan@google.com vmarmol@google.com proppy@google.com + +# TODO(vmarmol): Build from source. +# Get lmctfy and its dependencies. +RUN apt-get update -y --force-yes && apt-get install -y --no-install-recommends --force-yes pkg-config libapparmor1 +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libre2.so.0.0.0 /usr/lib/libre2.so.0 +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/lmctfy /usr/bin/lmctfy +RUN chmod +x /usr/bin/lmctfy + +# Install libprotobuf8. +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libprotobuf8_2.5.0-9_amd64.deb /tmp/libprotobuf8_2.5.0-9_amd64.deb +ADD http://storage.googleapis.com/cadvisor-bin/lmctfy/libc6_2.19-1_amd64.deb /tmp/libc6_2.19-1_amd64.deb +RUN dpkg -i /tmp/libc6_2.19-1_amd64.deb /tmp/libprotobuf8_2.5.0-9_amd64.deb + +# The image builds the app and exposes it on 8080. diff --git a/cadvisor.go b/cadvisor.go index 931dc4b9..c145c74e 100644 --- a/cadvisor.go +++ b/cadvisor.go @@ -25,6 +25,7 @@ import ( "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/container/lmctfy" + "github.com/google/cadvisor/info" "github.com/google/cadvisor/manager" "github.com/google/cadvisor/pages" "github.com/google/cadvisor/pages/static" @@ -92,6 +93,7 @@ func main() { go containerManager.Start() + log.Printf("Starting cAdvisor version: %q", info.VERSION) log.Print("About to serve on port ", *argPort) addr := fmt.Sprintf(":%v", *argPort) diff --git a/container/container.go b/container/container.go index c2661bba..45af3e1f 100644 --- a/container/container.go +++ b/container/container.go @@ -30,6 +30,7 @@ type ListType int // Interface for container operation handlers. type ContainerHandler interface { + ContainerReference() (info.ContainerReference, error) GetSpec() (*info.ContainerSpec, error) GetStats() (*info.ContainerStats, error) ListContainers(listType ListType) ([]info.ContainerReference, error) diff --git a/container/docker/factory.go b/container/docker/factory.go index ba8ffe51..b176f82b 100644 --- a/container/docker/factory.go +++ b/container/docker/factory.go @@ -40,11 +40,11 @@ func (self *dockerFactory) NewContainerHandler(name string) (handler container.C if err != nil { return } - handler = &dockerContainerHandler{ - client: client, - name: name, - machineInfoFactory: self.machineInfoFactory, - } + handler, err = newDockerContainerHandler( + client, + name, + self.machineInfoFactory, + ) return } diff --git a/container/docker/handler.go b/container/docker/handler.go index cb791108..c5ca9d0e 100644 --- a/container/docker/handler.go +++ b/container/docker/handler.go @@ -35,10 +35,43 @@ import ( type dockerContainerHandler struct { client *docker.Client name string + aliases []string machineInfoFactory info.MachineInfoFactory container.NoStatsSummary } +func newDockerContainerHandler( + client *docker.Client, + name string, + machineInfoFactory info.MachineInfoFactory, +) (container.ContainerHandler, error) { + handler := &dockerContainerHandler{ + client: client, + name: name, + machineInfoFactory: machineInfoFactory, + } + if !handler.isDockerContainer() { + return handler, nil + } + _, id, err := handler.splitName() + if err != nil { + return nil, fmt.Errorf("invalid docker container %v: %v", name, err) + } + ctnr, err := client.InspectContainer(id) + if err != nil { + return nil, fmt.Errorf("unable to inspect container %v: %v", name, err) + } + handler.aliases = append(handler.aliases, path.Join("/docker", ctnr.Name)) + return handler, nil +} + +func (self *dockerContainerHandler) ContainerReference() (info.ContainerReference, error) { + return info.ContainerReference{ + Name: self.name, + Aliases: self.aliases, + }, nil +} + func (self *dockerContainerHandler) splitName() (string, string, error) { parent, id := path.Split(self.name) cgroupSelf, err := os.Open("/proc/self/cgroup") diff --git a/container/factory.go b/container/factory.go index 97d43a45..24f82ed1 100644 --- a/container/factory.go +++ b/container/factory.go @@ -114,7 +114,7 @@ func (self *factoryManager) NewContainerHandler(path string) (ContainerHandler, err := fmt.Errorf("nil factory for container %v", path) return nil, err } - log.Printf("container handler factory for %v is %v\n", path, factory) + log.Printf("Container handler factory for %v is %v\n", path, factory) return factory.NewContainerHandler(path) } diff --git a/container/filter.go b/container/filter.go index 0d262c3f..313a861e 100644 --- a/container/filter.go +++ b/container/filter.go @@ -26,6 +26,10 @@ type containerListFilter struct { NoStatsSummary } +func (self *containerListFilter) ContainerReference() (info.ContainerReference, error) { + return self.handler.ContainerReference() +} + func (self *containerListFilter) GetSpec() (*info.ContainerSpec, error) { return self.handler.GetSpec() } diff --git a/container/filter_test.go b/container/filter_test.go index 3a3a3d46..91abf3df 100644 --- a/container/filter_test.go +++ b/container/filter_test.go @@ -32,6 +32,11 @@ func (self *mockContainerHandler) GetSpec() (*info.ContainerSpec, error) { return args.Get(0).(*info.ContainerSpec), args.Error(1) } +func (self *mockContainerHandler) ContainerReference() (info.ContainerReference, error) { + args := self.Called() + return args.Get(0).(info.ContainerReference), args.Error(1) +} + func (self *mockContainerHandler) GetStats() (*info.ContainerStats, error) { args := self.Called() return args.Get(0).(*info.ContainerStats), args.Error(1) diff --git a/container/lmctfy/lmctfy_container.go b/container/lmctfy/lmctfy_container.go index 24e06cee..c41278ae 100644 --- a/container/lmctfy/lmctfy_container.go +++ b/container/lmctfy/lmctfy_container.go @@ -47,6 +47,10 @@ func New(name string) (container.ContainerHandler, error) { return el, nil } +func (self *lmctfyContainerHandler) ContainerReference() (info.ContainerReference, error) { + return info.ContainerReference{Name: self.Name}, nil +} + func getExitCode(err error) int { msg, ok := err.(*exec.ExitError) if ok { diff --git a/container/samplermngr.go b/container/samplermngr.go index 0565cf22..f7f13e56 100644 --- a/container/samplermngr.go +++ b/container/samplermngr.go @@ -1,3 +1,17 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package container import ( diff --git a/container/statssum.go b/container/statssum.go index b562d93b..d24caed2 100644 --- a/container/statssum.go +++ b/container/statssum.go @@ -36,6 +36,10 @@ func (self *percentilesContainerHandlerWrapper) GetSpec() (*info.ContainerSpec, return self.handler.GetSpec() } +func (self *percentilesContainerHandlerWrapper) ContainerReference() (info.ContainerReference, error) { + return self.handler.ContainerReference() +} + func (self *percentilesContainerHandlerWrapper) updatePrevStats(stats *info.ContainerStats) { if stats == nil || stats.Cpu == nil || stats.Memory == nil { // discard incomplete stats diff --git a/container/statssum_test.go b/container/statssum_test.go index bd31fdb4..654f93c0 100644 --- a/container/statssum_test.go +++ b/container/statssum_test.go @@ -94,6 +94,12 @@ func containerWithTrace(duration time.Duration, cpuUsages []uint64, memUsages [] } } +func (self *replayTrace) ContainerReference() (info.ContainerReference, error) { + return info.ContainerReference{ + Name: "replay", + }, nil +} + func (self *replayTrace) GetStats() (*info.ContainerStats, error) { stats := new(info.ContainerStats) stats.Cpu = new(info.CpuStats) diff --git a/info/version.go b/info/version.go new file mode 100644 index 00000000..a00a0222 --- /dev/null +++ b/info/version.go @@ -0,0 +1,18 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package info + +// Version of cAdvisor. +const VERSION = "0.1.0" diff --git a/manager/container.go b/manager/container.go index 3b0e3422..9431483c 100644 --- a/manager/container.go +++ b/manager/container.go @@ -91,7 +91,12 @@ func NewContainerData(containerName string) (*containerData, error) { return nil, err } cont.handler = handler - cont.info.Name = containerName + ref, err := handler.ContainerReference() + if err != nil { + return nil, err + } + cont.info.Name = ref.Name + cont.info.Aliases = ref.Aliases cont.info.Stats = list.New() cont.stop = make(chan bool, 1) diff --git a/manager/manager.go b/manager/manager.go index 7158d380..c223d5fd 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -124,7 +124,8 @@ func (m *manager) GetContainerInfo(containerName string) (*info.ContainerInfo, e // Make a copy of the info for the user. ret := &info.ContainerInfo{ ContainerReference: info.ContainerReference{ - Name: cinfo.Name, + Name: cinfo.Name, + Aliases: cinfo.Aliases, }, Subcontainers: cinfo.Subcontainers, Spec: cinfo.Spec, @@ -169,9 +170,13 @@ func (m *manager) createContainer(containerName string) (*containerData, error) m.containersLock.Lock() defer m.containersLock.Unlock() - log.Printf("Added container: %s", containerName) + // Add the container name and all its aliases. m.containers[containerName] = cont + for _, alias := range cont.info.Aliases { + m.containers[alias] = cont + } }() + log.Printf("Added container: %s (aliases: %s)", containerName, cont.info.Aliases) // Start the container's housekeeping. cont.Start() @@ -193,9 +198,12 @@ func (m *manager) destroyContainer(containerName string) error { return err } - // Remove the container from our records. + // Remove the container from our records (and all its aliases). delete(m.containers, containerName) - log.Printf("Destroyed container: %s", containerName) + for _, alias := range cont.info.Aliases { + delete(m.containers, alias) + } + log.Printf("Destroyed container: %s (aliases: %s)", containerName, cont.info.Aliases) return nil } @@ -219,7 +227,10 @@ func (m *manager) getContainersDiff() (added []info.ContainerReference, removed // Determine which were added and which were removed. allContainersSet := make(map[string]*containerData) for name, d := range m.containers { - allContainersSet[name] = d + // Only add the canonical name. + if d.info.Name == name { + allContainersSet[name] = d + } } for _, c := range allContainers { delete(allContainersSet, c.Name) diff --git a/pages/containers.go b/pages/containers.go index 60034cec..fc4ed5ea 100644 --- a/pages/containers.go +++ b/pages/containers.go @@ -37,7 +37,6 @@ var funcMap = template.FuncMap{ "printMask": printMask, "printCores": printCores, "printMegabytes": printMegabytes, - "containerNameEquals": containerNameEquals, "getMemoryUsage": getMemoryUsage, "getMemoryUsagePercent": getMemoryUsagePercent, "getHotMemoryPercent": getHotMemoryPercent, @@ -85,10 +84,6 @@ func containerLink(container info.ContainerReference, basenameOnly bool, cssClas return template.HTML(fmt.Sprintf("%s", cssClasses, ContainersPage[:len(ContainersPage)-1], containerName, displayName)) } -func containerNameEquals(c1 string, c2 string) bool { - return c1 == c2 -} - func printMask(mask *info.CpuSpecMask, numCores int) interface{} { // TODO(vmarmol): Detect this correctly. // TODO(vmarmol): Support more than 64 cores. @@ -130,25 +125,31 @@ func printMegabytes(bytes uint64) string { return strconv.FormatFloat(megabytes, 'f', 3, 64) } -func toMemoryPercent(usage uint64, spec *info.ContainerSpec) int { - return int((usage * 100) / (spec.Memory.Limit)) +func toMemoryPercent(usage uint64, spec *info.ContainerSpec, machine *info.MachineInfo) int { + // Saturate limit to the machine size. + limit := uint64(spec.Memory.Limit) + if limit > uint64(machine.MemoryCapacity) { + limit = uint64(machine.MemoryCapacity) + } + + return int((usage * 100) / limit) } func getMemoryUsage(stats []*info.ContainerStats) string { return strconv.FormatFloat(toMegabytes((stats[len(stats)-1].Memory.Usage)), 'f', 2, 64) } -func getMemoryUsagePercent(spec *info.ContainerSpec, stats []*info.ContainerStats) int { - return toMemoryPercent((stats[len(stats)-1].Memory.Usage), spec) +func getMemoryUsagePercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { + return toMemoryPercent((stats[len(stats)-1].Memory.Usage), spec, machine) } -func getHotMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats) int { - return toMemoryPercent((stats[len(stats)-1].Memory.WorkingSet), spec) +func getHotMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { + return toMemoryPercent((stats[len(stats)-1].Memory.WorkingSet), spec, machine) } -func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats) int { +func getColdMemoryPercent(spec *info.ContainerSpec, stats []*info.ContainerStats, machine *info.MachineInfo) int { latestStats := stats[len(stats)-1].Memory - return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec) + return toMemoryPercent((latestStats.Usage)-(latestStats.WorkingSet), spec, machine) } func ServerContainersPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error { diff --git a/pages/containers_html.go b/pages/containers_html.go index 7e140e50..037aa3f4 100644 --- a/pages/containers_html.go +++ b/pages/containers_html.go @@ -132,16 +132,16 @@ const containersHtmlTemplate = `