Commit f650ddf8 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #35132 from dashpole/per_volume_inode

Automatic merge from submit-queue Per Volume Inode Accounting Collects volume inode stats using the same find command as cadvisor. The command is "find _path_ -xdev -printf '.' | wc -c". The output is passed to the summary api, and will be consumed by the eviction manager. This cannot be merged yet, as it depends on changes adding the InodesUsed field to the summary api, and the eviction manager consuming this. Expect tests to fail until this happens. DEPENDS ON #35137
parents 649c0ddd d494ef66
...@@ -114,9 +114,13 @@ func (s *volumeStatCalculator) calcAndStoreStats() { ...@@ -114,9 +114,13 @@ func (s *volumeStatCalculator) calcAndStoreStats() {
func (s *volumeStatCalculator) parsePodVolumeStats(podName string, metric *volume.Metrics) stats.VolumeStats { func (s *volumeStatCalculator) parsePodVolumeStats(podName string, metric *volume.Metrics) stats.VolumeStats {
available := uint64(metric.Available.Value()) available := uint64(metric.Available.Value())
capacity := uint64(metric.Capacity.Value()) capacity := uint64(metric.Capacity.Value())
used := uint64((metric.Used.Value())) used := uint64(metric.Used.Value())
inodes := uint64(metric.Inodes.Value())
inodesFree := uint64(metric.InodesFree.Value())
inodesUsed := uint64(metric.InodesUsed.Value())
return stats.VolumeStats{ return stats.VolumeStats{
Name: podName, Name: podName,
FsStats: stats.FsStats{AvailableBytes: &available, CapacityBytes: &capacity, UsedBytes: &used}, FsStats: stats.FsStats{AvailableBytes: &available, CapacityBytes: &capacity, UsedBytes: &used,
Inodes: &inodes, InodesFree: &inodesFree, InodesUsed: &inodesUsed},
} }
} }
...@@ -50,6 +50,11 @@ func (md *metricsDu) GetMetrics() (*Metrics, error) { ...@@ -50,6 +50,11 @@ func (md *metricsDu) GetMetrics() (*Metrics, error) {
return metrics, err return metrics, err
} }
err = md.runFind(metrics)
if err != nil {
return metrics, err
}
err = md.getFsInfo(metrics) err = md.getFsInfo(metrics)
if err != nil { if err != nil {
return metrics, err return metrics, err
...@@ -68,14 +73,26 @@ func (md *metricsDu) runDu(metrics *Metrics) error { ...@@ -68,14 +73,26 @@ func (md *metricsDu) runDu(metrics *Metrics) error {
return nil return nil
} }
// runFind executes the "find" command and writes the results to metrics.InodesUsed
func (md *metricsDu) runFind(metrics *Metrics) error {
inodesUsed, err := util.Find(md.path)
if err != nil {
return err
}
metrics.InodesUsed = resource.NewQuantity(inodesUsed, resource.BinarySI)
return nil
}
// getFsInfo writes metrics.Capacity and metrics.Available from the filesystem // getFsInfo writes metrics.Capacity and metrics.Available from the filesystem
// info // info
func (md *metricsDu) getFsInfo(metrics *Metrics) error { func (md *metricsDu) getFsInfo(metrics *Metrics) error {
available, capacity, _, err := util.FsInfo(md.path) available, capacity, _, inodes, inodesFree, _, err := util.FsInfo(md.path)
if err != nil { if err != nil {
return NewFsInfoFailedError(err) return NewFsInfoFailedError(err)
} }
metrics.Available = resource.NewQuantity(available, resource.BinarySI) metrics.Available = resource.NewQuantity(available, resource.BinarySI)
metrics.Capacity = resource.NewQuantity(capacity, resource.BinarySI) metrics.Capacity = resource.NewQuantity(capacity, resource.BinarySI)
metrics.Inodes = resource.NewQuantity(inodes, resource.BinarySI)
metrics.InodesFree = resource.NewQuantity(inodesFree, resource.BinarySI)
return nil return nil
} }
...@@ -54,12 +54,15 @@ func (md *metricsStatFS) GetMetrics() (*Metrics, error) { ...@@ -54,12 +54,15 @@ func (md *metricsStatFS) GetMetrics() (*Metrics, error) {
// getFsInfo writes metrics.Capacity, metrics.Used and metrics.Available from the filesystem info // getFsInfo writes metrics.Capacity, metrics.Used and metrics.Available from the filesystem info
func (md *metricsStatFS) getFsInfo(metrics *Metrics) error { func (md *metricsStatFS) getFsInfo(metrics *Metrics) error {
available, capacity, usage, err := util.FsInfo(md.path) available, capacity, usage, inodes, inodesFree, inodesUsed, err := util.FsInfo(md.path)
if err != nil { if err != nil {
return NewFsInfoFailedError(err) return NewFsInfoFailedError(err)
} }
metrics.Available = resource.NewQuantity(available, resource.BinarySI) metrics.Available = resource.NewQuantity(available, resource.BinarySI)
metrics.Capacity = resource.NewQuantity(capacity, resource.BinarySI) metrics.Capacity = resource.NewQuantity(capacity, resource.BinarySI)
metrics.Used = resource.NewQuantity(usage, resource.BinarySI) metrics.Used = resource.NewQuantity(usage, resource.BinarySI)
metrics.Inodes = resource.NewQuantity(inodes, resource.BinarySI)
metrics.InodesFree = resource.NewQuantity(inodesFree, resource.BinarySI)
metrics.InodesUsed = resource.NewQuantity(inodesUsed, resource.BinarySI)
return nil return nil
} }
...@@ -19,23 +19,25 @@ limitations under the License. ...@@ -19,23 +19,25 @@ limitations under the License.
package util package util
import ( import (
"bytes"
"fmt" "fmt"
"os/exec" "os/exec"
"strconv"
"strings" "strings"
"syscall" "syscall"
"k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/resource"
) )
// FSInfo linux returns (available bytes, byte capacity, byte usage, error) for the filesystem that // FSInfo linux returns (available bytes, byte capacity, byte usage, total inodes, inodes free, inode usage, error)
// path resides upon. // for the filesystem that path resides upon.
func FsInfo(path string) (int64, int64, int64, error) { func FsInfo(path string) (int64, int64, int64, int64, int64, int64, error) {
statfs := &syscall.Statfs_t{} statfs := &syscall.Statfs_t{}
err := syscall.Statfs(path, statfs) err := syscall.Statfs(path, statfs)
if err != nil { if err != nil {
return 0, 0, 0, err return 0, 0, 0, 0, 0, 0, err
} }
// TODO(vishh): Include inodes space
// Available is blocks available * fragment size // Available is blocks available * fragment size
available := int64(statfs.Bavail) * int64(statfs.Bsize) available := int64(statfs.Bavail) * int64(statfs.Bsize)
...@@ -45,7 +47,11 @@ func FsInfo(path string) (int64, int64, int64, error) { ...@@ -45,7 +47,11 @@ func FsInfo(path string) (int64, int64, int64, error) {
// Usage is block being used * fragment size (aka block size). // Usage is block being used * fragment size (aka block size).
usage := (int64(statfs.Blocks) - int64(statfs.Bfree)) * int64(statfs.Bsize) usage := (int64(statfs.Blocks) - int64(statfs.Bfree)) * int64(statfs.Bsize)
return available, capacity, usage, nil inodes := int64(statfs.Files)
inodesFree := int64(statfs.Ffree)
inodesUsed := inodes - inodesFree
return available, capacity, usage, inodes, inodesFree, inodesUsed, nil
} }
func Du(path string) (*resource.Quantity, error) { func Du(path string) (*resource.Quantity, error) {
...@@ -62,3 +68,36 @@ func Du(path string) (*resource.Quantity, error) { ...@@ -62,3 +68,36 @@ func Du(path string) (*resource.Quantity, error) {
used.Format = resource.BinarySI used.Format = resource.BinarySI
return &used, nil return &used, nil
} }
// Find uses the command `find <path> -dev -printf '.' | wc -c` to count files and directories.
// While this is not an exact measure of inodes used, it is a very good approximation.
func Find(path string) (int64, error) {
var stdout, stdwcerr, stdfinderr bytes.Buffer
var err error
findCmd := exec.Command("find", path, "-xdev", "-printf", ".")
wcCmd := exec.Command("wc", "-c")
if wcCmd.Stdin, err = findCmd.StdoutPipe(); err != nil {
return 0, fmt.Errorf("failed to setup stdout for cmd %v - %v", findCmd.Args, err)
}
wcCmd.Stdout, wcCmd.Stderr, findCmd.Stderr = &stdout, &stdwcerr, &stdfinderr
if err = findCmd.Start(); err != nil {
return 0, fmt.Errorf("failed to exec cmd %v - %v; stderr: %v", findCmd.Args, err, stdfinderr.String())
}
if err = wcCmd.Start(); err != nil {
return 0, fmt.Errorf("failed to exec cmd %v - %v; stderr %v", wcCmd.Args, err, stdwcerr.String())
}
err = findCmd.Wait()
if err != nil {
return 0, fmt.Errorf("cmd %v failed. stderr: %s; err: %v", findCmd.Args, stdfinderr.String(), err)
}
err = wcCmd.Wait()
if err != nil {
return 0, fmt.Errorf("cmd %v failed. stderr: %s; err: %v", wcCmd.Args, stdwcerr.String(), err)
}
inodeUsage, err := strconv.ParseInt(strings.TrimSpace(stdout.String()), 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot parse cmds: %v, %v output %s - %s", findCmd.Args, wcCmd.Args, stdout.String(), err)
}
return inodeUsage, nil
}
...@@ -64,6 +64,20 @@ type Metrics struct { ...@@ -64,6 +64,20 @@ type Metrics struct {
// emptydir, hostpath), this is the available space on the underlying // emptydir, hostpath), this is the available space on the underlying
// storage, and is shared with host processes and other Volumes. // storage, and is shared with host processes and other Volumes.
Available *resource.Quantity Available *resource.Quantity
// InodesUsed represents the total inodes used by the Volume.
InodesUsed *resource.Quantity
// Inodes represents the total number of inodes availible in the volume.
// For volumes that share a filesystem with the host (e.g. emptydir, hostpath),
// this is the inodes available in the underlying storage,
// and will not equal InodesUsed + InodesFree as the fs is shared.
Inodes *resource.Quantity
// InodesFree represent the inodes available for the volume. For Volues that share
// a filesystem with the host (e.g. emptydir, hostpath), this is the free inodes
// on the underlying sporage, and is shared with host processes and other volumes
InodesFree *resource.Quantity
} }
// Attributes represents the attributes of this mounter. // Attributes represents the attributes of this mounter.
......
...@@ -138,10 +138,9 @@ var _ = framework.KubeDescribe("Summary API", func() { ...@@ -138,10 +138,9 @@ var _ = framework.KubeDescribe("Summary API", func() {
"AvailableBytes": fsCapacityBounds, "AvailableBytes": fsCapacityBounds,
"CapacityBytes": fsCapacityBounds, "CapacityBytes": fsCapacityBounds,
"UsedBytes": bounded(kb, 1*mb), "UsedBytes": bounded(kb, 1*mb),
// Inodes are not reported for Volumes. "InodesFree": bounded(1E4, 1E8),
"InodesFree": BeNil(), "Inodes": bounded(1E4, 1E8),
"Inodes": BeNil(), "InodesUsed": bounded(0, 1E8),
"InodesUsed": BeNil(),
}), }),
}), }),
}), }),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment