Skip to content

Commit 6e87e4b

Browse files
committed
resources: add build step resource tracking via cgroups
Signed-off-by: Tonis Tiigi <[email protected]>
1 parent 3c4e8f5 commit 6e87e4b

File tree

27 files changed

+1188
-69
lines changed

27 files changed

+1188
-69
lines changed

Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ RUN --mount=target=/root/.cache,type=cache \
216216

217217
FROM buildkit-export AS buildkit-linux
218218
COPY --link --from=binaries / /usr/bin/
219+
ENV BUILDKIT_SETUP_CGROUPV2_ROOT=1
219220
ENTRYPOINT ["buildkitd"]
220221

221222
FROM binaries AS buildkit-darwin
@@ -255,6 +256,7 @@ ENTRYPOINT ["/docker-entrypoint.sh"]
255256
# musl is needed to directly use the registry binary that is built on alpine
256257
ENV BUILDKIT_INTEGRATION_CONTAINERD_EXTRA="containerd-1.6=/opt/containerd-alt-16/bin"
257258
ENV BUILDKIT_INTEGRATION_SNAPSHOTTER=stargz
259+
ENV BUILDKIT_SETUP_CGROUPV2_ROOT=1
258260
ENV CGO_ENABLED=0
259261
ENV GOTESTSUM_FORMAT=standard-verbose
260262
COPY --link --from=gotestsum /out/gotestsum /usr/bin/

executor/containerdexecutor/executor.go

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/docker/docker/pkg/idtools"
2222
"github.com/moby/buildkit/executor"
2323
"github.com/moby/buildkit/executor/oci"
24+
resourcestypes "github.com/moby/buildkit/executor/resources/types"
2425
gatewayapi "github.com/moby/buildkit/frontend/gateway/pb"
2526
"github.com/moby/buildkit/identity"
2627
"github.com/moby/buildkit/snapshot"
@@ -78,7 +79,7 @@ func New(client *containerd.Client, root, cgroup string, networkProviders map[pb
7879
}
7980
}
8081

