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  }