From 43eac6b3d4ab654c7283be9865203168ea3830f8 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 8 Oct 2024 16:51:24 -0700 Subject: [PATCH 1/2] libct/int: use testing.TB for utils ...so that they can be used for benchmarks, too. Signed-off-by: Kir Kolyshkin --- libcontainer/integration/template_test.go | 2 +- libcontainer/integration/utils_test.go | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libcontainer/integration/template_test.go b/libcontainer/integration/template_test.go index 473f601ed49..38024c571d3 100644 --- a/libcontainer/integration/template_test.go +++ b/libcontainer/integration/template_test.go @@ -32,7 +32,7 @@ type tParam struct { // and the default setup for devices. // // If p is nil, a default container is created. -func newTemplateConfig(t *testing.T, p *tParam) *configs.Config { +func newTemplateConfig(t testing.TB, p *tParam) *configs.Config { var allowedDevices []*devices.Rule for _, device := range specconv.AllowedDevices { allowedDevices = append(allowedDevices, &device.Rule) diff --git a/libcontainer/integration/utils_test.go b/libcontainer/integration/utils_test.go index 780288ad02b..9b4121bc5d6 100644 --- a/libcontainer/integration/utils_test.go +++ b/libcontainer/integration/utils_test.go @@ -85,7 +85,7 @@ func ok(t testing.TB, err error) { } } -func waitProcess(p *libcontainer.Process, t *testing.T) { +func waitProcess(p *libcontainer.Process, t testing.TB) { t.Helper() status, err := p.Wait() if err != nil { @@ -99,7 +99,7 @@ func waitProcess(p *libcontainer.Process, t *testing.T) { // newRootfs creates a new tmp directory and copies the busybox root // filesystem to it. -func newRootfs(t *testing.T) string { +func newRootfs(t testing.TB) string { t.Helper() dir := t.TempDir() if err := copyBusybox(dir); err != nil { @@ -165,7 +165,7 @@ func copyBusybox(dest string) error { return nil } -func newContainer(t *testing.T, config *configs.Config) (*libcontainer.Container, error) { +func newContainer(t testing.TB, config *configs.Config) (*libcontainer.Container, error) { name := strings.ReplaceAll(t.Name(), "/", "_") + strconv.FormatInt(-int64(time.Now().Nanosecond()), 35) root := t.TempDir() @@ -176,7 +176,7 @@ func newContainer(t *testing.T, config *configs.Config) (*libcontainer.Container // // buffers are returned containing the STDOUT and STDERR output for the run // along with the exit code and any go error -func runContainer(t *testing.T, config *configs.Config, args ...string) (buffers *stdBuffers, exitCode int, err error) { +func runContainer(t testing.TB, config *configs.Config, args ...string) (buffers *stdBuffers, exitCode int, err error) { container, err := newContainer(t, config) if err != nil { return nil, -1, err @@ -214,7 +214,7 @@ func runContainer(t *testing.T, config *configs.Config, args ...string) (buffers // runContainerOk is a wrapper for runContainer, simplifying its use for cases // when the run is expected to succeed and return exit code of 0. -func runContainerOk(t *testing.T, config *configs.Config, args ...string) *stdBuffers { +func runContainerOk(t testing.TB, config *configs.Config, args ...string) *stdBuffers { buffers, exitCode, err := runContainer(t, config, args...) t.Helper() From e8aa2e8fd72f4e8bc4ba23daa773a1f56736b2c4 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Tue, 8 Oct 2024 17:20:14 -0700 Subject: [PATCH 2/2] libct/int: add exec benchmark This is a benchmark which checks how fast we can execute /bin/true inside a container. Results from my machine are below. As you can see, in default setup about 70% of exec time is spent for CVE-2019-5736 (copying runc binary), and using either RUNC_DMZ=true or memfd-bind helps a lot. This can also be used for profiling (using -test.cpuprofile option). === Default setup === [kir@kir-tp1 integration]$ sudo ./integration.test -test.run xxx -test.v -test.benchtime 5s -test.count 5 -test.bench . . goos: linux goarch: amd64 pkg: github.com/opencontainers/runc/libcontainer/integration cpu: 12th Gen Intel(R) Core(TM) i7-12800H BenchmarkExecTrue BenchmarkExecTrue-20 327 24475677 ns/op BenchmarkExecTrue-20 244 25242718 ns/op BenchmarkExecTrue-20 232 26187174 ns/op BenchmarkExecTrue-20 237 26780030 ns/op BenchmarkExecTrue-20 318 18487219 ns/op PASS === With DMZ enabled === [kir@kir-tp1 integration]$ sudo -E RUNC_DMZ=true ./integration.test -test.run xxx -test.v -test.benchtime 5s -test.count 5 -test.bench . . goos: linux goarch: amd64 pkg: github.com/opencontainers/runc/libcontainer/integration cpu: 12th Gen Intel(R) Core(TM) i7-12800H BenchmarkExecTrue BenchmarkExecTrue-20 694 8263744 ns/op BenchmarkExecTrue-20 778 8483228 ns/op BenchmarkExecTrue-20 784 8456018 ns/op BenchmarkExecTrue-20 732 8160239 ns/op BenchmarkExecTrue-20 769 8236972 ns/op PASS === With memfd-bind === [kir@kir-tp1 integration]$ sudo systemctl start memfd-bind@$(systemd-escape -p $PWD/integration.test) [kir@kir-tp1 integration]$ sudo ./integration.test -test.run xxx -test.v -test.benchtime 5s -test.count 5 -test.bench . . goos: linux goarch: amd64 pkg: github.com/opencontainers/runc/libcontainer/integration cpu: 12th Gen Intel(R) Core(TM) i7-12800H BenchmarkExecTrue BenchmarkExecTrue-20 800 7538839 ns/op BenchmarkExecTrue-20 717 7424755 ns/op BenchmarkExecTrue-20 848 7747787 ns/op BenchmarkExecTrue-20 800 7668740 ns/op BenchmarkExecTrue-20 751 7304373 ns/op PASS Signed-off-by: Kir Kolyshkin --- libcontainer/integration/bench_test.go | 51 ++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 libcontainer/integration/bench_test.go diff --git a/libcontainer/integration/bench_test.go b/libcontainer/integration/bench_test.go new file mode 100644 index 00000000000..da95b20b0b7 --- /dev/null +++ b/libcontainer/integration/bench_test.go @@ -0,0 +1,51 @@ +package integration + +import ( + "os" + "testing" + + "github.com/opencontainers/runc/libcontainer" +) + +func BenchmarkExecTrue(b *testing.B) { + config := newTemplateConfig(b, nil) + container, err := newContainer(b, config) + ok(b, err) + defer destroyContainer(container) + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(b, err) + process := &libcontainer.Process{ + Cwd: "/", + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + Init: true, + } + err = container.Run(process) + _ = stdinR.Close() + defer func() { + _ = stdinW.Close() + if _, err := process.Wait(); err != nil { + b.Log(err) + } + }() + ok(b, err) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + exec := &libcontainer.Process{ + Cwd: "/", + Args: []string{"/bin/true"}, + Env: standardEnvironment, + LogLevel: "0", // Minimize forwardChildLogs involvement. + } + err := container.Run(exec) + if err != nil { + b.Fatal("exec failed:", err) + } + waitProcess(exec, b) + } + b.StopTimer() +}