github.com/criyle/go-sandbox@v0.10.3/pkg/forkexec/bench_linux_test.go (about) 1 package forkexec 2 3 import ( 4 "os" 5 "syscall" 6 "testing" 7 8 "github.com/criyle/go-sandbox/pkg/mount" 9 "golang.org/x/sys/unix" 10 ) 11 12 // All testing data were from docker env on amd64 arch 13 14 const ( 15 roBind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE | unix.MS_RDONLY 16 ) 17 18 var ( 19 defaultBind = []string{"/usr", "/lib", "/lib64", "/bin"} 20 ) 21 22 func BenchmarkStdFork(b *testing.B) { 23 b.RunParallel(func(pb *testing.PB) { 24 for pb.Next() { 25 pid, err := syscall.ForkExec("/bin/echo", nil, &syscall.ProcAttr{ 26 Env: []string{"PATH=/bin"}, 27 }) 28 if err != nil { 29 b.Fatal(err) 30 } 31 wait4(pid, b) 32 } 33 }) 34 } 35 36 // BenchmarkSimpleFork is about 0.70ms/op 37 func BenchmarkSimpleFork(b *testing.B) { 38 r, f := getRunner(b) 39 defer f.Close() 40 benchmarkRun(r, b) 41 } 42 43 // BenchmarkUnsharePid is about 0.79ms/op 44 func BenchmarkUnsharePid(b *testing.B) { 45 r, f := getRunner(b) 46 defer f.Close() 47 r.CloneFlags = unix.CLONE_NEWPID 48 benchmarkRun(r, b) 49 } 50 51 // BenchmarkUnshareUser is about 0.84ms/op 52 func BenchmarkUnshareUser(b *testing.B) { 53 r, f := getRunner(b) 54 defer f.Close() 55 r.CloneFlags = unix.CLONE_NEWUSER 56 benchmarkRun(r, b) 57 } 58 59 // BenchmarkUnshareUts is about 0.78ms/op 60 func BenchmarkUnshareUts(b *testing.B) { 61 r, f := getRunner(b) 62 defer f.Close() 63 r.CloneFlags = unix.CLONE_NEWUTS 64 benchmarkRun(r, b) 65 } 66 67 // BenchmarkUnshareCgroup is about 0.85ms/op 68 func BenchmarkUnshareCgroup(b *testing.B) { 69 r, f := getRunner(b) 70 defer f.Close() 71 r.CloneFlags = unix.CLONE_NEWCGROUP 72 benchmarkRun(r, b) 73 } 74 75 // BenchmarkUnshareIpc is about 51ms/op 76 func BenchmarkUnshareIpc(b *testing.B) { 77 r, f := getRunner(b) 78 defer f.Close() 79 r.CloneFlags = unix.CLONE_NEWIPC 80 benchmarkRun(r, b) 81 } 82 83 // BenchmarkUnshareMount is about 51ms/op 84 func BenchmarkUnshareMount(b *testing.B) { 85 r, f := getRunner(b) 86 defer f.Close() 87 r.CloneFlags = unix.CLONE_NEWNS 88 benchmarkRun(r, b) 89 } 90 91 // BenchmarkUnshareNet is about 426ms/op 92 func BenchmarkUnshareNet(b *testing.B) { 93 r, f := getRunner(b) 94 defer f.Close() 95 r.CloneFlags = unix.CLONE_NEWNET 96 benchmarkRun(r, b) 97 } 98 99 // BenchmarkFastUnshareMountPivot is about 104ms/op 100 func BenchmarkFastUnshareMountPivot(b *testing.B) { 101 root, err := os.MkdirTemp("", "ns") 102 if err != nil { 103 b.Errorf("failed to create temp dir") 104 } 105 defer os.RemoveAll(root) 106 r, f := getRunner(b) 107 defer f.Close() 108 r.CloneFlags = unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP 109 r.PivotRoot = root 110 r.NoNewPrivs = true 111 r.DropCaps = true 112 r.Mounts = getMounts(defaultBind) 113 benchmarkRun(r, b) 114 } 115 116 // BenchmarkUnshareAll is about 800ms/op 117 func BenchmarkUnshareAll(b *testing.B) { 118 r, f := getRunner(b) 119 defer f.Close() 120 r.CloneFlags = UnshareFlags 121 r.NoNewPrivs = true 122 r.DropCaps = true 123 benchmarkRun(r, b) 124 } 125 126 // BenchmarkUnshareMountPivot is about 880ms/op 127 func BenchmarkUnshareMountPivot(b *testing.B) { 128 root, err := os.MkdirTemp("", "ns") 129 if err != nil { 130 b.Errorf("failed to create temp dir") 131 } 132 defer os.RemoveAll(root) 133 r, f := getRunner(b) 134 defer f.Close() 135 r.CloneFlags = UnshareFlags 136 r.PivotRoot = root 137 r.NoNewPrivs = true 138 r.DropCaps = true 139 r.Mounts = getMounts(defaultBind) 140 benchmarkRun(r, b) 141 } 142 143 func getRunner(b *testing.B) (*Runner, *os.File) { 144 f := openNull(b) 145 return &Runner{ 146 Args: []string{"/bin/echo"}, 147 Env: []string{"PATH=/bin"}, 148 Files: []uintptr{f.Fd(), f.Fd(), f.Fd()}, 149 WorkDir: "/bin", 150 }, f 151 } 152 153 func benchmarkRun(r *Runner, b *testing.B) { 154 b.ResetTimer() 155 b.RunParallel(func(pb *testing.PB) { 156 for pb.Next() { 157 pid, err := r.Start() 158 if err != nil { 159 b.Fatal(err) 160 } 161 wait4(pid, b) 162 } 163 }) 164 } 165 166 func getMounts(dirs []string) []mount.SyscallParams { 167 builder := mount.NewBuilder() 168 for _, d := range dirs { 169 builder.WithMount(mount.Mount{ 170 Source: d, 171 Target: d[1:], 172 Flags: roBind, 173 }) 174 } 175 m, _ := builder.FilterNotExist().Build() 176 return m 177 } 178 179 func openNull(b *testing.B) *os.File { 180 f, err := os.OpenFile("/dev/null", os.O_RDWR, 0666) 181 if err != nil { 182 b.Errorf("Failed to open %v", err) 183 } 184 return f 185 } 186 187 func wait4(pid int, b *testing.B) { 188 var wstat syscall.WaitStatus 189 for { 190 syscall.Wait4(pid, &wstat, 0, nil) 191 if wstat.Exited() { 192 if s := wstat.ExitStatus(); s != 0 { 193 b.Errorf("Exited: %d", s) 194 } 195 break 196 } 197 } 198 }