@@ -3,83 +3,86 @@ package clistat
3
3
import (
4
4
"bufio"
5
5
"bytes"
6
- "os"
7
6
"strconv"
8
7
"time"
9
8
9
+ "github.com/spf13/afero"
10
10
"golang.org/x/xerrors"
11
11
"tailscale.com/types/ptr"
12
12
)
13
13
14
- const ()
14
+ const (
15
+ cgroupV2CPUMax = "/sys/fs/cgroup/cpu.max"
16
+ cgroupV2CPUStat = "/sys/fs/cgroup/cpu.stat"
17
+ )
15
18
16
19
// CGroupCPU returns the CPU usage of the container cgroup.
17
20
// On non-Linux platforms, this always returns nil.
18
21
func (s * Statter ) ContainerCPU () (* Result , error ) {
19
22
// Firstly, check if we are containerized.
20
- if ok , err := IsContainerized (); err != nil || ! ok {
23
+ if ok , err := IsContainerized (s . fs ); err != nil || ! ok {
21
24
return nil , nil //nolint: nilnil
22
25
}
23
26
24
- used1 , total , err := cgroupCPU ()
27
+ used1 , total , err := s . cgroupCPU ()
25
28
if err != nil {
26
29
return nil , xerrors .Errorf ("get cgroup CPU usage: %w" , err )
27
30
}
28
31
<- time .After (s .sampleInterval )
29
32
30
33
// total is unlikely to change. Use the first value.
31
- used2 , _ , err := cgroupCPU ()
34
+ used2 , _ , err := s . cgroupCPU ()
32
35
if err != nil {
33
36
return nil , xerrors .Errorf ("get cgroup CPU usage: %w" , err )
34
37
}
35
38
36
39
r := & Result {
37
40
Unit : "cores" ,
38
41
Used : (used2 - used1 ).Seconds (),
39
- Total : ptr .To (total .Seconds ()), // close enough to the truth
42
+ Total : ptr .To (total .Seconds () / s . sampleInterval . Seconds () ), // close enough to the truth
40
43
}
41
44
return r , nil
42
45
}
43
46
44
- func cgroupCPU () (used , total time.Duration , err error ) {
45
- if isCGroupV2 () {
46
- return cGroupV2CPU ()
47
+ func ( s * Statter ) cgroupCPU () (used , total time.Duration , err error ) {
48
+ if s . isCGroupV2 () {
49
+ return s . cGroupV2CPU ()
47
50
}
48
51
49
52
// Fall back to CGroupv1
50
- return cGroupV1CPU ()
53
+ return s . cGroupV1CPU ()
51
54
}
52
55
53
- func isCGroupV2 () bool {
56
+ func ( s * Statter ) isCGroupV2 () bool {
54
57
// Check for the presence of /sys/fs/cgroup/cpu.max
55
- _ , err := os .Stat ("/sys/fs/cgroup/cpu.max" )
58
+ _ , err := s . fs .Stat ("/sys/fs/cgroup/cpu.max" )
56
59
return err == nil
57
60
}
58
61
59
- func cGroupV2CPU () (used , total time.Duration , err error ) {
60
- total , err = cGroupv2CPUTotal ()
62
+ func ( s * Statter ) cGroupV2CPU () (used , total time.Duration , err error ) {
63
+ total , err = s . cGroupv2CPUTotal ()
61
64
if err != nil {
62
65
return 0 , 0 , xerrors .Errorf ("get cgroup v2 CPU cores: %w" , err )
63
66
}
64
67
65
- used , err = cGroupv2CPUUsed ()
68
+ used , err = s . cGroupv2CPUUsed ()
66
69
if err != nil {
67
70
return 0 , 0 , xerrors .Errorf ("get cgroup v2 CPU used: %w" , err )
68
71
}
69
72
70
73
return used , total , nil
71
74
}
72
75
73
- func cGroupv2CPUUsed () (used time.Duration , err error ) {
76
+ func ( s * Statter ) cGroupv2CPUUsed () (used time.Duration , err error ) {
74
77
var data []byte
75
- data , err = os .ReadFile ("/sys/fs/cgroup/cpu.stat" )
78
+ data , err = afero .ReadFile (s . fs , cgroupV2CPUStat )
76
79
if err != nil {
77
80
return 0 , xerrors .Errorf ("read /sys/fs/cgroup/cpu.stat: %w" , err )
78
81
}
79
82
80
- s := bufio .NewScanner (bytes .NewReader (data ))
81
- for s .Scan () {
82
- line := s .Bytes ()
83
+ bs := bufio .NewScanner (bytes .NewReader (data ))
84
+ for bs .Scan () {
85
+ line := bs .Bytes ()
83
86
if ! bytes .HasPrefix (line , []byte ("usage_usec " )) {
84
87
continue
85
88
}
@@ -100,10 +103,10 @@ func cGroupv2CPUUsed() (used time.Duration, err error) {
100
103
return 0 , xerrors .Errorf ("did not find expected usage_usec in /sys/fs/cgroup/cpu.stat" )
101
104
}
102
105
103
- func cGroupv2CPUTotal () (total time.Duration , err error ) {
106
+ func ( s * Statter ) cGroupv2CPUTotal () (total time.Duration , err error ) {
104
107
var data []byte
105
108
var quotaUs int
106
- data , err = os .ReadFile ("/sys/fs/cgroup/cpu.max" )
109
+ data , err = afero .ReadFile (s . fs , "/sys/fs/cgroup/cpu.max" )
107
110
if err != nil {
108
111
return 0 , xerrors .Errorf ("read /sys/fs/cgroup/cpu.max: %w" , err )
109
112
}
@@ -120,7 +123,7 @@ func cGroupv2CPUTotal() (total time.Duration, err error) {
120
123
}
121
124
122
125
if bytes .Equal (parts [0 ], []byte ("max" )) {
123
- quotaUs = nproc * int (time .Second .Microseconds ())
126
+ quotaUs = s . nproc * int (time .Second .Microseconds ())
124
127
} else {
125
128
quotaUs , err = strconv .Atoi (string (parts [0 ]))
126
129
if err != nil {
@@ -131,30 +134,30 @@ func cGroupv2CPUTotal() (total time.Duration, err error) {
131
134
return time .Duration (quotaUs ) * time .Microsecond , nil
132
135
}
133
136
134
- func cGroupV1CPU () (time.Duration , time.Duration , error ) {
137
+ func ( * Statter ) cGroupV1CPU () (time.Duration , time.Duration , error ) {
135
138
// TODO: implement
136
139
return 0 , 0 , nil
137
140
}
138
141
139
142
func (s * Statter ) ContainerMemory () (* Result , error ) {
140
- if ok , err := IsContainerized (); err != nil || ! ok {
143
+ if ok , err := IsContainerized (s . fs ); err != nil || ! ok {
141
144
return nil , nil
142
145
}
143
146
144
- if isCGroupV2 () {
145
- return cGroupv2Memory ()
147
+ if s . isCGroupV2 () {
148
+ return s . cGroupv2Memory ()
146
149
}
147
150
148
151
// Fall back to CGroupv1
149
- return cGroupv1Memory ()
152
+ return s . cGroupv1Memory ()
150
153
}
151
154
152
- func cGroupv2Memory () (* Result , error ) {
155
+ func ( * Statter ) cGroupv2Memory () (* Result , error ) {
153
156
// TODO implement
154
157
return nil , nil
155
158
}
156
159
157
- func cGroupv1Memory () (* Result , error ) {
160
+ func ( * Statter ) cGroupv1Memory () (* Result , error ) {
158
161
// TODO implement
159
162
return nil , nil
160
163
}
0 commit comments