diff --git a/probe/docker/tagger.go b/probe/docker/tagger.go index ec2b7d120..0c8d7cd57 100644 --- a/probe/docker/tagger.go +++ b/probe/docker/tagger.go @@ -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, diff --git a/probe/docker/tagger_test.go b/probe/docker/tagger_test.go index 7a6d3f4b1..6fc8b697f 100644 --- a/probe/docker/tagger_test.go +++ b/probe/docker/tagger_test.go @@ -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 } diff --git a/probe/endpoint/reporter.go b/probe/endpoint/reporter.go index ba88b48cd..2689700b1 100644 --- a/probe/endpoint/reporter.go +++ b/probe/endpoint/reporter.go @@ -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 diff --git a/probe/endpoint/reporter_test.go b/probe/endpoint/reporter_test.go index 8ebc1cceb..af746048a 100644 --- a/probe/endpoint/reporter_test.go +++ b/probe/endpoint/reporter_test.go @@ -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) diff --git a/probe/main.go b/probe/main.go index 5b4cff731..33afccf39 100644 --- a/probe/main.go +++ b/probe/main.go @@ -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 { diff --git a/probe/proc/mocks.go b/probe/proc/mocks.go index 057ce2e2c..f31d2b4b2 100644 --- a/probe/proc/mocks.go +++ b/probe/proc/mocks.go @@ -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 +} diff --git a/probe/proc/mocks_test.go b/probe/proc/mocks_test.go new file mode 100644 index 000000000..3b3905a44 --- /dev/null +++ b/probe/proc/mocks_test.go @@ -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) } diff --git a/probe/proc/net_linux.go b/probe/proc/net_linux.go index 8a0871514..e9b7de05b 100644 --- a/probe/proc/net_linux.go +++ b/probe/proc/net_linux.go @@ -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 diff --git a/probe/proc/reader.go b/probe/proc/reader.go index fa77c0805..6ef7c4a4b 100644 --- a/probe/proc/reader.go +++ b/probe/proc/reader.go @@ -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{} diff --git a/probe/proc/reader_darwin.go b/probe/proc/reader_darwin.go index 67fb70280..3e48f2730 100644 --- a/probe/proc/reader_darwin.go +++ b/probe/proc/reader_darwin.go @@ -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 diff --git a/probe/proc/reader_linux.go b/probe/proc/reader_linux.go index ba41feced..8a27ec946 100644 --- a/probe/proc/reader_linux.go +++ b/probe/proc/reader_linux.go @@ -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 { diff --git a/probe/proc/reader_linux_test.go b/probe/proc/reader_linux_test.go index 1974276b0..29402eb73 100644 --- a/probe/proc/reader_linux_test.go +++ b/probe/proc/reader_linux_test.go @@ -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 diff --git a/probe/proc/reader_test.go b/probe/proc/reader_test.go index 8273268cd..9d78997d3 100644 --- a/probe/proc/reader_test.go +++ b/probe/proc/reader_test.go @@ -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) diff --git a/probe/proc/tree.go b/probe/proc/tree.go index 9a4206786..f9d48b959 100644 --- a/probe/proc/tree.go +++ b/probe/proc/tree.go @@ -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 diff --git a/probe/proc/tree_test.go b/probe/proc/tree_test.go index 92ec42c30..5377a9e92 100644 --- a/probe/proc/tree_test.go +++ b/probe/proc/tree_test.go @@ -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"}, diff --git a/probe/process/reporter.go b/probe/process/reporter.go index 196d2d6dc..97fb8d67c 100644 --- a/probe/process/reporter.go +++ b/probe/process/reporter.go @@ -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, diff --git a/probe/process/reporter_test.go b/probe/process/reporter_test.go index a0c84f4fa..0aac3e500 100644 --- a/probe/process/reporter_test.go +++ b/probe/process/reporter_test.go @@ -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"},