81-
func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.Mount, mounts []executor.Mount, process executor.ProcessInfo, started chan<- struct{}) (err error) {
82+
func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.Mount, mounts []executor.Mount, process executor.ProcessInfo, started chan<- struct{}) (rec resourcestypes.Recorder, err error) {
8283
if id == "" {
8384
id = identity.NewID()
8485
}
@@ -105,25 +106,25 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
105106

106107
resolvConf, err := oci.GetResolvConf(ctx, w.root, nil, w.dnsConfig)
107108
if err != nil {
108-
return err
109+
return nil, err
109110
}
110111

111112
hostsFile, clean, err := oci.GetHostsFile(ctx, w.root, meta.ExtraHosts, nil, meta.Hostname)
112113
if err != nil {
113-
return err
114+
return nil, err
114115
}
115116
if clean != nil {
116117
defer clean()
117118
}
118119

119120
mountable, err := root.Src.Mount(ctx, false)
120121
if err != nil {
121-
return err
122+
return nil, err
122123
}
123124

124125
rootMounts, release, err := mountable.Mount()
125126
if err != nil {
126-
return err
127+
return nil, err
127128
}
128129
if release != nil {
129130
defer release()
@@ -132,14 +133,14 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
132133
lm := snapshot.LocalMounterWithMounts(rootMounts)
133134
rootfsPath, err := lm.Mount()
134135
if err != nil {
135-
return err
136+
return nil, err
136137
}
137138
defer lm.Unmount()
138139
defer executor.MountStubsCleaner(ctx, rootfsPath, mounts, meta.RemoveMountStubsRecursive)()
139140

140141
uid, gid, sgids, err := oci.GetUser(rootfsPath, meta.User)
141142
if err != nil {
142-
return err
143+
return nil, err
143144
}
144145

145146
identity := idtools.Identity{
@@ -149,21 +150,21 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
149150

150151
newp, err := fs.RootPath(rootfsPath, meta.Cwd)
151152
if err != nil {
152-
return errors.Wrapf(err, "working dir %s points to invalid target", newp)
153+
return nil, errors.Wrapf(err, "working dir %s points to invalid target", newp)
153154
}
154155
if _, err := os.Stat(newp); err != nil {
155156
if err := idtools.MkdirAllAndChown(newp, 0755, identity); err != nil {
156-
return errors.Wrapf(err, "failed to create working directory %s", newp)
157+
return nil, errors.Wrapf(err, "failed to create working directory %s", newp)
157158
}
158159
}
159160

160161
provider, ok := w.networkProviders[meta.NetMode]
161162
if !ok {
162-
return errors.Errorf("unknown network mode %s", meta.NetMode)
163+
return nil, errors.Errorf("unknown network mode %s", meta.NetMode)
163164
}
164165
namespace, err := provider.New(ctx, meta.Hostname)
165166
if err != nil {
166-
return err
167+
return nil, err
167168
}
168169
defer namespace.Close()
169170

@@ -179,21 +180,21 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
179180
processMode := oci.ProcessSandbox // FIXME(AkihiroSuda)
180181
spec, cleanup, err := oci.GenerateSpec(ctx, meta, mounts, id, resolvConf, hostsFile, namespace, w.cgroupParent, processMode, nil, w.apparmorProfile, w.selinux, w.traceSocket, opts...)
181182
if err != nil {
182-
return err
183+
return nil, err
183184
}
184185
defer cleanup()
185186
spec.Process.Terminal = meta.Tty
186187
if w.rootless {
187188
if err := rootlessspecconv.ToRootless(spec); err != nil {
188-
return err
189+
return nil, err
189190
}
190191
}
191192

192193
container, err := w.client.NewContainer(ctx, id,
193194
containerd.WithSpec(spec),
194195
)
195196
if err != nil {
196-
return err
197+
return nil, err
197198
}
198199

199200
defer func() {
@@ -214,7 +215,7 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
214215
Options: []string{"rbind"},
215216
}}))
216217
if err != nil {
217-
return err
218+
return nil, err
218219
}
219220

220221
defer func() {
@@ -225,7 +226,7 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
225226

226227
if nn, ok := namespace.(OnCreateRuntimer); ok {
227228
if err := nn.OnCreateRuntime(task.Pid()); err != nil {
228-
return err
229+
return nil, err
229230
}
230231
}
231232

@@ -238,7 +239,7 @@ func (w *containerdExecutor) Run(ctx context.Context, id string, root executor.M
238239
}
239240
})
240241
})
241-
return err
242+
return nil, err
242243
}
243244

