diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 864a9cdd134..99ff32c8e10 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -204,88 +204,88 @@ }, { "ImportPath": "github.com/google/cadvisor/api", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/collector", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/container", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/events", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/fs", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/healthz", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/http", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/info/v1", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/info/v2", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/manager", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/metrics", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/pages", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/storage", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/summary", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/utils", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/validate", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/cadvisor/version", - "Comment": "0.13.0-56-ga9f4b69", - "Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" + "Comment": "0.14.0-9-ga96f2f9", + "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac" }, { "ImportPath": "github.com/google/go-github/github", diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/base/collector.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/base/collector.go deleted file mode 100644 index e3043cde9de..00000000000 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/base/collector.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2015 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 base - -import ( - "time" - - "github.com/google/cadvisor/collector" - "github.com/google/cadvisor/info/v2" -) - -type Collector struct { -} - -var _ collector.Collector = &Collector{} - -func (c *Collector) Collect() (time.Time, []v2.Metric, error) { - return time.Now(), []v2.Metrics{}, nil -} - -func (c *Collector) Name() string { - return "default" -} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/collector/base/metrics.go b/Godeps/_workspace/src/github.com/google/cadvisor/collector/base/metrics.go deleted file mode 100644 index 3cabf53ac54..00000000000 --- a/Godeps/_workspace/src/github.com/google/cadvisor/collector/base/metrics.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2015 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 base - -import () diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/factory.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/factory.go index ec5c26e0177..b8cd4ad27e3 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/factory.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/docker/factory.go @@ -149,6 +149,10 @@ func (self *dockerFactory) CanHandleAndAccept(name string) (bool, bool, error) { return true, canAccept, nil } +func (self *dockerFactory) DebugInfo() map[string][]string { + return map[string][]string{} +} + func parseDockerVersion(full_version_string string) ([]int, error) { version_regexp_string := "(\\d+)\\.(\\d+)\\.(\\d+)" version_re := regexp.MustCompile(version_regexp_string) diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/factory.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/factory.go index 04a26b17093..d5ab8290359 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/factory.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/factory.go @@ -30,6 +30,9 @@ type ContainerHandlerFactory interface { // Name of the factory. String() string + + // Returns debugging information. Map of lines per category. + DebugInfo() map[string][]string } // TODO(vmarmol): Consider not making this global. @@ -90,3 +93,17 @@ func ClearContainerHandlerFactories() { factories = make([]ContainerHandlerFactory, 0, 4) } + +func DebugInfo() map[string][]string { + factoriesLock.RLock() + defer factoriesLock.RUnlock() + + // Get debug information for all factories. + out := make(map[string][]string) + for _, factory := range factories { + for k, v := range factory.DebugInfo() { + out[k] = v + } + } + return out +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/factory_test.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/factory_test.go index 535fcb07a3f..991c365ab3b 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/factory_test.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/factory_test.go @@ -31,6 +31,10 @@ func (self *mockContainerHandlerFactory) String() string { return self.Name } +func (self *mockContainerHandlerFactory) DebugInfo() map[string][]string { + return map[string][]string{} +} + func (self *mockContainerHandlerFactory) CanHandleAndAccept(name string) (bool, bool, error) { return self.CanHandleValue, self.CanAcceptValue, nil } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/factory.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/factory.go index 27020eeb015..be1c799b75f 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/factory.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/factory.go @@ -36,6 +36,9 @@ type rawFactory struct { // Information about mounted filesystems. fsInfo fs.FsInfo + + // Watcher for inotify events. + watcher *InotifyWatcher } func (self *rawFactory) String() string { @@ -43,7 +46,7 @@ func (self *rawFactory) String() string { } func (self *rawFactory) NewContainerHandler(name string) (container.ContainerHandler, error) { - return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo) + return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo, self.watcher) } // The raw factory can handle any container. If --docker_only is set to false, non-docker containers are ignored. @@ -52,6 +55,23 @@ func (self *rawFactory) CanHandleAndAccept(name string) (bool, bool, error) { return true, accept, nil } +func (self *rawFactory) DebugInfo() map[string][]string { + out := make(map[string][]string) + + // Get information about inotify watches. + watches := self.watcher.GetWatches() + lines := make([]string, 0, len(watches)) + for containerName, cgroupWatches := range watches { + lines = append(lines, fmt.Sprintf("%s:", containerName)) + for _, cg := range cgroupWatches { + lines = append(lines, fmt.Sprintf("\t%s", cg)) + } + } + out["Inotify watches"] = lines + + return out +} + func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo) error { cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() if err != nil { @@ -61,11 +81,17 @@ func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo) erro return fmt.Errorf("failed to find supported cgroup mounts for the raw factory") } + watcher, err := NewInotifyWatcher() + if err != nil { + return err + } + glog.Infof("Registering Raw factory") factory := &rawFactory{ machineInfoFactory: machineInfoFactory, fsInfo: fsInfo, cgroupSubsystems: &cgroupSubsystems, + watcher: watcher, } container.RegisterContainerHandlerFactory(factory) return nil diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/handler.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/handler.go index d8ea85240dc..160477f6d0a 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/handler.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/handler.go @@ -43,17 +43,11 @@ type rawContainerHandler struct { machineInfoFactory info.MachineInfoFactory // Inotify event watcher. - watcher *inotify.Watcher + watcher *InotifyWatcher // Signal for watcher thread to stop. stopWatcher chan error - // Containers being watched for new subcontainers. - watches map[string]struct{} - - // Cgroup paths being watched for new subcontainers - cgroupWatches map[string]struct{} - // Absolute path to the cgroup hierarchies of this container. // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") cgroupPaths map[string]string @@ -68,7 +62,7 @@ type rawContainerHandler struct { externalMounts []mount } -func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo) (container.ContainerHandler, error) { +func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, watcher *InotifyWatcher) (container.ContainerHandler, error) { // Create the cgroup paths. cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) for key, val := range cgroupSubsystems.MountPoints { @@ -107,13 +101,12 @@ func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSu cgroupSubsystems: cgroupSubsystems, machineInfoFactory: machineInfoFactory, stopWatcher: make(chan error), - watches: make(map[string]struct{}), - cgroupWatches: make(map[string]struct{}), cgroupPaths: cgroupPaths, cgroupManager: cgroupManager, fsInfo: fsInfo, hasNetwork: hasNetwork, externalMounts: externalMounts, + watcher: watcher, }, nil } @@ -402,29 +395,43 @@ func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]i return libcontainer.GetProcesses(self.cgroupManager) } -func (self *rawContainerHandler) watchDirectory(dir string, containerName string) error { - err := self.watcher.AddWatch(dir, inotify.IN_CREATE|inotify.IN_DELETE|inotify.IN_MOVE) +// Watches the specified directory and all subdirectories. Returns whether the path was +// already being watched and an error (if any). +func (self *rawContainerHandler) watchDirectory(dir string, containerName string) (bool, error) { + alreadyWatching, err := self.watcher.AddWatch(containerName, dir) if err != nil { - return err + return alreadyWatching, err } - self.watches[containerName] = struct{}{} - self.cgroupWatches[dir] = struct{}{} + + // Remove the watch if further operations failed. + cleanup := true + defer func() { + if cleanup { + _, err := self.watcher.RemoveWatch(containerName, dir) + if err != nil { + glog.Warningf("Failed to remove inotify watch for %q: %v", dir, err) + } + } + }() // TODO(vmarmol): We should re-do this once we're done to ensure directories were not added in the meantime. // Watch subdirectories as well. entries, err := ioutil.ReadDir(dir) if err != nil { - return err + return alreadyWatching, err } for _, entry := range entries { if entry.IsDir() { - err = self.watchDirectory(path.Join(dir, entry.Name()), path.Join(containerName, entry.Name())) + // TODO(vmarmol): We don't have to fail here, maybe we can recover and try to get as many registrations as we can. + _, err = self.watchDirectory(path.Join(dir, entry.Name()), path.Join(containerName, entry.Name())) if err != nil { - return err + return alreadyWatching, err } } } - return nil + + cleanup = false + return alreadyWatching, nil } func (self *rawContainerHandler) processEvent(event *inotify.Event, events chan container.SubcontainerEvent) error { @@ -460,10 +467,8 @@ func (self *rawContainerHandler) processEvent(event *inotify.Event, events chan // Maintain the watch for the new or deleted container. switch { case eventType == container.SubcontainerAdd: - _, alreadyWatched := self.watches[containerName] - // New container was created, watch it. - err := self.watchDirectory(event.Name, containerName) + alreadyWatched, err := self.watchDirectory(event.Name, containerName) if err != nil { return err } @@ -473,20 +478,16 @@ func (self *rawContainerHandler) processEvent(event *inotify.Event, events chan return nil } case eventType == container.SubcontainerDelete: - // Container was deleted, stop watching for it. Only delete the event if we registered it. - if _, ok := self.cgroupWatches[event.Name]; ok { - err := self.watcher.RemoveWatch(event.Name) - if err != nil { - return err - } - delete(self.cgroupWatches, event.Name) + // Container was deleted, stop watching for it. + lastWatched, err := self.watcher.RemoveWatch(containerName, event.Name) + if err != nil { + return err } // Only report container deletion once. - if _, ok := self.watches[containerName]; !ok { + if !lastWatched { return nil } - delete(self.watches, containerName) default: return fmt.Errorf("unknown event type %v", eventType) } @@ -501,18 +502,9 @@ func (self *rawContainerHandler) processEvent(event *inotify.Event, events chan } func (self *rawContainerHandler) WatchSubcontainers(events chan container.SubcontainerEvent) error { - // Lazily initialize the watcher so we don't use it when not asked to. - if self.watcher == nil { - w, err := inotify.NewWatcher() - if err != nil { - return err - } - self.watcher = w - } - // Watch this container (all its cgroups) and all subdirectories. for _, cgroupPath := range self.cgroupPaths { - err := self.watchDirectory(cgroupPath, self.name) + _, err := self.watchDirectory(cgroupPath, self.name) if err != nil { return err } @@ -522,18 +514,17 @@ func (self *rawContainerHandler) WatchSubcontainers(events chan container.Subcon go func() { for { select { - case event := <-self.watcher.Event: + case event := <-self.watcher.Event(): err := self.processEvent(event, events) if err != nil { glog.Warningf("Error while processing event (%+v): %v", event, err) } - case err := <-self.watcher.Error: + case err := <-self.watcher.Error(): glog.Warningf("Error while watching %q:", self.name, err) case <-self.stopWatcher: err := self.watcher.Close() if err == nil { self.stopWatcher <- err - self.watcher = nil return } } @@ -544,10 +535,6 @@ func (self *rawContainerHandler) WatchSubcontainers(events chan container.Subcon } func (self *rawContainerHandler) StopWatchingSubcontainers() error { - if self.watcher == nil { - return fmt.Errorf("can't stop watch that has not started for container %q", self.name) - } - // Rendezvous with the watcher thread. self.stopWatcher <- nil return <-self.stopWatcher diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/inotify_watcher.go b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/inotify_watcher.go new file mode 100644 index 00000000000..8f1e5c9887e --- /dev/null +++ b/Godeps/_workspace/src/github.com/google/cadvisor/container/raw/inotify_watcher.go @@ -0,0 +1,135 @@ +// Copyright 2015 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 raw + +import ( + "sync" + + "golang.org/x/exp/inotify" +) + +// Watcher for container-related inotify events in the cgroup hierarchy. +// +// Implementation is thread-safe. +type InotifyWatcher struct { + // Underlying inotify watcher. + watcher *inotify.Watcher + + // Map of containers being watched to cgroup paths watched for that container. + containersWatched map[string]map[string]bool + + // Lock for all datastructure access. + lock sync.Mutex +} + +func NewInotifyWatcher() (*InotifyWatcher, error) { + w, err := inotify.NewWatcher() + if err != nil { + return nil, err + } + + return &InotifyWatcher{ + watcher: w, + containersWatched: make(map[string]map[string]bool), + }, nil +} + +// Add a watch to the specified directory. Returns if the container was already being watched. +func (iw *InotifyWatcher) AddWatch(containerName, dir string) (bool, error) { + iw.lock.Lock() + defer iw.lock.Unlock() + + cgroupsWatched, alreadyWatched := iw.containersWatched[containerName] + + // Register an inotify notification. + if !cgroupsWatched[dir] { + err := iw.watcher.AddWatch(dir, inotify.IN_CREATE|inotify.IN_DELETE|inotify.IN_MOVE) + if err != nil { + return alreadyWatched, err + } + + if cgroupsWatched == nil { + cgroupsWatched = make(map[string]bool) + } + cgroupsWatched[dir] = true + } + + // Record our watching of the container. + if !alreadyWatched { + iw.containersWatched[containerName] = cgroupsWatched + } + return alreadyWatched, nil +} + +// Remove watch from the specified directory. Returns if this was the last watch on the specified container. +func (iw *InotifyWatcher) RemoveWatch(containerName, dir string) (bool, error) { + iw.lock.Lock() + defer iw.lock.Unlock() + + // If we don't have a watch registed for this, just return. + cgroupsWatched, ok := iw.containersWatched[containerName] + if !ok { + return false, nil + } + + // Remove the inotify watch if it exists. + if cgroupsWatched[dir] { + err := iw.watcher.RemoveWatch(dir) + if err != nil { + return false, nil + } + delete(cgroupsWatched, dir) + } + + // Remove the record if this is the last watch. + if len(cgroupsWatched) == 0 { + delete(iw.containersWatched, containerName) + return true, nil + } + + return false, nil +} + +// Errors are returned on this channel. +func (iw *InotifyWatcher) Error() chan error { + return iw.watcher.Error +} + +// Events are returned on this channel. +func (iw *InotifyWatcher) Event() chan *inotify.Event { + return iw.watcher.Event +} + +// Closes the inotify watcher. +func (iw *InotifyWatcher) Close() error { + return iw.watcher.Close() +} + +// Returns a map of containers to the cgroup paths being watched. +func (iw *InotifyWatcher) GetWatches() map[string][]string { + out := make(map[string][]string, len(iw.containersWatched)) + for k, v := range iw.containersWatched { + out[k] = mapToSlice(v) + } + return out +} + +func mapToSlice(m map[string]bool) []string { + out := make([]string, 0, len(m)) + for k := range m { + out = append(out, k) + } + return out +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/info/v2/container.go b/Godeps/_workspace/src/github.com/google/cadvisor/info/v2/container.go index 1ea7d0dd8f4..326c9ce0094 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/info/v2/container.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/info/v2/container.go @@ -171,15 +171,16 @@ type RequestOptions struct { } type ProcessInfo struct { - User string `json:"user"` - Pid int `json:"pid"` - Ppid int `json:"parent_pid"` - StartTime string `json:"start_time"` - PercentCpu string `json:"percent_cpu"` - PercentMemory string `json:"percent_mem"` - RSS string `json:"rss"` - VirtualSize string `json:"virtual_size"` - Status string `json:"status"` - RunningTime string `json:"running_time"` - Cmd string `json:"cmd"` + User string `json:"user"` + Pid int `json:"pid"` + Ppid int `json:"parent_pid"` + StartTime string `json:"start_time"` + PercentCpu float32 `json:"percent_cpu"` + PercentMemory float32 `json:"percent_mem"` + RSS uint64 `json:"rss"` + VirtualSize uint64 `json:"virtual_size"` + Status string `json:"status"` + RunningTime string `json:"running_time"` + CgroupPath string `json:"cgroup_path"` + Cmd string `json:"cmd"` } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/manager/container.go b/Godeps/_workspace/src/github.com/google/cadvisor/manager/container.go index e9968ef0918..1289f85ebda 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/manager/container.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/manager/container.go @@ -19,6 +19,7 @@ import ( "fmt" "math" "os/exec" + "regexp" "sort" "strconv" "strings" @@ -41,6 +42,8 @@ var HousekeepingInterval = flag.Duration("housekeeping_interval", 1*time.Second, var maxHousekeepingInterval = flag.Duration("max_housekeeping_interval", 60*time.Second, "Largest interval to allow between container housekeepings") var allowDynamicHousekeeping = flag.Bool("allow_dynamic_housekeeping", true, "Whether to allow the housekeeping interval to be dynamic") +var cgroupPathRegExp = regexp.MustCompile(".*:devices:(.*?),.*") + // Decay value used for load average smoothing. Interval length of 10 seconds is used. var loadDecay = math.Exp(float64(-1 * (*HousekeepingInterval).Seconds() / 10)) @@ -116,6 +119,19 @@ func (c *containerData) DerivedStats() (v2.DerivedStats, error) { return c.summaryReader.DerivedStats() } +func (c *containerData) getCgroupPath(cgroups string) (string, error) { + if cgroups == "-" { + return "/", nil + } + matches := cgroupPathRegExp.FindSubmatch([]byte(cgroups)) + if len(matches) != 2 { + glog.V(3).Infof("failed to get devices cgroup path from %q", cgroups) + // return root in case of failures - devices hierarchy might not be enabled. + return "/", nil + } + return string(matches[1]), nil +} + func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { // report all processes for root. isRoot := c.info.Name == "/" @@ -130,9 +146,9 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { } } // TODO(rjnagal): Take format as an option? - format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm" + format := "user,pid,ppid,stime,pcpu,pmem,rss,vsz,stat,time,comm,cgroup" args := []string{"-e", "-o", format} - expectedFields := 11 + expectedFields := 12 out, err := exec.Command("ps", args...).Output() if err != nil { return nil, fmt.Errorf("failed to execute ps command: %v", err) @@ -155,19 +171,44 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) { if err != nil { return nil, fmt.Errorf("invalid ppid %q: %v", fields[2], err) } + percentCpu, err := strconv.ParseFloat(fields[4], 32) + if err != nil { + return nil, fmt.Errorf("invalid cpu percent %q: %v", fields[4], err) + } + percentMem, err := strconv.ParseFloat(fields[5], 32) + if err != nil { + return nil, fmt.Errorf("invalid memory percent %q: %v", fields[5], err) + } + rss, err := strconv.ParseUint(fields[6], 0, 64) + if err != nil { + return nil, fmt.Errorf("invalid rss %q: %v", fields[6], err) + } + vs, err := strconv.ParseUint(fields[7], 0, 64) + if err != nil { + return nil, fmt.Errorf("invalid virtual size %q: %v", fields[7], err) + } + var cgroupPath string + if isRoot { + cgroupPath, err = c.getCgroupPath(fields[11]) + if err != nil { + return nil, fmt.Errorf("could not parse cgroup path from %q: %v", fields[10], err) + } + } + if isRoot || pidMap[pid] == true { processes = append(processes, v2.ProcessInfo{ User: fields[0], Pid: pid, Ppid: ppid, StartTime: fields[3], - PercentCpu: fields[4], - PercentMemory: fields[5], - RSS: fields[6], - VirtualSize: fields[7], + PercentCpu: float32(percentCpu), + PercentMemory: float32(percentMem), + RSS: rss, + VirtualSize: vs, Status: fields[8], RunningTime: fields[9], - Cmd: strings.Join(fields[10:], " "), + Cmd: fields[10], + CgroupPath: cgroupPath, }) } } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go b/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go index 6b9e3715f29..e5e309529ae 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/manager/manager.go @@ -107,6 +107,9 @@ type Manager interface { // Get details about interesting docker images. DockerImages() ([]DockerImage, error) + + // Returns debugging information. Map of lines per category. + DebugInfo() map[string][]string } // New takes a memory storage and returns a new manager. @@ -1131,3 +1134,38 @@ func (m *manager) DockerInfo() (DockerStatus, error) { } return out, nil } + +func (m *manager) DebugInfo() map[string][]string { + debugInfo := container.DebugInfo() + + // Get unique containers. + var conts map[*containerData]struct{} + func() { + m.containersLock.RLock() + defer m.containersLock.RUnlock() + + conts = make(map[*containerData]struct{}, len(m.containers)) + for _, c := range m.containers { + conts[c] = struct{}{} + } + }() + + // List containers. + lines := make([]string, 0, len(conts)) + for cont := range conts { + lines = append(lines, cont.info.Name) + if cont.info.Namespace != "" { + lines = append(lines, fmt.Sprintf("\tNamespace: %s", cont.info.Namespace)) + } + + if len(cont.info.Aliases) != 0 { + lines = append(lines, "\tAliases:") + for _, alias := range cont.info.Aliases { + lines = append(lines, fmt.Sprintf("\t\t%s", alias)) + } + } + } + + debugInfo["Managed containers"] = lines + return debugInfo +} diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers_html.go b/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers_html.go index ba7e9356d1d..41af3f95c58 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers_html.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/pages/containers_html.go @@ -64,6 +64,36 @@ const containersHtmlTemplate = ` {{end}} + {{if .DockerStatus}} +
+ + + {{end}} +
+ {{end}} + {{if .DockerImages}} +
+ +
+

+
+ {{end}} {{if .ResourcesAvailable}}
diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/pages/docker.go b/Godeps/_workspace/src/github.com/google/cadvisor/pages/docker.go index 472b34f27c9..d02f1ceebdb 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/pages/docker.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/pages/docker.go @@ -19,6 +19,7 @@ import ( "net/http" "net/url" "path" + "strconv" "time" "github.com/golang/glog" @@ -29,6 +30,25 @@ import ( const DockerPage = "/docker/" +func toStatusKV(status manager.DockerStatus) ([]keyVal, []keyVal) { + ds := []keyVal{ + {Key: "Driver", Value: status.Driver}, + } + for k, v := range status.DriverStatus { + ds = append(ds, keyVal{Key: k, Value: v}) + } + return []keyVal{ + {Key: "Docker Version", Value: status.Version}, + {Key: "Kernel Version", Value: status.KernelVersion}, + {Key: "OS Version", Value: status.OS}, + {Key: "Host Name", Value: status.Hostname}, + {Key: "Docker Root Directory", Value: status.RootDir}, + {Key: "Execution Driver", Value: status.ExecDriver}, + {Key: "Number of Images", Value: strconv.Itoa(status.NumImages)}, + {Key: "Number of Containers", Value: strconv.Itoa(status.NumContainers)}, + }, ds +} + func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error { start := time.Now() @@ -54,6 +74,19 @@ func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error }) } + // Get Docker status + status, err := m.DockerInfo() + if err != nil { + return err + } + + dockerStatus, driverStatus := toStatusKV(status) + // Get Docker Images + images, err := m.DockerImages() + if err != nil { + return err + } + dockerContainersText := "Docker Containers" data = &pageData{ DisplayName: dockerContainersText, @@ -62,8 +95,11 @@ func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error Text: dockerContainersText, Link: DockerPage, }}, - Subcontainers: subcontainers, - Root: rootDir, + Subcontainers: subcontainers, + Root: rootDir, + DockerStatus: dockerStatus, + DockerDriverStatus: driverStatus, + DockerImages: images, } } else { // Get the container. @@ -92,7 +128,6 @@ func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error if err != nil { return err } - data = &pageData{ DisplayName: displayName, ContainerName: cont.Name, diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/pages/pages.go b/Godeps/_workspace/src/github.com/google/cadvisor/pages/pages.go index 2086f8a6c93..fed0401f27f 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/pages/pages.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/pages/pages.go @@ -37,6 +37,11 @@ type link struct { Link string } +type keyVal struct { + Key string + Value string +} + type pageData struct { DisplayName string ContainerName string @@ -52,6 +57,9 @@ type pageData struct { NetworkAvailable bool FsAvailable bool Root string + DockerStatus []keyVal + DockerDriverStatus []keyVal + DockerImages []manager.DockerImage } func init() { diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/pages/static/containers_css.go b/Godeps/_workspace/src/github.com/google/cadvisor/pages/static/containers_css.go index 48da8a9d61d..98f0175c84b 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/pages/static/containers_css.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/pages/static/containers_css.go @@ -39,6 +39,17 @@ const containersCss = ` .isolation-title { color:#FFFFFF; } +.table-row { + font-family: "courier", "monospace"; + font-size: 15px; + text-align: right; + vertical-align: top; + border: 5px; + margin-left: 3px; + margin-right: 3px; + margin-top: 3px; + margin-bottom: 3px; +} #logo { height: 200px; margin-top: 20px; diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/pages/static/containers_js.go b/Godeps/_workspace/src/github.com/google/cadvisor/pages/static/containers_js.go index 6afaa842c62..945450a62a2 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/pages/static/containers_js.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/pages/static/containers_js.go @@ -25,10 +25,9 @@ function humanize(num, size, units) { // Following the IEC naming convention function humanizeIEC(num) { - var ret = humanize(num, 1024, ["TiB", "GiB", "MiB", "KiB", "Bytes"]); + var ret = humanize(num, 1024, ["TiB", "GiB", "MiB", "KiB", "B"]); return ret[0].toFixed(2) + " " + ret[1]; } - // Following the Metric naming convention function humanizeMetric(num) { var ret = humanize(num, 1000, ["TB", "GB", "MB", "KB", "Bytes"]); @@ -36,7 +35,7 @@ function humanizeMetric(num) { } // Draw a table. -function drawTable(seriesTitles, titleTypes, data, elementId) { +function drawTable(seriesTitles, titleTypes, data, elementId, numPages, sortIndex) { var dataTable = new google.visualization.DataTable(); for (var i = 0; i < seriesTitles.length; i++) { dataTable.addColumn(titleTypes[i], seriesTitles[i]); @@ -46,10 +45,19 @@ function drawTable(seriesTitles, titleTypes, data, elementId) { window.charts[elementId] = new google.visualization.Table(document.getElementById(elementId)); } + var cssClassNames = { + 'headerRow': '', + 'tableRow': 'table-row', + 'oddTableRow': 'table-row' + }; var opts = { alternatingRowStyle: true, page: 'enable', - pageSize: 25, + pageSize: numPages, + allowHtml: true, + sortColumn: sortIndex, + sortAscending: false, + cssClassNames: cssClassNames, }; window.charts[elementId].draw(dataTable, opts); } @@ -167,8 +175,12 @@ function getMachineInfo(rootDir, callback) { // Get ps info. function getProcessInfo(rootDir, containerName, callback) { - $.getJSON(rootDir + "api/v2.0/ps" + containerName, function(data) { + $.getJSON(rootDir + "api/v2.0/ps" + containerName) + .done(function(data) { callback(data); + }) + .fail(function(jqhxr, textStatus, error) { + callback([]); }); } @@ -426,26 +438,72 @@ function drawFileSystemUsage(machineInfo, stats) { } } -function drawProcesses(processInfo) { +function drawImages(images) { + if (images == null || images.length == 0) { + return; + } + window.charts = {}; + var titles = ["Repository", "Tags", "ID", "Virtual Size", "Creation Time"]; + var titleTypes = ['string', 'string', 'string', 'number', 'number']; + var sortIndex = 0; + var data = []; + for (var i = 0; i < images.length; i++) { + var elements = []; + var tags = []; + var repos = images[i].repo_tags[0].split(":"); + repos.splice(-1,1) + for (var j = 0; j < images[i].repo_tags.length; j++) { + var splits = images[i].repo_tags[j].split(":") + if (splits.length > 1) { + tags.push(splits[splits.length - 1]) + } + } + elements.push(repos.join(":")); + elements.push(tags.join(", ")); + elements.push(images[i].id.substr(0,24)); + elements.push({v: images[i].virtual_size, f: humanizeIEC(images[i].virtual_size)}); + var d = new Date(images[i].created * 1000); + elements.push({v: images[i].created, f: d.toLocaleString()}); + data.push(elements); + } + drawTable(titles, titleTypes, data, "docker-images", 30, sortIndex); +} + +function drawProcesses(isRoot, rootDir, processInfo) { + if (processInfo.length == 0) { + $("#processes-top").text("No processes found"); + return; + } var titles = ["User", "PID", "PPID", "Start Time", "CPU %", "MEM %", "RSS", "Virtual Size", "Status", "Running Time", "Command"]; - var titleTypes = ['string', 'number', 'number', 'string', 'string', 'string', 'string', 'string', 'string', 'string', 'string']; + var titleTypes = ['string', 'number', 'number', 'string', 'number', 'number', 'number', 'number', 'string', 'string', 'string']; + var sortIndex = 4 + if (isRoot) { + titles.push("Cgroup"); + titleTypes.push('string'); + } var data = [] - for (var i = 1; i < processInfo.length; i++) { + for (var i = 0; i < processInfo.length; i++) { var elements = []; elements.push(processInfo[i].user); elements.push(processInfo[i].pid); elements.push(processInfo[i].parent_pid); elements.push(processInfo[i].start_time); - elements.push(processInfo[i].percent_cpu); - elements.push(processInfo[i].percent_mem); - elements.push(processInfo[i].rss); - elements.push(processInfo[i].virtual_size); + elements.push({ v:processInfo[i].percent_cpu, f:processInfo[i].percent_cpu.toFixed(2)}); + elements.push({ v:processInfo[i].percent_mem, f:processInfo[i].percent_mem.toFixed(2)}); + elements.push({ v:processInfo[i].rss, f:humanizeIEC(processInfo[i].rss)}); + elements.push({ v:processInfo[i].virtual_size, f:humanizeIEC(processInfo[i].virtual_size)}); elements.push(processInfo[i].status); elements.push(processInfo[i].running_time); elements.push(processInfo[i].cmd); + if (isRoot) { + var cgroup = processInfo[i].cgroup_path + // Use the raw cgroup link as it works for all containers. + var cgroupLink = '' + cgroup.substr(0,30) + ' '; + elements.push({v:cgroup, f:cgroupLink}); + } data.push(elements); } - drawTable(titles, titleTypes, data, "processes-top"); + drawTable(titles, titleTypes, data, "processes-top", 25, sortIndex); } // Draw the filesystem usage nodes. @@ -561,7 +619,7 @@ function drawCharts(machineInfo, containerInfo) { } // Executed when the page finishes loading. -function startPage(containerName, hasCpu, hasMemory, rootDir) { +function startPage(containerName, hasCpu, hasMemory, rootDir, isRoot) { // Don't fetch data if we don't have any resource. if (!hasCpu && !hasMemory) { return; @@ -573,11 +631,11 @@ function startPage(containerName, hasCpu, hasMemory, rootDir) { // Draw process information at start and refresh every 60s. getProcessInfo(rootDir, containerName, function(processInfo) { - drawProcesses(processInfo) + drawProcesses(isRoot, rootDir, processInfo) }); setInterval(function() { getProcessInfo(rootDir, containerName, function(processInfo) { - drawProcesses(processInfo) + drawProcesses(isRoot, rootDir, processInfo) }); }, 60000); diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/validate/validate.go b/Godeps/_workspace/src/github.com/google/cadvisor/validate/validate.go index 5534d0decf6..264b3444a3f 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/validate/validate.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/validate/validate.go @@ -313,6 +313,13 @@ func HandleRequest(w http.ResponseWriter, containerManager manager.Manager) erro ioSchedulerValidation, desc := validateIoScheduler(containerManager) out += fmt.Sprintf(OutputFormat, "Block device setup", ioSchedulerValidation, desc) + + // Output debug info. + debugInfo := containerManager.DebugInfo() + for category, lines := range debugInfo { + out += fmt.Sprintf(OutputFormat, category, "", strings.Join(lines, "\n\t")) + } + _, err = w.Write([]byte(out)) return err } diff --git a/Godeps/_workspace/src/github.com/google/cadvisor/version/version.go b/Godeps/_workspace/src/github.com/google/cadvisor/version/version.go index b3e2ac4c14f..58e2e35953f 100644 --- a/Godeps/_workspace/src/github.com/google/cadvisor/version/version.go +++ b/Godeps/_workspace/src/github.com/google/cadvisor/version/version.go @@ -15,4 +15,4 @@ package version // Version of cAdvisor. -const VERSION = "0.13.0" +const VERSION = "0.14.0"