mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-21 18:50:13 +00:00
test(agentcompat): add process supervision harness
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
//go:build linux
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func readRSSBytes(pid int) (uint64, error) {
|
||||
path := filepath.Join("/proc", strconv.Itoa(pid), "status")
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer file.Close()
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) == 3 && fields[0] == "VmRSS:" && fields[2] == "kB" {
|
||||
kilobytes, err := strconv.ParseUint(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse VmRSS: %w", err)
|
||||
}
|
||||
return kilobytes * 1024, nil
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return 0, fmt.Errorf("read %s: %w", path, err)
|
||||
}
|
||||
return 0, errors.New("VmRSS not found")
|
||||
}
|
||||
|
||||
func descendantPIDs(rootPID int) ([]int, error) {
|
||||
entries, err := os.ReadDir("/proc")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read /proc: %w", err)
|
||||
}
|
||||
children := make(map[int][]int)
|
||||
for _, entry := range entries {
|
||||
pid, err := strconv.Atoi(entry.Name())
|
||||
if err != nil || !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
parentPID, err := readParentPID(pid)
|
||||
if err != nil {
|
||||
// /proc is a live snapshot: an unrelated process can disappear
|
||||
// between ReadDir and reading stat. Root PID reads stay strict.
|
||||
if os.IsNotExist(err) || errors.Is(err, syscall.ESRCH) {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
children[parentPID] = append(children[parentPID], pid)
|
||||
}
|
||||
descendants := make([]int, 0)
|
||||
queue := append([]int(nil), children[rootPID]...)
|
||||
for len(queue) > 0 {
|
||||
pid := queue[0]
|
||||
queue = queue[1:]
|
||||
descendants = append(descendants, pid)
|
||||
queue = append(queue, children[pid]...)
|
||||
}
|
||||
sort.Ints(descendants)
|
||||
return descendants, nil
|
||||
}
|
||||
|
||||
func readParentPID(pid int) (int, error) {
|
||||
path := filepath.Join("/proc", strconv.Itoa(pid), "stat")
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
closingParenthesis := strings.LastIndexByte(string(data), ')')
|
||||
if closingParenthesis < 0 {
|
||||
return 0, fmt.Errorf("parse %s: missing command terminator", path)
|
||||
}
|
||||
fields := strings.Fields(string(data[closingParenthesis+1:]))
|
||||
if len(fields) < 2 {
|
||||
return 0, fmt.Errorf("parse %s: missing parent PID", path)
|
||||
}
|
||||
parentPID, err := strconv.Atoi(fields[1])
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("parse %s parent PID: %w", path, err)
|
||||
}
|
||||
return parentPID, nil
|
||||
}
|
||||
|
||||
func processFDs(pid int) (int, map[uint64]struct{}, error) {
|
||||
directory := filepath.Join("/proc", strconv.Itoa(pid), "fd")
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
return 0, nil, err
|
||||
}
|
||||
count := 0
|
||||
sockets := make(map[uint64]struct{})
|
||||
for _, entry := range entries {
|
||||
descriptor, err := strconv.Atoi(entry.Name())
|
||||
if err != nil || descriptor < 3 {
|
||||
continue
|
||||
}
|
||||
target, err := os.Readlink(filepath.Join(directory, entry.Name()))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return 0, nil, err
|
||||
}
|
||||
count++
|
||||
if inode, exists := parseSocketInode(target); exists {
|
||||
sockets[inode] = struct{}{}
|
||||
}
|
||||
}
|
||||
return count, sockets, nil
|
||||
}
|
||||
|
||||
func parseSocketInode(target string) (uint64, bool) {
|
||||
if !strings.HasPrefix(target, "socket:[") || !strings.HasSuffix(target, "]") {
|
||||
return 0, false
|
||||
}
|
||||
inode, err := strconv.ParseUint(strings.TrimSuffix(strings.TrimPrefix(target, "socket:["), "]"), 10, 64)
|
||||
return inode, err == nil
|
||||
}
|
||||
|
||||
func listeningSocketInodes(pid int, protocol string) (map[uint64]struct{}, error) {
|
||||
path := filepath.Join("/proc", strconv.Itoa(pid), "net", protocol)
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
listeners := make(map[uint64]struct{})
|
||||
scanner := bufio.NewScanner(file)
|
||||
if scanner.Scan() {
|
||||
// Skip the stable kernel table header.
|
||||
}
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) < 10 || fields[3] != "0A" {
|
||||
continue
|
||||
}
|
||||
inode, err := strconv.ParseUint(fields[9], 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parse %s listener inode: %w", path, err)
|
||||
}
|
||||
listeners[inode] = struct{}{}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
return nil, fmt.Errorf("read %s: %w", path, err)
|
||||
}
|
||||
return listeners, nil
|
||||
}
|
||||
Reference in New Issue
Block a user