Merge pull request #8675 from vmarmol/cadvisor

Update cAdvisor godep to 0.14.0+.
pull/6/head
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",
"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",

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -64,6 +64,36 @@ const containersHtmlTemplate = `
</div>
</div>
{{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}}
<div class="col-sm-12">
<div class="page-header">
@ -185,7 +215,8 @@ const containersHtmlTemplate = `
{{end}}
</div>
<script type="text/javascript">
startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}}, {{.Root}});
startPage({{.ContainerName}}, {{.CpuAvailable}}, {{.MemoryAvailable}}, {{.Root}}, {{.IsRoot}});
drawImages({{.DockerImages}});
</script>
</body>
</html>

View File

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

View File

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

View File

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

View File

@ -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 = '<a href="' + rootDir + 'containers/' + cgroup +'">' + cgroup.substr(0,30) + ' </a>';
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);

View File

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

View File

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