mirror of
https://github.com/siderolabs/talos.git
synced 2025-10-10 07:01:12 +02:00
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>
197 lines
4.0 KiB
Go
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()
|
|
}
|