Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: get inodes via pure go instead of find #1576

Closed
wants to merge 5 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 49 additions & 25 deletions fs/fs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package fs

import (
"bufio"
"bytes"
"fmt"
"io/ioutil"
"os"
Expand Down Expand Up @@ -471,27 +470,60 @@ func (self *RealFsInfo) GetDirDiskUsage(dir string, timeout time.Duration) (uint
}

func (self *RealFsInfo) GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error) {
return GetDirInodeUsage(dir, timeout)
}

// GetDirInodeUsage determines the inodes used by a given directory. It avoids
// traversing devices.
// If the directory does not exist or it is unable to access a file for some
// reason, it will return an error.
func GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error) {
if dir == "" {
return 0, fmt.Errorf("invalid directory")
}
var counter byteCounter
var stderr bytes.Buffer
claimToken()
defer releaseToken()
findCmd := exec.Command("find", dir, "-xdev", "-printf", ".")
findCmd.Stdout, findCmd.Stderr = &counter, &stderr
if err := findCmd.Start(); err != nil {
return 0, fmt.Errorf("failed to exec cmd %v - %v; stderr: %v", findCmd.Args, err, stderr.String())

rootInfo, err := os.Stat(dir)
if err != nil {
return 0, fmt.Errorf("could not stat %q to get inode usage: %v", dir, err)
}
timer := time.AfterFunc(timeout, func() {
glog.Infof("killing cmd %v due to timeout(%s)", findCmd.Args, timeout.String())
findCmd.Process.Kill()
})
if err := findCmd.Wait(); err != nil {
return 0, fmt.Errorf("cmd %v failed. stderr: %s; err: %v", findCmd.Args, stderr.String(), err)
rootStat, ok := rootInfo.Sys().(*syscall.Stat_t)
if !ok {
return 0, fmt.Errorf("unsuported fileinfo for getting inode usage of %q", dir)
}
timer.Stop()
return counter.bytesWritten, nil
rootDevId := rootStat.Dev

// inodes tracks the inodes that can't have duplicate hardlinks (nlink = 1);
// this allows us to save space in the inode map below
var inodes uint64
// dedupedInode stores inodes that could be duplicates (nlink > 1)
dedupedInodes := map[uint64]struct{}{}

err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if os.IsNotExist(err) {
// expected if files appear/vanish
return nil
}
if err != nil {
return fmt.Errorf("unable to count inodes for part of dir %s: %s", dir, err)
}
s, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("unsupported fileinfo; could not convert to stat_t")
}
if s.Dev != rootDevId {
// don't descend into directories on other devices
return filepath.SkipDir
}
if s.Nlink > 1 {
// Dedupe things that could be hardlinks
dedupedInodes[s.Ino] = struct{}{}
} else {
inodes++
}
return nil
})

return inodes + uint64(len(dedupedInodes)), err
}

func getVfsStats(path string) (total uint64, free uint64, avail uint64, inodes uint64, inodesFree uint64, err error) {
Expand Down Expand Up @@ -603,11 +635,3 @@ func getZfstats(poolName string) (uint64, uint64, uint64, error) {

return total, dataset.Avail, dataset.Avail, nil
}

// Simple io.Writer implementation that counts how many bytes were written.
type byteCounter struct{ bytesWritten uint64 }

func (b *byteCounter) Write(p []byte) (int, error) {
b.bytesWritten += uint64(len(p))
return len(p), nil
}