mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-28 01:31:17 +00:00
Keep a cache of open files, reducing the number of open/close cycles in the /proc dir
This commit is contained in:
@@ -7,39 +7,106 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/bluele/gcache"
|
||||
)
|
||||
|
||||
type procReader struct {
|
||||
proc ProcDir
|
||||
const (
|
||||
filesCacheLen = 512
|
||||
filesCacheExpiration = 60 * time.Second
|
||||
)
|
||||
|
||||
var tcpFiles = []string{
|
||||
"net/tcp",
|
||||
"net/tcp6",
|
||||
}
|
||||
|
||||
// NewProcReader creates a new /proc reader.
|
||||
func NewProcReader(proc ProcDir) *procReader {
|
||||
return &procReader{proc}
|
||||
// A cache for files handles
|
||||
// Note: not intended to be used from multiple goroutines
|
||||
type filesCache struct {
|
||||
handles gcache.Cache
|
||||
}
|
||||
|
||||
func newFilesCache(proc Dir) *filesCache {
|
||||
loadFunc := func(fileName interface{}) (interface{}, error) {
|
||||
return proc.Open(fileName.(string))
|
||||
}
|
||||
evictionFunc := func(key, value interface{}) {
|
||||
value.(File).Close()
|
||||
}
|
||||
return &filesCache{
|
||||
handles: gcache.New(filesCacheLen).LoaderFunc(loadFunc).EvictedFunc(evictionFunc).Expiration(filesCacheExpiration).ARC().Build(),
|
||||
}
|
||||
}
|
||||
|
||||
// Read a "/proc" file, identified as a subdir (eg "1134/comm"), into a buffer
|
||||
// Note: this is not goroutine-safe: two goroutines getting and reading from
|
||||
// the same handle can obtain some unexpected contents...
|
||||
func (fc *filesCache) ReadInto(filename string, buf *bytes.Buffer) error {
|
||||
// we could use a lock here, but this is only used from Processes()/Connections(),
|
||||
// and they are always invoked sequentially...
|
||||
h, err := fc.handles.Get(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return h.(File).ReadInto(buf)
|
||||
}
|
||||
|
||||
// Close closes all the handles in the cache
|
||||
func (fc *filesCache) Close() error {
|
||||
for _, key := range fc.handles.Keys() {
|
||||
fc.handles.Remove(key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// the Linux "/proc" reader
|
||||
type reader struct {
|
||||
proc Dir
|
||||
handles *filesCache
|
||||
}
|
||||
|
||||
// NewReader creates a new /proc reader.
|
||||
func NewReader(proc Dir) Reader {
|
||||
return &reader{
|
||||
proc: proc,
|
||||
handles: newFilesCache(proc),
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the Linux "/proc" reader
|
||||
func (w *reader) Close() error {
|
||||
return w.handles.Close()
|
||||
}
|
||||
|
||||
// Processes walks the /proc directory and marshalls the files into
|
||||
// instances of Process, which it then passes one-by-one to the
|
||||
// supplied function. Processes() is only made public so that is
|
||||
// can be tested.
|
||||
func (w *procReader) Processes(f func(Process)) error {
|
||||
dirEntries, err := w.proc.ReadDir(w.proc.Root())
|
||||
func (w *reader) Processes(f func(Process)) error {
|
||||
dirEntries, err := w.proc.ReadDirNames(w.proc.Root())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, dirEntry := range dirEntries {
|
||||
filename := dirEntry.Name()
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 5000))
|
||||
readIntoBuffer := func(filename string) error {
|
||||
buf.Reset()
|
||||
res := w.handles.ReadInto(filename, buf)
|
||||
return res
|
||||
}
|
||||
|
||||
for _, filename := range dirEntries {
|
||||
pid, err := strconv.Atoi(filename)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
stat, err := w.proc.ReadFile(path.Join(w.proc.Root(), filename, "stat"))
|
||||
if err != nil {
|
||||
if readIntoBuffer(path.Join(filename, "stat")) != nil {
|
||||
continue
|
||||
}
|
||||
splits := strings.Fields(string(stat))
|
||||
splits := strings.Fields(buf.String())
|
||||
ppid, err := strconv.Atoi(splits[3])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -51,18 +118,18 @@ func (w *procReader) Processes(f func(Process)) error {
|
||||
}
|
||||
|
||||
cmdline := ""
|
||||
if cmdlineBuf, err := w.proc.ReadFile(path.Join(w.proc.Root(), filename, "cmdline")); err == nil {
|
||||
cmdlineBuf = bytes.Replace(cmdlineBuf, []byte{'\000'}, []byte{' '}, -1)
|
||||
if readIntoBuffer(path.Join(filename, "cmdline")) == nil {
|
||||
cmdlineBuf := bytes.Replace(buf.Bytes(), []byte{'\000'}, []byte{' '}, -1)
|
||||
cmdline = string(cmdlineBuf)
|
||||
}
|
||||
|
||||
comm := "(unknown)"
|
||||
if commBuf, err := w.proc.ReadFile(path.Join(w.proc.Root(), filename, "comm")); err == nil {
|
||||
comm = strings.TrimSpace(string(commBuf))
|
||||
if readIntoBuffer(path.Join(filename, "comm")) == nil {
|
||||
comm = strings.TrimSpace(buf.String())
|
||||
}
|
||||
|
||||
fdBase := path.Join(w.proc.Root(), strconv.Itoa(pid), "fd")
|
||||
fdNames, err := w.proc.ReadDir(fdBase)
|
||||
fdNames, err := w.proc.ReadDirNames(fdBase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -71,7 +138,7 @@ func (w *procReader) Processes(f func(Process)) error {
|
||||
for _, fdName := range fdNames {
|
||||
var fdStat syscall.Stat_t
|
||||
// Direct use of syscall.Stat() to save garbage.
|
||||
fdPath := path.Join(fdBase, fdName.Name())
|
||||
fdPath := path.Join(fdBase, fdName)
|
||||
err = syscall.Stat(fdPath, &fdStat)
|
||||
if err == nil && (fdStat.Mode&syscall.S_IFMT == syscall.S_IFSOCK) { // We want sockets only.
|
||||
inodes = append(inodes, fdStat.Ino)
|
||||
@@ -97,7 +164,8 @@ var bufPool = sync.Pool{
|
||||
},
|
||||
}
|
||||
|
||||
func (w *procReader) Connections(withProcs bool, f func(Connection)) error {
|
||||
// Connections walks through all the connections in the "/proc"
|
||||
func (w *reader) Connections(withProcs bool, f func(Connection)) error {
|
||||
// create a map of inode->Process
|
||||
procs := make(map[uint64]Process)
|
||||
if withProcs {
|
||||
@@ -112,10 +180,14 @@ func (w *procReader) Connections(withProcs bool, f func(Connection)) error {
|
||||
buf.Reset()
|
||||
defer bufPool.Put(buf)
|
||||
|
||||
w.proc.ReadFileInto(path.Join(w.proc.Root(), "net", "tcp"), buf)
|
||||
w.proc.ReadFileInto(path.Join(w.proc.Root(), "net", "tcp6"), buf)
|
||||
for _, tcpFile := range tcpFiles {
|
||||
err := w.handles.ReadInto(tcpFile, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
pn := NewProcNet(buf.Bytes(), tcpEstablished)
|
||||
pn := newNetReader(buf.Bytes(), tcpEstablished)
|
||||
for {
|
||||
conn := pn.Next()
|
||||
if conn == nil {
|
||||
|
||||
Reference in New Issue
Block a user