@@ -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,70 @@ 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 ) {
564+ func (self * RealFsInfo ) GetDirUsage (dir string ) (UsageInfo , error ) {
563565 claimToken ()
564566 defer releaseToken ()
565- return GetDirDiskUsage (dir , timeout )
566- }
567567
568- func GetDirDiskUsage (dir string , timeout time.Duration ) (uint64 , error ) {
568+ var usage UsageInfo
569+
569570 if dir == "" {
570- return 0 , fmt .Errorf ("invalid directory" )
571- }
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 ()
578- if err != nil {
579- return 0 , fmt .Errorf ("failed to setup stderr for cmd %v - %v" , cmd .Args , err )
571+ return usage , fmt .Errorf ("invalid directory" )
580572 }
581573
582- if err := cmd .Start (); err != nil {
583- return 0 , fmt .Errorf ("failed to exec du - %v" , err )
584- }
585- timer := time .AfterFunc (timeout , func () {
586- klog .Warningf ("Killing cmd %v due to timeout(%s)" , cmd .Args , timeout .String ())
587- cmd .Process .Kill ()
588- })
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 ()
574+ rootInfo , err := os .Stat (dir )
596575 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 )
576+ return usage , fmt .Errorf ("could not stat %q to get inode usage : %v" , dir , err )
598577 }
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 )
578+
579+ rootStat , ok := rootInfo . Sys ().( * syscall. Stat_t )
580+ if ! ok {
581+ return usage , fmt .Errorf ("unsuported fileinfo for getting inode usage of %q " , dir )
603582 }
604- return usageInKb * 1024 , nil
605- }
606583
607- func (self * RealFsInfo ) GetDirInodeUsage (dir string , timeout time.Duration ) (uint64 , error ) {
608- claimToken ()
609- defer releaseToken ()
610- return GetDirInodeUsage (dir , timeout )
611- }
584+ rootDevId := rootStat .Dev
612585
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 ()
586+ // dedupedInode stores inodes that could be duplicates (nlink > 1)
587+ dedupedInodes := make (map [uint64 ]struct {})
588+
589+ err = filepath .Walk (dir , func (path string , info os.FileInfo , err error ) error {
590+ if os .IsNotExist (err ) {
591+ // expected if files appear/vanish
592+ return nil
593+ }
594+ if err != nil {
595+ return fmt .Errorf ("unable to count inodes for part of dir %s: %s" , dir , err )
596+ }
597+
598+ // according to the docs, Sys can be nil
599+ if info .Sys () == nil {
600+ return fmt .Errorf ("fileinfo Sys is nil" )
601+ }
602+
603+ s , ok := info .Sys ().(* syscall.Stat_t )
604+ if ! ok {
605+ return fmt .Errorf ("unsupported fileinfo; could not convert to stat_t" )
606+ }
607+
608+ if s .Dev != rootDevId {
609+ // don't descend into directories on other devices
610+ return filepath .SkipDir
611+ }
612+ if s .Nlink > 1 {
613+ if _ , ok := dedupedInodes [s .Ino ]; ! ok {
614+ // Dedupe things that could be hardlinks
615+ dedupedInodes [s .Ino ] = struct {}{}
616+
617+ usage .Bytes += uint64 (s .Blocks ) * statBlockSize
618+ usage .Inodes ++
619+ }
620+ } else {
621+ usage .Bytes += uint64 (s .Blocks ) * statBlockSize
622+ usage .Inodes ++
623+ }
624+ return nil
627625 })
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
626+
627+ return usage , nil
634628}
635629
636630func getVfsStats (path string ) (total uint64 , free uint64 , avail uint64 , inodes uint64 , inodesFree uint64 , err error ) {
0 commit comments