From ec1d9df37dba4a17065b091bbe9e03c9635cd0dc Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Mon, 15 Apr 2024 12:36:36 +0200 Subject: 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). --- pkg/csource/csource.go | 2 +- prog/decodeexec.go | 69 ++++++++++++++++++++++++++++------------------- prog/encoding_test.go | 4 +-- prog/encodingexec_test.go | 10 +++++-- prog/test/fuzz.go | 2 +- 5 files changed, 54 insertions(+), 33 deletions(-) diff --git a/pkg/csource/csource.go b/pkg/csource/csource.go index 53821a854..96237e64b 100644 --- a/pkg/csource/csource.go +++ b/pkg/csource/csource.go @@ -244,7 +244,7 @@ func (ctx *context) generateProgCalls(p *prog.Prog, trace bool) ([]string, []uin if err != nil { return nil, nil, fmt.Errorf("failed to serialize program: %w", err) } - decoded, err := ctx.target.DeserializeExec(exec[:progSize]) + decoded, err := ctx.target.DeserializeExec(exec[:progSize], nil) if err != nil { return nil, nil, err } 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) } } -- cgit mrf-deployment