1package process
2
3import (
4	"context"
5	"encoding/json"
6	"errors"
7	"runtime"
8	"time"
9
10	"github.com/shirou/gopsutil/cpu"
11	"github.com/shirou/gopsutil/internal/common"
12	"github.com/shirou/gopsutil/mem"
13)
14
15var (
16	invoke          common.Invoker = common.Invoke{}
17	ErrorNoChildren                = errors.New("process does not have children")
18)
19
20type Process struct {
21	Pid            int32 `json:"pid"`
22	name           string
23	status         string
24	parent         int32
25	numCtxSwitches *NumCtxSwitchesStat
26	uids           []int32
27	gids           []int32
28	numThreads     int32
29	memInfo        *MemoryInfoStat
30	sigInfo        *SignalInfoStat
31
32	lastCPUTimes *cpu.TimesStat
33	lastCPUTime  time.Time
34
35	tgid int32
36}
37
38type OpenFilesStat struct {
39	Path string `json:"path"`
40	Fd   uint64 `json:"fd"`
41}
42
43type MemoryInfoStat struct {
44	RSS    uint64 `json:"rss"`    // bytes
45	VMS    uint64 `json:"vms"`    // bytes
46	Data   uint64 `json:"data"`   // bytes
47	Stack  uint64 `json:"stack"`  // bytes
48	Locked uint64 `json:"locked"` // bytes
49	Swap   uint64 `json:"swap"`   // bytes
50}
51
52type SignalInfoStat struct {
53	PendingProcess uint64 `json:"pending_process"`
54	PendingThread  uint64 `json:"pending_thread"`
55	Blocked        uint64 `json:"blocked"`
56	Ignored        uint64 `json:"ignored"`
57	Caught         uint64 `json:"caught"`
58}
59
60type RlimitStat struct {
61	Resource int32  `json:"resource"`
62	Soft     int32  `json:"soft"` //TODO too small. needs to be uint64
63	Hard     int32  `json:"hard"` //TODO too small. needs to be uint64
64	Used     uint64 `json:"used"`
65}
66
67type IOCountersStat struct {
68	ReadCount  uint64 `json:"readCount"`
69	WriteCount uint64 `json:"writeCount"`
70	ReadBytes  uint64 `json:"readBytes"`
71	WriteBytes uint64 `json:"writeBytes"`
72}
73
74type NumCtxSwitchesStat struct {
75	Voluntary   int64 `json:"voluntary"`
76	Involuntary int64 `json:"involuntary"`
77}
78
79// Resource limit constants are from /usr/include/x86_64-linux-gnu/bits/resource.h
80// from libc6-dev package in Ubuntu 16.10
81const (
82	RLIMIT_CPU        int32 = 0
83	RLIMIT_FSIZE      int32 = 1
84	RLIMIT_DATA       int32 = 2
85	RLIMIT_STACK      int32 = 3
86	RLIMIT_CORE       int32 = 4
87	RLIMIT_RSS        int32 = 5
88	RLIMIT_NPROC      int32 = 6
89	RLIMIT_NOFILE     int32 = 7
90	RLIMIT_MEMLOCK    int32 = 8
91	RLIMIT_AS         int32 = 9
92	RLIMIT_LOCKS      int32 = 10
93	RLIMIT_SIGPENDING int32 = 11
94	RLIMIT_MSGQUEUE   int32 = 12
95	RLIMIT_NICE       int32 = 13
96	RLIMIT_RTPRIO     int32 = 14
97	RLIMIT_RTTIME     int32 = 15
98)
99
100func (p Process) String() string {
101	s, _ := json.Marshal(p)
102	return string(s)
103}
104
105func (o OpenFilesStat) String() string {
106	s, _ := json.Marshal(o)
107	return string(s)
108}
109
110func (m MemoryInfoStat) String() string {
111	s, _ := json.Marshal(m)
112	return string(s)
113}
114
115func (r RlimitStat) String() string {
116	s, _ := json.Marshal(r)
117	return string(s)
118}
119
120func (i IOCountersStat) String() string {
121	s, _ := json.Marshal(i)
122	return string(s)
123}
124
125func (p NumCtxSwitchesStat) String() string {
126	s, _ := json.Marshal(p)
127	return string(s)
128}
129
130func PidExists(pid int32) (bool, error) {
131	return PidExistsWithContext(context.Background(), pid)
132}
133
134func PidExistsWithContext(ctx context.Context, pid int32) (bool, error) {
135	pids, err := Pids()
136	if err != nil {
137		return false, err
138	}
139
140	for _, i := range pids {
141		if i == pid {
142			return true, err
143		}
144	}
145
146	return false, err
147}
148
149// If interval is 0, return difference from last call(non-blocking).
150// If interval > 0, wait interval sec and return diffrence between start and end.
151func (p *Process) Percent(interval time.Duration) (float64, error) {
152	return p.PercentWithContext(context.Background(), interval)
153}
154
155func (p *Process) PercentWithContext(ctx context.Context, interval time.Duration) (float64, error) {
156	cpuTimes, err := p.Times()
157	if err != nil {
158		return 0, err
159	}
160	now := time.Now()
161
162	if interval > 0 {
163		p.lastCPUTimes = cpuTimes
164		p.lastCPUTime = now
165		time.Sleep(interval)
166		cpuTimes, err = p.Times()
167		now = time.Now()
168		if err != nil {
169			return 0, err
170		}
171	} else {
172		if p.lastCPUTimes == nil {
173			// invoked first time
174			p.lastCPUTimes = cpuTimes
175			p.lastCPUTime = now
176			return 0, nil
177		}
178	}
179
180	numcpu := runtime.NumCPU()
181	delta := (now.Sub(p.lastCPUTime).Seconds()) * float64(numcpu)
182	ret := calculatePercent(p.lastCPUTimes, cpuTimes, delta, numcpu)
183	p.lastCPUTimes = cpuTimes
184	p.lastCPUTime = now
185	return ret, nil
186}
187
188func calculatePercent(t1, t2 *cpu.TimesStat, delta float64, numcpu int) float64 {
189	if delta == 0 {
190		return 0
191	}
192	delta_proc := t2.Total() - t1.Total()
193	overall_percent := ((delta_proc / delta) * 100) * float64(numcpu)
194	return overall_percent
195}
196
197// MemoryPercent returns how many percent of the total RAM this process uses
198func (p *Process) MemoryPercent() (float32, error) {
199	return p.MemoryPercentWithContext(context.Background())
200}
201
202func (p *Process) MemoryPercentWithContext(ctx context.Context) (float32, error) {
203	machineMemory, err := mem.VirtualMemory()
204	if err != nil {
205		return 0, err
206	}
207	total := machineMemory.Total
208
209	processMemory, err := p.MemoryInfo()
210	if err != nil {
211		return 0, err
212	}
213	used := processMemory.RSS
214
215	return (100 * float32(used) / float32(total)), nil
216}
217
218// CPU_Percent returns how many percent of the CPU time this process uses
219func (p *Process) CPUPercent() (float64, error) {
220	return p.CPUPercentWithContext(context.Background())
221}
222
223func (p *Process) CPUPercentWithContext(ctx context.Context) (float64, error) {
224	crt_time, err := p.CreateTime()
225	if err != nil {
226		return 0, err
227	}
228
229	cput, err := p.Times()
230	if err != nil {
231		return 0, err
232	}
233
234	created := time.Unix(0, crt_time*int64(time.Millisecond))
235	totalTime := time.Since(created).Seconds()
236	if totalTime <= 0 {
237		return 0, nil
238	}
239
240	return 100 * cput.Total() / totalTime, nil
241}
242