1package cpu
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7	"math"
8	"strconv"
9	"strings"
10	"sync"
11	"time"
12
13	"github.com/shirou/gopsutil/internal/common"
14)
15
16// TimesStat contains the amounts of time the CPU has spent performing different
17// kinds of work. Time units are in USER_HZ or Jiffies (typically hundredths of
18// a second). It is based on linux /proc/stat file.
19type TimesStat struct {
20	CPU       string  `json:"cpu"`
21	User      float64 `json:"user"`
22	System    float64 `json:"system"`
23	Idle      float64 `json:"idle"`
24	Nice      float64 `json:"nice"`
25	Iowait    float64 `json:"iowait"`
26	Irq       float64 `json:"irq"`
27	Softirq   float64 `json:"softirq"`
28	Steal     float64 `json:"steal"`
29	Guest     float64 `json:"guest"`
30	GuestNice float64 `json:"guestNice"`
31}
32
33type InfoStat struct {
34	CPU        int32    `json:"cpu"`
35	VendorID   string   `json:"vendorId"`
36	Family     string   `json:"family"`
37	Model      string   `json:"model"`
38	Stepping   int32    `json:"stepping"`
39	PhysicalID string   `json:"physicalId"`
40	CoreID     string   `json:"coreId"`
41	Cores      int32    `json:"cores"`
42	ModelName  string   `json:"modelName"`
43	Mhz        float64  `json:"mhz"`
44	CacheSize  int32    `json:"cacheSize"`
45	Flags      []string `json:"flags"`
46	Microcode  string   `json:"microcode"`
47}
48
49type lastPercent struct {
50	sync.Mutex
51	lastCPUTimes    []TimesStat
52	lastPerCPUTimes []TimesStat
53}
54
55var lastCPUPercent lastPercent
56var invoke common.Invoker = common.Invoke{}
57
58func init() {
59	lastCPUPercent.Lock()
60	lastCPUPercent.lastCPUTimes, _ = Times(false)
61	lastCPUPercent.lastPerCPUTimes, _ = Times(true)
62	lastCPUPercent.Unlock()
63}
64
65// Counts returns the number of physical or logical cores in the system
66func Counts(logical bool) (int, error) {
67	return CountsWithContext(context.Background(), logical)
68}
69
70func (c TimesStat) String() string {
71	v := []string{
72		`"cpu":"` + c.CPU + `"`,
73		`"user":` + strconv.FormatFloat(c.User, 'f', 1, 64),
74		`"system":` + strconv.FormatFloat(c.System, 'f', 1, 64),
75		`"idle":` + strconv.FormatFloat(c.Idle, 'f', 1, 64),
76		`"nice":` + strconv.FormatFloat(c.Nice, 'f', 1, 64),
77		`"iowait":` + strconv.FormatFloat(c.Iowait, 'f', 1, 64),
78		`"irq":` + strconv.FormatFloat(c.Irq, 'f', 1, 64),
79		`"softirq":` + strconv.FormatFloat(c.Softirq, 'f', 1, 64),
80		`"steal":` + strconv.FormatFloat(c.Steal, 'f', 1, 64),
81		`"guest":` + strconv.FormatFloat(c.Guest, 'f', 1, 64),
82		`"guestNice":` + strconv.FormatFloat(c.GuestNice, 'f', 1, 64),
83	}
84
85	return `{` + strings.Join(v, ",") + `}`
86}
87
88// Total returns the total number of seconds in a CPUTimesStat
89func (c TimesStat) Total() float64 {
90	total := c.User + c.System + c.Nice + c.Iowait + c.Irq + c.Softirq +
91		c.Steal + c.Idle
92	return total
93}
94
95func (c InfoStat) String() string {
96	s, _ := json.Marshal(c)
97	return string(s)
98}
99
100func getAllBusy(t TimesStat) (float64, float64) {
101	busy := t.User + t.System + t.Nice + t.Iowait + t.Irq +
102		t.Softirq + t.Steal
103	return busy + t.Idle, busy
104}
105
106func calculateBusy(t1, t2 TimesStat) float64 {
107	t1All, t1Busy := getAllBusy(t1)
108	t2All, t2Busy := getAllBusy(t2)
109
110	if t2Busy <= t1Busy {
111		return 0
112	}
113	if t2All <= t1All {
114		return 100
115	}
116	return math.Min(100, math.Max(0, (t2Busy-t1Busy)/(t2All-t1All)*100))
117}
118
119func calculateAllBusy(t1, t2 []TimesStat) ([]float64, error) {
120	// Make sure the CPU measurements have the same length.
121	if len(t1) != len(t2) {
122		return nil, fmt.Errorf(
123			"received two CPU counts: %d != %d",
124			len(t1), len(t2),
125		)
126	}
127
128	ret := make([]float64, len(t1))
129	for i, t := range t2 {
130		ret[i] = calculateBusy(t1[i], t)
131	}
132	return ret, nil
133}
134
135// Percent calculates the percentage of cpu used either per CPU or combined.
136// If an interval of 0 is given it will compare the current cpu times against the last call.
137// Returns one value per cpu, or a single value if percpu is set to false.
138func Percent(interval time.Duration, percpu bool) ([]float64, error) {
139	return PercentWithContext(context.Background(), interval, percpu)
140}
141
142func PercentWithContext(ctx context.Context, interval time.Duration, percpu bool) ([]float64, error) {
143	if interval <= 0 {
144		return percentUsedFromLastCall(percpu)
145	}
146
147	// Get CPU usage at the start of the interval.
148	cpuTimes1, err := Times(percpu)
149	if err != nil {
150		return nil, err
151	}
152
153	time.Sleep(interval)
154
155	// And at the end of the interval.
156	cpuTimes2, err := Times(percpu)
157	if err != nil {
158		return nil, err
159	}
160
161	return calculateAllBusy(cpuTimes1, cpuTimes2)
162}
163
164func percentUsedFromLastCall(percpu bool) ([]float64, error) {
165	cpuTimes, err := Times(percpu)
166	if err != nil {
167		return nil, err
168	}
169	lastCPUPercent.Lock()
170	defer lastCPUPercent.Unlock()
171	var lastTimes []TimesStat
172	if percpu {
173		lastTimes = lastCPUPercent.lastPerCPUTimes
174		lastCPUPercent.lastPerCPUTimes = cpuTimes
175	} else {
176		lastTimes = lastCPUPercent.lastCPUTimes
177		lastCPUPercent.lastCPUTimes = cpuTimes
178	}
179
180	if lastTimes == nil {
181		return nil, fmt.Errorf("error getting times for cpu percent. lastTimes was nil")
182	}
183	return calculateAllBusy(lastTimes, cpuTimes)
184}
185