diff --git a/probe/docker/tagger.go b/probe/docker/tagger.go index 1ccecadb8..2c1923485 100644 --- a/probe/docker/tagger.go +++ b/probe/docker/tagger.go @@ -3,6 +3,7 @@ package docker import ( "strconv" + "github.com/weaveworks/scope/probe/proc" "github.com/weaveworks/scope/probe/process" "github.com/weaveworks/scope/report" ) @@ -16,18 +17,18 @@ const ( // These vars are exported for testing. var ( - NewProcessTreeStub = process.NewTree + NewProcessTreeStub = proc.NewTree ) // Tagger is a tagger that tags Docker container information to process // nodes that have a PID. type Tagger struct { registry Registry - procWalker process.Walker + procWalker proc.Walker } // NewTagger returns a usable Tagger. -func NewTagger(registry Registry, procWalker process.Walker) *Tagger { +func NewTagger(registry Registry, procWalker proc.Walker) *Tagger { return &Tagger{ registry: registry, procWalker: procWalker, @@ -44,7 +45,7 @@ func (t *Tagger) Tag(r report.Report) (report.Report, error) { return r, nil } -func (t *Tagger) tag(tree process.Tree, topology *report.Topology) { +func (t *Tagger) tag(tree proc.Tree, topology *report.Topology) { for nodeID, nodeMetadata := range topology.Nodes { pidStr, ok := nodeMetadata.Metadata[process.PID] if !ok { diff --git a/probe/docker/tagger_test.go b/probe/docker/tagger_test.go index 261fd4215..ce2d48871 100644 --- a/probe/docker/tagger_test.go +++ b/probe/docker/tagger_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/weaveworks/scope/probe/docker" - "github.com/weaveworks/scope/probe/process" + "github.com/weaveworks/scope/probe/proc" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" ) @@ -27,7 +27,7 @@ func TestTagger(t *testing.T) { oldProcessTree := docker.NewProcessTreeStub defer func() { docker.NewProcessTreeStub = oldProcessTree }() - docker.NewProcessTreeStub = func(_ process.Walker) (process.Tree, error) { + docker.NewProcessTreeStub = func(_ proc.Walker) (proc.Tree, error) { return &mockProcessTree{map[int]int{2: 1}}, nil } diff --git a/probe/endpoint/reporter.go b/probe/endpoint/reporter.go index 6df5eb229..1841b2db2 100644 --- a/probe/endpoint/reporter.go +++ b/probe/endpoint/reporter.go @@ -7,7 +7,7 @@ import ( "github.com/prometheus/client_golang/prometheus" - "github.com/weaveworks/procspy" + "github.com/weaveworks/scope/probe/proc" "github.com/weaveworks/scope/probe/process" "github.com/weaveworks/scope/report" ) @@ -42,7 +42,7 @@ var SpyDuration = prometheus.NewSummaryVec( []string{}, ) -// NewReporter creates a new Reporter that invokes procspy.Connections to +// NewReporter creates a new Reporter that invokes Connections to // generate a report.Report that contains every discovered (spied) connection // on the host machine, at the granularity of host and port. That information // is stored in the Endpoint topology. It optionally enriches that topology @@ -94,7 +94,7 @@ func (r *Reporter) Report() (report.Report, error) { }(time.Now()) rpt := report.MakeReport() - conns, err := procspy.Connections(r.includeProcesses) + conns, err := proc.Connections(r.includeProcesses) if err != nil { return rpt, err } diff --git a/probe/endpoint/reporter_test.go b/probe/endpoint/reporter_test.go index c7ec41995..f6bc0a461 100644 --- a/probe/endpoint/reporter_test.go +++ b/probe/endpoint/reporter_test.go @@ -5,7 +5,7 @@ import ( "strconv" "testing" - "github.com/weaveworks/procspy" + "github.com/weaveworks/scope/probe/proc" "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/probe/endpoint" "github.com/weaveworks/scope/report" @@ -20,7 +20,7 @@ var ( fixProcessPID = uint(4242) fixProcessName = "nginx" - fixConnections = []procspy.Connection{ + fixConnections = []proc.Connection{ { Transport: "tcp", LocalAddress: fixLocalAddress, @@ -37,14 +37,14 @@ var ( }, } - fixConnectionsWithProcesses = []procspy.Connection{ + fixConnectionsWithProcesses = []proc.Connection{ { Transport: "tcp", LocalAddress: fixLocalAddress, LocalPort: fixLocalPort, RemoteAddress: fixRemoteAddress, RemotePort: fixRemotePort, - Proc: procspy.Proc{ + Proc: proc.Proc{ PID: fixProcessPID, Name: fixProcessName, }, @@ -55,7 +55,7 @@ var ( LocalPort: fixLocalPort, RemoteAddress: fixRemoteAddress, RemotePort: fixRemotePort, - Proc: procspy.Proc{ + Proc: proc.Proc{ PID: fixProcessPID, Name: fixProcessName, }, @@ -64,7 +64,7 @@ var ( ) func TestSpyNoProcesses(t *testing.T) { - procspy.SetFixtures(fixConnections) + proc.SetFixtures(fixConnections) const ( nodeID = "heinz-tomato-ketchup" // TODO rename to hostID @@ -100,7 +100,7 @@ func TestSpyNoProcesses(t *testing.T) { } func TestSpyWithProcesses(t *testing.T) { - procspy.SetFixtures(fixConnectionsWithProcesses) + proc.SetFixtures(fixConnectionsWithProcesses) const ( nodeID = "nikon" // TODO rename to hostID diff --git a/probe/main.go b/probe/main.go index eb4495ff9..f0878c04a 100644 --- a/probe/main.go +++ b/probe/main.go @@ -14,7 +14,7 @@ import ( "syscall" "time" - "github.com/weaveworks/procspy" + "github.com/weaveworks/scope/probe/proc" "github.com/weaveworks/scope/probe/docker" "github.com/weaveworks/scope/probe/endpoint" "github.com/weaveworks/scope/probe/host" @@ -67,7 +67,7 @@ func main() { } log.Printf("publishing to: %s", strings.Join(targets, ", ")) - procspy.SetProcRoot(*procRoot) + proc.SetProcRoot(*procRoot) if *httpListen != "" { log.Printf("profiling data being exported to %s", *httpListen) @@ -112,7 +112,7 @@ func main() { var ( endpointReporter = endpoint.NewReporter(hostID, hostName, *spyProcs, *useConntrack) - processCache = process.NewCachingWalker(process.NewWalker(*procRoot)) + processCache = proc.NewCachingWalker(proc.NewWalker(*procRoot)) tickers = []Ticker{processCache} reporters = []Reporter{ endpointReporter, diff --git a/probe/proc/fixture.go b/probe/proc/fixture.go new file mode 100644 index 000000000..1d69a84c9 --- /dev/null +++ b/probe/proc/fixture.go @@ -0,0 +1,26 @@ +package proc + +// SetFixtures declares constant Connection and ConnectionProcs which will +// always be returned by the package-level Connections and Processes +// functions. It's designed to be used in tests. + +type fixedConnIter []Connection + +func (f *fixedConnIter) Next() *Connection { + if len(*f) == 0 { + return nil + } + + car := (*f)[0] + *f = (*f)[1:] + + return &car +} + +// SetFixtures is used in test scenarios to have known output. +func SetFixtures(c []Connection) { + cbConnections = func(bool) (ConnIter, error) { + f := fixedConnIter(c) + return &f, nil + } +} diff --git a/probe/proc/proc.go b/probe/proc/proc.go new file mode 100644 index 000000000..fb893e414 --- /dev/null +++ b/probe/proc/proc.go @@ -0,0 +1,125 @@ +package proc + +// /proc-based implementation. + +import ( + "bytes" + "os" + "strconv" + "syscall" + "path" +) + +var ( + procRoot = "/proc" +) + +// SetProcRoot sets the location of the proc filesystem. +func SetProcRoot(root string) { + procRoot = root +} + +// walkProcPid walks over all numerical (PID) /proc entries, and sees if their +// ./fd/* files are symlink to sockets. Returns a map from socket ID (inode) +// to PID. Will return an error if /proc isn't there. +func walkProcPid() (map[uint64]Proc, error) { + fh, err := os.Open(procRoot) + if err != nil { + return nil, err + } + + dirNames, err := fh.Readdirnames(-1) + fh.Close() + if err != nil { + return nil, err + } + + var ( + res = map[uint64]Proc{} + stat syscall.Stat_t + ) + for _, dirName := range dirNames { + pid, err := strconv.ParseUint(dirName, 10, 0) + if err != nil { + // Not a number, so not a PID subdir. + continue + } + + fdBase := procRoot + "/" + dirName + "/fd/" + dfh, err := os.Open(fdBase) + if err != nil { + // Process is be gone by now, or we don't have access. + continue + } + + fdNames, err := dfh.Readdirnames(-1) + dfh.Close() + if err != nil { + continue + } + + var name string + + for _, fdName := range fdNames { + // Direct use of syscall.Stat() to save garbage. + err = syscall.Stat(fdBase + fdName, &stat) + if err != nil { + continue + } + + // We want sockets only. + if stat.Mode & syscall.S_IFMT != syscall.S_IFSOCK { + continue + } + + if name == "" { + if name = procName(path.Join(procRoot, dirName)); name == "" { + // Process might be gone by now + break + } + } + + res[stat.Ino] = Proc{ + PID: uint(pid), + Name: name, + } + } + } + + return res, nil +} + +// procName does a pid->name lookup. +func procName(base string) string { + fh, err := os.Open(base + "/comm") + if err != nil { + return "" + } + + name := make([]byte, 64) + l, err := fh.Read(name) + fh.Close() + if err != nil { + return "" + } + + if l < 2 { + return "" + } + + // drop trailing "\n" + return string(name[:l - 1]) +} + +// readFile reads an arbitrary file into a buffer. It's a variable so it can +// be overwritten for benchmarks. That's bad practice and we should change it +// to be a dependency. +var readFile = func(filename string, buf *bytes.Buffer) error { + f, err := os.Open(filename) + if err != nil { + return err + } + _, err = buf.ReadFrom(f) + f.Close() + return err +} diff --git a/probe/proc/procnet.go b/probe/proc/procnet.go new file mode 100644 index 000000000..4f18229f4 --- /dev/null +++ b/probe/proc/procnet.go @@ -0,0 +1,164 @@ +package proc + +import ( + "bytes" + "net" +) + +// ProcNet is an iterator to parse /proc/net/tcp{,6} files. +type ProcNet 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{ + b: b, + c: Connection{}, + wantedState: wantedState, + } +} + +// 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 { + again: + if len(p.b) == 0 { + return nil + } + b := p.b + + if p.b[2] == 's' { + // Skip header + p.b = nextLine(b) + goto again + } + + var ( + local, remote, state, inode []byte + ) + _, b = nextField(b) // 'sl' column + local, b = nextField(b) + remote, b = nextField(b) + state, b = nextField(b) + if parseHex(state) != p.wantedState { + p.b = nextLine(b) + goto again + } + _, b = nextField(b) // 'tx_queue' column + _, b = nextField(b) // 'rx_queue' column + _, b = nextField(b) // 'tr' column + _, b = nextField(b) // 'uid' column + _, b = nextField(b) // 'timeout' column + inode, b = nextField(b) + + p.c.LocalAddress, p.c.LocalPort = scanAddressNA(local, &p.bytesLocal) + p.c.RemoteAddress, p.c.RemotePort = scanAddressNA(remote, &p.bytesRemote) + p.c.inode = parseDec(inode) + p.b = nextLine(b) + return &p.c +} + +// scanAddressNA parses 'A12CF62E:00AA' to the address/port. Handles IPv4 and +// IPv6 addresses. The address is a big endian 32 bit ints, hex encoded. We +// just decode the hex and flip the bytes in every group of 4. +func scanAddressNA(in []byte, buf *[16]byte) (net.IP, uint16) { + col := bytes.IndexByte(in, ':') + if col == -1 { + return nil, 0 + } + + // Network address is big endian. Can be either ipv4 or ipv6. + address := hexDecode32bigNA(in[:col], buf) + return net.IP(address), uint16(parseHex(in[col + 1:])) +} + +// hexDecode32big decodes sequences of 32bit big endian bytes. +func hexDecode32bigNA(src []byte, buf *[16]byte) []byte { + blocks := len(src) / 8 + for block := 0; block < blocks; block++ { + for i := 0; i < 4; i++ { + a := fromHexChar(src[block * 8 + i * 2]) + b := fromHexChar(src[block * 8 + i * 2 + 1]) + buf[block * 4 + 3 - i] = (a << 4) | b + } + } + return buf[:blocks * 4] +} + +func nextField(s []byte) ([]byte, []byte) { + // Skip whitespace. + for i, b := range s { + if b != ' ' { + s = s[i:] + break + } + } + + // Up until the next whitespace field. + for i, b := range s { + if b == ' ' { + return s[:i], s[i:] + } + } + + return nil, nil +} + +func nextLine(s []byte) []byte { + i := bytes.IndexByte(s, '\n') + if i == -1 { + return nil + } + return s[i + 1:] +} + +// Simplified copy of strconv.ParseUint(16). +func parseHex(s []byte) uint { + n := uint(0) + for i := 0; i < len(s); i++ { + n *= 16 + n += uint(fromHexChar(s[i])) + } + return n +} + +// Simplified copy of strconv.ParseUint(10). +func parseDec(s []byte) uint64 { + n := uint64(0) + for _, c := range s { + n *= 10 + n += uint64(c - '0') + } + return n +} + +// hexDecode32big decodes sequences of 32bit big endian bytes. +func hexDecode32big(src []byte) []byte { + dst := make([]byte, len(src) / 2) + blocks := len(src) / 8 + for block := 0; block < blocks; block++ { + for i := 0; i < 4; i++ { + a := fromHexChar(src[block * 8 + i * 2]) + b := fromHexChar(src[block * 8 + i * 2 + 1]) + dst[block * 4 + 3 - i] = (a << 4) | b + } + } + return dst +} + +// fromHexChar converts a hex character into its value. +func fromHexChar(c byte) uint8 { + switch { + case '0' <= c && c <= '9': + return c - '0' + case 'a' <= c && c <= 'f': + return c - 'a' + 10 + case 'A' <= c && c <= 'F': + return c - 'A' + 10 + } + return 0 +} diff --git a/probe/proc/spy.go b/probe/proc/spy.go new file mode 100644 index 000000000..d1b93c201 --- /dev/null +++ b/probe/proc/spy.go @@ -0,0 +1,40 @@ +package proc + +import ( + "net" +) + +const ( + tcpEstablished = 1 // according to /include/net/tcp_states.h +) + +// Connection is a (TCP) connection. The Proc struct might not be filled in. +type Connection struct { + Transport string + LocalAddress net.IP + LocalPort uint16 + RemoteAddress net.IP + RemotePort uint16 + inode uint64 + Proc +} + +// Proc is a single process with PID and process name. +type Proc struct { + PID uint + Name string +} + +// ConnIter is returned by Connections(). +type ConnIter interface { + Next() *Connection +} + +// Connections returns all established (TCP) connections. If processes is +// false we'll just list all TCP connections, and there is no need to be root. +// If processes is true it'll additionally try to lookup the process owning the +// connection, filling in the Proc field. You will need to run this as root to +// find all processes. +func Connections(processes bool) (ConnIter, error) { + return cbConnections(processes) +} diff --git a/probe/proc/spy_linux.go b/probe/proc/spy_linux.go new file mode 100644 index 000000000..fc8141c41 --- /dev/null +++ b/probe/proc/spy_linux.go @@ -0,0 +1,52 @@ +package proc + +import ( + "bytes" + "path" + "sync" +) + +var bufPool = sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 0, 5000)) + }, +} + +type pnConnIter struct { + pn *ProcNet + buf *bytes.Buffer + procs map[uint64]Proc +} + +func (c *pnConnIter) Next() *Connection { + n := c.pn.Next() + if n == nil { + // Done! + bufPool.Put(c.buf) + return nil + } + if proc, ok := c.procs[n.inode]; ok { + n.Proc = proc + } + return n +} + +// cbConnections sets Connections() +var cbConnections = func(processes bool) (ConnIter, error) { + buf := bufPool.Get().(*bytes.Buffer) + buf.Reset() + readFile(path.Join(procRoot, "net", "tcp"), buf) + readFile(path.Join(procRoot, "net", "tcp6"), buf) + var procs map[uint64]Proc + if processes { + var err error + if procs, err = walkProcPid(); err != nil { + return nil, err + } + } + return &pnConnIter{ + pn: NewProcNet(buf.Bytes(), tcpEstablished), + buf: buf, + procs: procs, + }, nil +} diff --git a/probe/process/tree.go b/probe/proc/tree.go similarity index 97% rename from probe/process/tree.go rename to probe/proc/tree.go index 2d03a9c0f..12506d57b 100644 --- a/probe/process/tree.go +++ b/probe/proc/tree.go @@ -1,4 +1,4 @@ -package process +package proc import ( "fmt" diff --git a/probe/process/tree_test.go b/probe/proc/tree_test.go similarity index 80% rename from probe/process/tree_test.go rename to probe/proc/tree_test.go index 6a93cc226..cb3c79227 100644 --- a/probe/process/tree_test.go +++ b/probe/proc/tree_test.go @@ -1,15 +1,15 @@ -package process_test +package proc_test import ( "reflect" "testing" - "github.com/weaveworks/scope/probe/process" + "github.com/weaveworks/scope/probe/proc" ) func TestTree(t *testing.T) { walker := &mockWalker{ - processes: []process.Process{ + processes: []proc.Process{ {PID: 1, PPID: 0, Comm: "init"}, {PID: 2, PPID: 1, Comm: "bash"}, {PID: 3, PPID: 1, Comm: "apache", Threads: 2}, @@ -17,7 +17,7 @@ func TestTree(t *testing.T) { }, } - tree, err := process.NewTree(walker) + tree, err := proc.NewTree(walker) if err != nil { t.Fatalf("newProcessTree error: %v", err) } diff --git a/probe/process/walker.go b/probe/proc/walker.go similarity index 98% rename from probe/process/walker.go rename to probe/proc/walker.go index 4ba20027e..a7c5b1541 100644 --- a/probe/process/walker.go +++ b/probe/proc/walker.go @@ -1,4 +1,4 @@ -package process +package proc import "sync" diff --git a/probe/process/walker_darwin.go b/probe/proc/walker_darwin.go similarity index 99% rename from probe/process/walker_darwin.go rename to probe/proc/walker_darwin.go index cd25b3df3..c997d9385 100644 --- a/probe/process/walker_darwin.go +++ b/probe/proc/walker_darwin.go @@ -1,4 +1,4 @@ -package process +package proc import ( "fmt" diff --git a/probe/process/walker_linux.go b/probe/proc/walker_linux.go similarity index 99% rename from probe/process/walker_linux.go rename to probe/proc/walker_linux.go index f84ded6a6..51cee7d07 100644 --- a/probe/process/walker_linux.go +++ b/probe/proc/walker_linux.go @@ -1,4 +1,4 @@ -package process +package proc import ( "bytes" diff --git a/probe/process/walker_linux_test.go b/probe/proc/walker_linux_test.go similarity index 80% rename from probe/process/walker_linux_test.go rename to probe/proc/walker_linux_test.go index 6b449edd9..819b472f8 100644 --- a/probe/process/walker_linux_test.go +++ b/probe/proc/walker_linux_test.go @@ -1,4 +1,4 @@ -package process_test +package proc_test import ( "fmt" @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/weaveworks/scope/probe/process" + "github.com/weaveworks/scope/probe/proc" "github.com/weaveworks/scope/test" ) @@ -25,10 +25,10 @@ func (p mockProcess) IsDir() bool { return true } func (p mockProcess) Sys() interface{} { return nil } func TestWalker(t *testing.T) { - oldReadDir, oldReadFile := process.ReadDir, process.ReadFile + oldReadDir, oldReadFile := proc.ReadDir, proc.ReadFile defer func() { - process.ReadDir = oldReadDir - process.ReadFile = oldReadFile + proc.ReadDir = oldReadDir + proc.ReadFile = oldReadFile }() processes := map[string]mockProcess{ @@ -39,7 +39,7 @@ func TestWalker(t *testing.T) { "1": {name: "1", comm: "init\n"}, } - process.ReadDir = func(path string) ([]os.FileInfo, error) { + proc.ReadDir = func(path string) ([]os.FileInfo, error) { result := []os.FileInfo{} for _, p := range processes { result = append(result, p) @@ -47,7 +47,7 @@ func TestWalker(t *testing.T) { return result, nil } - process.ReadFile = func(path string) ([]byte, error) { + proc.ReadFile = func(path string) ([]byte, error) { splits := strings.Split(path, "/") pid := splits[len(splits)-2] @@ -71,16 +71,16 @@ func TestWalker(t *testing.T) { return nil, fmt.Errorf("not found") } - want := map[int]process.Process{ + want := map[int]proc.Process{ 3: {PID: 3, PPID: 2, Comm: "curl", Cmdline: "curl google.com", Threads: 1}, 2: {PID: 2, PPID: 1, Comm: "bash", Cmdline: "", Threads: 1}, 4: {PID: 4, PPID: 3, Comm: "apache", Cmdline: "", Threads: 1}, 1: {PID: 1, PPID: 0, Comm: "init", Cmdline: "", Threads: 1}, } - have := map[int]process.Process{} - walker := process.NewWalker("unused") - err := walker.Walk(func(p process.Process) { + have := map[int]proc.Process{} + walker := proc.NewWalker("unused") + err := walker.Walk(func(p proc.Process) { have[p.PID] = p }) diff --git a/probe/process/walker_test.go b/probe/proc/walker_test.go similarity index 64% rename from probe/process/walker_test.go rename to probe/proc/walker_test.go index 6046006bb..2cac7548c 100644 --- a/probe/process/walker_test.go +++ b/probe/proc/walker_test.go @@ -1,25 +1,36 @@ -package process_test +package proc_test import ( "reflect" "testing" - "github.com/weaveworks/scope/probe/process" + "github.com/weaveworks/scope/probe/proc" "github.com/weaveworks/scope/test" ) +type mockWalker struct { + processes []proc.Process +} + +func (m *mockWalker) Walk(f func(proc.Process)) error { + for _, p := range m.processes { + f(p) + } + return nil +} + func TestBasicWalk(t *testing.T) { var ( procRoot = "/proc" - procFunc = func(process.Process) {} + procFunc = func(proc.Process) {} ) - if err := process.NewWalker(procRoot).Walk(procFunc); err != nil { + if err := proc.NewWalker(procRoot).Walk(procFunc); err != nil { t.Fatal(err) } } func TestCache(t *testing.T) { - processes := []process.Process{ + processes := []proc.Process{ {PID: 1, PPID: 0, Comm: "init"}, {PID: 2, PPID: 1, Comm: "bash"}, {PID: 3, PPID: 1, Comm: "apache", Threads: 2}, @@ -28,7 +39,7 @@ func TestCache(t *testing.T) { walker := &mockWalker{ processes: processes, } - cachingWalker := process.NewCachingWalker(walker) + cachingWalker := proc.NewCachingWalker(walker) err := cachingWalker.Tick() if err != nil { t.Fatal(err) @@ -39,7 +50,7 @@ func TestCache(t *testing.T) { t.Errorf("%v (%v)", test.Diff(processes, have), err) } - walker.processes = []process.Process{} + walker.processes = []proc.Process{} have, err = all(cachingWalker) if err != nil || !reflect.DeepEqual(processes, have) { t.Errorf("%v (%v)", test.Diff(processes, have), err) @@ -51,15 +62,15 @@ func TestCache(t *testing.T) { } have, err = all(cachingWalker) - want := []process.Process{} + want := []proc.Process{} if err != nil || !reflect.DeepEqual(want, have) { t.Errorf("%v (%v)", test.Diff(want, have), err) } } -func all(w process.Walker) ([]process.Process, error) { - all := []process.Process{} - err := w.Walk(func(p process.Process) { +func all(w proc.Walker) ([]proc.Process, error) { + all := []proc.Process{} + err := w.Walk(func(p proc.Process) { all = append(all, p) }) return all, err diff --git a/probe/process/reporter.go b/probe/process/reporter.go index 3d46de07d..64eecef1c 100644 --- a/probe/process/reporter.go +++ b/probe/process/reporter.go @@ -4,6 +4,7 @@ import ( "strconv" "github.com/weaveworks/scope/report" + "github.com/weaveworks/scope/probe/proc" ) // We use these keys in node metadata @@ -18,11 +19,11 @@ const ( // Reporter generates Reports containing the Process topology. type Reporter struct { scope string - walker Walker + walker proc.Walker } // NewReporter makes a new Reporter. -func NewReporter(walker Walker, scope string) *Reporter { +func NewReporter(walker proc.Walker, scope string) *Reporter { return &Reporter{ scope: scope, walker: walker, @@ -42,7 +43,7 @@ func (r *Reporter) Report() (report.Report, error) { func (r *Reporter) processTopology() (report.Topology, error) { t := report.MakeTopology() - err := r.walker.Walk(func(p Process) { + err := r.walker.Walk(func(p proc.Process) { pidstr := strconv.Itoa(p.PID) nodeID := report.MakeProcessNodeID(r.scope, pidstr) t.Nodes[nodeID] = report.MakeNode() diff --git a/probe/process/reporter_test.go b/probe/process/reporter_test.go index d13b2611f..1eab60451 100644 --- a/probe/process/reporter_test.go +++ b/probe/process/reporter_test.go @@ -4,16 +4,17 @@ import ( "reflect" "testing" + "github.com/weaveworks/scope/probe/proc" "github.com/weaveworks/scope/probe/process" "github.com/weaveworks/scope/report" "github.com/weaveworks/scope/test" ) type mockWalker struct { - processes []process.Process + processes []proc.Process } -func (m *mockWalker) Walk(f func(process.Process)) error { +func (m *mockWalker) Walk(f func(proc.Process)) error { for _, p := range m.processes { f(p) } @@ -22,7 +23,7 @@ func (m *mockWalker) Walk(f func(process.Process)) error { func TestReporter(t *testing.T) { walker := &mockWalker{ - processes: []process.Process{ + processes: []proc.Process{ {PID: 1, PPID: 0, Comm: "init"}, {PID: 2, PPID: 1, Comm: "bash"}, {PID: 3, PPID: 1, Comm: "apache", Threads: 2},