add benchmark to forkexec / seccomp

This commit is contained in:
criyle 2019-07-22 22:35:05 -07:00
parent b8c180569f
commit 67c0621b7f
5 changed files with 225 additions and 1 deletions

View File

@ -53,6 +53,17 @@ Configuations:
- run_program/config.go: all configs toward running specs
Benchmarks for unshare:
- 1ms: fork, unshare pid / user / cgroup
- 50ms: unshare ipc / mount
- 100ms: unshare pid & user & cgroup & mount & pivot root
- 400ms: unshare net
- 800ms: unshare all
- 880ms: unshare all & pivot root
It seems unshare net or ipc takes time, maybe limits action by seccomp instead.
TODO:
1. Use Linux Control Groups to limit & acct CPU & memory (elimilate wait4 rusage)

188
forkexec/bench_test.go Normal file
View File

@ -0,0 +1,188 @@
package forkexec
import (
"io/ioutil"
"os"
"syscall"
"testing"
"github.com/criyle/go-judger/types/mount"
"golang.org/x/sys/unix"
)
// All testing data were from docker env on amd64 arch
const (
roBind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE | unix.MS_RDONLY
)
var (
defaultBind = []string{"/usr", "/lib", "/lib64", "/bin"}
)
// BenchmarkSimpleFork is about 0.70ms/op
func BenchmarkSimpleFork(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
benchmarkRun(r, b)
}
// BenchmarkUnsharePid is about 0.79ms/op
func BenchmarkUnsharePid(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = unix.CLONE_NEWPID
benchmarkRun(r, b)
}
// BenchmarkUnshareUser is about 0.84ms/op
func BenchmarkUnshareUser(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = unix.CLONE_NEWUSER
benchmarkRun(r, b)
}
// BenchmarkUnshareUts is about 0.78ms/op
func BenchmarkUnshareUts(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = unix.CLONE_NEWUTS
benchmarkRun(r, b)
}
// BenchmarkUnshareCgroup is about 0.85ms/op
func BenchmarkUnshareCgroup(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = unix.CLONE_NEWCGROUP
benchmarkRun(r, b)
}
// BenchmarkUnshareIpc is about 51ms/op
func BenchmarkUnshareIpc(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = unix.CLONE_NEWIPC
benchmarkRun(r, b)
}
// BenchmarkUnshareMount is about 51ms/op
func BenchmarkUnshareMount(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = unix.CLONE_NEWNS
benchmarkRun(r, b)
}
// BenchmarkUnshareNet is about 426ms/op
func BenchmarkUnshareNet(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = unix.CLONE_NEWNET
benchmarkRun(r, b)
}
// BenchmarkFastUnshareMountPivot is about 104ms/op
func BenchmarkFastUnshareMountPivot(b *testing.B) {
root, err := ioutil.TempDir("", "ns")
if err != nil {
b.Errorf("failed to create temp dir")
}
defer os.RemoveAll(root)
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP
r.PivotRoot = root
r.NoNewPrivs = true
r.DropCaps = true
r.Mounts = getMounts(defaultBind)
benchmarkRun(r, b)
}
// BenchmarkUnshareAll is about 800ms/op
func BenchmarkUnshareAll(b *testing.B) {
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = UnshareFlags
r.NoNewPrivs = true
r.DropCaps = true
benchmarkRun(r, b)
}
// BenchmarkUnshareMountPivot is about 880ms/op
func BenchmarkUnshareMountPivot(b *testing.B) {
root, err := ioutil.TempDir("", "ns")
if err != nil {
b.Errorf("failed to create temp dir")
}
defer os.RemoveAll(root)
r, f := getRunner(b)
defer f.Close()
r.UnshareFlags = UnshareFlags
r.PivotRoot = root
r.NoNewPrivs = true
r.DropCaps = true
r.Mounts = getMounts(defaultBind)
benchmarkRun(r, b)
}
func getRunner(b *testing.B) (*Runner, *os.File) {
f := openNull(b)
return &Runner{
Args: []string{"/bin/echo"},
Env: []string{"PATH=/bin"},
Files: []uintptr{f.Fd(), f.Fd(), f.Fd()},
WorkDir: "/bin",
}, f
}
func benchmarkRun(r *Runner, b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
pid, err := r.Start()
if err != nil {
b.Fail()
}
wait4(pid, b)
}
}
func getMounts(dirs []string) []*mount.Mount {
ret := make([]*mount.Mount, 0, len(dirs))
for _, d := range dirs {
if _, err := os.Stat(d); !os.IsNotExist(err) {
ret = append(ret, getMount(d))
}
}
return ret
}
func getMount(dir string) *mount.Mount {
return &mount.Mount{
Source: dir,
Target: dir[1:],
Flags: roBind,
}
}
func openNull(b *testing.B) *os.File {
f, err := os.OpenFile("/dev/null", os.O_RDWR, 0666)
if err != nil {
b.Errorf("Failed to open %v", err)
}
return f
}
func wait4(pid int, b *testing.B) {
var wstat syscall.WaitStatus
for {
syscall.Wait4(pid, &wstat, 0, nil)
if wstat.Exited() {
if s := wstat.ExitStatus(); s != 0 {
b.Errorf("Exited: %d", s)
}
break
}
}
}

2
go.mod
View File

@ -4,5 +4,5 @@ go 1.12
require (
github.com/seccomp/libseccomp-golang v0.9.1
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7
)

2
go.sum
View File

@ -2,3 +2,5 @@ github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46r
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -6,6 +6,20 @@ import (
libseccomp "github.com/seccomp/libseccomp-golang"
)
var (
defaultSyscallAllows = []string{
"read", "write", "readv", "writev", "close", "fstat", "lseek", "dup", "dup2", "dup3", "ioctl", "fcntl", "fadvise64",
"mmap", "mprotect", "munmap", "brk", "mremap", "msync", "mincore", "madvise",
"rt_sigaction", "rt_sigprocmask", "rt_sigreturn", "rt_sigpending", "sigaltstack",
"getcwd", "exit", "exit_group", "arch_prctl",
"gettimeofday", "getrlimit", "getrusage", "times", "time", "clock_gettime", "restart_syscall",
}
defaultSyscallTraces = []string{
"execve", "open", "openat", "unlink", "unlinkat", "readlink", "readlinkat", "lstat", "stat", "access", "faccessat",
}
)
func TestBuildFilter(t *testing.T) {
defaultAction := libseccomp.ActKill
filter, err := buildFilterMock(defaultAction)
@ -64,6 +78,15 @@ func TestAddActionFail(t *testing.T) {
}
}
// BenchmarkBuildDefaultFilter is about 0.2ms/op
func BenchmarkBuildDefaultFilter(b *testing.B) {
for i := 0; i < b.N; i++ {
filter, _ := BuildFilter(libseccomp.ActKill, libseccomp.ActTrace, defaultSyscallAllows, defaultSyscallTraces)
_, _ = FilterToBPF(filter)
filter.Release()
}
}
func buildFilterMock(d libseccomp.ScmpAction) (*libseccomp.ScmpFilter, error) {
defaultTrace := libseccomp.ActTrace
allow := []string{"fork"}