diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2024-04-15 12:36:36 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2024-04-15 13:23:33 +0000 |
| commit | ec1d9df37dba4a17065b091bbe9e03c9635cd0dc (patch) | |
| tree | d1c9767c3330e3704efd1d4fac8a007dd8be7df4 /prog | |
| parent | b6f969371dc1e824c76636c0d3754a91b55d69b9 (diff) | |
prog: profile what consumes space in exec encoding
Allow to profile how many bytes are consumed for what in the exec encoding.
The profile shows there are not many opportunities left.
53% are consumed by data blobs.
13% for const args.
18% for non-arg things (syscall number, copyout index, props, etc).
Diffstat (limited to 'prog')
| -rw-r--r-- | prog/decodeexec.go | 69 | ||||
| -rw-r--r-- | prog/encoding_test.go | 4 | ||||
| -rw-r--r-- | prog/encodingexec_test.go | 10 | ||||
| -rw-r--r-- | prog/test/fuzz.go | 2 |
4 files changed, 53 insertions, 32 deletions
diff --git a/prog/decodeexec.go b/prog/decodeexec.go index 2113d24c7..2356e051b 100644 --- a/prog/decodeexec.go +++ b/prog/decodeexec.go @@ -7,6 +7,7 @@ import ( "encoding/binary" "fmt" "reflect" + "strings" ) type ExecProg struct { @@ -71,8 +72,8 @@ type ExecCsumChunk struct { Size uint64 } -func (target *Target) DeserializeExec(exec []byte) (ExecProg, error) { - dec := &execDecoder{target: target, data: exec} +func (target *Target) DeserializeExec(exec []byte, stats map[string]int) (ExecProg, error) { + dec := &execDecoder{target: target, data: exec, stats: stats} dec.parse() if dec.err != nil { return ExecProg{}, dec.err @@ -96,22 +97,23 @@ type execDecoder struct { vars []uint64 call ExecCall calls []ExecCall + stats map[string]int } func (dec *execDecoder) parse() { for dec.err == nil { - switch instr := dec.read(); instr { + switch instr := dec.read("instr/opcode"); instr { case execInstrCopyin: dec.commitCall() dec.call.Copyin = append(dec.call.Copyin, ExecCopyin{ - Addr: dec.read() + dec.target.DataOffset, + Addr: dec.read("instr/copyin") + dec.target.DataOffset, Arg: dec.readArg(), }) case execInstrCopyout: dec.call.Copyout = append(dec.call.Copyout, ExecCopyout{ - Index: dec.read(), - Addr: dec.read() + dec.target.DataOffset, - Size: dec.read(), + Index: dec.read("instr/copyout/index"), + Addr: dec.read("instr/copyout/addr") + dec.target.DataOffset, + Size: dec.read("instr/copyout/size"), }) case execInstrEOF: dec.commitCall() @@ -126,8 +128,8 @@ func (dec *execDecoder) parse() { return } dec.call.Meta = dec.target.Syscalls[instr] - dec.call.Index = dec.read() - for i := dec.read(); i > 0; i-- { + dec.call.Index = dec.read("instr/index") + for i := dec.read("instr/nargs"); i > 0; i-- { switch arg := dec.readArg(); arg.(type) { case ExecArgConst, ExecArgResult: dec.call.Args = append(dec.call.Args, arg) @@ -142,7 +144,7 @@ func (dec *execDecoder) parse() { func (dec *execDecoder) readCallProps(props *CallProps) { props.ForeachProp(func(_, _ string, value reflect.Value) { - arg := dec.read() + arg := dec.read("call prop") switch kind := value.Kind(); kind { case reflect.Int: value.SetInt(int64(arg)) @@ -157,11 +159,11 @@ func (dec *execDecoder) readCallProps(props *CallProps) { } func (dec *execDecoder) readArg() ExecArg { - switch typ := dec.read(); typ { + switch typ := dec.read("arg/type"); typ { case execArgConst: - meta := dec.read() + meta := dec.read("arg/const/meta") return ExecArgConst{ - Value: dec.read(), + Value: dec.read("arg/const/value"), Size: meta & 0xff, Format: BinaryFormat((meta >> 8) & 0xff), BitfieldOffset: (meta >> 16) & 0xff, @@ -176,18 +178,18 @@ func (dec *execDecoder) readArg() ExecArg { size = 8 } return ExecArgConst{ - Value: dec.read() + dec.target.DataOffset, + Value: dec.read("arg/addr") + dec.target.DataOffset, Size: uint64(size), } case execArgResult: - meta := dec.read() + meta := dec.read("arg/result/meta") arg := ExecArgResult{ Size: meta & 0xff, Format: BinaryFormat((meta >> 8) & 0xff), - Index: dec.read(), - DivOp: dec.read(), - AddOp: dec.read(), - Default: dec.read(), + Index: dec.read("arg/result/index"), + DivOp: dec.read("arg/result/divop"), + AddOp: dec.read("arg/result/addop"), + Default: dec.read("arg/result/default"), } for uint64(len(dec.vars)) <= arg.Index { dec.vars = append(dec.vars, 0) @@ -195,22 +197,23 @@ func (dec *execDecoder) readArg() ExecArg { dec.vars[arg.Index] = arg.Default return arg case execArgData: - flags := dec.read() + flags := dec.read("arg/data/size") size := flags & ^execArgDataReadable + dec.addStat("arg/data/blob", int(size)) readable := flags&execArgDataReadable != 0 return ExecArgData{ Data: dec.readBlob(size), Readable: readable, } case execArgCsum: - size := dec.read() - switch kind := dec.read(); kind { + size := dec.read("arg/csum/size") + switch kind := dec.read("arg/csum/kind"); kind { case ExecArgCsumInet: - chunks := make([]ExecCsumChunk, dec.read()) + chunks := make([]ExecCsumChunk, dec.read("arg/csum/chunks")) for i := range chunks { - kind := dec.read() - addr := dec.read() - size := dec.read() + kind := dec.read("arg/csum/chunk/kind") + addr := dec.read("arg/csum/chunk/addr") + size := dec.read("arg/csum/chunk/size") if kind == ExecArgCsumChunkData { addr += dec.target.DataOffset } @@ -235,7 +238,7 @@ func (dec *execDecoder) readArg() ExecArg { } } -func (dec *execDecoder) read() uint64 { +func (dec *execDecoder) read(stat string) uint64 { if dec.err != nil { return 0 } @@ -244,6 +247,7 @@ func (dec *execDecoder) read() uint64 { dec.setErr(fmt.Errorf("exec program overflow")) return 0 } + dec.addStat(stat, n) dec.data = dec.data[n:] return uint64(v) } @@ -281,3 +285,14 @@ func (dec *execDecoder) commitCall() { dec.calls = append(dec.calls, dec.call) dec.call = ExecCall{} } + +func (dec *execDecoder) addStat(stat string, n int) { + if dec.stats == nil { + return + } + prefix := "" + for _, part := range strings.Split(stat, "/") { + dec.stats[prefix+part] += n + prefix += part + "/" + } +} diff --git a/prog/encoding_test.go b/prog/encoding_test.go index 42f16036c..e5d5a2d38 100644 --- a/prog/encoding_test.go +++ b/prog/encoding_test.go @@ -404,11 +404,11 @@ func TestSerializeDeserializeRandom(t *testing.T) { if ok { t.Log("flaky?") } - decoded0, err := target.DeserializeExec(data0[:n0]) + decoded0, err := target.DeserializeExec(data0[:n0], nil) if err != nil { t.Fatal(err) } - decoded1, err := target.DeserializeExec(data1[:n1]) + decoded1, err := target.DeserializeExec(data1[:n1], nil) if err != nil { t.Fatal(err) } diff --git a/prog/encodingexec_test.go b/prog/encodingexec_test.go index 1b34f4cab..68e42f07e 100644 --- a/prog/encodingexec_test.go +++ b/prog/encodingexec_test.go @@ -19,16 +19,19 @@ func TestSerializeForExecRandom(t *testing.T) { buf := make([]byte, ExecBufferSize) execSizes := histogram.New(1000) textSizes := histogram.New(1000) + totalSize := 0 + sizes := make(map[string]int) for i := 0; i < iters; i++ { p := target.Generate(rs, 10, ct) n, err := p.SerializeForExec(buf) if err != nil { t.Fatalf("failed to serialize: %v", err) } - _, err = target.DeserializeExec(buf[:n]) + _, err = target.DeserializeExec(buf[:n], sizes) if err != nil { t.Fatal(err) } + totalSize += n execSizes.Add(float64(n)) textSizes.Add(float64(len(p.Serialize()))) } @@ -36,6 +39,9 @@ func TestSerializeForExecRandom(t *testing.T) { int(execSizes.Quantile(0.1)), int(execSizes.Quantile(0.5)), int(execSizes.Quantile(0.9))) t.Logf("text sizes: 10%%:%v 50%%:%v 90%%:%v", int(textSizes.Quantile(0.1)), int(textSizes.Quantile(0.5)), int(textSizes.Quantile(0.9))) + for what, size := range sizes { + t.Logf("%-24v: %5.2f%%", what, float64(size)/float64(totalSize)*100) + } } // nolint: funlen @@ -675,7 +681,7 @@ test$res1(r0) t.Logf("got: %q", data) t.Fatalf("mismatch") } - decoded, err := target.DeserializeExec(data) + decoded, err := target.DeserializeExec(data, nil) if err != nil { t.Fatal(err) } diff --git a/prog/test/fuzz.go b/prog/test/fuzz.go index 5857ff98d..354d6c3cd 100644 --- a/prog/test/fuzz.go +++ b/prog/test/fuzz.go @@ -49,7 +49,7 @@ func FuzzDeserialize(data []byte) int { panic("got different data") } if n, err := p0.SerializeForExec(fuzzBuffer); err == nil { - if _, err := fuzzTarget.DeserializeExec(fuzzBuffer[:n]); err != nil { + if _, err := fuzzTarget.DeserializeExec(fuzzBuffer[:n], nil); err != nil { panic(err) } } |
