mirror of
https://github.com/weaveworks/scope.git
synced 2026-07-25 00:06:46 +00:00
Keep a cache of open files, reducing the number of open/close cycles in the /proc dir
This commit is contained in:
@@ -24,11 +24,11 @@ var (
|
||||
// nodes that have a PID.
|
||||
type Tagger struct {
|
||||
registry Registry
|
||||
procWalker proc.ProcReader
|
||||
procWalker proc.Reader
|
||||
}
|
||||
|
||||
// NewTagger returns a usable Tagger.
|
||||
func NewTagger(registry Registry, procWalker proc.ProcReader) *Tagger {
|
||||
func NewTagger(registry Registry, procWalker proc.Reader) *Tagger {
|
||||
return &Tagger{
|
||||
registry: registry,
|
||||
procWalker: procWalker,
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestTagger(t *testing.T) {
|
||||
oldProcessTree := docker.NewProcessTreeStub
|
||||
defer func() { docker.NewProcessTreeStub = oldProcessTree }()
|
||||
|
||||
docker.NewProcessTreeStub = func(_ proc.ProcReader) (proc.Tree, error) {
|
||||
docker.NewProcessTreeStub = func(_ proc.Reader) (proc.Tree, error) {
|
||||
return &mockProcessTree{map[int]int{2: 1}}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ type Reporter struct {
|
||||
conntracker *Conntracker
|
||||
natmapper *natmapper
|
||||
revResolver *ReverseResolver
|
||||
procReader proc.ProcReader
|
||||
procReader proc.Reader
|
||||
}
|
||||
|
||||
// SpyDuration is an exported prometheus metric
|
||||
@@ -48,7 +48,7 @@ var SpyDuration = prometheus.NewSummaryVec(
|
||||
// on the host machine, at the granularity of host and port. That information
|
||||
// is stored in the Endpoint topology. It optionally enriches that topology
|
||||
// with process (PID) information.
|
||||
func NewReporter(hostID, hostName string, includeProcesses bool, procReader proc.ProcReader, useConntrack bool) *Reporter {
|
||||
func NewReporter(hostID, hostName string, includeProcesses bool, procReader proc.Reader, useConntrack bool) *Reporter {
|
||||
var (
|
||||
conntrackModulePresent = ConntrackModulePresent()
|
||||
conntracker *Conntracker
|
||||
|
||||
@@ -17,7 +17,7 @@ var (
|
||||
fixRemoteAddress = net.ParseIP("192.168.1.2")
|
||||
fixRemotePort = uint16(12345)
|
||||
fixRemotePortB = uint16(12346)
|
||||
fixProcessPID = int(4242)
|
||||
fixProcessPID = int(4242)
|
||||
fixProcessName = "nginx"
|
||||
|
||||
fixConnections = []proc.Connection{
|
||||
@@ -69,7 +69,7 @@ func TestSpyNoProcesses(t *testing.T) {
|
||||
nodeName = "frenchs-since-1904" // TODO rename to hostNmae
|
||||
)
|
||||
|
||||
procReader := proc.MockedProcReader{Conns: fixConnections}
|
||||
procReader := proc.MockedReader{Conns: fixConnections}
|
||||
reporter := endpoint.NewReporter(nodeID, nodeName, false, &procReader, false)
|
||||
r, _ := reporter.Report()
|
||||
//buf, _ := json.MarshalIndent(r, "", " ")
|
||||
@@ -104,7 +104,7 @@ func TestSpyWithProcesses(t *testing.T) {
|
||||
nodeName = "fishermans-friend" // TODO rename to hostNmae
|
||||
)
|
||||
|
||||
procReader := proc.MockedProcReader{Conns: fixConnectionsWithProcesses}
|
||||
procReader := proc.MockedReader{Conns: fixConnectionsWithProcesses}
|
||||
reporter := endpoint.NewReporter(nodeID, nodeName, true, &procReader, false)
|
||||
r, _ := reporter.Report()
|
||||
// buf, _ := json.MarshalIndent(r, "", " ") ; t.Logf("\n%s\n", buf)
|
||||
|
||||
@@ -109,8 +109,8 @@ func main() {
|
||||
}
|
||||
|
||||
var (
|
||||
procDir = proc.OSProcDir{Dir: *procRoot}
|
||||
procReader = proc.NewCachingProcReader(proc.NewProcReader(procDir), *spyProcs)
|
||||
procDir = proc.OSDir{Dir: *procRoot}
|
||||
procReader = proc.NewCachingProcReader(proc.NewReader(procDir), *spyProcs)
|
||||
tickers = []Ticker{procReader}
|
||||
endpointReporter = endpoint.NewReporter(hostID, hostName, *spyProcs, procReader, *useConntrack)
|
||||
reporters = []Reporter{
|
||||
@@ -120,6 +120,7 @@ func main() {
|
||||
}
|
||||
taggers = []Tagger{newTopologyTagger(), host.NewTagger(hostID)}
|
||||
)
|
||||
defer procReader.Close()
|
||||
defer endpointReporter.Stop()
|
||||
|
||||
if *dockerEnabled {
|
||||
|
||||
@@ -1,59 +1,30 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
// note: we must keep this in the "proc" package so it can be used from other packages..
|
||||
|
||||
// a mocked process
|
||||
type MockedProcess struct {
|
||||
Id, Comm, Cmdline string
|
||||
}
|
||||
|
||||
func (p MockedProcess) Name() string { return p.Id }
|
||||
func (p MockedProcess) Size() int64 { return 0 }
|
||||
func (p MockedProcess) Mode() os.FileMode { return 0 }
|
||||
func (p MockedProcess) ModTime() time.Time { return time.Now() }
|
||||
func (p MockedProcess) IsDir() bool { return true }
|
||||
func (p MockedProcess) Sys() interface{} { return nil }
|
||||
|
||||
// a mocked "/proc" directory
|
||||
type MockedProcDir struct {
|
||||
Dir string
|
||||
ReadDirFunc func(string) ([]os.FileInfo, error)
|
||||
ReadFileFunc func(string) ([]byte, error)
|
||||
ReadFileIntoFunc func(string, *bytes.Buffer) error
|
||||
}
|
||||
|
||||
func (p MockedProcDir) Root() string { return p.Dir }
|
||||
func (p MockedProcDir) ReadDir(s string) ([]os.FileInfo, error) { return p.ReadDirFunc(s) }
|
||||
func (p MockedProcDir) ReadFile(s string) ([]byte, error) { return p.ReadFileFunc(s) }
|
||||
func (p MockedProcDir) ReadFileInto(s string, b *bytes.Buffer) error { return p.ReadFileIntoFunc(s, b) }
|
||||
|
||||
var EmptyProcDir = MockedProcDir{
|
||||
Dir: "",
|
||||
ReadDirFunc: func(string) ([]os.FileInfo, error) { return []os.FileInfo{}, nil },
|
||||
ReadFileFunc: func(string) ([]byte, error) { return []byte{}, nil },
|
||||
ReadFileIntoFunc: func(string, *bytes.Buffer) error { return nil },
|
||||
}
|
||||
|
||||
// a mocked /proc reader
|
||||
type MockedProcReader struct {
|
||||
// MockedReader is a mocked "/proc" reader
|
||||
type MockedReader struct {
|
||||
Procs []Process
|
||||
Conns []Connection
|
||||
}
|
||||
|
||||
func (mw MockedProcReader) Processes(f func(Process)) error {
|
||||
// Processes walks through the processes provided in the mocked "/proc" reader
|
||||
func (mw MockedReader) Processes(f func(Process)) error {
|
||||
for _, p := range mw.Procs {
|
||||
f(p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mw *MockedProcReader) Connections(_ bool, f func(Connection)) error {
|
||||
// Connections walks through the connections provided in the mocked "/proc" reader
|
||||
func (mw *MockedReader) Connections(_ bool, f func(Connection)) error {
|
||||
for _, c := range mw.Conns {
|
||||
f(c)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close (mocked version)
|
||||
func (mw *MockedReader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
27
probe/proc/mocks_test.go
Normal file
27
probe/proc/mocks_test.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package proc_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/weaveworks/scope/probe/proc"
|
||||
)
|
||||
|
||||
// mockedFile is a mocked file in the "/proc" directory
|
||||
type mockedFile struct {
|
||||
Path string
|
||||
ReadIntoFunc func(buf *bytes.Buffer) error
|
||||
}
|
||||
|
||||
func (mpf mockedFile) ReadInto(buf *bytes.Buffer) error { return mpf.ReadIntoFunc(buf) }
|
||||
func (mpf mockedFile) Close() error { return nil }
|
||||
|
||||
// mockedDir is a mocked "/proc" directory
|
||||
type mockedDir struct {
|
||||
Dir string
|
||||
OpenFunc func(string) (proc.File, error)
|
||||
ReadDirNamesFunc func(string) ([]string, error)
|
||||
}
|
||||
|
||||
func (p mockedDir) Root() string { return p.Dir }
|
||||
func (p mockedDir) Open(s string) (proc.File, error) { return p.OpenFunc(s) }
|
||||
func (p mockedDir) ReadDirNames(s string) ([]string, error) { return p.ReadDirNamesFunc(s) }
|
||||
@@ -5,17 +5,15 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// ProcNet is an iterator to parse /proc/net/tcp{,6} files.
|
||||
type ProcNet struct {
|
||||
type netReader struct {
|
||||
b []byte
|
||||
c Connection
|
||||
wantedState uint
|
||||
bytesLocal, bytesRemote [16]byte
|
||||
}
|
||||
|
||||
// NewProcNet gives a new ProcNet parser.
|
||||
func NewProcNet(b []byte, wantedState uint) *ProcNet {
|
||||
return &ProcNet{
|
||||
func newNetReader(b []byte, wantedState uint) *netReader {
|
||||
return &netReader{
|
||||
b: b,
|
||||
c: Connection{},
|
||||
wantedState: wantedState,
|
||||
@@ -24,7 +22,7 @@ func NewProcNet(b []byte, wantedState uint) *ProcNet {
|
||||
|
||||
// Next returns the next connection. All buffers are re-used, so if you want
|
||||
// to keep the IPs you have to copy them.
|
||||
func (p *ProcNet) Next() *Connection {
|
||||
func (p *netReader) Next() *Connection {
|
||||
again:
|
||||
if len(p.b) == 0 {
|
||||
return nil
|
||||
|
||||
@@ -2,37 +2,70 @@ package proc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// ProcDir is the '/proc' directory and the associated ops for
|
||||
// reading subdirs or files.
|
||||
type ProcDir interface {
|
||||
Root() string // proc directory
|
||||
ReadDir(string) ([]os.FileInfo, error) // read a subdirectory in the "root"
|
||||
ReadFile(string) ([]byte, error) // read a file in the "root"
|
||||
ReadFileInto(string, *bytes.Buffer) error // read a file in the "root" in a buffer
|
||||
// File is a file in the "/proc" directory
|
||||
type File interface {
|
||||
ReadInto(buf *bytes.Buffer) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type OSProcDir struct{ Dir string }
|
||||
// OSFile is a native file
|
||||
type OSFile struct{ *os.File }
|
||||
|
||||
func (dp OSProcDir) Root() string { return dp.Dir }
|
||||
func (dp OSProcDir) ReadDir(s string) ([]os.FileInfo, error) { return ioutil.ReadDir(s) }
|
||||
func (dp OSProcDir) ReadFile(s string) ([]byte, error) { return ioutil.ReadFile(s) }
|
||||
func (dp OSProcDir) ReadFileInto(filename string, buf *bytes.Buffer) error {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
// ReadInto reads the whole file into a buffer
|
||||
func (of *OSFile) ReadInto(buf *bytes.Buffer) error {
|
||||
if _, err := of.File.Seek(0, 0); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = buf.ReadFrom(f)
|
||||
f.Close()
|
||||
_, err := buf.ReadFrom(of.File)
|
||||
return err
|
||||
}
|
||||
|
||||
// Dir is the '/proc' directory and the associated ops for
|
||||
// reading subdirs or files.
|
||||
type Dir interface {
|
||||
Root() string // the "/proc" directory
|
||||
Open(s string) (File, error) // open a file in the "/proc" dir
|
||||
ReadDirNames(string) ([]string, error) // list a subdirectory in the "/proc"
|
||||
}
|
||||
|
||||
// OSDir is a OS "/proc" firectory
|
||||
type OSDir struct{ Dir string }
|
||||
|
||||
// Root returns the "/proc" top dir
|
||||
func (dp OSDir) Root() string {
|
||||
return dp.Dir
|
||||
}
|
||||
|
||||
// Open returns a ProcFile
|
||||
func (dp OSDir) Open(s string) (File, error) {
|
||||
h, err := os.Open(path.Join(dp.Root(), s))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &OSFile{h}, nil
|
||||
}
|
||||
|
||||
// ReadDirNames reads all the directory entries
|
||||
func (dp OSDir) ReadDirNames(s string) ([]string, error) {
|
||||
f, err := os.Open(s)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := f.Readdirnames(-1)
|
||||
f.Close()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// DefaultProcDir is the default '/proc' directory
|
||||
var DefaultProcDir = OSProcDir{Dir: "/proc"}
|
||||
var DefaultProcDir = OSDir{Dir: "/proc"}
|
||||
|
||||
// Process represents a single process.
|
||||
type Process struct {
|
||||
@@ -43,13 +76,15 @@ type Process struct {
|
||||
Inodes []uint64
|
||||
}
|
||||
|
||||
// ProcReader is something that reads the /proc directory and
|
||||
// Reader is something that reads the /proc directory and
|
||||
// returns some info like processes and connections
|
||||
type ProcReader interface {
|
||||
type Reader interface {
|
||||
// Processes walks through the processes
|
||||
Processes(func(Process)) error
|
||||
// Connections walks through the connections
|
||||
Connections(bool, func(Connection)) error
|
||||
// Close closes the "/proc" reader
|
||||
Close() error
|
||||
}
|
||||
|
||||
// CachingProcReader is a '/proc' reader than caches a copy of the output from another
|
||||
@@ -57,13 +92,13 @@ type ProcReader interface {
|
||||
type CachingProcReader struct {
|
||||
procsCache []Process
|
||||
connsCache []Connection
|
||||
source ProcReader
|
||||
source Reader
|
||||
includeProcs bool
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// NewCachingProcReader returns a new CachingProcReader
|
||||
func NewCachingProcReader(source ProcReader, includeProcs bool) *CachingProcReader {
|
||||
func NewCachingProcReader(source Reader, includeProcs bool) *CachingProcReader {
|
||||
return &CachingProcReader{source: source, includeProcs: includeProcs}
|
||||
}
|
||||
|
||||
@@ -91,6 +126,11 @@ func (c *CachingProcReader) Connections(_ bool, f func(Connection)) error {
|
||||
|
||||
}
|
||||
|
||||
// Close closes the "/proc" reader
|
||||
func (c *CachingProcReader) Close() error {
|
||||
return c.source.Close()
|
||||
}
|
||||
|
||||
// Update updates the cached copy of the processes and connections lists
|
||||
func (c *CachingProcReader) Tick() error {
|
||||
newProcsCache := []Process{}
|
||||
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type procReader struct{}
|
||||
type reader struct{}
|
||||
|
||||
// NewProcReader returns a Darwin (lsof-based) '/proc' reader
|
||||
func NewProcReader(proc ProcDir) *procReader {
|
||||
return &procReader{}
|
||||
// NewReader returns a Darwin (lsof-based) '/proc' reader
|
||||
func NewReader(proc Dir) Reader {
|
||||
return &reader{}
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
lsofBinary = "lsof"
|
||||
)
|
||||
|
||||
func (procReader) Processes(f func(Process)) error {
|
||||
func (reader) Processes(f func(Process)) error {
|
||||
output, err := exec.Command(
|
||||
lsofBinary,
|
||||
"-i", // only Internet files
|
||||
@@ -45,7 +45,7 @@ func (procReader) Processes(f func(Process)) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *walker) Connections(withProcs bool, f func(Connection)) error {
|
||||
func (w *reader) Connections(withProcs bool, f func(Connection)) error {
|
||||
out, err := exec.Command(
|
||||
netstatBinary,
|
||||
"-n", // no number resolving
|
||||
@@ -93,6 +93,11 @@ func (w *walker) Connections(withProcs bool, f func(Connection)) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the Darwin "/proc" reader
|
||||
func (w *reader) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseLSOF(output string) (map[string]Process, error) {
|
||||
var (
|
||||
processes = map[string]Process{} // Local addr -> Proc
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,24 +1,37 @@
|
||||
package proc_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/scope/probe/proc"
|
||||
"github.com/weaveworks/scope/test"
|
||||
)
|
||||
|
||||
type mockedProcess struct {
|
||||
ID, Comm, Cmdline string
|
||||
}
|
||||
|
||||
func (p mockedProcess) Name() string { return p.ID }
|
||||
func (p mockedProcess) Size() int64 { return 0 }
|
||||
func (p mockedProcess) Mode() os.FileMode { return 0 }
|
||||
func (p mockedProcess) ModTime() time.Time { return time.Now() }
|
||||
func (p mockedProcess) IsDir() bool { return true }
|
||||
func (p mockedProcess) Sys() interface{} { return nil }
|
||||
|
||||
func TestProcReaderProcesses(t *testing.T) {
|
||||
processes := map[string]proc.MockedProcess{
|
||||
"3": {Id: "3", Comm: "curl\n", Cmdline: "curl\000google.com"},
|
||||
"2": {Id: "2", Comm: "bash\n"},
|
||||
"4": {Id: "4", Comm: "apache\n"},
|
||||
"notapid": {Id: "notapid"},
|
||||
"1": {Id: "1", Comm: "init\n"},
|
||||
processes := map[string]mockedProcess{
|
||||
"3": {ID: "3", Comm: "curl\n", Cmdline: "curl\000google.com"},
|
||||
"2": {ID: "2", Comm: "bash\n"},
|
||||
"4": {ID: "4", Comm: "apache\n"},
|
||||
"notapid": {ID: "notapid"},
|
||||
"1": {ID: "1", Comm: "init\n"},
|
||||
}
|
||||
|
||||
want := map[int]proc.Process{
|
||||
@@ -29,17 +42,16 @@ func TestProcReaderProcesses(t *testing.T) {
|
||||
}
|
||||
|
||||
// use a mocked /proc that reads from our mocked processes
|
||||
procDir := proc.MockedProcDir{
|
||||
ReadDirFunc: func(path string) ([]os.FileInfo, error) {
|
||||
result := []os.FileInfo{}
|
||||
for _, p := range processes {
|
||||
result = append(result, p)
|
||||
procDir := mockedDir{
|
||||
ReadDirNamesFunc: func(path string) ([]string, error) {
|
||||
result := []string{}
|
||||
for k := range processes {
|
||||
result = append(result, k)
|
||||
}
|
||||
return result, nil
|
||||
},
|
||||
|
||||
ReadFileFunc: func(path string) ([]byte, error) {
|
||||
splits := strings.Split(path, "/")
|
||||
OpenFunc: func(filename string) (proc.File, error) {
|
||||
splits := strings.Split(filename, "/")
|
||||
pid := splits[len(splits)-2]
|
||||
process, ok := processes[pid]
|
||||
if !ok {
|
||||
@@ -47,22 +59,30 @@ func TestProcReaderProcesses(t *testing.T) {
|
||||
}
|
||||
|
||||
file := splits[len(splits)-1]
|
||||
var content []byte
|
||||
switch file {
|
||||
case "comm":
|
||||
return []byte(process.Comm), nil
|
||||
content = []byte(process.Comm)
|
||||
case "stat":
|
||||
pid, _ := strconv.Atoi(splits[len(splits)-2])
|
||||
parent := pid - 1
|
||||
return []byte(fmt.Sprintf("%d na R %d 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1", pid, parent)), nil
|
||||
content = []byte(fmt.Sprintf("%d na R %d 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1", pid, parent))
|
||||
case "cmdline":
|
||||
return []byte(process.Cmdline), nil
|
||||
content = []byte(process.Cmdline)
|
||||
default:
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("not found")
|
||||
return mockedFile{
|
||||
ReadIntoFunc: func(buf *bytes.Buffer) error {
|
||||
_, err := buf.Write(content)
|
||||
return err
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
|
||||
procReader := proc.NewProcReader(procDir)
|
||||
procReader := proc.NewReader(procDir)
|
||||
have := map[int]proc.Process{}
|
||||
err := procReader.Processes(func(p proc.Process) {
|
||||
have[p.PID] = p
|
||||
|
||||
@@ -10,13 +10,19 @@ import (
|
||||
|
||||
func TestProcReaderBasic(t *testing.T) {
|
||||
procFunc := func(proc.Process) {}
|
||||
if err := proc.NewProcReader(proc.EmptyProcDir).Processes(procFunc); err != nil {
|
||||
nullProcDir := mockedDir{
|
||||
Dir: "",
|
||||
OpenFunc: func(string) (proc.File, error) { return &proc.OSFile{File: nil}, nil },
|
||||
ReadDirNamesFunc: func(string) ([]string, error) { return []string{}, nil },
|
||||
}
|
||||
|
||||
if err := proc.NewReader(nullProcDir).Processes(procFunc); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCachingProcReader(t *testing.T) {
|
||||
all := func(w proc.ProcReader) ([]proc.Process, error) {
|
||||
all := func(w proc.Reader) ([]proc.Process, error) {
|
||||
all := []proc.Process{}
|
||||
err := w.Processes(func(p proc.Process) {
|
||||
all = append(all, p)
|
||||
@@ -30,7 +36,7 @@ func TestCachingProcReader(t *testing.T) {
|
||||
{PID: 3, PPID: 1, Comm: "apache", Threads: 2},
|
||||
{PID: 4, PPID: 2, Comm: "ping", Cmdline: "ping foo.bar.local"},
|
||||
}
|
||||
procReader := &proc.MockedProcReader{
|
||||
procReader := &proc.MockedReader{
|
||||
Procs: processes,
|
||||
}
|
||||
cachingProcReader := proc.NewCachingProcReader(procReader, true)
|
||||
|
||||
@@ -14,7 +14,7 @@ type tree struct {
|
||||
}
|
||||
|
||||
// NewTree returns a new Tree that can be polled.
|
||||
func NewTree(walker ProcReader) (Tree, error) {
|
||||
func NewTree(walker Reader) (Tree, error) {
|
||||
pt := tree{processes: map[int]Process{}}
|
||||
err := walker.Processes(func(p Process) {
|
||||
pt.processes[p.PID] = p
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestTree(t *testing.T) {
|
||||
walker := &proc.MockedProcReader{
|
||||
walker := &proc.MockedReader{
|
||||
Procs: []proc.Process{
|
||||
{PID: 1, PPID: 0, Comm: "init"},
|
||||
{PID: 2, PPID: 1, Comm: "bash"},
|
||||
|
||||
@@ -3,8 +3,8 @@ package process
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/weaveworks/scope/report"
|
||||
"github.com/weaveworks/scope/probe/proc"
|
||||
"github.com/weaveworks/scope/report"
|
||||
)
|
||||
|
||||
// We use these keys in node metadata
|
||||
@@ -19,11 +19,11 @@ const (
|
||||
// Reporter generates Reports containing the Process topology.
|
||||
type Reporter struct {
|
||||
scope string
|
||||
walker proc.ProcReader
|
||||
walker proc.Reader
|
||||
}
|
||||
|
||||
// NewReporter makes a new Reporter.
|
||||
func NewReporter(walker proc.ProcReader, scope string) *Reporter {
|
||||
func NewReporter(walker proc.Reader, scope string) *Reporter {
|
||||
return &Reporter{
|
||||
scope: scope,
|
||||
walker: walker,
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestReporter(t *testing.T) {
|
||||
procReader := &proc.MockedProcReader{
|
||||
procReader := &proc.MockedReader{
|
||||
Procs: []proc.Process{
|
||||
{PID: 1, PPID: 0, Comm: "init"},
|
||||
{PID: 2, PPID: 1, Comm: "bash"},
|
||||
|
||||
Reference in New Issue
Block a user