244245
func (w *containerdExecutor) Exec(ctx context.Context, id string, process executor.ProcessInfo) (err error) {

executor/executor.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net"
77
"syscall"
88

9+
resourcestypes "github.com/moby/buildkit/executor/resources/types"
910
"github.com/moby/buildkit/snapshot"
1011
"github.com/moby/buildkit/solver/pb"
1112
)
@@ -55,7 +56,7 @@ type Executor interface {
5556
// Run will start a container for the given process with rootfs, mounts.
5657
// `id` is an optional name for the container so it can be referenced later via Exec.
5758
// `started` is an optional channel that will be closed when the container setup completes and has started running.
58-
Run(ctx context.Context, id string, rootfs Mount, mounts []Mount, process ProcessInfo, started chan<- struct{}) error
59+
Run(ctx context.Context, id string, rootfs Mount, mounts []Mount, process ProcessInfo, started chan<- struct{}) (resourcestypes.Recorder, error)
5960
// Exec will start a process in container matching `id`. An error will be returned
6061
// if the container failed to start (via Run) or has exited before Exec is called.
6162
Exec(ctx context.Context, id string, process ProcessInfo) error

executor/resources/cpu.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package resources
2+
3+
import (
4+
"bufio"
5+
"os"
6+
"path/filepath"
7+
"strconv"
8+
"strings"
9+
10+
"github.com/moby/buildkit/executor/resources/types"
11+
)
12+
13+
const (
14+
cpuUsageUsec = "usage_usec"
15+
cpuUserUsec = "user_usec"
16+
cpuSystemUsec = "system_usec"
17+
cpuNrPeriods = "nr_periods"
18+
cpuNrThrottled = "nr_throttled"
19+
cpuThrottledUsec = "throttled_usec"
20+
)
21+
22+
func getCgroupCPUStat(cgroupPath string) (*types.CPUStat, error) {
23+
cpuStat := &types.CPUStat{}
24+
25+
// Read cpu.stat file
26+
cpuStatFile, err := os.Open(filepath.Join(cgroupPath, "cpu.stat"))
27+
if err != nil {
28+
return nil, err
29+
}
30+
defer cpuStatFile.Close()
31+
32+
scanner := bufio.NewScanner(cpuStatFile)
33+
for scanner.Scan() {
34+
line := scanner.Text()
35+
fields := strings.Fields(line)
36+
37+
if len(fields) < 2 {
38+
continue
39+
}
40+
41+
key := fields[0]
42+
value, err := strconv.ParseUint(fields[1], 10, 64)
43+
if err != nil {
44+
continue
45+
}
46+
47+
switch key {
48+
case cpuUsageUsec:
49+
cpuStat.UsageNanos = uint64Ptr(value * 1000)
50+
case cpuUserUsec:
51+
cpuStat.UserNanos = uint64Ptr(value * 1000)
52+
case cpuSystemUsec:
53+
cpuStat.SystemNanos = uint64Ptr(value * 1000)
54+
case cpuNrPeriods:
55+
cpuStat.NrPeriods = new(uint32)
56+
*cpuStat.NrPeriods = uint32(value)
57+
case cpuNrThrottled:
58+
cpuStat.NrThrottled = new(uint32)
59+
*cpuStat.NrThrottled = uint32(value)
60+
case cpuThrottledUsec:
61+
cpuStat.ThrottledNanos = uint64Ptr(value * 1000)
62+
}
63+
}
64+
65+
if err := scanner.Err(); err != nil {
66+
return nil, err
67+
}
68+
69+
// Read cpu.pressure file
70+
pressure, err := parsePressureFile(filepath.Join(cgroupPath, "cpu.pressure"))
71+
if err == nil {
72+
cpuStat.Pressure = pressure
73+
}
74+
75+
return cpuStat, nil
76+
}
77+
func parsePressureFile(filename string) (*types.Pressure, error) {
78+
content, err := os.ReadFile(filename)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
lines := strings.Split(string(content), "\n")
84+
85+
pressure := &types.Pressure{}
86+
for _, line := range lines {
87+
// Skip empty lines
88+
if len(strings.TrimSpace(line)) == 0 {
89+
continue
90+
}
91+
92+
fields := strings.Fields(line)
93+
prefix := fields[0]
94+
pressureValues := &types.PressureValues{}
95+
96+
for i := 1; i < len(fields); i++ {
97+
keyValue := strings.Split(fields[i], "=")
98+
key := keyValue[0]
99+
valueStr := keyValue[1]
100+
101+
if key == "total" {
102+
totalValue, err := strconv.ParseUint(valueStr, 10, 64)
103+
if err != nil {
104+
return nil, err
105+
}
106+
pressureValues.Total = &totalValue
107+
} else {
108+
value, err := strconv.ParseFloat(valueStr, 64)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
switch key {
114+
case "avg10":
115+
pressureValues.Avg10 = &value
116+
case "avg60":
117+
pressureValues.Avg60 = &value
118+
case "avg300":
119+
pressureValues.Avg300 = &value
120+
}
121+
}
122+
}
123+
124+
switch prefix {
125+
case "some":
126+
pressure.Some = pressureValues
127+
case "full":
128+
pressure.Full = pressureValues
129+
}
130+
}
131+
132+
return pressure, nil
133+
}

0 commit comments

Comments
 (0)