diff --git a/probe/process/cache.go b/probe/process/cache.go deleted file mode 100644 index 3d076eb9b..000000000 --- a/probe/process/cache.go +++ /dev/null @@ -1,41 +0,0 @@ -package process - -import ( - "time" - - "github.com/armon/go-metrics" - "github.com/coocood/freecache" - - "github.com/weaveworks/common/fs" -) - -const ( - generalTimeout = 30 // seconds - statsTimeout = 10 //seconds -) - -var ( - hitMetricsKey = []string{"process", "cache", "hit"} - missMetricsKey = []string{"process", "cache", "miss"} -) - -var fileCache = freecache.NewCache(1024 * 16) - -type entry struct { - buf []byte - err error - ts time.Time -} - -func cachedReadFile(path string) ([]byte, error) { - key := []byte(path) - if v, err := fileCache.Get(key); err == nil { - metrics.IncrCounter(hitMetricsKey, 1.0) - return v, nil - } - - buf, err := fs.ReadFile(path) - fileCache.Set(key, buf, generalTimeout) - metrics.IncrCounter(missMetricsKey, 1.0) - return buf, err -} diff --git a/probe/process/walker_linux.go b/probe/process/walker_linux.go index 4e9e2a9aa..2191044e3 100644 --- a/probe/process/walker_linux.go +++ b/probe/process/walker_linux.go @@ -2,12 +2,15 @@ package process import ( "bytes" + "encoding/binary" + "fmt" "os" "path" "strconv" "strings" linuxproc "github.com/c9s/goprocinfo/linux" + "github.com/coocood/freecache" "github.com/weaveworks/common/fs" "github.com/weaveworks/scope/probe/host" @@ -17,6 +20,23 @@ type walker struct { procRoot string } +var ( + // limitsCache caches /proc//limits + // key: filename in /proc. Example: "42" + // value: max open files (soft limit) stored in a [8]byte (uint64, little endian) + limitsCache = freecache.NewCache(1024 * 16) + + // cmdlineCache caches /proc//cmdline and /proc//name + // key: filename in /proc. Example: "42" + // value: two strings separated by a '\0' + cmdlineCache = freecache.NewCache(1024 * 16) +) + +const ( + limitsCacheTimeout = 60 + cmdlineCacheTimeout = 60 +) + // NewWalker creates a new process Walker. func NewWalker(procRoot string) Walker { return &walker{procRoot: procRoot} @@ -94,7 +114,7 @@ func readStats(path string) (ppid, threads int, jiffies, rss, rssLimit uint64, e } func readLimits(path string) (openFilesLimit uint64, err error) { - buf, err := cachedReadFile(path) + buf, err := fs.ReadFile(path) if err != nil { return 0, err } @@ -125,6 +145,27 @@ func readLimits(path string) (openFilesLimit uint64, err error) { return softLimit, nil } +func (w *walker) readCmdline(filename string) (cmdline, name string) { + if cmdlineBuf, err := fs.ReadFile(path.Join(w.procRoot, filename, "cmdline")); err == nil { + // like proc, treat name as the first element of command line + i := bytes.IndexByte(cmdlineBuf, '\000') + if i == -1 { + i = len(cmdlineBuf) + } + name = string(cmdlineBuf[:i]) + cmdlineBuf = bytes.Replace(cmdlineBuf, []byte{'\000'}, []byte{' '}, -1) + cmdline = string(cmdlineBuf) + } + if name == "" { + if commBuf, err := fs.ReadFile(path.Join(w.procRoot, filename, "comm")); err == nil { + name = "[" + strings.TrimSpace(string(commBuf)) + "]" + } else { + name = "(unknown)" + } + } + return +} + // Walk walks the supplied directory (expecting it to look like /proc) // and marshalls the files into instances of Process, which it then // passes one-by-one to the supplied function. Walk is only made public @@ -151,29 +192,29 @@ func (w *walker) Walk(f func(Process, Process)) error { continue } - openFilesLimit, err := readLimits(path.Join(w.procRoot, filename, "limits")) - if err != nil { - continue + var openFilesLimit uint64 + if v, err := limitsCache.Get([]byte(filename)); err == nil { + openFilesLimit = binary.LittleEndian.Uint64(v) + } else { + openFilesLimit, err = readLimits(path.Join(w.procRoot, filename, "limits")) + if err != nil { + continue + } + buf := make([]byte, 8) + binary.LittleEndian.PutUint64(buf, openFilesLimit) + limitsCache.Set([]byte(filename), buf, limitsCacheTimeout) } cmdline, name := "", "" - if cmdlineBuf, err := cachedReadFile(path.Join(w.procRoot, filename, "cmdline")); err == nil { - // like proc, treat name as the first element of command line - i := bytes.IndexByte(cmdlineBuf, '\000') - if i == -1 { - i = len(cmdlineBuf) - } - name = string(cmdlineBuf[:i]) - cmdlineBuf = bytes.Replace(cmdlineBuf, []byte{'\000'}, []byte{' '}, -1) - cmdline = string(cmdlineBuf) - } - if name == "" { - if commBuf, err := cachedReadFile(path.Join(w.procRoot, filename, "comm")); err == nil { - name = "[" + strings.TrimSpace(string(commBuf)) + "]" - } else { - name = "(unknown)" - } + if v, err := cmdlineCache.Get([]byte(filename)); err == nil { + separatorPos := strings.Index(string(v), "\x00") + cmdline = string(v[:separatorPos]) + name = string(v[separatorPos+1:]) + } else { + cmdline, name = w.readCmdline(filename) + cmdlineCache.Set([]byte(filename), []byte(fmt.Sprintf("%s\x00%s", cmdline, name)), cmdlineCacheTimeout) } + f(Process{ PID: pid, PPID: ppid,