Skip to content

Commit a2d1c24

Browse files
authored
Merge pull request moby#3860 from tonistiigi/step-usage-monitoring
Add resource usage monitoring for build steps
2 parents 23d38a7 + 262b708 commit a2d1c24

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

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

0 commit comments

Comments
 (0)