Add /storage endpoint to 2.0 API.
/storage returns {device, mountpoint, capacity, usage} for all filesystems. In addition, it also detect and applies label for each filesystem - currently two - "root", "docker-images". /storage/<label> returns info about the filesystem with specific label. eg. /storage/root returns info for root filesystem.
This commit is contained in:
parent
62a1788621
commit
a0a419614f
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
98
fs/fs.go
98
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) {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user