forked from livekit/protocol
/
cpu_linux.go
133 lines (110 loc) · 2.46 KB
/
cpu_linux.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//go:build linux
package utils
import (
"errors"
"os"
"regexp"
"runtime"
"strconv"
"time"
)
var (
usageRegex = regexp.MustCompile("usage_usec ([0-9]+)")
)
const (
cpuStatsPathV1 = "/sys/fs/cgroup/cpu,cpuacct/cpuacct.usage"
cpuStatsPathV2 = "/sys/fs/cgroup/cpu.stat"
)
type platformCPUMonitor struct {
lastSampleTime int64
lastTotalCPUTime int64
cpuTimeFunc func() (int64, error)
}
func newPlatformCPUMonitor() (*platformCPUMonitor, error) {
// probe for the cgroup version
var cpuTimeFunc func() (int64, error)
for k, v := range map[string]func() (int64, error){
cpuStatsPathV1: getTotalCPUTimeV1,
cpuStatsPathV2: getTotalCPUTimeV2,
} {
e, err := fileExists(k)
if err != nil {
return nil, err
}
if e {
cpuTimeFunc = v
break
}
}
if cpuTimeFunc == nil {
return nil, errors.New("failed reading cpu stats file")
}
cpu, err := cpuTimeFunc()
if err != nil {
return nil, err
}
return &platformCPUMonitor{
lastSampleTime: time.Now().UnixNano(),
lastTotalCPUTime: cpu,
cpuTimeFunc: cpuTimeFunc,
}, nil
}
func (p *platformCPUMonitor) getCPUIdle() (float64, error) {
next, err := p.cpuTimeFunc()
if err != nil {
return 0, err
}
t := time.Now().UnixNano()
duration := t - p.lastSampleTime
cpuTime := next - p.lastTotalCPUTime
busyRatio := float64(cpuTime) / float64(duration)
idleRatio := float64(runtime.NumCPU()) - busyRatio
// Clamp the value as we do not get all the timestamps at the same time
if idleRatio > float64(runtime.NumCPU()) {
idleRatio = float64(runtime.NumCPU())
} else if idleRatio < 0 {
idleRatio = 0
}
p.lastSampleTime = t
p.lastTotalCPUTime = next
return idleRatio, nil
}
func getTotalCPUTimeV1() (int64, error) {
b, err := os.ReadFile(cpuStatsPathV1)
if err != nil {
return 0, err
}
// Skip the trailing EOL
i, err := strconv.ParseInt(string(b[:len(b)-1]), 10, 64)
if err != nil {
return 0, err
}
return i, nil
}
func getTotalCPUTimeV2() (int64, error) {
b, err := os.ReadFile(cpuStatsPathV2)
if err != nil {
return 0, err
}
m := usageRegex.FindSubmatch(b)
if len(m) <= 1 {
return 0, errors.New("could not parse cpu stats")
}
i, err := strconv.ParseInt(string(m[1]), 10, 64)
if err != nil {
return 0, err
}
// Caller expexts time in ns
return i * 1000, nil
}
func fileExists(path string) (bool, error) {
_, err := os.Lstat(path)
switch {
case err == nil:
return true, nil
case errors.Is(err, os.ErrNotExist):
return false, nil
default:
return false, err
}
}