From a5adaad26d730f77eb66c5577759e1b38fe55aac Mon Sep 17 00:00:00 2001 From: Yang Guo Date: Mon, 21 Aug 2017 18:49:39 -0700 Subject: [PATCH] Add an API to get FsStats from filesystem UUID --- README.md | 1 + api/versions.go | 23 +++++++++++------ fs/fs.go | 61 +++++++++++++++++++++++++++++++++++++++++--- fs/types.go | 13 +++++++++- info/v2/container.go | 3 +++ manager/manager.go | 44 ++++++++++++++++++++++---------- 6 files changed, 119 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 44e9e75003..f7af47652a 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ sudo docker run \ --volume=/var/run:/var/run:rw \ --volume=/sys:/sys:ro \ --volume=/var/lib/docker/:/var/lib/docker:ro \ + --volume=/dev/disk/:/dev/disk:ro \ --publish=8080:8080 \ --detach=true \ --name=cadvisor \ diff --git a/api/versions.go b/api/versions.go index be6ca3de14..7fb0c01de4 100644 --- a/api/versions.go +++ b/api/versions.go @@ -420,23 +420,30 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma } return writeResult(specs, 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("") + uuid := r.URL.Query().Get("uuid") + switch { + case uuid != "": + fi, err := m.GetFsInfoByFsUUID(uuid) if err != nil { return err } - } else { + return writeResult(fi, w) + case label != "": // Get a specific label. - fi, err = m.GetFsInfo(label) + fi, err := m.GetFsInfo(label) + if err != nil { + return err + } + return writeResult(fi, w) + default: + // Get all global filesystems info. + fi, err := m.GetFsInfo("") if err != nil { return err } + return writeResult(fi, w) } - return writeResult(fi, w) case eventsApi: return handleEventRequest(request, m, w, r) case psApi: diff --git a/fs/fs.go b/fs/fs.go index de40d5fd28..361a0c2557 100644 --- a/fs/fs.go +++ b/fs/fs.go @@ -83,6 +83,8 @@ type RealFsInfo struct { mounts map[string]*mount.Info // devicemapper client dmsetup devicemapper.DmsetupClient + // fsUUIDToDeviceName is a map from the filesystem UUID to its device name. + fsUUIDToDeviceName map[string]string } type Context struct { @@ -103,13 +105,19 @@ func NewFsInfo(context Context) (FsInfo, error) { return nil, err } + fsUUIDToDeviceName, err := getFsUUIDToDeviceNameMap() + if err != nil { + return nil, err + } + // Avoid devicemapper container mounts - these are tracked by the ThinPoolWatcher excluded := []string{fmt.Sprintf("%s/devicemapper/mnt", context.Docker.Root)} fsInfo := &RealFsInfo{ - partitions: processMounts(mounts, excluded), - labels: make(map[string]string, 0), - mounts: make(map[string]*mount.Info, 0), - dmsetup: devicemapper.NewDmsetupClient(), + partitions: processMounts(mounts, excluded), + labels: make(map[string]string, 0), + mounts: make(map[string]*mount.Info, 0), + dmsetup: devicemapper.NewDmsetupClient(), + fsUUIDToDeviceName: fsUUIDToDeviceName, } for _, mount := range mounts { @@ -121,11 +129,44 @@ func NewFsInfo(context Context) (FsInfo, error) { // add a "partition" for devicemapper to fsInfo.partitions fsInfo.addDockerImagesLabel(context, mounts) + glog.Infof("Filesystem UUIDs: %+v", fsInfo.fsUUIDToDeviceName) glog.Infof("Filesystem partitions: %+v", fsInfo.partitions) fsInfo.addSystemRootLabel(mounts) return fsInfo, nil } +// getFsUUIDToDeviceNameMap creates the filesystem uuid to device name map +// using the information in /dev/disk/by-uuid. If the directory does not exist, +// this function will return an empty map. +func getFsUUIDToDeviceNameMap() (map[string]string, error) { + const dir = "/dev/disk/by-uuid" + + if _, err := os.Stat(dir); os.IsNotExist(err) { + return make(map[string]string), nil + } + + files, err := ioutil.ReadDir(dir) + if err != nil { + return nil, err + } + + fsUUIDToDeviceName := make(map[string]string) + for _, file := range files { + path := filepath.Join(dir, file.Name()) + target, err := os.Readlink(path) + if err != nil { + glog.Infof("Failed to resolve symlink for %q", path) + continue + } + device, err := filepath.Abs(filepath.Join(dir, target)) + if err != nil { + return nil, fmt.Errorf("failed to resolve the absolute path of %q", filepath.Join(dir, target)) + } + fsUUIDToDeviceName[file.Name()] = device + } + return fsUUIDToDeviceName, nil +} + func processMounts(mounts []*mount.Info, excludedMountpointPrefixes []string) map[string]partition { partitions := make(map[string]partition, 0) @@ -433,6 +474,18 @@ func minor(devNumber uint64) uint { return uint((devNumber & 0xff) | ((devNumber >> 12) & 0xfff00)) } +func (self *RealFsInfo) GetDeviceInfoByFsUUID(uuid string) (*DeviceInfo, error) { + deviceName, found := self.fsUUIDToDeviceName[uuid] + if !found { + return nil, ErrNoSuchDevice + } + p, found := self.partitions[deviceName] + if !found { + return nil, fmt.Errorf("cannot find device %q in partitions", deviceName) + } + return &DeviceInfo{deviceName, p.major, p.minor}, nil +} + func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) { buf := new(syscall.Stat_t) err := syscall.Stat(dir, buf) diff --git a/fs/types.go b/fs/types.go index 0bed080d13..59305819a5 100644 --- a/fs/types.go +++ b/fs/types.go @@ -14,7 +14,10 @@ package fs -import "time" +import ( + "errors" + "time" +) type DeviceInfo struct { Device string @@ -59,6 +62,9 @@ type DiskStats struct { WeightedIoTime uint64 } +// ErrNoSuchDevice is the error indicating the requested device does not exist. +var ErrNoSuchDevice = errors.New("cadvisor: no such device") + type FsInfo interface { // Returns capacity and free space, in bytes, of all the ext2, ext3, ext4 filesystems on the host. GetGlobalFsInfo() ([]Fs, error) @@ -72,6 +78,11 @@ type FsInfo interface { // Returns number of inodes used by 'dir'. GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error) + // GetDeviceInfoByFsUUID returns the information of the device with the + // specified filesystem uuid. If no such device exists, this function will + // return the ErrNoSuchDevice error. + GetDeviceInfoByFsUUID(uuid string) (*DeviceInfo, error) + // Returns the block device info of the filesystem on which 'dir' resides. GetDirFsDevice(dir string) (*DeviceInfo, error) diff --git a/info/v2/container.go b/info/v2/container.go index cda1208a1b..ce102ec897 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -199,6 +199,9 @@ type DerivedStats struct { } type FsInfo struct { + // Time of generation of these stats. + Timestamp time.Time `json:"timestamp"` + // The block device name associated with the filesystem. Device string `json:"device"` diff --git a/manager/manager.go b/manager/manager.go index e9415fa8a9..8086c058a2 100644 --- a/manager/manager.go +++ b/manager/manager.go @@ -101,6 +101,11 @@ type Manager interface { // Get version information about different components we depend on. GetVersionInfo() (*info.VersionInfo, error) + // GetFsInfoByFsUUID returns the information of the device having the + // specified filesystem uuid. If no such device with the UUID exists, this + // function will return the fs.ErrNoSuchDevice error. + GetFsInfoByFsUUID(uuid string) (v2.FsInfo, error) + // Get filesystem information for the filesystem that contains the given directory GetDirFsInfo(dir string) (v2.FsInfo, error) @@ -676,24 +681,19 @@ func (self *manager) getRequestedContainers(containerName string, options v2.Req } func (self *manager) GetDirFsInfo(dir string) (v2.FsInfo, error) { - dirDevice, err := self.fsInfo.GetDirFsDevice(dir) - if err != nil { - return v2.FsInfo{}, fmt.Errorf("error trying to get filesystem Device for dir %v: err: %v", dir, err) - } - dirMountpoint, err := self.fsInfo.GetMountpointForDevice(dirDevice.Device) + device, err := self.fsInfo.GetDirFsDevice(dir) if err != nil { - return v2.FsInfo{}, fmt.Errorf("error trying to get MountPoint for Root Device: %v, err: %v", dirDevice, err) + return v2.FsInfo{}, fmt.Errorf("failed to get device for dir %q: %v", dir, err) } - infos, err := self.GetFsInfo("") + return self.getFsInfoByDeviceName(device.Device) +} + +func (self *manager) GetFsInfoByFsUUID(uuid string) (v2.FsInfo, error) { + device, err := self.fsInfo.GetDeviceInfoByFsUUID(uuid) if err != nil { return v2.FsInfo{}, err } - for _, info := range infos { - if info.Mountpoint == dirMountpoint { - return info, nil - } - } - return v2.FsInfo{}, fmt.Errorf("did not find fs info for dir: %v", dir) + return self.getFsInfoByDeviceName(device.Device) } func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) { @@ -726,6 +726,7 @@ func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) { } fi := v2.FsInfo{ + Timestamp: stats[0].Timestamp, Device: fs.Device, Mountpoint: mountpoint, Capacity: fs.Limit, @@ -1265,6 +1266,23 @@ func (m *manager) DebugInfo() map[string][]string { return debugInfo } +func (self *manager) getFsInfoByDeviceName(deviceName string) (v2.FsInfo, error) { + mountPoint, err := self.fsInfo.GetMountpointForDevice(deviceName) + if err != nil { + return v2.FsInfo{}, fmt.Errorf("failed to get mount point for device %q: %v", deviceName, err) + } + infos, err := self.GetFsInfo("") + if err != nil { + return v2.FsInfo{}, err + } + for _, info := range infos { + if info.Mountpoint == mountPoint { + return info, nil + } + } + return v2.FsInfo{}, fmt.Errorf("cannot find filesystem info for device %q", deviceName) +} + func getVersionInfo() (*info.VersionInfo, error) { kernel_version := machine.KernelVersion()