talos/internal/pkg/miniprocfs/processes.go
Andrey Smirnov 96aa9638f7
chore: rename talos-systems/talos to siderolabs/talos
There's a cyclic dependency on siderolink library which imports talos
machinery back. We will fix that after we get talos pushed under a new
name.

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
2022-11-03 16:50:32 +04:00

197 lines
4.0 KiB
Go

// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package miniprocfs
import (
"bytes"
"fmt"
"io"
"os"
"strconv"
"github.com/siderolabs/talos/pkg/machinery/api/machine"
)
const (
procsPageSize = 256
procsBufSize = 16 * 1024
userHz = 100
)
// Processes wraps iterative walker over processes under /proc.
type Processes struct {
fd *os.File
dirnames []string
idx int
buf []byte
pagesize int
RootPath string
}
// NewProcesses initializes process info iterator with path /proc.
func NewProcesses() (*Processes, error) {
return NewProcessesWithPath("/proc")
}
// NewProcessesWithPath initializes process info iterator with non-default path.
func NewProcessesWithPath(rootPath string) (*Processes, error) {
procs := &Processes{
RootPath: rootPath,
buf: make([]byte, procsBufSize),
}
var err error
procs.fd, err = os.Open(rootPath)
if err != nil {
return nil, err
}
procs.pagesize = os.Getpagesize()
return procs, nil
}
// Close the iterator.
func (procs *Processes) Close() error {
return procs.fd.Close()
}
// Next returns process info until the list of processes is exhausted.
//
// Next returns nil, nil when all processes were processed.
// Next skips processes which can't be analyzed.
func (procs *Processes) Next() (*machine.ProcessInfo, error) {
for {
if procs.idx >= len(procs.dirnames) {
var err error
procs.dirnames, err = procs.fd.Readdirnames(procsPageSize)
if err == io.EOF {
return nil, nil
}
if err != nil {
return nil, err
}
procs.idx = 0
}
info, err := procs.readProc(procs.dirnames[procs.idx])
procs.idx++
// if err != nil, this process was killed before we were able to read /proc data
if err == nil {
return info, nil
}
}
}
//nolint:gocyclo
func (procs *Processes) readProc(pidString string) (*machine.ProcessInfo, error) {
pid, err := strconv.ParseInt(pidString, 10, 32)
if err != nil {
return nil, err
}
path := procs.RootPath + "/" + pidString + "/"
executable, err := os.Readlink(path + "exe")
if err != nil {
return nil, err
}
if err = procs.readFileIntoBuf(path + "comm"); err != nil {
return nil, err
}
command := string(bytes.TrimSpace(procs.buf))
if err = procs.readFileIntoBuf(path + "cmdline"); err != nil {
return nil, err
}
args := string(bytes.ReplaceAll(bytes.TrimRight(procs.buf, "\x00"), []byte{0}, []byte{' '}))
if err = procs.readFileIntoBuf(path + "stat"); err != nil {
return nil, err
}
rbracket := bytes.LastIndexByte(procs.buf, ')')
if rbracket == -1 {
return nil, fmt.Errorf("unexpected format")
}
fields := bytes.Fields(procs.buf[rbracket+2:])
state := string(fields[0])
ppid, err := strconv.ParseInt(string(fields[1]), 10, 32)
if err != nil {
return nil, err
}
numThreads, err := strconv.ParseInt(string(fields[17]), 10, 32)
if err != nil {
return nil, err
}
uTime, err := strconv.ParseUint(string(fields[11]), 10, 64)
if err != nil {
return nil, err
}
sTime, err := strconv.ParseUint(string(fields[12]), 10, 64)
if err != nil {
return nil, err
}
vSize, err := strconv.ParseUint(string(fields[20]), 10, 64)
if err != nil {
return nil, err
}
rss, err := strconv.ParseUint(string(fields[21]), 10, 64)
if err != nil {
return nil, err
}
return &machine.ProcessInfo{
Pid: int32(pid),
Ppid: int32(ppid),
State: state,
Threads: int32(numThreads),
CpuTime: float64(uTime+sTime) / userHz,
VirtualMemory: vSize,
ResidentMemory: rss * uint64(procs.pagesize),
Command: command,
Executable: executable,
Args: args,
}, nil
}
func (procs *Processes) readFileIntoBuf(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close() //nolint:errcheck
procs.buf = procs.buf[:cap(procs.buf)]
n, err := f.Read(procs.buf)
if err != nil {
return err
}
procs.buf = procs.buf[:n]
return f.Close()
}