talm
196 строк · 4.0 Кб
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5package miniprocfs
6
7import (
8"bytes"
9"errors"
10"io"
11"os"
12"strconv"
13
14"github.com/siderolabs/talos/pkg/machinery/api/machine"
15)
16
17const (
18procsPageSize = 256
19procsBufSize = 16 * 1024
20userHz = 100
21)
22
23// Processes wraps iterative walker over processes under /proc.
24type Processes struct {
25fd *os.File
26dirnames []string
27idx int
28
29buf []byte
30pagesize int
31
32RootPath string
33}
34
35// NewProcesses initializes process info iterator with path /proc.
36func NewProcesses() (*Processes, error) {
37return NewProcessesWithPath("/proc")
38}
39
40// NewProcessesWithPath initializes process info iterator with non-default path.
41func NewProcessesWithPath(rootPath string) (*Processes, error) {
42procs := &Processes{
43RootPath: rootPath,
44buf: make([]byte, procsBufSize),
45}
46
47var err error
48
49procs.fd, err = os.Open(rootPath)
50if err != nil {
51return nil, err
52}
53
54procs.pagesize = os.Getpagesize()
55
56return procs, nil
57}
58
59// Close the iterator.
60func (procs *Processes) Close() error {
61return procs.fd.Close()
62}
63
64// Next returns process info until the list of processes is exhausted.
65//
66// Next returns nil, nil when all processes were processed.
67// Next skips processes which can't be analyzed.
68func (procs *Processes) Next() (*machine.ProcessInfo, error) {
69for {
70if procs.idx >= len(procs.dirnames) {
71var err error
72
73procs.dirnames, err = procs.fd.Readdirnames(procsPageSize)
74if err == io.EOF {
75return nil, nil
76}
77
78if err != nil {
79return nil, err
80}
81
82procs.idx = 0
83}
84
85info, err := procs.readProc(procs.dirnames[procs.idx])
86procs.idx++
87
88// if err != nil, this process was killed before we were able to read /proc data
89if err == nil {
90return info, nil
91}
92}
93}
94
95//nolint:gocyclo
96func (procs *Processes) readProc(pidString string) (*machine.ProcessInfo, error) {
97pid, err := strconv.ParseInt(pidString, 10, 32)
98if err != nil {
99return nil, err
100}
101
102path := procs.RootPath + "/" + pidString + "/"
103
104executable, err := os.Readlink(path + "exe")
105if err != nil {
106return nil, err
107}
108
109if err = procs.readFileIntoBuf(path + "comm"); err != nil {
110return nil, err
111}
112
113command := string(bytes.TrimSpace(procs.buf))
114
115if err = procs.readFileIntoBuf(path + "cmdline"); err != nil {
116return nil, err
117}
118
119args := string(bytes.ReplaceAll(bytes.TrimRight(procs.buf, "\x00"), []byte{0}, []byte{' '}))
120
121if err = procs.readFileIntoBuf(path + "stat"); err != nil {
122return nil, err
123}
124
125rbracket := bytes.LastIndexByte(procs.buf, ')')
126if rbracket == -1 {
127return nil, errors.New("unexpected format")
128}
129
130fields := bytes.Fields(procs.buf[rbracket+2:])
131
132state := string(fields[0])
133
134ppid, err := strconv.ParseInt(string(fields[1]), 10, 32)
135if err != nil {
136return nil, err
137}
138
139numThreads, err := strconv.ParseInt(string(fields[17]), 10, 32)
140if err != nil {
141return nil, err
142}
143
144uTime, err := strconv.ParseUint(string(fields[11]), 10, 64)
145if err != nil {
146return nil, err
147}
148
149sTime, err := strconv.ParseUint(string(fields[12]), 10, 64)
150if err != nil {
151return nil, err
152}
153
154vSize, err := strconv.ParseUint(string(fields[20]), 10, 64)
155if err != nil {
156return nil, err
157}
158
159rss, err := strconv.ParseUint(string(fields[21]), 10, 64)
160if err != nil {
161return nil, err
162}
163
164return &machine.ProcessInfo{
165Pid: int32(pid),
166Ppid: int32(ppid),
167State: state,
168Threads: int32(numThreads),
169CpuTime: float64(uTime+sTime) / userHz,
170VirtualMemory: vSize,
171ResidentMemory: rss * uint64(procs.pagesize),
172Command: command,
173Executable: executable,
174Args: args,
175}, nil
176}
177
178func (procs *Processes) readFileIntoBuf(path string) error {
179f, err := os.Open(path)
180if err != nil {
181return err
182}
183
184defer f.Close() //nolint:errcheck
185
186procs.buf = procs.buf[:cap(procs.buf)]
187
188n, err := f.Read(procs.buf)
189if err != nil {
190return err
191}
192
193procs.buf = procs.buf[:n]
194
195return f.Close()
196}
197