Merge pull request #8675 from vmarmol/cadvisor

Update cAdvisor godep to 0.14.0+.
This commit is contained in:
Saad Ali 2015-05-26 18:46:01 -07:00
commit 91ba6c44d2
19 changed files with 525 additions and 174 deletions

68
Godeps/Godeps.json generated
View File

@ -204,88 +204,88 @@
}, },
{ {
"ImportPath": "github.com/google/cadvisor/api", "ImportPath": "github.com/google/cadvisor/api",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/collector", "ImportPath": "github.com/google/cadvisor/collector",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/container", "ImportPath": "github.com/google/cadvisor/container",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/events", "ImportPath": "github.com/google/cadvisor/events",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/fs", "ImportPath": "github.com/google/cadvisor/fs",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/healthz", "ImportPath": "github.com/google/cadvisor/healthz",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/http", "ImportPath": "github.com/google/cadvisor/http",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/info/v1", "ImportPath": "github.com/google/cadvisor/info/v1",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/info/v2", "ImportPath": "github.com/google/cadvisor/info/v2",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/manager", "ImportPath": "github.com/google/cadvisor/manager",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/metrics", "ImportPath": "github.com/google/cadvisor/metrics",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/pages", "ImportPath": "github.com/google/cadvisor/pages",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/storage", "ImportPath": "github.com/google/cadvisor/storage",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/summary", "ImportPath": "github.com/google/cadvisor/summary",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/utils", "ImportPath": "github.com/google/cadvisor/utils",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/validate", "ImportPath": "github.com/google/cadvisor/validate",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/cadvisor/version", "ImportPath": "github.com/google/cadvisor/version",
"Comment": "0.13.0-56-ga9f4b69", "Comment": "0.14.0-9-ga96f2f9",
"Rev": "a9f4b691c1e0336e5d8e310d5664046012988437" "Rev": "a96f2f93256f34a1f330728d146374e43c6c87ac"
}, },
{ {
"ImportPath": "github.com/google/go-github/github", "ImportPath": "github.com/google/go-github/github",

View File

@ -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"
}

View File

@ -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 ()

View File

@ -149,6 +149,10 @@ func (self *dockerFactory) CanHandleAndAccept(name string) (bool, bool, error) {
return true, canAccept, nil return true, canAccept, nil
} }
func (self *dockerFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
func parseDockerVersion(full_version_string string) ([]int, error) { func parseDockerVersion(full_version_string string) ([]int, error) {
version_regexp_string := "(\\d+)\\.(\\d+)\\.(\\d+)" version_regexp_string := "(\\d+)\\.(\\d+)\\.(\\d+)"
version_re := regexp.MustCompile(version_regexp_string) version_re := regexp.MustCompile(version_regexp_string)

View File

@ -30,6 +30,9 @@ type ContainerHandlerFactory interface {
// Name of the factory. // Name of the factory.
String() string String() string
// Returns debugging information. Map of lines per category.
DebugInfo() map[string][]string
} }
// TODO(vmarmol): Consider not making this global. // TODO(vmarmol): Consider not making this global.
@ -90,3 +93,17 @@ func ClearContainerHandlerFactories() {
factories = make([]ContainerHandlerFactory, 0, 4) 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
}

View File

@ -31,6 +31,10 @@ func (self *mockContainerHandlerFactory) String() string {
return self.Name return self.Name
} }
func (self *mockContainerHandlerFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
func (self *mockContainerHandlerFactory) CanHandleAndAccept(name string) (bool, bool, error) { func (self *mockContainerHandlerFactory) CanHandleAndAccept(name string) (bool, bool, error) {
return self.CanHandleValue, self.CanAcceptValue, nil return self.CanHandleValue, self.CanAcceptValue, nil
} }

View File

@ -36,6 +36,9 @@ type rawFactory struct {
// Information about mounted filesystems. // Information about mounted filesystems.
fsInfo fs.FsInfo fsInfo fs.FsInfo
// Watcher for inotify events.
watcher *InotifyWatcher
} }
func (self *rawFactory) String() string { func (self *rawFactory) String() string {
@ -43,7 +46,7 @@ func (self *rawFactory) String() string {
} }
func (self *rawFactory) NewContainerHandler(name string) (container.ContainerHandler, error) { 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. // 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 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 { func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo) error {
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() cgroupSubsystems, err := libcontainer.GetCgroupSubsystems()
if err != nil { 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") 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") glog.Infof("Registering Raw factory")
factory := &rawFactory{ factory := &rawFactory{
machineInfoFactory: machineInfoFactory, machineInfoFactory: machineInfoFactory,
fsInfo: fsInfo, fsInfo: fsInfo,
cgroupSubsystems: &cgroupSubsystems, cgroupSubsystems: &cgroupSubsystems,
watcher: watcher,
} }
container.RegisterContainerHandlerFactory(factory) container.RegisterContainerHandlerFactory(factory)
return nil return nil

View File

@ -43,17 +43,11 @@ type rawContainerHandler struct {
machineInfoFactory info.MachineInfoFactory machineInfoFactory info.MachineInfoFactory
// Inotify event watcher. // Inotify event watcher.
watcher *inotify.Watcher watcher *InotifyWatcher
// Signal for watcher thread to stop. // Signal for watcher thread to stop.
stopWatcher chan error 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. // Absolute path to the cgroup hierarchies of this container.
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string cgroupPaths map[string]string
@ -68,7 +62,7 @@ type rawContainerHandler struct {
externalMounts []mount 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. // Create the cgroup paths.
cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints))
for key, val := range cgroupSubsystems.MountPoints { for key, val := range cgroupSubsystems.MountPoints {
@ -107,13 +101,12 @@ func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSu
cgroupSubsystems: cgroupSubsystems, cgroupSubsystems: cgroupSubsystems,
machineInfoFactory: machineInfoFactory, machineInfoFactory: machineInfoFactory,
stopWatcher: make(chan error), stopWatcher: make(chan error),
watches: make(map[string]struct{}),
cgroupWatches: make(map[string]struct{}),
cgroupPaths: cgroupPaths, cgroupPaths: cgroupPaths,
cgroupManager: cgroupManager, cgroupManager: cgroupManager,
fsInfo: fsInfo, fsInfo: fsInfo,
hasNetwork: hasNetwork, hasNetwork: hasNetwork,
externalMounts: externalMounts, externalMounts: externalMounts,
watcher: watcher,
}, nil }, nil
} }
@ -402,29 +395,43 @@ func (self *rawContainerHandler) ListProcesses(listType container.ListType) ([]i
return libcontainer.GetProcesses(self.cgroupManager) return libcontainer.GetProcesses(self.cgroupManager)
} }
func (self *rawContainerHandler) watchDirectory(dir string, containerName string) error { // Watches the specified directory and all subdirectories. Returns whether the path was
err := self.watcher.AddWatch(dir, inotify.IN_CREATE|inotify.IN_DELETE|inotify.IN_MOVE) // 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 { 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. // TODO(vmarmol): We should re-do this once we're done to ensure directories were not added in the meantime.
// Watch subdirectories as well. // Watch subdirectories as well.
entries, err := ioutil.ReadDir(dir) entries, err := ioutil.ReadDir(dir)
if err != nil { if err != nil {
return err return alreadyWatching, err
} }
for _, entry := range entries { for _, entry := range entries {
if entry.IsDir() { 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 { 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 { 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. // Maintain the watch for the new or deleted container.
switch { switch {
case eventType == container.SubcontainerAdd: case eventType == container.SubcontainerAdd:
_, alreadyWatched := self.watches[containerName]
// New container was created, watch it. // New container was created, watch it.
err := self.watchDirectory(event.Name, containerName) alreadyWatched, err := self.watchDirectory(event.Name, containerName)
if err != nil { if err != nil {
return err return err
} }
@ -473,20 +478,16 @@ func (self *rawContainerHandler) processEvent(event *inotify.Event, events chan
return nil return nil
} }
case eventType == container.SubcontainerDelete: case eventType == container.SubcontainerDelete:
// Container was deleted, stop watching for it. Only delete the event if we registered it. // Container was deleted, stop watching for it.
if _, ok := self.cgroupWatches[event.Name]; ok { lastWatched, err := self.watcher.RemoveWatch(containerName, event.Name)
err := self.watcher.RemoveWatch(event.Name)
if err != nil { if err != nil {
return err return err
} }
delete(self.cgroupWatches, event.Name)
}
// Only report container deletion once. // Only report container deletion once.
if _, ok := self.watches[containerName]; !ok { if !lastWatched {
return nil return nil
} }
delete(self.watches, containerName)
default: default:
return fmt.Errorf("unknown event type %v", eventType) 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 { 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. // Watch this container (all its cgroups) and all subdirectories.
for _, cgroupPath := range self.cgroupPaths { for _, cgroupPath := range self.cgroupPaths {
err := self.watchDirectory(cgroupPath, self.name) _, err := self.watchDirectory(cgroupPath, self.name)
if err != nil { if err != nil {
return err return err
} }
@ -522,18 +514,17 @@ func (self *rawContainerHandler) WatchSubcontainers(events chan container.Subcon
go func() { go func() {
for { for {
select { select {
case event := <-self.watcher.Event: case event := <-self.watcher.Event():
err := self.processEvent(event, events) err := self.processEvent(event, events)
if err != nil { if err != nil {
glog.Warningf("Error while processing event (%+v): %v", event, err) 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) glog.Warningf("Error while watching %q:", self.name, err)
case <-self.stopWatcher: case <-self.stopWatcher:
err := self.watcher.Close() err := self.watcher.Close()
if err == nil { if err == nil {
self.stopWatcher <- err self.stopWatcher <- err
self.watcher = nil
return return
} }
} }
@ -544,10 +535,6 @@ func (self *rawContainerHandler) WatchSubcontainers(events chan container.Subcon
} }
func (self *rawContainerHandler) StopWatchingSubcontainers() error { 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. // Rendezvous with the watcher thread.
self.stopWatcher <- nil self.stopWatcher <- nil
return <-self.stopWatcher return <-self.stopWatcher

View File

@ -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
}

View File

@ -175,11 +175,12 @@ type ProcessInfo struct {
Pid int `json:"pid"` Pid int `json:"pid"`
Ppid int `json:"parent_pid"` Ppid int `json:"parent_pid"`
StartTime string `json:"start_time"` StartTime string `json:"start_time"`
PercentCpu string `json:"percent_cpu"` PercentCpu float32 `json:"percent_cpu"`
PercentMemory string `json:"percent_mem"` PercentMemory float32 `json:"percent_mem"`
RSS string `json:"rss"` RSS uint64 `json:"rss"`
VirtualSize string `json:"virtual_size"` VirtualSize uint64 `json:"virtual_size"`
Status string `json:"status"` Status string `json:"status"`
RunningTime string `json:"running_time"` RunningTime string `json:"running_time"`
CgroupPath string `json:"cgroup_path"`
Cmd string `json:"cmd"` Cmd string `json:"cmd"`
} }

View File

@ -19,6 +19,7 @@ import (
"fmt" "fmt"
"math" "math"
"os/exec" "os/exec"
"regexp"
"sort" "sort"
"strconv" "strconv"
"strings" "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 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 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. // Decay value used for load average smoothing. Interval length of 10 seconds is used.
var loadDecay = math.Exp(float64(-1 * (*HousekeepingInterval).Seconds() / 10)) var loadDecay = math.Exp(float64(-1 * (*HousekeepingInterval).Seconds() / 10))
@ -116,6 +119,19 @@ func (c *containerData) DerivedStats() (v2.DerivedStats, error) {
return c.summaryReader.DerivedStats() 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) { func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) {
// report all processes for root. // report all processes for root.
isRoot := c.info.Name == "/" isRoot := c.info.Name == "/"
@ -130,9 +146,9 @@ func (c *containerData) GetProcessList() ([]v2.ProcessInfo, error) {
} }
} }
// TODO(rjnagal): Take format as an option? // 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} args := []string{"-e", "-o", format}
expectedFields := 11 expectedFields := 12
out, err := exec.Command("ps", args...).Output() out, err := exec.Command("ps", args...).Output()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to execute ps command: %v", err) 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 { if err != nil {
return nil, fmt.Errorf("invalid ppid %q: %v", fields[2], err) 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 { if isRoot || pidMap[pid] == true {
processes = append(processes, v2.ProcessInfo{ processes = append(processes, v2.ProcessInfo{
User: fields[0], User: fields[0],
Pid: pid, Pid: pid,
Ppid: ppid, Ppid: ppid,
StartTime: fields[3], StartTime: fields[3],
PercentCpu: fields[4], PercentCpu: float32(percentCpu),
PercentMemory: fields[5], PercentMemory: float32(percentMem),
RSS: fields[6], RSS: rss,
VirtualSize: fields[7], VirtualSize: vs,
Status: fields[8], Status: fields[8],
RunningTime: fields[9], RunningTime: fields[9],
Cmd: strings.Join(fields[10:], " "), Cmd: fields[10],
CgroupPath: cgroupPath,
}) })
} }
} }

View File

@ -107,6 +107,9 @@ type Manager interface {
// Get details about interesting docker images. // Get details about interesting docker images.
DockerImages() ([]DockerImage, error) 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. // New takes a memory storage and returns a new manager.
@ -1131,3 +1134,38 @@ func (m *manager) DockerInfo() (DockerStatus, error) {
} }
return out, nil 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
}

View File

@ -64,6 +64,36 @@ const containersHtmlTemplate = `
</div> </div>
</div> </div>
{{end}} {{end}}
{{if .DockerStatus}}
<div class="col-sm-12">
<div class="page-header">
<h3>Driver Status</h3>
</div>
<ul class="list-group">
{{range $dockerstatus := .DockerStatus}}
<li class ="list-group-item"><span class="stat-label">{{$dockerstatus.Key}}</span> {{$dockerstatus.Value}}</li>
{{end}}
{{if .DockerDriverStatus}}
<li class ="list-group-item"><span class="stat-label">Storage<br></span>
<ul class="list-group">
{{range $driverstatus := .DockerDriverStatus}}
<li class="list-group-item"><span class="stat-label">{{$driverstatus.Key}}</span> {{$driverstatus.Value}}</li>
{{end}}
</ul>
</li>
</ul>
{{end}}
</div>
{{end}}
{{if .DockerImages}}
<div class="col-sm-12">
<div class="page-header">
<h3>Images</h3>
</div>
<div id="docker-images"></div>
<br><br>
</div>
{{end}}
{{if .ResourcesAvailable}} {{if .ResourcesAvailable}}
<div class="col-sm-12"> <div class="col-sm-12">
<div class="page-header"> <div class="page-header">
@ -185,7 +215,8 @@ const containersHtmlTemplate = `
{{end}} {{end}}
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}}, {{.Root}}); startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}}, {{.Root}}, {{.IsRoot}});
drawImages({{.DockerImages}});
</script> </script>
</body> </body>
</html> </html>

View File

@ -19,6 +19,7 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"strconv"
"time" "time"
"github.com/golang/glog" "github.com/golang/glog"
@ -29,6 +30,25 @@ import (
const DockerPage = "/docker/" 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 { func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error {
start := time.Now() 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" dockerContainersText := "Docker Containers"
data = &pageData{ data = &pageData{
DisplayName: dockerContainersText, DisplayName: dockerContainersText,
@ -64,6 +97,9 @@ func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error
}}, }},
Subcontainers: subcontainers, Subcontainers: subcontainers,
Root: rootDir, Root: rootDir,
DockerStatus: dockerStatus,
DockerDriverStatus: driverStatus,
DockerImages: images,
} }
} else { } else {
// Get the container. // Get the container.
@ -92,7 +128,6 @@ func serveDockerPage(m manager.Manager, w http.ResponseWriter, u *url.URL) error
if err != nil { if err != nil {
return err return err
} }
data = &pageData{ data = &pageData{
DisplayName: displayName, DisplayName: displayName,
ContainerName: cont.Name, ContainerName: cont.Name,

View File

@ -37,6 +37,11 @@ type link struct {
Link string Link string
} }
type keyVal struct {
Key string
Value string
}
type pageData struct { type pageData struct {
DisplayName string DisplayName string
ContainerName string ContainerName string
@ -52,6 +57,9 @@ type pageData struct {
NetworkAvailable bool NetworkAvailable bool
FsAvailable bool FsAvailable bool
Root string Root string
DockerStatus []keyVal
DockerDriverStatus []keyVal
DockerImages []manager.DockerImage
} }
func init() { func init() {

View File

@ -39,6 +39,17 @@ const containersCss = `
.isolation-title { .isolation-title {
color:#FFFFFF; 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 { #logo {
height: 200px; height: 200px;
margin-top: 20px; margin-top: 20px;

View File

@ -25,10 +25,9 @@ function humanize(num, size, units) {
// Following the IEC naming convention // Following the IEC naming convention
function humanizeIEC(num) { 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]; return ret[0].toFixed(2) + " " + ret[1];
} }
// Following the Metric naming convention // Following the Metric naming convention
function humanizeMetric(num) { function humanizeMetric(num) {
var ret = humanize(num, 1000, ["TB", "GB", "MB", "KB", "Bytes"]); var ret = humanize(num, 1000, ["TB", "GB", "MB", "KB", "Bytes"]);
@ -36,7 +35,7 @@ function humanizeMetric(num) {
} }
// Draw a table. // Draw a table.
function drawTable(seriesTitles, titleTypes, data, elementId) { function drawTable(seriesTitles, titleTypes, data, elementId, numPages, sortIndex) {
var dataTable = new google.visualization.DataTable(); var dataTable = new google.visualization.DataTable();
for (var i = 0; i < seriesTitles.length; i++) { for (var i = 0; i < seriesTitles.length; i++) {
dataTable.addColumn(titleTypes[i], seriesTitles[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)); window.charts[elementId] = new google.visualization.Table(document.getElementById(elementId));
} }
var cssClassNames = {
'headerRow': '',
'tableRow': 'table-row',
'oddTableRow': 'table-row'
};
var opts = { var opts = {
alternatingRowStyle: true, alternatingRowStyle: true,
page: 'enable', page: 'enable',
pageSize: 25, pageSize: numPages,
allowHtml: true,
sortColumn: sortIndex,
sortAscending: false,
cssClassNames: cssClassNames,
}; };
window.charts[elementId].draw(dataTable, opts); window.charts[elementId].draw(dataTable, opts);
} }
@ -167,8 +175,12 @@ function getMachineInfo(rootDir, callback) {
// Get ps info. // Get ps info.
function getProcessInfo(rootDir, containerName, callback) { 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); 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 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 = [] var data = []
for (var i = 1; i < processInfo.length; i++) { for (var i = 0; i < processInfo.length; i++) {
var elements = []; var elements = [];
elements.push(processInfo[i].user); elements.push(processInfo[i].user);
elements.push(processInfo[i].pid); elements.push(processInfo[i].pid);
elements.push(processInfo[i].parent_pid); elements.push(processInfo[i].parent_pid);
elements.push(processInfo[i].start_time); elements.push(processInfo[i].start_time);
elements.push(processInfo[i].percent_cpu); elements.push({ v:processInfo[i].percent_cpu, f:processInfo[i].percent_cpu.toFixed(2)});
elements.push(processInfo[i].percent_mem); elements.push({ v:processInfo[i].percent_mem, f:processInfo[i].percent_mem.toFixed(2)});
elements.push(processInfo[i].rss); elements.push({ v:processInfo[i].rss, f:humanizeIEC(processInfo[i].rss)});
elements.push(processInfo[i].virtual_size); elements.push({ v:processInfo[i].virtual_size, f:humanizeIEC(processInfo[i].virtual_size)});
elements.push(processInfo[i].status); elements.push(processInfo[i].status);
elements.push(processInfo[i].running_time); elements.push(processInfo[i].running_time);
elements.push(processInfo[i].cmd); 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 = '<a href="' + rootDir + 'containers/' + cgroup +'">' + cgroup.substr(0,30) + ' </a>';
elements.push({v:cgroup, f:cgroupLink});
}
data.push(elements); data.push(elements);
} }
drawTable(titles, titleTypes, data, "processes-top"); drawTable(titles, titleTypes, data, "processes-top", 25, sortIndex);
} }
// Draw the filesystem usage nodes. // Draw the filesystem usage nodes.
@ -561,7 +619,7 @@ function drawCharts(machineInfo, containerInfo) {
} }
// Executed when the page finishes loading. // 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. // Don't fetch data if we don't have any resource.
if (!hasCpu && !hasMemory) { if (!hasCpu && !hasMemory) {
return; return;
@ -573,11 +631,11 @@ function startPage(containerName, hasCpu, hasMemory, rootDir) {
// Draw process information at start and refresh every 60s. // Draw process information at start and refresh every 60s.
getProcessInfo(rootDir, containerName, function(processInfo) { getProcessInfo(rootDir, containerName, function(processInfo) {
drawProcesses(processInfo) drawProcesses(isRoot, rootDir, processInfo)
}); });
setInterval(function() { setInterval(function() {
getProcessInfo(rootDir, containerName, function(processInfo) { getProcessInfo(rootDir, containerName, function(processInfo) {
drawProcesses(processInfo) drawProcesses(isRoot, rootDir, processInfo)
}); });
}, 60000); }, 60000);

View File

@ -313,6 +313,13 @@ func HandleRequest(w http.ResponseWriter, containerManager manager.Manager) erro
ioSchedulerValidation, desc := validateIoScheduler(containerManager) ioSchedulerValidation, desc := validateIoScheduler(containerManager)
out += fmt.Sprintf(OutputFormat, "Block device setup", ioSchedulerValidation, desc) 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)) _, err = w.Write([]byte(out))
return err return err
} }

View File

@ -15,4 +15,4 @@
package version package version
// Version of cAdvisor. // Version of cAdvisor.
const VERSION = "0.13.0" const VERSION = "0.14.0"