17
17
package container
18
18
19
19
import (
20
+ "bufio"
20
21
"errors"
21
22
"fmt"
23
+ "io"
22
24
"net"
25
+ "os"
26
+ "strconv"
23
27
"strings"
24
28
"time"
25
29
@@ -33,8 +37,17 @@ import (
33
37
"github.com/containerd/nerdctl/v2/pkg/statsutil"
34
38
)
35
39
40
+ const (
41
+ // The value comes from `C.sysconf(C._SC_CLK_TCK)`, and
42
+ // on Linux it's a constant which is safe to be hard coded,
43
+ // so we can avoid using cgo here. For details, see:
44
+ // https://github.com/containerd/cgroups/pull/12
45
+ clockTicksPerSecond = 100
46
+ nanoSecondsPerSecond = 1e9
47
+ )
48
+
36
49
//nolint:nakedret
37
- func setContainerStatsAndRenderStatsEntry (previousStats * statsutil.ContainerStats , firstSet bool , anydata interface {}, pid int , interfaces []native.NetInterface ) (statsEntry statsutil.StatsEntry , err error ) {
50
+ func setContainerStatsAndRenderStatsEntry (previousStats * statsutil.ContainerStats , firstSet bool , anydata interface {}, pid int , interfaces []native.NetInterface , systemInfo statsutil. SystemInfo ) (statsEntry statsutil.StatsEntry , err error ) {
38
51
39
52
var (
40
53
data * v1.Metrics
@@ -96,10 +109,10 @@ func setContainerStatsAndRenderStatsEntry(previousStats *statsutil.ContainerStat
96
109
97
110
if data != nil {
98
111
if ! firstSet {
99
- statsEntry , err = statsutil .SetCgroupStatsFields (previousStats , data , nlinks )
112
+ statsEntry , err = statsutil .SetCgroupStatsFields (previousStats , data , nlinks , systemInfo )
100
113
}
101
114
previousStats .CgroupCPU = data .CPU .Usage .Total
102
- previousStats .CgroupSystem = data . CPU . Usage . Kernel
115
+ previousStats .CgroupSystem = systemInfo . SystemUsage
103
116
if err != nil {
104
117
return
105
118
}
@@ -117,3 +130,59 @@ func setContainerStatsAndRenderStatsEntry(previousStats *statsutil.ContainerStat
117
130
118
131
return
119
132
}
133
+
134
+ // getSystemCPUUsage reads the system's CPU usage from /proc/stat and returns
135
+ // the total CPU usage in nanoseconds and the number of CPUs.
136
+ func getSystemCPUUsage () (cpuUsage uint64 , cpuNum uint32 , _ error ) {
137
+ f , err := os .Open ("/proc/stat" )
138
+ if err != nil {
139
+ return 0 , 0 , err
140
+ }
141
+ defer f .Close ()
142
+
143
+ return readSystemCPUUsage (f )
144
+ }
145
+
146
+ // readSystemCPUUsage parses CPU usage information from a reader providing
147
+ // /proc/stat format data. It returns the total CPU usage in nanoseconds
148
+ // and the number of CPUs. More:
149
+ // https://github.com/moby/moby/blob/26db31fdab628a2345ed8f179e575099384166a9/daemon/stats_unix.go#L327-L368
150
+ func readSystemCPUUsage (r io.Reader ) (cpuUsage uint64 , cpuNum uint32 , _ error ) {
151
+ rdr := bufio .NewReaderSize (r , 1024 )
152
+
153
+ for {
154
+ data , isPartial , err := rdr .ReadLine ()
155
+
156
+ if err != nil {
157
+ return 0 , 0 , fmt .Errorf ("error scanning /proc/stat file: %w" , err )
158
+ }
159
+ // Assume all cpu* records are at the start of the file, like glibc:
160
+ // https://github.com/bminor/glibc/blob/5d00c201b9a2da768a79ea8d5311f257871c0b43/sysdeps/unix/sysv/linux/getsysstats.c#L108-L135
161
+ if isPartial || len (data ) < 4 {
162
+ break
163
+ }
164
+ line := string (data )
165
+ if line [:3 ] != "cpu" {
166
+ break
167
+ }
168
+ if line [3 ] == ' ' {
169
+ parts := strings .Fields (line )
170
+ if len (parts ) < 8 {
171
+ return 0 , 0 , fmt .Errorf ("invalid number of cpu fields" )
172
+ }
173
+ var totalClockTicks uint64
174
+ for _ , i := range parts [1 :8 ] {
175
+ v , err := strconv .ParseUint (i , 10 , 64 )
176
+ if err != nil {
177
+ return 0 , 0 , fmt .Errorf ("unable to convert value %s to int: %w" , i , err )
178
+ }
179
+ totalClockTicks += v
180
+ }
181
+ cpuUsage = (totalClockTicks * nanoSecondsPerSecond ) / clockTicksPerSecond
182
+ }
183
+ if '0' <= line [3 ] && line [3 ] <= '9' {
184
+ cpuNum ++
185
+ }
186
+ }
187
+ return cpuUsage , cpuNum , nil
188
+ }
0 commit comments