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 }