@@ -19,7 +19,6 @@ package fs
1919
2020import (
2121 "bufio"
22- "bytes"
2322 "fmt"
2423 "io/ioutil"
2524 "os"
@@ -30,7 +29,6 @@ import (
3029 "strconv"
3130 "strings"
3231 "syscall"
33- "time"
3432
3533 "github.com/docker/docker/pkg/mount"
3634 "github.com/google/cadvisor/devicemapper"
@@ -47,8 +45,12 @@ const (
4745 LabelCrioImages = "crio-images"
4846)
4947
50- // The maximum number of `du` and `find` tasks that can be running at once.
51- const maxConcurrentOps = 20
48+ const (
49+ // The block size in bytes.
50+ statBlockSize uint64 = 512
51+ // The maximum number of `disk usage` tasks that can be running at once.
52+ maxConcurrentOps = 20
53+ )
5254
5355// A pool for restricting the number of consecutive `du` and `find` tasks running.
5456var pool = make (chan struct {}, maxConcurrentOps )
@@ -559,78 +561,73 @@ func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) {
559561 return nil , fmt .Errorf ("could not find device with major: %d, minor: %d in cached partitions map" , major , minor )
560562}
561563
562- func (self * RealFsInfo ) GetDirDiskUsage (dir string , timeout time.Duration ) (uint64 , error ) {
563- claimToken ()
564- defer releaseToken ()
565- return GetDirDiskUsage (dir , timeout )
566- }
564+ func GetDirUsage (dir string ) (UsageInfo , error ) {
565+ var usage UsageInfo
567566
568- func GetDirDiskUsage (dir string , timeout time.Duration ) (uint64 , error ) {
569567 if dir == "" {
570- return 0 , fmt .Errorf ("invalid directory" )
568+ return usage , fmt .Errorf ("invalid directory" )
571569 }
572- cmd := exec .Command ("ionice" , "-c3" , "nice" , "-n" , "19" , "du" , "-s" , dir )
573- stdoutp , err := cmd .StdoutPipe ()
574- if err != nil {
575- return 0 , fmt .Errorf ("failed to setup stdout for cmd %v - %v" , cmd .Args , err )
576- }
577- stderrp , err := cmd .StderrPipe ()
570+
571+ rootInfo , err := os .Stat (dir )
578572 if err != nil {
579- return 0 , fmt .Errorf ("failed to setup stderr for cmd %v - %v" , cmd . Args , err )
573+ return usage , fmt .Errorf ("could not stat %q to get inode usage: %v" , dir , err )
580574 }
581575
582- if err := cmd .Start (); err != nil {
583- return 0 , fmt .Errorf ("failed to exec du - %v" , err )
576+ rootStat , ok := rootInfo .Sys ().(* syscall.Stat_t )
577+ if ! ok {
578+ return usage , fmt .Errorf ("unsuported fileinfo for getting inode usage of %q" , dir )
584579 }
585- timer := time .AfterFunc (timeout , func () {
586- klog .Warningf ("Killing cmd %v due to timeout(%s)" , cmd .Args , timeout .String ())
587- cmd .Process .Kill ()
580+
581+ rootDevId := rootStat .Dev
582+
583+ // dedupedInode stores inodes that could be duplicates (nlink > 1)
584+ dedupedInodes := make (map [uint64 ]struct {})
585+
586+ err = filepath .Walk (dir , func (path string , info os.FileInfo , err error ) error {
587+ if os .IsNotExist (err ) {
588+ // expected if files appear/vanish
589+ return nil
590+ }
591+ if err != nil {
592+ return fmt .Errorf ("unable to count inodes for part of dir %s: %s" , dir , err )
593+ }
594+
595+ // according to the docs, Sys can be nil
596+ if info .Sys () == nil {
597+ return fmt .Errorf ("fileinfo Sys is nil" )
598+ }
599+
600+ s , ok := info .Sys ().(* syscall.Stat_t )
601+ if ! ok {
602+ return fmt .Errorf ("unsupported fileinfo; could not convert to stat_t" )
603+ }
604+
605+ if s .Dev != rootDevId {
606+ // don't descend into directories on other devices
607+ return filepath .SkipDir
608+ }
609+ if s .Nlink > 1 {
610+ if _ , ok := dedupedInodes [s .Ino ]; ! ok {
611+ // Dedupe things that could be hardlinks
612+ dedupedInodes [s .Ino ] = struct {}{}
613+
614+ usage .Bytes += uint64 (s .Blocks ) * statBlockSize
615+ usage .Inodes ++
616+ }
617+ } else {
618+ usage .Bytes += uint64 (s .Blocks ) * statBlockSize
619+ usage .Inodes ++
620+ }
621+ return nil
588622 })
589- stdoutb , souterr := ioutil .ReadAll (stdoutp )
590- if souterr != nil {
591- klog .Errorf ("Failed to read from stdout for cmd %v - %v" , cmd .Args , souterr )
592- }
593- stderrb , _ := ioutil .ReadAll (stderrp )
594- err = cmd .Wait ()
595- timer .Stop ()
596- if err != nil {
597- return 0 , fmt .Errorf ("du command failed on %s with output stdout: %s, stderr: %s - %v" , dir , string (stdoutb ), string (stderrb ), err )
598- }
599- stdout := string (stdoutb )
600- usageInKb , err := strconv .ParseUint (strings .Fields (stdout )[0 ], 10 , 64 )
601- if err != nil {
602- return 0 , fmt .Errorf ("cannot parse 'du' output %s - %s" , stdout , err )
603- }
604- return usageInKb * 1024 , nil
623+
624+ return usage , nil
605625}
606626
607- func (self * RealFsInfo ) GetDirInodeUsage (dir string , timeout time. Duration ) (uint64 , error ) {
627+ func (self * RealFsInfo ) GetDirUsage (dir string ) (UsageInfo , error ) {
608628 claimToken ()
609629 defer releaseToken ()
610- return GetDirInodeUsage (dir , timeout )
611- }
612-
613- func GetDirInodeUsage (dir string , timeout time.Duration ) (uint64 , error ) {
614- if dir == "" {
615- return 0 , fmt .Errorf ("invalid directory" )
616- }
617- var counter byteCounter
618- var stderr bytes.Buffer
619- findCmd := exec .Command ("ionice" , "-c3" , "nice" , "-n" , "19" , "find" , dir , "-xdev" , "-printf" , "." )
620- findCmd .Stdout , findCmd .Stderr = & counter , & stderr
621- if err := findCmd .Start (); err != nil {
622- return 0 , fmt .Errorf ("failed to exec cmd %v - %v; stderr: %v" , findCmd .Args , err , stderr .String ())
623- }
624- timer := time .AfterFunc (timeout , func () {
625- klog .Warningf ("Killing cmd %v due to timeout(%s)" , findCmd .Args , timeout .String ())
626- findCmd .Process .Kill ()
627- })
628- err := findCmd .Wait ()
629- timer .Stop ()
630- if err != nil {
631- return 0 , fmt .Errorf ("cmd %v failed. stderr: %s; err: %v" , findCmd .Args , stderr .String (), err )
632- }
633- return counter .bytesWritten , nil
630+ return GetDirUsage (dir )
634631}
635632
636633func getVfsStats (path string ) (total uint64 , free uint64 , avail uint64 , inodes uint64 , inodesFree uint64 , err error ) {
0 commit comments