gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/secbench/runner.go (about)

     1  // Copyright 2023 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // The runner binary executes a single benchmark run and prints out results.
    16  // Because seccomp-bpf filters cannot be removed from a process, this runs as
    17  // a subprocess of the secbench library.
    18  // This requires the ability to write(2) to stdout even after installing the
    19  // seccomp-bpf filter.
    20  package main
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"io"
    26  	"math/rand"
    27  	"os"
    28  
    29  	"gvisor.dev/gvisor/pkg/abi/linux"
    30  	"gvisor.dev/gvisor/pkg/bpf"
    31  	"gvisor.dev/gvisor/pkg/gohacks"
    32  	"gvisor.dev/gvisor/pkg/seccomp"
    33  	"gvisor.dev/gvisor/test/secbench/secbenchdef"
    34  )
    35  
    36  // install installs the given program on the runner.
    37  func install(program []bpf.Instruction) error {
    38  	// Rewrite the program so that all return actions are either ALLOW or
    39  	// RET_ERRNO. This allows us to benchmark the program without worrying
    40  	// that we'll crash if we call a bad system call.
    41  	rewritten := make([]bpf.Instruction, len(program))
    42  	copy(rewritten, program)
    43  	for pc, ins := range rewritten {
    44  		switch ins.OpCode {
    45  		case bpf.Ret | bpf.A:
    46  			// Override the return action value to RET_ERRNO.
    47  			ins.K = uint32(linux.SECCOMP_RET_ERRNO)
    48  		case bpf.Ret | bpf.K:
    49  			switch linux.BPFAction(ins.K) {
    50  			case linux.SECCOMP_RET_ALLOW, linux.SECCOMP_RET_ERRNO:
    51  				// Do nothing.
    52  			default:
    53  				// Override the return action value to RET_ERRNO.
    54  				ins.K = uint32(linux.SECCOMP_RET_ERRNO)
    55  			}
    56  		default:
    57  			// Do nothing.
    58  		}
    59  		rewritten[pc] = ins
    60  	}
    61  	return seccomp.SetFilter(rewritten)
    62  }
    63  
    64  // run runs a Bench request.
    65  func run(req secbenchdef.BenchRunRequest) (secbenchdef.BenchRunResponse, error) {
    66  	bn := req.Bench
    67  	rng := rand.New(rand.NewSource(req.RandomSeed))
    68  
    69  	sequenceMetrics := make([]secbenchdef.SequenceMetrics, len(bn.Profile.Sequences))
    70  	var totalWeight int
    71  	for _, seq := range bn.Profile.Sequences {
    72  		if seq.Weight < 0 {
    73  			return secbenchdef.BenchRunResponse{}, fmt.Errorf("weight of sequence %v cannot be zero or negative: %d", seq, seq.Weight)
    74  		}
    75  		totalWeight += seq.Weight
    76  	}
    77  
    78  	// We're ready. Install the BPF program.
    79  	if req.InstallFilter {
    80  		if err := install(bn.Instructions); err != nil {
    81  			panic(fmt.Sprintf("cannot install BPF program: %v", err))
    82  		}
    83  	}
    84  
    85  	var (
    86  		before, after            int64
    87  		si, seqIndex, randWeight int
    88  		seq                      secbenchdef.Sequence
    89  		seqSyscalls              []secbenchdef.Syscall
    90  		sc                       secbenchdef.Syscall
    91  		duration, totalNanos     uint64
    92  	)
    93  	for i := uint64(0); i < req.Iterations; i++ {
    94  		randWeight = rng.Intn(totalWeight)
    95  		seqIndex = -1
    96  		for si, seq = range bn.Profile.Sequences {
    97  			if randWeight -= seq.Weight; randWeight < 0 {
    98  				seqIndex = si
    99  				break
   100  			}
   101  		}
   102  		if seqIndex == -1 {
   103  			panic("logic error in weight randomization")
   104  		}
   105  		if !req.ActiveSequences[seqIndex] {
   106  			continue
   107  		}
   108  		seqSyscalls = bn.Profile.Sequences[seqIndex].Syscalls
   109  		if len(seqSyscalls) == 1 {
   110  			// If we have only one syscall to call (common), measure this directly to
   111  			// avoid measuring the loop overhead.
   112  			sc = seqSyscalls[0]
   113  			before = gohacks.Nanotime()
   114  			sc.Call()
   115  			after = gohacks.Nanotime()
   116  		} else {
   117  			before = gohacks.Nanotime()
   118  			for _, sc = range seqSyscalls {
   119  				sc.Call()
   120  			}
   121  			after = gohacks.Nanotime()
   122  		}
   123  		duration = uint64(after - before)
   124  		sequenceMetrics[seqIndex].Iterations++
   125  		sequenceMetrics[seqIndex].TotalNanos += duration
   126  		totalNanos += duration
   127  	}
   128  
   129  	return secbenchdef.BenchRunResponse{
   130  		TotalNanos:      totalNanos,
   131  		SequenceMetrics: sequenceMetrics,
   132  	}, nil
   133  }
   134  
   135  func main() {
   136  	data, err := io.ReadAll(os.Stdin)
   137  	if err != nil {
   138  		panic(fmt.Sprintf("cannot read from stdin: %v", err))
   139  	}
   140  	var runReq secbenchdef.BenchRunRequest
   141  	if err = json.Unmarshal(data, &runReq); err != nil {
   142  		panic(fmt.Sprintf("cannot deserialize bench data: %v", err))
   143  	}
   144  	resp, err := run(runReq)
   145  	if err != nil {
   146  		panic(fmt.Sprintf("cannot run bench: %v", err))
   147  	}
   148  	respData, err := json.Marshal(&resp)
   149  	if err != nil {
   150  		panic(fmt.Sprintf("cannot serialize bench response: %v", err))
   151  	}
   152  	if _, err := os.Stdout.Write(respData); err != nil {
   153  		panic(fmt.Sprintf("cannot write response to stdout: %v", err))
   154  	}
   155  	os.Stdout.Close()
   156  }