diff --git a/api/versions.go b/api/versions.go index c4d6f7c7..aa1910b7 100644 --- a/api/versions.go +++ b/api/versions.go @@ -31,6 +31,7 @@ const ( containersApi = "containers" subcontainersApi = "subcontainers" machineApi = "machine" + machineStatsApi = "machinestats" dockerApi = "docker" summaryApi = "summary" statsApi = "stats" @@ -62,6 +63,7 @@ func getApiVersions() []ApiVersion { v1_2 := newVersion1_2(v1_1) v1_3 := newVersion1_3(v1_2) v2_0 := newVersion2_0() + v2_1 := newVersion2_1() return []ApiVersion{v1_0, v1_1, v1_2, v1_3, v2_0} @@ -449,6 +451,56 @@ func (self *version2_0) HandleRequest(requestType string, request []string, m ma } } +type version2_1 struct { + baseVersion *version2_0 +} + +func newVersion2_1(v *version2_0) *version2_1 { + return &version2_1{ + baseVersion: v, + } +} + +func (self *version2_1) Version() string { + return "v2.1" +} + +func (self *version2_1) SupportedRequestTypes() []string { + return self.baseVersion.SupportedRequestTypes() +} + +func (self *version2_1) HandleRequest(requestType string, request []string, m manager.Manager, w http.ResponseWriter, r *http.Request) error { + // Get the query request. + opt, err := getContainerInfoRequest(r.Body) + if err != nil { + return err + } + + switch requestType { + case machineStatsApi: + glog.V(4).Infof("Api - MachineStats(%v)", request) + cont, err := m.GetRequestedContainersInfo("/", opt) + if err != nil { + return err + } + return writeResult(convertMachineStats(cont), w) + case statsApi: + name := getContainerName(request) + glog.V(4).Infof("Api - Stats: Looking for stats for container %q, options %+v", name, opt) + conts, err := m.GetRequestedContainersInfo(name, opt) + if err != nil { + return err + } + contStats := make(map[string][]v2.ContainerStats, 0) + for name, cont := range conts { + contStats[name] = convertStats(cont) + } + return writeResult(contStats, w) + default: + return self.baseVersion.HandleRequest(requestType, request, m, w, r) + } +} + func getRequestOptions(r *http.Request) (v2.RequestOptions, error) { supportedTypes := map[string]bool{ v2.TypeName: true, diff --git a/container/docker/fsHandler.go b/container/docker/fsHandler.go index 6a0abb3a..d5d307e6 100644 --- a/container/docker/fsHandler.go +++ b/container/docker/fsHandler.go @@ -32,11 +32,13 @@ type fsHandler interface { type realFsHandler struct { sync.RWMutex - lastUpdate time.Time - usageBytes uint64 - period time.Duration - storageDirs []string - fsInfo fs.FsInfo + lastUpdate time.Time + usageBytes uint64 + baseUsageBytes uint64 + period time.Duration + writable string + other string + fsInfo fs.FsInfo // Tells the container to stop. stopChan chan struct{} } @@ -45,14 +47,16 @@ const longDu = time.Second var _ fsHandler = &realFsHandler{} -func newFsHandler(period time.Duration, storageDirs []string, fsInfo fs.FsInfo) fsHandler { +func newFsHandler(period time.Duration, rootfs, other string, fsInfo fs.FsInfo) fsHandler { return &realFsHandler{ - lastUpdate: time.Time{}, - usageBytes: 0, - period: period, - storageDirs: storageDirs, - fsInfo: fsInfo, - stopChan: make(chan struct{}, 1), + lastUpdate: time.Time{}, + usageBytes: 0, + baseUsageBytes: 0, + period: period, + rootfs: rootfs, + other: other, + fsInfo: fsInfo, + stopChan: make(chan struct{}, 1), } } @@ -61,19 +65,22 @@ func (fh *realFsHandler) needsUpdate() bool { } func (fh *realFsHandler) update() error { - var usage uint64 - for _, dir := range fh.storageDirs { - // TODO(Vishh): Add support for external mounts. - dirUsage, err := fh.fsInfo.GetDirUsage(dir) - if err != nil { - return err - } - usage += dirUsage + // TODO(vishh): Add support for external mounts. + baseUsage, err := fh.fsInfo.GetDirUsage(fh.rootfs) + if err != nil { + return err } + + otherUsage, error := fh.fsInfo.GetDirUsage(fh.other) + if err != nil { + return err + } + fh.Lock() defer fh.Unlock() fh.lastUpdate = time.Now() - fh.usageBytes = usage + fh.usageBytes = baseUsage + otherUsage + fh.baseUsage = baseUsage return nil } @@ -104,8 +111,8 @@ func (fh *realFsHandler) stop() { close(fh.stopChan) } -func (fh *realFsHandler) usage() uint64 { +func (fh *realFsHandler) usage() (baseUsageBytes, totalUsageBytes uint64) { fh.RLock() defer fh.RUnlock() - return fh.usageBytes + return fh.baseUsageBytes, fh.usageBytes } diff --git a/container/docker/handler.go b/container/docker/handler.go index 7e400f77..0dfb7b54 100644 --- a/container/docker/handler.go +++ b/container/docker/handler.go @@ -59,9 +59,9 @@ type dockerContainerHandler struct { // Manager of this container's cgroups. cgroupManager cgroups.Manager - storageDriver storageDriver - fsInfo fs.FsInfo - storageDirs []string + storageDriver storageDriver + fsInfo fs.FsInfo + rootfsStorageDir string // Time at which this container was created. creationTime time.Time @@ -118,14 +118,13 @@ func newDockerContainerHandler( id := ContainerNameToDockerId(name) // Add the Containers dir where the log files are stored. - storageDirs := []string{path.Join(*dockerRootDir, pathToContainersDir, id)} - + otherStorageDir := path.Join(*dockerRootDir, pathToContainersDir, id) + var rootfsStorageDir string switch storageDriver { case aufsStorageDriver: - // Add writable layer for aufs. - storageDirs = append(storageDirs, path.Join(*dockerRootDir, pathToAufsDir, id)) + rootfsStorageDir = path.Join(*dockerRootDir, pathToAufsDir, id) case overlayStorageDriver: - storageDirs = append(storageDirs, path.Join(*dockerRootDir, pathToOverlayDir, id)) + rootfsStorageDir = path.Join(*dockerRootDir, pathToOverlayDir, id) } handler := &dockerContainerHandler{ @@ -138,8 +137,8 @@ func newDockerContainerHandler( storageDriver: storageDriver, fsInfo: fsInfo, rootFs: rootFs, - storageDirs: storageDirs, - fsHandler: newFsHandler(time.Minute, storageDirs, fsInfo), + rootfsStorageDir: rootfsStorageDir, + fsHandler: newFsHandler(time.Minute, rootfsStorageDir, otherStorageDir, fsInfo), } // We assume that if Inspect fails then the container is not known to docker. @@ -274,9 +273,7 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error return nil } - // As of now we assume that all the storage dirs are on the same device. - // The first storage dir will be that of the image layers. - deviceInfo, err := self.fsInfo.GetDirFsDevice(self.storageDirs[0]) + deviceInfo, err := self.fsInfo.GetDirFsDevice(self.rootfsStorageDir) if err != nil { return err } @@ -296,7 +293,7 @@ func (self *dockerContainerHandler) getFsStats(stats *info.ContainerStats) error fsStat := info.FsStats{Device: deviceInfo.Device, Limit: limit} - fsStat.Usage = self.fsHandler.usage() + fs.Stat.BaseUsage, fsStat.Usage = self.fsHandler.usage() stats.Filesystem = append(stats.Filesystem, fsStat) return nil diff --git a/info/v1/container.go b/info/v1/container.go index 61d64bcb..6b7f40de 100644 --- a/info/v1/container.go +++ b/info/v1/container.go @@ -398,6 +398,10 @@ type FsStats struct { // Number of bytes that is consumed by the container on this filesystem. Usage uint64 `json:"usage"` + // Base Usage that is consumed by the container's writable layer. + // This field is only applicable for docker container's as of now. + BaseUsage uint64 `json:"base_usage"` + // Number of bytes available for non-root user. Available uint64 `json:"available"` diff --git a/info/v2/container.go b/info/v2/container.go index a6388931..805f73d3 100644 --- a/info/v2/container.go +++ b/info/v2/container.go @@ -95,7 +95,7 @@ type ContainerSpec struct { Image string `json:"image,omitempty"` } -type ContainerStats struct { +type DeprecatedContainerStats struct { // The time of this stat point. Timestamp time.Time `json:"timestamp"` // CPU statistics @@ -124,6 +124,28 @@ type ContainerStats struct { CustomMetrics map[string][]v1.MetricVal `json:"custom_metrics,omitempty"` } +type ContainerStats struct { + // The time of this stat point. + Timestamp time.Time `json:"timestamp"` + // CPU statistics + // In nanoseconds (aggregated) + Cpu *v1.CpuStats `json:"cpu,omitempty"` + // In nanocores per second (instantaneous) + CpuInst *CpuInstStats `json:"cpu_inst,omitempty"` + // Disk IO statistics + DiskIo *v1.DiskIoStats `json:"diskio,omitempty"` + // Memory statistics + Memory *v1.MemoryStats `json:"memory,omitempty"` + // Network statistics + Network *NetworkStats `json:"network,omitempty"` + // Filesystem statistics + Filesystem *FilesystemStats `json:"filesystem,omitempty"` + // Task load statistics + Load *v1.LoadStats `json:"load_stats,omitempty"` + // Custom Metrics + CustomMetrics map[string][]v1.MetricVal `json:"custom_metrics,omitempty"` +} + type Percentiles struct { // Indicates whether the stats are present or not. // If true, values below do not have any data. @@ -262,3 +284,11 @@ type CpuInstUsage struct { // Unit: nanocores per second System uint64 `json:"system"` } + +// Filesystem usage statistics. +type FilesystemStats struct { + // Total Number of bytes consumed by container. + TotalUsageBytes *uint64 `json:"totalUsageBytes,omitempty"` + // Number of bytes consumed by a container through its root filesystem. + BaseUsageBytes *uint64 `json:"baseUsageBytes,omitempty"` +} diff --git a/info/v2/machine.go b/info/v2/machine.go index 4aef3d83..fb74d4cf 100644 --- a/info/v2/machine.go +++ b/info/v2/machine.go @@ -16,6 +16,8 @@ package v2 import ( // TODO(rjnagal): Move structs from v1. + "time" + "github.com/google/cadvisor/info/v1" ) @@ -86,3 +88,125 @@ func GetAttributes(mi *v1.MachineInfo, vi *v1.VersionInfo) Attributes { InstanceType: mi.InstanceType, } } + +// MachineStats contains usage statistics for the entire machine. +type MachineStats struct { + // The time of this stat point. + Timestamp time.Time `json:"timestamp"` + // In nanoseconds (aggregated) + Cpu *v1.CpuStats `json:"cpu,omitempty"` + // In nanocores per second (instantaneous) + CpuInst *CpuInstStats `json:"cpu_inst,omitempty"` + // Disk IO statistics + DiskIo *v1.DiskIoStats `json:"diskio,omitempty"` + // Memory statistics + Memory *v1.MemoryStats `json:"memory,omitempty"` + // Network statistics + Network *NetworkStats `json:"network,omitempty"` + // Filesystem statistics + Filesystem []MachineFsStats `json:"filesystem,omitempty"` + // Task load statistics + Load *v1.LoadStats `json:"load_stats,omitempty"` +} + +// MachineFsStats contains per filesystem capacity and usage information. +type MachineFsStats struct { + // The block device name associated with the filesystem. + Device string `json:"device"` + + // Number of bytes that can be consumed on this filesystem. + Capacity *uint64 `json:"capacity,omitempty"` + + // Number of bytes that is currently consumed on this filesystem. + Usage *uint64 `json:"usage,omitempty"` + + // Number of bytes available for non-root user on this filesystem. + Available *uint64 `json:"available,omitempty"` + + // DiskStats for this device. + DiskStats `json:"inline"` +} + +// DiskStats contains per partition usage information. +// This information is only available at the machine level. +type DiskStats struct { + // Number of reads completed + // This is the total number of reads completed successfully. + ReadsCompleted *uint64 `json:"reads_completed,omitempty"` + + // Number of reads merged + // Reads and writes which are adjacent to each other may be merged for + // efficiency. Thus two 4K reads may become one 8K read before it is + // ultimately handed to the disk, and so it will be counted (and queued) + // as only one I/O. This field lets you know how often this was done. + ReadsMerged *uint64 `json:"reads_merged,omitempty"` + + // Number of sectors read + // This is the total number of sectors read successfully. + SectorsRead *uint64 `json:"sectors_read,omitempty"` + + // Number of milliseconds spent reading + // This is the total number of milliseconds spent by all reads (as + // measured from __make_request() to end_that_request_last()). + ReadTime *uint64 `json:"read_time,omitempty"` + + // Number of writes completed + // This is the total number of writes completed successfully. + WritesCompleted *uint64 `json:"writes_completed,omitempty"` + + // Number of writes merged + // See the description of reads merged. + WritesMerged *uint64 `json:"writes_merged,omitempty"` + + // Number of sectors written + // This is the total number of sectors written successfully. + SectorsWritten *uint64 `json:"sectors_written,omitempty"` + + // Number of milliseconds spent writing + // This is the total number of milliseconds spent by all writes (as + // measured from __make_request() to end_that_request_last()). + WriteTime *uint64 `json:"write_time,omitempty"` + + // Number of I/Os currently in progress + // The only field that should go to zero. Incremented as requests are + // given to appropriate struct request_queue and decremented as they finish. + IoInProgress *uint64 `json:"io_in_progress,omitempty"` + + // Number of milliseconds spent doing I/Os + // This field increases so long as field 9 is nonzero. + IoTime *uint64 `json:"io_time,omitempty"` + + // weighted number of milliseconds spent doing I/Os + // This field is incremented at each I/O start, I/O completion, I/O + // merge, or read of these stats by the number of I/Os in progress + // (field 9) times the number of milliseconds spent doing I/O since the + // last update of this field. This can provide an easy measure of both + // I/O completion time and the backlog that may be accumulating. + WeightedIoTime *uint64 `json:"weighted_io_time,omitempty"` +} + +func GetMachineFsStats(fsStats []v1.FsStats) []MachineFsStats { + var result []MachineFsStats + for _, stat := range fsStats { + result = append(result, MachineFsStats{ + Device: stat.Device, + Capacity: &stat.Limit, + Usage: &stat.Usage, + Available: &stat.Available, + DiskStats: DiskStats{ + ReadsCompleted: &stat.ReadsCompleted, + ReadsMerged: &stat.ReadsMerged, + SectorsRead: &stat.SectorsRead, + ReadTime: &stat.ReadTime, + WritesCompleted: &stat.WritesCompleted, + WritesMerged: &stat.WritesMerged, + SectorsWritten: &stat.SectorsWritten, + WriteTime: &stat.WriteTime, + IoInProgress: &stat.IoInProgress, + IoTime: &stat.IoTime, + WeightedIoTime: &stat.WeightedIoTime, + }, + }) + } + return result +}