// 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 // so that is can be tested. func (w *walker) Walk(f func(Process, Process)) error { dirEntries, err := fs.ReadDirNames(w.procRoot) if err != nil { return err } for _, filename := range dirEntries { pid, err := strconv.Atoi(filename) if err != nil { continue } ppid, threads, jiffies, rss, rssLimit, err := readStats(path.Join(w.procRoot, filename, "stat")) if err != nil { continue } openFiles, err := fs.ReadDirNames(path.Join(w.procRoot, filename, "fd")) if err != nil { continue } openFilesLimit, err := readLimits(path.Join(w.procRoot, filename, "limits")) if err != nil { continue } cmdline, name := "", "(unknown)" 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) } f(Process{ PID: pid, PPID: ppid, Name: name, Cmdline: cmdline, Threads: threads, Jiffies: jiffies, RSSBytes: rss, RSSBytesLimit: rssLimit, OpenFilesCount: len(openFiles), OpenFilesLimit: openFilesLimit, }, Process{}) } return nil }
func (p dir) ReadDirNames(path string) ([]string, error) { if path == "/" { result := []string{} for _, v := range p.entries { result = append(result, v.Name()) } return result, nil } head, tail := split(path) fs, ok := p.entries[head] if !ok { return nil, fmt.Errorf("Not found: %s", path) } return fs.ReadDirNames(tail) }
// walkNamespace does the work of walk for a single namespace func (w pidWalker) walkNamespace(buf *bytes.Buffer, sockets map[uint64]*Proc, namespaceProcs []*process.Process) error { if found, err := readProcessConnections(buf, namespaceProcs); err != nil || !found { return err } var statT syscall.Stat_t var fdBlockCount uint64 for i, p := range namespaceProcs { // Get the sockets for all the processes in the namespace dirName := strconv.Itoa(p.PID) fdBase := filepath.Join(procRoot, dirName, "fd") if fdBlockCount > w.fdBlockSize { // we surpassed the filedescriptor rate limit select { case <-w.tickc: case <-w.stopc: return nil // abort } fdBlockCount = 0 // read the connections again to // avoid the race between between /net/tcp{,6} and /proc/PID/fd/* if found, err := readProcessConnections(buf, namespaceProcs[i:]); err != nil || !found { return err } } fds, err := fs.ReadDirNames(fdBase) if err != nil { // Process is gone by now, or we don't have access. continue } var proc *Proc for _, fd := range fds { fdBlockCount++ // Direct use of syscall.Stat() to save garbage. err = fs.Stat(filepath.Join(fdBase, fd), &statT) if err != nil { continue } // We want sockets only. if statT.Mode&syscall.S_IFMT != syscall.S_IFSOCK { continue } // Initialize proc lazily to avoid creating unnecessary // garbage if proc == nil { proc = &Proc{ PID: uint(p.PID), Name: p.Name, } } sockets[statT.Ino] = proc } } return nil }