Avoid select inside a recursive function to avoid CPU spikes (#8923)

Additionally also allow configurable go-routines
master
Harshavardhana 5 years ago committed by GitHub
parent 9bbf5cb74f
commit 2d295a31de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 69
      cmd/data-usage.go
  2. 23
      cmd/fastwalk.go
  3. 13
      cmd/fs-v1.go
  4. 10
      cmd/posix.go

@ -22,6 +22,7 @@ import (
"encoding/json" "encoding/json"
"os" "os"
"path/filepath" "path/filepath"
"sync"
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
@ -150,77 +151,55 @@ type Item struct {
} }
type getSizeFn func(item Item) (int64, error) type getSizeFn func(item Item) (int64, error)
type activeIOFn func() error
func updateUsage(basePath string, endCh <-chan struct{}, waitForLowActiveIO activeIOFn, getSize getSizeFn) DataUsageInfo { func updateUsage(basePath string, doneCh <-chan struct{}, waitForLowActiveIO func(), getSize getSizeFn) DataUsageInfo {
var dataUsageInfo = DataUsageInfo{ var dataUsageInfo = DataUsageInfo{
BucketsSizes: make(map[string]uint64), BucketsSizes: make(map[string]uint64),
ObjectsSizesHistogram: make(map[string]uint64), ObjectsSizesHistogram: make(map[string]uint64),
} }
itemCh := make(chan Item) numWorkers := 4
skipCh := make(chan error)
defer close(skipCh)
go func() { var mutex sync.Mutex // Mutex to update dataUsageInfo
defer close(itemCh)
fastWalk(basePath, func(path string, typ os.FileMode) error {
if err := waitForLowActiveIO(); err != nil {
return filepath.SkipDir
}
select { fastWalk(basePath, numWorkers, doneCh, func(path string, typ os.FileMode) error {
case <-endCh: // Wait for I/O to go down.
return filepath.SkipDir waitForLowActiveIO()
case itemCh <- Item{path, typ}:
}
return <-skipCh
})
}()
for { bucket, entry := path2BucketObjectWithBasePath(basePath, path)
select {
case <-endCh:
return dataUsageInfo
case item, ok := <-itemCh:
if !ok {
return dataUsageInfo
}
bucket, entry := path2BucketObjectWithBasePath(basePath, item.Path)
if bucket == "" { if bucket == "" {
skipCh <- nil return nil
continue
} }
if isReservedOrInvalidBucket(bucket, false) { if isReservedOrInvalidBucket(bucket, false) {
skipCh <- filepath.SkipDir return filepath.SkipDir
continue
} }
if entry == "" && item.Typ&os.ModeDir != 0 { if entry == "" && typ&os.ModeDir != 0 {
mutex.Lock()
dataUsageInfo.BucketsCount++ dataUsageInfo.BucketsCount++
dataUsageInfo.BucketsSizes[bucket] = 0 dataUsageInfo.BucketsSizes[bucket] = 0
skipCh <- nil mutex.Unlock()
continue return nil
} }
if item.Typ&os.ModeDir != 0 { if typ&os.ModeDir != 0 {
skipCh <- nil return nil
continue
} }
size, err := getSize(item) size, err := getSize(Item{path, typ})
if err != nil { if err != nil {
skipCh <- errSkipFile return errSkipFile
continue
} }
mutex.Lock()
dataUsageInfo.ObjectsCount++ dataUsageInfo.ObjectsCount++
dataUsageInfo.ObjectsTotalSize += uint64(size) dataUsageInfo.ObjectsTotalSize += uint64(size)
dataUsageInfo.BucketsSizes[bucket] += uint64(size) dataUsageInfo.BucketsSizes[bucket] += uint64(size)
dataUsageInfo.ObjectsSizesHistogram[objSizeToHistoInterval(uint64(size))]++ dataUsageInfo.ObjectsSizesHistogram[objSizeToHistoInterval(uint64(size))]++
skipCh <- nil mutex.Unlock()
} return nil
} })
return dataUsageInfo
} }

@ -12,7 +12,6 @@ import (
"errors" "errors"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"strings" "strings"
"sync" "sync"
) )
@ -44,16 +43,7 @@ var errSkipFile = errors.New("fastwalk: skip this file")
// * fastWalk can follow symlinks if walkFn returns the TraverseLink // * fastWalk can follow symlinks if walkFn returns the TraverseLink
// sentinel error. It is the walkFn's responsibility to prevent // sentinel error. It is the walkFn's responsibility to prevent
// fastWalk from going into symlink cycles. // fastWalk from going into symlink cycles.
func fastWalk(root string, walkFn func(path string, typ os.FileMode) error) error { func fastWalk(root string, nworkers int, doneCh <-chan struct{}, walkFn func(path string, typ os.FileMode) error) error {
// TODO(bradfitz): make numWorkers configurable? We used a
// minimum of 4 to give the kernel more info about multiple
// things we want, in hopes its I/O scheduling can take
// advantage of that. Hopefully most are in cache. Maybe 4 is
// even too low of a minimum. Profile more.
numWorkers := 4
if n := runtime.NumCPU(); n > numWorkers {
numWorkers = n
}
// Make sure to wait for all workers to finish, otherwise // Make sure to wait for all workers to finish, otherwise
// walkFn could still be called after returning. This Wait call // walkFn could still be called after returning. This Wait call
@ -63,19 +53,20 @@ func fastWalk(root string, walkFn func(path string, typ os.FileMode) error) erro
w := &walker{ w := &walker{
fn: walkFn, fn: walkFn,
enqueuec: make(chan walkItem, numWorkers), // buffered for performance enqueuec: make(chan walkItem, nworkers), // buffered for performance
workc: make(chan walkItem, numWorkers), // buffered for performance workc: make(chan walkItem, nworkers), // buffered for performance
donec: make(chan struct{}), donec: make(chan struct{}),
// buffered for correctness & not leaking goroutines: // buffered for correctness & not leaking goroutines:
resc: make(chan error, numWorkers), resc: make(chan error, nworkers),
} }
defer close(w.donec) defer close(w.donec)
for i := 0; i < numWorkers; i++ { for i := 0; i < nworkers; i++ {
wg.Add(1) wg.Add(1)
go w.doWork(&wg) go w.doWork(&wg)
} }
todo := []walkItem{{dir: root}} todo := []walkItem{{dir: root}}
out := 0 out := 0
for { for {
@ -87,6 +78,8 @@ func fastWalk(root string, walkFn func(path string, typ os.FileMode) error) erro
workItem = todo[len(todo)-1] workItem = todo[len(todo)-1]
} }
select { select {
case <-doneCh:
return nil
case workc <- workItem: case workc <- workItem:
todo = todo[:len(todo)-1] todo = todo[:len(todo)-1]
out++ out++

@ -19,7 +19,6 @@ package cmd
import ( import (
"bytes" "bytes"
"context" "context"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -226,18 +225,10 @@ func (fs *FSObjects) StorageInfo(ctx context.Context) StorageInfo {
return storageInfo return storageInfo
} }
func (fs *FSObjects) waitForLowActiveIO() error { func (fs *FSObjects) waitForLowActiveIO() {
for atomic.LoadInt64(&fs.activeIOCount) >= fs.maxActiveIOCount { for atomic.LoadInt64(&fs.activeIOCount) >= fs.maxActiveIOCount {
select { time.Sleep(lowActiveIOWaitTick)
case <-GlobalServiceDoneCh:
return errors.New("forced exit")
case <-time.NewTimer(lowActiveIOWaitTick).C:
continue
}
} }
return nil
} }
// CrawlAndGetDataUsage returns data usage stats of the current FS deployment // CrawlAndGetDataUsage returns data usage stats of the current FS deployment

@ -333,16 +333,10 @@ func isQuitting(endCh chan struct{}) bool {
} }
} }
func (s *posix) waitForLowActiveIO() error { func (s *posix) waitForLowActiveIO() {
for atomic.LoadInt32(&s.activeIOCount) >= s.maxActiveIOCount { for atomic.LoadInt32(&s.activeIOCount) >= s.maxActiveIOCount {
select { time.Sleep(lowActiveIOWaitTick)
case <-GlobalServiceDoneCh:
return errors.New("forced exit")
case <-time.NewTimer(lowActiveIOWaitTick).C:
continue
} }
}
return nil
} }
func (s *posix) CrawlAndGetDataUsage(endCh <-chan struct{}) (DataUsageInfo, error) { func (s *posix) CrawlAndGetDataUsage(endCh <-chan struct{}) (DataUsageInfo, error) {

Loading…
Cancel
Save