diff --git a/api/versions.go b/api/versions.go index 1553ddf9..fe83a527 100644 --- a/api/versions.go +++ b/api/versions.go @@ -33,6 +33,7 @@ const ( summaryApi = "summary" specApi = "spec" eventsApi = "events" + storageApi = "storage" ) // Interface for a cAdvisor API version @@ -322,6 +323,24 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma } specV2 := convertSpec(spec) return writeResult(specV2, w) + case storageApi: + var err error + fi := []v2.FsInfo{} + label := r.URL.Query().Get("label") + if len(label) == 0 { + // Get all global filesystems info. + fi, err = m.GetFsInfo("") + if err != nil { + return err + } + } else { + // Get a specific label. + fi, err = m.GetFsInfo(label) + if err != nil { + return err + } + } + return writeResult(fi, w) default: return self.baseVersion.HandleRequest(requestType, request, m, w, r) } diff --git a/container/docker/factory.go b/container/docker/factory.go index d7454e60..58ff4388 100644 --- a/container/docker/factory.go +++ b/container/docker/factory.go @@ -28,6 +28,7 @@ import ( "github.com/golang/glog" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/libcontainer" + "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/utils" ) @@ -62,6 +63,10 @@ func UseSystemd() bool { return useSystemd } +func RootDir() string { + return *dockerRootDir +} + type dockerFactory struct { machineInfoFactory info.MachineInfoFactory @@ -72,6 +77,9 @@ type dockerFactory struct { // Information about the mounted cgroup subsystems. cgroupSubsystems libcontainer.CgroupSubsystems + + // Information about mounted filesystems. + fsInfo fs.FsInfo } func (self *dockerFactory) String() string { @@ -87,6 +95,7 @@ func (self *dockerFactory) NewContainerHandler(name string) (handler container.C client, name, self.machineInfoFactory, + self.fsInfo, *dockerRootDir, self.usesAufsDriver, &self.cgroupSubsystems, @@ -151,7 +160,7 @@ func parseDockerVersion(full_version_string string) ([]int, error) { } // Register root container before running this function! -func Register(factory info.MachineInfoFactory) error { +func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo) error { client, err := docker.NewClient(*ArgDockerEndpoint) if err != nil { return fmt.Errorf("unable to communicate with docker daemon: %v", err) @@ -213,6 +222,7 @@ func Register(factory info.MachineInfoFactory) error { client: client, usesAufsDriver: usesAufsDriver, cgroupSubsystems: cgroupSubsystems, + fsInfo: fsInfo, } container.RegisterContainerHandlerFactory(f) return nil diff --git a/container/docker/handler.go b/container/docker/handler.go index a05dfe27..f0d63ac3 100644 --- a/container/docker/handler.go +++ b/container/docker/handler.go @@ -82,16 +82,11 @@ func newDockerContainerHandler( client *docker.Client, name string, machineInfoFactory info.MachineInfoFactory, + fsInfo fs.FsInfo, dockerRootDir string, usesAufsDriver bool, cgroupSubsystems *containerLibcontainer.CgroupSubsystems, ) (container.ContainerHandler, error) { - // TODO(vmarmol): Get from factory. - fsInfo, err := fs.NewFsInfo() - if err != nil { - return nil, err - } - // Create the cgroup paths. cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) for key, val := range cgroupSubsystems.MountPoints { diff --git a/container/raw/factory.go b/container/raw/factory.go index 4cd918f3..111e422f 100644 --- a/container/raw/factory.go +++ b/container/raw/factory.go @@ -20,6 +20,7 @@ import ( "github.com/golang/glog" "github.com/google/cadvisor/container" "github.com/google/cadvisor/container/libcontainer" + "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" ) @@ -29,6 +30,9 @@ type rawFactory struct { // Information about the cgroup subsystems. cgroupSubsystems *libcontainer.CgroupSubsystems + + // Information about mounted filesystems. + fsInfo fs.FsInfo } func (self *rawFactory) String() string { @@ -36,7 +40,7 @@ func (self *rawFactory) String() string { } func (self *rawFactory) NewContainerHandler(name string) (container.ContainerHandler, error) { - return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory) + return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo) } // The raw factory can handle any container. @@ -44,7 +48,7 @@ func (self *rawFactory) CanHandle(name string) (bool, error) { return true, nil } -func Register(machineInfoFactory info.MachineInfoFactory) error { +func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo) error { cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() if err != nil { return fmt.Errorf("failed to get cgroup subsystems: %v", err) @@ -56,6 +60,7 @@ func Register(machineInfoFactory info.MachineInfoFactory) error { glog.Infof("Registering Raw factory") factory := &rawFactory{ machineInfoFactory: machineInfoFactory, + fsInfo: fsInfo, cgroupSubsystems: &cgroupSubsystems, } container.RegisterContainerHandlerFactory(factory) diff --git a/container/raw/handler.go b/container/raw/handler.go index d5781e76..c8f0c2e4 100644 --- a/container/raw/handler.go +++ b/container/raw/handler.go @@ -71,18 +71,13 @@ type rawContainerHandler struct { externalMounts []mount } -func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory) (container.ContainerHandler, error) { +func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSubsystems, machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo) (container.ContainerHandler, error) { // Create the cgroup paths. cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) for key, val := range cgroupSubsystems.MountPoints { cgroupPaths[key] = path.Join(val, name) } - // TODO(vmarmol): Get from factory. - fsInfo, err := fs.NewFsInfo() - if err != nil { - return nil, err - } cHints, err := getContainerHintsFromFile(*argContainerHints) if err != nil { return nil, err diff --git a/fs/fs.go b/fs/fs.go index 2869f72c..613ebbcc 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -29,6 +29,7 @@ import ( "os" "os/exec" "path" + "path/filepath" "regexp" "strconv" "strings" @@ -41,6 +42,11 @@ import ( var partitionRegex = regexp.MustCompile("^(:?(:?s|xv)d[a-z]+\\d*|dm-\\d+)$") +const ( + LabelSystemRoot = "root" + LabelDockerImages = "docker-images" +) + type partition struct { mountpoint string major uint @@ -48,15 +54,26 @@ type partition struct { } type RealFsInfo struct { + // Map from block device path to partition information. partitions map[string]partition + // Map from label to block device path. + // Labels are intent-specific tags that are auto-detected. + labels map[string]string } -func NewFsInfo() (FsInfo, error) { +type Context struct { + // docker root directory. + DockerRoot string +} + +func NewFsInfo(context Context) (FsInfo, error) { mounts, err := mount.GetMounts() if err != nil { return nil, err } partitions := make(map[string]partition, 0) + fsInfo := &RealFsInfo{} + fsInfo.labels = make(map[string]string, 0) for _, mount := range mounts { if !strings.HasPrefix(mount.Fstype, "ext") && mount.Fstype != "btrfs" { continue @@ -68,7 +85,84 @@ func NewFsInfo() (FsInfo, error) { partitions[mount.Source] = partition{mount.Mountpoint, uint(mount.Major), uint(mount.Minor)} } glog.Infof("Filesystem partitions: %+v", partitions) - return &RealFsInfo{partitions}, nil + fsInfo.partitions = partitions + fsInfo.addLabels(context) + return fsInfo, nil +} + +func (self *RealFsInfo) addLabels(context Context) { + dockerPaths := getDockerImagePaths(context) + for src, p := range self.partitions { + if p.mountpoint == "/" { + if _, ok := self.labels[LabelSystemRoot]; !ok { + self.labels[LabelSystemRoot] = src + } + } + self.updateDockerImagesPath(src, p.mountpoint, dockerPaths) + // TODO(rjnagal): Add label for docker devicemapper pool. + } +} + +// Generate a list of possible mount points for docker image management from the docker root directory. +// Right now, we look for each type of supported graph driver directories, but we can do better by parsing +// some of the context from `docker info`. +func getDockerImagePaths(context Context) []string { + // TODO(rjnagal): Detect docker root and graphdriver directories from docker info. + dockerRoot := context.DockerRoot + dockerImagePaths := []string{} + for _, dir := range []string{"devicemapper", "btrfs", "aufs"} { + dockerImagePaths = append(dockerImagePaths, path.Join(dockerRoot, dir)) + } + for dockerRoot != "/" && dockerRoot != "." { + dockerImagePaths = append(dockerImagePaths, dockerRoot) + dockerRoot = filepath.Dir(dockerRoot) + } + dockerImagePaths = append(dockerImagePaths, "/") + return dockerImagePaths +} + +// This method compares the mountpoint with possible docker image mount points. If a match is found, +// docker images label is added to the partition. +func (self *RealFsInfo) updateDockerImagesPath(source string, mountpoint string, dockerImagePaths []string) { + for _, v := range dockerImagePaths { + if v == mountpoint { + if i, ok := self.labels[LabelDockerImages]; ok { + // pick the innermost mountpoint. + mnt := self.partitions[i].mountpoint + if len(mnt) < len(mountpoint) { + self.labels[LabelDockerImages] = source + } + } else { + self.labels[LabelDockerImages] = source + } + } + } +} + +func (self *RealFsInfo) GetDeviceForLabel(label string) (string, error) { + dev, ok := self.labels[label] + if !ok { + return "", fmt.Errorf("non-existent label %q", label) + } + return dev, nil +} + +func (self *RealFsInfo) GetLabelsForDevice(device string) ([]string, error) { + labels := []string{} + for label, dev := range self.labels { + if dev == device { + labels = append(labels, label) + } + } + return labels, nil +} + +func (self *RealFsInfo) GetMountpointForDevice(dev string) (string, error) { + p, ok := self.partitions[dev] + if !ok { + return "", fmt.Errorf("no partition info for device %q", dev) + } + return p.mountpoint, nil } func (self *RealFsInfo) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error) { diff --git a/fs/types.go b/fs/types.go index 9cba902b..77a46a88 100644 --- a/fs/types.go +++ b/fs/types.go @@ -53,4 +53,13 @@ type FsInfo interface { // Returns the block device info of the filesystem on which 'dir' resides. GetDirFsDevice(dir string) (*DeviceInfo, error) + + // Returns the device name associated with a particular label. + GetDeviceForLabel(label string) (string, error) + + // Returns all labels associated with a particular device name. + GetLabelsForDevice(device string) ([]string, error) + + // Returns the mountpoint associated with a particular device. + GetMountpointForDevice(device string) (string, error) } diff --git a/info/v2/container.go b/info/v2/container.go index 65d85c2e..33745ec6 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -97,3 +97,20 @@ type DerivedStats struct { // Percentile in last day. DayUsage Usage `json:"day_usage"` } + +type FsInfo struct { + // The block device name associated with the filesystem. + Device string `json:"device"` + + // Path where the filesystem is mounted. + Mountpoint string `json:"mountpoint"` + + // Filesystem usage in bytes. + Capacity uint64 `json:"capacity"` + + // Number of bytes used on this filesystem. + Usage uint64 `json:"usage"` + + // Labels associated with this filesystem. + Labels []string `json:"labels"` +} diff --git a/manager/machine.go b/manager/machine.go index 8a0a6592..c424cbf6 100644 --- a/manager/machine.go +++ b/manager/machine.go @@ -223,7 +223,7 @@ func getMachineID() string { return "" } -func getMachineInfo(sysFs sysfs.SysFs) (*info.MachineInfo, error) { +func getMachineInfo(sysFs sysfs.SysFs, fsInfo fs.FsInfo) (*info.MachineInfo, error) { cpuinfo, err := ioutil.ReadFile("/proc/cpuinfo") clockSpeed, err := getClockSpeed(cpuinfo) if err != nil { @@ -241,10 +241,6 @@ func getMachineInfo(sysFs sysfs.SysFs) (*info.MachineInfo, error) { return nil, err } - fsInfo, err := fs.NewFsInfo() - if err != nil { - return nil, err - } filesystems, err := fsInfo.GetGlobalFsInfo() if err != nil { return nil, err diff --git a/manager/manager.go b/manager/manager.go index b7edeed9..b35a622e 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -30,6 +30,7 @@ import ( "github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/container/raw" "github.com/google/cadvisor/events" + "github.com/google/cadvisor/fs" info "github.com/google/cadvisor/info/v1" "github.com/google/cadvisor/info/v2" "github.com/google/cadvisor/storage/memory" @@ -74,6 +75,10 @@ type Manager interface { // Get version information about different components we depend on. GetVersionInfo() (*info.VersionInfo, error) + // Get filesystem information for a given label. + // Returns information for all global filesystems if label is empty. + GetFsInfo(label string) ([]v2.FsInfo, error) + // Get events streamed through passedChannel that fit the request. WatchForEvents(request *events.Request, passedChannel chan *events.Event) error @@ -94,15 +99,21 @@ func New(memoryStorage *memory.InMemoryStorage, sysfs sysfs.SysFs) (Manager, err } glog.Infof("cAdvisor running in container: %q", selfContainer) + context := fs.Context{DockerRoot: docker.RootDir()} + fsInfo, err := fs.NewFsInfo(context) + if err != nil { + return nil, err + } newManager := &manager{ containers: make(map[namespacedContainerName]*containerData), quitChannels: make([]chan error, 0, 2), memoryStorage: memoryStorage, + fsInfo: fsInfo, cadvisorContainer: selfContainer, startupTime: time.Now(), } - machineInfo, err := getMachineInfo(sysfs) + machineInfo, err := getMachineInfo(sysfs, fsInfo) if err != nil { return nil, err } @@ -119,13 +130,13 @@ func New(memoryStorage *memory.InMemoryStorage, sysfs sysfs.SysFs) (Manager, err newManager.eventHandler = events.NewEventManager() // Register Docker container factory. - err = docker.Register(newManager) + err = docker.Register(newManager, fsInfo) if err != nil { glog.Errorf("Docker container factory registration failed: %v.", err) } // Register the raw driver. - err = raw.Register(newManager) + err = raw.Register(newManager, fsInfo) if err != nil { return nil, fmt.Errorf("registration of the raw container factory failed: %v", err) } @@ -146,6 +157,7 @@ type manager struct { containers map[namespacedContainerName]*containerData containersLock sync.RWMutex memoryStorage *memory.InMemoryStorage + fsInfo fs.FsInfo machineInfo info.MachineInfo versionInfo info.VersionInfo quitChannels []chan error @@ -439,6 +451,45 @@ func (self *manager) GetContainerDerivedStats(containerName string) (v2.DerivedS return cont.DerivedStats() } +func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) { + var empty time.Time + // Get latest data from filesystems hanging off root container. + stats, err := self.memoryStorage.RecentStats("/", empty, empty, 1) + if err != nil { + return nil, err + } + dev := "" + if len(label) != 0 { + dev, err = self.fsInfo.GetDeviceForLabel(label) + if err != nil { + return nil, err + } + } + fsInfo := []v2.FsInfo{} + for _, fs := range stats[0].Filesystem { + if len(label) != 0 && fs.Device != dev { + continue + } + mountpoint, err := self.fsInfo.GetMountpointForDevice(fs.Device) + if err != nil { + return nil, err + } + labels, err := self.fsInfo.GetLabelsForDevice(fs.Device) + if err != nil { + return nil, err + } + fi := v2.FsInfo{ + Device: fs.Device, + Mountpoint: mountpoint, + Capacity: fs.Limit, + Usage: fs.Usage, + Labels: labels, + } + fsInfo = append(fsInfo, fi) + } + return fsInfo, nil +} + func (m *manager) GetMachineInfo() (*info.MachineInfo, error) { // Copy and return the MachineInfo. return &m.machineInfo, nil diff --git a/manager/manager_mock.go b/manager/manager_mock.go index 655c4b2e..a14619e3 100644 --- a/manager/manager_mock.go +++ b/manager/manager_mock.go @@ -84,3 +84,8 @@ func (c *ManagerMock) GetVersionInfo() (*info.VersionInfo, error) { args := c.Called() return args.Get(0).(*info.VersionInfo), args.Error(1) } + +func (c *ManagerMock) GetFsInfo() ([]v2.FsInfo, error) { + args := c.Called() + return args.Get(0).([]v2.FsInfo), args.Error(1) +}