From 9ef6d208896edbc03f46852787f40053660b8958 Mon Sep 17 00:00:00 2001 From: xhd2015 Date: Fri, 26 Jul 2024 19:30:47 +0800 Subject: [PATCH] enable trace snapshot --- cmd/xgo/runtime_gen/trace/stack.go | 9 +- cmd/xgo/runtime_gen/trace/stack_export.go | 8 +- cmd/xgo/runtime_gen/trace/trace.go | 79 +++- cmd/xgo/version.go | 4 +- runtime/test/go.mod | 6 +- runtime/test/go.sum | 2 + runtime/test/trace/snapshot/snapshot.go | 37 ++ runtime/test/trace/snapshot/snapshot_test.go | 392 +++++++++++++++++++ runtime/trace/stack.go | 9 +- runtime/trace/stack_export.go | 8 +- runtime/trace/trace.go | 79 +++- script/run-test/main.go | 4 + 12 files changed, 606 insertions(+), 31 deletions(-) create mode 100644 runtime/test/go.sum create mode 100644 runtime/test/trace/snapshot/snapshot.go create mode 100644 runtime/test/trace/snapshot/snapshot_test.go diff --git a/cmd/xgo/runtime_gen/trace/stack.go b/cmd/xgo/runtime_gen/trace/stack.go index 476c8983..263b9896 100755 --- a/cmd/xgo/runtime_gen/trace/stack.go +++ b/cmd/xgo/runtime_gen/trace/stack.go @@ -27,8 +27,12 @@ type Stack struct { Begin int64 // us End int64 // us - Args core.Object - Results core.Object + Args core.Object + Results core.Object + + // is recorded as snapshot + Snapshot bool + Panic bool Error error Children []*Stack @@ -177,6 +181,7 @@ func (c *Stack) Export(opts *ExportOptions) *StackExport { End: c.End, Args: args, Results: results, + Snapshot: c.Snapshot, Panic: c.Panic, Error: errMsg, Children: ((stacks)(c.Children)).Export(opts), diff --git a/cmd/xgo/runtime_gen/trace/stack_export.go b/cmd/xgo/runtime_gen/trace/stack_export.go index 0612a05e..eea7bd10 100755 --- a/cmd/xgo/runtime_gen/trace/stack_export.go +++ b/cmd/xgo/runtime_gen/trace/stack_export.go @@ -25,8 +25,12 @@ type StackExport struct { Args interface{} Results interface{} - Panic bool - Error string + + // is recorded as snapshot + Snapshot bool + + Panic bool + Error string Children []*StackExport } diff --git a/cmd/xgo/runtime_gen/trace/trace.go b/cmd/xgo/runtime_gen/trace/trace.go index 5573f0b6..e6bdbe82 100755 --- a/cmd/xgo/runtime_gen/trace/trace.go +++ b/cmd/xgo/runtime_gen/trace/trace.go @@ -155,12 +155,14 @@ type CollectOptions struct { } type collectOpts struct { - name string - onComplete func(root *Root) - filter []func(stack *Stack) bool - root *Root - options *CollectOptions - exportOptions *ExportOptions + name string + onComplete func(root *Root) + filters []func(stack *Stack) bool + postFilters []func(stack *Stack) + snapshotFilters []func(stack *Stack) bool + root *Root + options *CollectOptions + exportOptions *ExportOptions } func Options() *collectOpts { @@ -178,7 +180,17 @@ func (c *collectOpts) OnComplete(f func(root *Root)) *collectOpts { } func (c *collectOpts) WithFilter(f func(stack *Stack) bool) *collectOpts { - c.filter = append(c.filter, f) + c.filters = append(c.filters, f) + return c +} + +func (c *collectOpts) WithPostFilter(f func(stack *Stack)) *collectOpts { + c.postFilters = append(c.postFilters, f) + return c +} + +func (c *collectOpts) WithSnapshot(f func(stack *Stack) bool) *collectOpts { + c.snapshotFilters = append(c.snapshotFilters, f) return c } @@ -256,10 +268,22 @@ func handleTracePre(ctx context.Context, f *core.FuncInfo, args core.Object, res initial = true } } else { - if !checkFilter(stack, localOpts.filter) { + if !checkFilters(stack, localOpts.filters) { // do not collect trace if filtered out return nil, trap.ErrSkip } + var anySnapshot bool + for _, f := range localOpts.snapshotFilters { + if f(stack) { + anySnapshot = true + break + } + } + if anySnapshot { + stack.Snapshot = true + stack.Args = premarshal(stack.Args) + } + localRoot = localOpts.root if localRoot == nil { initial = true @@ -305,7 +329,7 @@ func handleTracePre(ctx context.Context, f *core.FuncInfo, args core.Object, res return prevTop, nil } -func checkFilter(stack *Stack, filters []func(stack *Stack) bool) bool { +func checkFilters(stack *Stack, filters []func(stack *Stack) bool) bool { for _, f := range filters { if !f(stack) { return false @@ -333,6 +357,9 @@ func handleTracePost(ctx context.Context, f *core.FuncInfo, args core.Object, re panic(fmt.Errorf("unbalanced stack")) } root = localOpts.root + for _, f := range localOpts.postFilters { + f(root.Top) + } } else { v, ok := stackMap.Load(key) if !ok { @@ -340,6 +367,9 @@ func handleTracePost(ctx context.Context, f *core.FuncInfo, args core.Object, re } root = v.(*Root) } + if root.Top != nil && root.Top.Snapshot { + root.Top.Results = premarshal(root.Top.Results) + } // detect panic pe := __xgo_link_peek_panic() @@ -543,3 +573,34 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { }) return err } + +func premarshal(v core.Object) (res core.Object) { + var err error + var data []byte + defer func() { + if e := recover(); e != nil { + if pe, ok := e.(error); ok { + err = pe + } else { + err = fmt.Errorf("marshal %T: %v", v, e) + } + } + if err != nil { + data = []byte(`{"err":` + strconv.Quote(err.Error()) + ` }`) + } + res = &premarshaled{Object: v, data: data} + }() + data, err = MarshalAnyJSON(v) + return +} + +type premarshaled struct { + core.Object + data []byte +} + +var _ core.Object = (*premarshaled)(nil) + +func (c *premarshaled) MarshalJSON() ([]byte, error) { + return c.data, nil +} diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index ff41d9fd..5f8d60b5 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -4,8 +4,8 @@ import "fmt" // auto updated const VERSION = "1.0.46" -const REVISION = "baec4d50e4c26d279fe32cd30c7649d707b8f1ce+1" -const NUMBER = 299 +const REVISION = "ddb79e33f33b43c8eb97936e31212bc2f54ac3c1+1" +const NUMBER = 300 // manually updated const CORE_VERSION = "1.0.43" diff --git a/runtime/test/go.mod b/runtime/test/go.mod index cd53b2d4..1d50481f 100644 --- a/runtime/test/go.mod +++ b/runtime/test/go.mod @@ -2,8 +2,8 @@ module github.com/xhd2015/xgo/runtime/test go 1.18 -require ( - github.com/xhd2015/xgo/runtime v1.0.16 -) +require github.com/xhd2015/xgo/runtime v1.0.16 + +require github.com/xhd2015/xgo v1.0.47-0.20240726085913-ddb79e33f33b replace github.com/xhd2015/xgo/runtime => ../ diff --git a/runtime/test/go.sum b/runtime/test/go.sum new file mode 100644 index 00000000..01101650 --- /dev/null +++ b/runtime/test/go.sum @@ -0,0 +1,2 @@ +github.com/xhd2015/xgo v1.0.47-0.20240726085913-ddb79e33f33b h1:D0+4qUvmlcG2QTi6RU7xBnzemRrLyM7QYnYVd4jhR+g= +github.com/xhd2015/xgo v1.0.47-0.20240726085913-ddb79e33f33b/go.mod h1:LJxlcYSaXo/9YpsnB3yHh9NHe7BRettYCytaNGWY2BE= diff --git a/runtime/test/trace/snapshot/snapshot.go b/runtime/test/trace/snapshot/snapshot.go new file mode 100644 index 00000000..950154c2 --- /dev/null +++ b/runtime/test/trace/snapshot/snapshot.go @@ -0,0 +1,37 @@ +package snapshot + +type _InOut struct { + Last string + Count int +} + +type _Response struct { + Last string + Count int +} + +func handleL1() *_Response { + _, resp := handleL2() + resp.Last = "handleL1" + resp.Count++ + return resp +} + +func handleL2() (*_InOut, *_Response) { + a := &_InOut{ + Last: "handleL2", + } + resp := handleL3(a) + + resp.Last = "handleL2" + resp.Count++ + return a, resp +} + +func handleL3(a *_InOut) *_Response { + a.Last = "handleL3" + a.Count++ + return &_Response{ + Last: "handleL3", + } +} diff --git a/runtime/test/trace/snapshot/snapshot_test.go b/runtime/test/trace/snapshot/snapshot_test.go new file mode 100644 index 00000000..6cba158a --- /dev/null +++ b/runtime/test/trace/snapshot/snapshot_test.go @@ -0,0 +1,392 @@ +package snapshot + +import ( + "encoding/json" + "testing" + "time" + + "github.com/xhd2015/xgo/runtime/trace" + "github.com/xhd2015/xgo/support/assert" +) + +func TestNoSnapshot(t *testing.T) { + test(t, noSnapshotExpect, nil) +} + +func TestSnapshot(t *testing.T) { + test(t, snapshotExpect, func(stack *trace.Stack) bool { + return true + }) +} + +func test(t *testing.T, expectTrace string, f func(stack *trace.Stack) bool) { + var record *trace.Root + opts := trace.Options() + if f != nil { + opts.WithSnapshot(f) + } + opts.OnComplete(func(root *trace.Root) { + record = root + }).Collect(func() { + handleL1() + }) + exportRoot := record.Export(nil) + // zero + exportRoot.Begin = time.Time{} + for _, child := range exportRoot.Children { + stablizeTrace(child) + } + + data, err := trace.MarshalAnyJSON(exportRoot) + if err != nil { + t.Error(err) + return + } + + var v *trace.RootExport + err = json.Unmarshal([]byte(expectTrace), &v) + if err != nil { + t.Error(err) + return + } + + expectBytes, err := json.Marshal(v) + if err != nil { + t.Error(err) + return + } + + dataStr := string(data) + expect := string(expectBytes) + // t.Logf("trace: %s", dataStr) + if diff := assert.Diff(expect, dataStr); diff != "" { + t.Errorf("trace not match: %s", diff) + } +} + +func stablizeTrace(t *trace.StackExport) { + if t == nil { + return + } + t.Begin = 0 + t.End = 0 + t.FuncInfo.Line = 0 + + for _, child := range t.Children { + stablizeTrace(child) + } +} + +const snapshotExpect = `{ + "Begin": "0001-01-01T00:00:00Z", + "Children": [ + { + "FuncInfo": { + "Kind": "func", + "Pkg": "github.com/xhd2015/xgo/runtime/test/trace/snapshot", + "IdentityName": "test.func2", + "Name": "", + "RecvType": "", + "RecvPtr": false, + "Interface": false, + "Generic": false, + "Closure": true, + "Stdlib": false, + "File": "/Users/xhd2015/Projects/xhd2015/xgo/runtime/test/trace/snapshot/snapshot_test.go", + "Line": 0, + "RecvName": "", + "ArgNames": null, + "ResNames": null, + "FirstArgCtx": false, + "LastResultErr": false + }, + "Begin": 0, + "End": 0, + "Args": {}, + "Results": {}, + "Snapshot": true, + "Panic": false, + "Error": "", + "Children": [ + { + "FuncInfo": { + "Kind": "func", + "Pkg": "github.com/xhd2015/xgo/runtime/test/trace/snapshot", + "IdentityName": "handleL1", + "Name": "handleL1", + "RecvType": "", + "RecvPtr": false, + "Interface": false, + "Generic": false, + "Closure": false, + "Stdlib": false, + "File": "/Users/xhd2015/Projects/xhd2015/xgo/runtime/test/trace/snapshot/snapshot.go", + "Line": 0, + "RecvName": "", + "ArgNames": null, + "ResNames": [ + "_r0" + ], + "FirstArgCtx": false, + "LastResultErr": false + }, + "Begin": 0, + "End": 0, + "Args": {}, + "Results": { + "_r0": { + "Last": "handleL1", + "Count": 2 + } + }, + "Snapshot": true, + "Panic": false, + "Error": "", + "Children": [ + { + "FuncInfo": { + "Kind": "func", + "Pkg": "github.com/xhd2015/xgo/runtime/test/trace/snapshot", + "IdentityName": "handleL2", + "Name": "handleL2", + "RecvType": "", + "RecvPtr": false, + "Interface": false, + "Generic": false, + "Closure": false, + "Stdlib": false, + "File": "/Users/xhd2015/Projects/xhd2015/xgo/runtime/test/trace/snapshot/snapshot.go", + "Line": 0, + "RecvName": "", + "ArgNames": null, + "ResNames": [ + "_r0", + "_r1" + ], + "FirstArgCtx": false, + "LastResultErr": false + }, + "Begin": 0, + "End": 0, + "Args": {}, + "Results": { + "_r0": { + "Last": "handleL3", + "Count": 1 + }, + "_r1": { + "Last": "handleL2", + "Count": 1 + } + }, + "Snapshot": true, + "Panic": false, + "Error": "", + "Children": [ + { + "FuncInfo": { + "Kind": "func", + "Pkg": "github.com/xhd2015/xgo/runtime/test/trace/snapshot", + "IdentityName": "handleL3", + "Name": "handleL3", + "RecvType": "", + "RecvPtr": false, + "Interface": false, + "Generic": false, + "Closure": false, + "Stdlib": false, + "File": "/Users/xhd2015/Projects/xhd2015/xgo/runtime/test/trace/snapshot/snapshot.go", + "Line": 0, + "RecvName": "", + "ArgNames": [ + "a" + ], + "ResNames": [ + "_r0" + ], + "FirstArgCtx": false, + "LastResultErr": false + }, + "Begin": 0, + "End": 0, + "Args": { + "a": { + "Last": "handleL2", + "Count": 0 + } + }, + "Results": { + "_r0": { + "Last": "handleL3", + "Count": 0 + } + }, + "Snapshot": true, + "Panic": false, + "Error": "", + "Children": null + } + ] + } + ] + } + ] + } + ] +}` + +// NOTE in this original output without snapshot, handleL3's input.Last = handleL3,while it +// should be handleL2 if snapshot +const noSnapshotExpect = `{ + "Begin": "0001-01-01T00:00:00Z", + "Children": [ + { + "FuncInfo": { + "Kind": "func", + "Pkg": "github.com/xhd2015/xgo/runtime/test/trace/snapshot", + "IdentityName": "test.func2", + "Name": "", + "RecvType": "", + "RecvPtr": false, + "Interface": false, + "Generic": false, + "Closure": true, + "Stdlib": false, + "File": "/Users/xhd2015/Projects/xhd2015/xgo/runtime/test/trace/snapshot/snapshot_test.go", + "Line": 0, + "RecvName": "", + "ArgNames": null, + "ResNames": null, + "FirstArgCtx": false, + "LastResultErr": false + }, + "Begin": 0, + "End": 0, + "Args": {}, + "Results": {}, + "Panic": false, + "Error": "", + "Children": [ + { + "FuncInfo": { + "Kind": "func", + "Pkg": "github.com/xhd2015/xgo/runtime/test/trace/snapshot", + "IdentityName": "handleL1", + "Name": "handleL1", + "RecvType": "", + "RecvPtr": false, + "Interface": false, + "Generic": false, + "Closure": false, + "Stdlib": false, + "File": "/Users/xhd2015/Projects/xhd2015/xgo/runtime/test/trace/snapshot/snapshot.go", + "Line": 0, + "RecvName": "", + "ArgNames": null, + "ResNames": [ + "_r0" + ], + "FirstArgCtx": false, + "LastResultErr": false + }, + "Begin": 0, + "End": 0, + "Args": {}, + "Results": { + "_r0": { + "Last": "handleL1", + "Count": 2 + } + }, + "Panic": false, + "Error": "", + "Children": [ + { + "FuncInfo": { + "Kind": "func", + "Pkg": "github.com/xhd2015/xgo/runtime/test/trace/snapshot", + "IdentityName": "handleL2", + "Name": "handleL2", + "RecvType": "", + "RecvPtr": false, + "Interface": false, + "Generic": false, + "Closure": false, + "Stdlib": false, + "File": "/Users/xhd2015/Projects/xhd2015/xgo/runtime/test/trace/snapshot/snapshot.go", + "Line": 0, + "RecvName": "", + "ArgNames": null, + "ResNames": [ + "_r0", + "_r1" + ], + "FirstArgCtx": false, + "LastResultErr": false + }, + "Begin": 0, + "End": 0, + "Args": {}, + "Results": { + "_r0": { + "Last": "handleL3", + "Count": 1 + }, + "_r1": { + "Last": "handleL1", + "Count": 2 + } + }, + "Panic": false, + "Error": "", + "Children": [ + { + "FuncInfo": { + "Kind": "func", + "Pkg": "github.com/xhd2015/xgo/runtime/test/trace/snapshot", + "IdentityName": "handleL3", + "Name": "handleL3", + "RecvType": "", + "RecvPtr": false, + "Interface": false, + "Generic": false, + "Closure": false, + "Stdlib": false, + "File": "/Users/xhd2015/Projects/xhd2015/xgo/runtime/test/trace/snapshot/snapshot.go", + "Line": 0, + "RecvName": "", + "ArgNames": [ + "a" + ], + "ResNames": [ + "_r0" + ], + "FirstArgCtx": false, + "LastResultErr": false + }, + "Begin": 0, + "End": 0, + "Args": { + "a": { + "Last": "handleL3", + "Count": 1 + } + }, + "Results": { + "_r0": { + "Last": "handleL1", + "Count": 2 + } + }, + "Panic": false, + "Error": "", + "Children": null + } + ] + } + ] + } + ] + } + ] +}` diff --git a/runtime/trace/stack.go b/runtime/trace/stack.go index 476c8983..263b9896 100644 --- a/runtime/trace/stack.go +++ b/runtime/trace/stack.go @@ -27,8 +27,12 @@ type Stack struct { Begin int64 // us End int64 // us - Args core.Object - Results core.Object + Args core.Object + Results core.Object + + // is recorded as snapshot + Snapshot bool + Panic bool Error error Children []*Stack @@ -177,6 +181,7 @@ func (c *Stack) Export(opts *ExportOptions) *StackExport { End: c.End, Args: args, Results: results, + Snapshot: c.Snapshot, Panic: c.Panic, Error: errMsg, Children: ((stacks)(c.Children)).Export(opts), diff --git a/runtime/trace/stack_export.go b/runtime/trace/stack_export.go index 0612a05e..eea7bd10 100644 --- a/runtime/trace/stack_export.go +++ b/runtime/trace/stack_export.go @@ -25,8 +25,12 @@ type StackExport struct { Args interface{} Results interface{} - Panic bool - Error string + + // is recorded as snapshot + Snapshot bool + + Panic bool + Error string Children []*StackExport } diff --git a/runtime/trace/trace.go b/runtime/trace/trace.go index 5573f0b6..e6bdbe82 100644 --- a/runtime/trace/trace.go +++ b/runtime/trace/trace.go @@ -155,12 +155,14 @@ type CollectOptions struct { } type collectOpts struct { - name string - onComplete func(root *Root) - filter []func(stack *Stack) bool - root *Root - options *CollectOptions - exportOptions *ExportOptions + name string + onComplete func(root *Root) + filters []func(stack *Stack) bool + postFilters []func(stack *Stack) + snapshotFilters []func(stack *Stack) bool + root *Root + options *CollectOptions + exportOptions *ExportOptions } func Options() *collectOpts { @@ -178,7 +180,17 @@ func (c *collectOpts) OnComplete(f func(root *Root)) *collectOpts { } func (c *collectOpts) WithFilter(f func(stack *Stack) bool) *collectOpts { - c.filter = append(c.filter, f) + c.filters = append(c.filters, f) + return c +} + +func (c *collectOpts) WithPostFilter(f func(stack *Stack)) *collectOpts { + c.postFilters = append(c.postFilters, f) + return c +} + +func (c *collectOpts) WithSnapshot(f func(stack *Stack) bool) *collectOpts { + c.snapshotFilters = append(c.snapshotFilters, f) return c } @@ -256,10 +268,22 @@ func handleTracePre(ctx context.Context, f *core.FuncInfo, args core.Object, res initial = true } } else { - if !checkFilter(stack, localOpts.filter) { + if !checkFilters(stack, localOpts.filters) { // do not collect trace if filtered out return nil, trap.ErrSkip } + var anySnapshot bool + for _, f := range localOpts.snapshotFilters { + if f(stack) { + anySnapshot = true + break + } + } + if anySnapshot { + stack.Snapshot = true + stack.Args = premarshal(stack.Args) + } + localRoot = localOpts.root if localRoot == nil { initial = true @@ -305,7 +329,7 @@ func handleTracePre(ctx context.Context, f *core.FuncInfo, args core.Object, res return prevTop, nil } -func checkFilter(stack *Stack, filters []func(stack *Stack) bool) bool { +func checkFilters(stack *Stack, filters []func(stack *Stack) bool) bool { for _, f := range filters { if !f(stack) { return false @@ -333,6 +357,9 @@ func handleTracePost(ctx context.Context, f *core.FuncInfo, args core.Object, re panic(fmt.Errorf("unbalanced stack")) } root = localOpts.root + for _, f := range localOpts.postFilters { + f(root.Top) + } } else { v, ok := stackMap.Load(key) if !ok { @@ -340,6 +367,9 @@ func handleTracePost(ctx context.Context, f *core.FuncInfo, args core.Object, re } root = v.(*Root) } + if root.Top != nil && root.Top.Snapshot { + root.Top.Results = premarshal(root.Top.Results) + } // detect panic pe := __xgo_link_peek_panic() @@ -543,3 +573,34 @@ func emitTrace(name string, root *Root, opts *ExportOptions) error { }) return err } + +func premarshal(v core.Object) (res core.Object) { + var err error + var data []byte + defer func() { + if e := recover(); e != nil { + if pe, ok := e.(error); ok { + err = pe + } else { + err = fmt.Errorf("marshal %T: %v", v, e) + } + } + if err != nil { + data = []byte(`{"err":` + strconv.Quote(err.Error()) + ` }`) + } + res = &premarshaled{Object: v, data: data} + }() + data, err = MarshalAnyJSON(v) + return +} + +type premarshaled struct { + core.Object + data []byte +} + +var _ core.Object = (*premarshaled)(nil) + +func (c *premarshaled) MarshalJSON() ([]byte, error) { + return c.data, nil +} diff --git a/script/run-test/main.go b/script/run-test/main.go index 54ca387e..d78cc6fa 100644 --- a/script/run-test/main.go +++ b/script/run-test/main.go @@ -178,6 +178,10 @@ var extraSubTests = []*TestCase{ dir: "runtime/test/trace/marshal/exclude", flags: []string{"--mock-rule", `{"pkg":"encoding/json","name":"newTypeEncoder","action":"exclude"}`}, }, + { + name: "trace-snapshot", + dir: "runtime/test/trace/snapshot", + }, } func main() {