diff --git a/cmd/xgo/version.go b/cmd/xgo/version.go index 73195047..89dbb952 100644 --- a/cmd/xgo/version.go +++ b/cmd/xgo/version.go @@ -3,8 +3,8 @@ package main import "fmt" const VERSION = "1.0.22" -const REVISION = "b41281e9fca79eb3bbde5e4347ab8f37763bc545+1" -const NUMBER = 176 +const REVISION = "b95076921b299cab29e79a1642abf005462fb732+1" +const NUMBER = 177 func getRevision() string { revSuffix := "" diff --git a/runtime/core/version.go b/runtime/core/version.go index aacad95d..c51ef083 100644 --- a/runtime/core/version.go +++ b/runtime/core/version.go @@ -7,8 +7,8 @@ import ( ) const VERSION = "1.0.22" -const REVISION = "b41281e9fca79eb3bbde5e4347ab8f37763bc545+1" -const NUMBER = 176 +const REVISION = "b95076921b299cab29e79a1642abf005462fb732+1" +const NUMBER = 177 // these fields will be filled by compiler const XGO_VERSION = "" diff --git a/runtime/test/trace/trace_mock_test.go b/runtime/test/trace/trace_mock_test.go index 7c03029f..449cf881 100644 --- a/runtime/test/trace/trace_mock_test.go +++ b/runtime/test/trace/trace_mock_test.go @@ -18,7 +18,7 @@ func TestMockedFuncShouldShowInTrace(t *testing.T) { }) h() }) - data, err := trace.MarshalAnyJSON(root.Export()) + data, err := trace.MarshalAnyJSON(root.Export(nil)) if err != nil { t.Fatal(err) } diff --git a/runtime/test/trace_marshal/marshal_test.go b/runtime/test/trace_marshal/marshal_test.go index 8bbb1bf4..3832c599 100644 --- a/runtime/test/trace_marshal/marshal_test.go +++ b/runtime/test/trace_marshal/marshal_test.go @@ -72,20 +72,20 @@ type cyclic struct { Name string } -func TestMarshalCyclicJSON(t *testing.T) { +func TestMarshalCyclicJSONShouldError(t *testing.T) { c := &cyclic{ Name: "cyclic", } c.Self = c - res, err := trace.MarshalAnyJSON(c) - if err != nil { - t.Fatal(err) + // + _, err := trace.MarshalAnyJSON(c) + if err == nil { + t.Fatalf("expect marshal err") } - resStr := string(res) - expect := `{"Self":null,"Name":"cyclic"}` - if resStr != expect { - t.Fatalf("expect res to be %q, actual: %q", expect, resStr) + expectErrMsg := "encountered a cycle via *trace_marshal.cyclic" + if !strings.Contains(err.Error(), expectErrMsg) { + t.Fatalf("expect err: %q, actual: %q", expectErrMsg, err.Error()) } } diff --git a/runtime/trace/marshal.go b/runtime/trace/marshal.go index f273522e..04572e19 100644 --- a/runtime/trace/marshal.go +++ b/runtime/trace/marshal.go @@ -28,9 +28,9 @@ func MarshalAnyJSON(v interface{}) ([]byte, error) { var err error // mock the encoding json trap.WithFuncOverride(newTypeEncoder, &trap.Interceptor{ - Pre: func(ctx context.Context, f *core.FuncInfo, args, result core.Object) (interface{}, error) { - return nil, nil - }, + // Pre: func(ctx context.Context, f *core.FuncInfo, args, result core.Object) (interface{}, error) { + // return nil, nil + // }, Post: func(ctx context.Context, f *core.FuncInfo, args, result core.Object, data interface{}) error { if f != newTypeEncoder { return nil diff --git a/runtime/trace/stack.go b/runtime/trace/stack.go index 2bbd03b3..6cad54aa 100644 --- a/runtime/trace/stack.go +++ b/runtime/trace/stack.go @@ -1,6 +1,8 @@ package trace import ( + "encoding/json" + "fmt" "time" "github.com/xhd2015/xgo/runtime/core" @@ -30,21 +32,87 @@ type Stack struct { // // for example: google.golang.org/protobuf/internal/order type ExportOptions struct { + // suppress error when marshalling + // arguments and results + DisableErrSilent bool + SizeLimit int // 0: default limit 4K + AppearanceLimit int // 0: default limit 100 + FilterStack func(stack *StackExport) *StackExport + FilterRoot func(root *RootExport) *RootExport - MarshalJSON func(root *RootExport) ([]byte, error) + MarshalRoot func(root *RootExport) ([]byte, error) + + stats map[string]map[string]*stat +} +type stat struct { + total int + current int +} + +func (c *ExportOptions) getSizeLimit() int { + if c == nil || c.SizeLimit == 0 { + return 4 * 1024 + } + return c.SizeLimit +} +func (c *ExportOptions) getAppearanceLimit() int { + if c == nil || c.AppearanceLimit == 0 { + return 100 + } + return c.AppearanceLimit } func (c *Root) Export(opts *ExportOptions) *RootExport { if c == nil { return nil } + if opts == nil { + opts = &ExportOptions{} + } + if opts.getAppearanceLimit() > 0 { + opts.stats = getStats(c) + } + return &RootExport{ Begin: c.Begin, Children: (stacks)(c.Children).Export(opts), } } +func getStats(root *Root) map[string]map[string]*stat { + mapping := make(map[string]map[string]*stat) + var traverse func(stack *Stack) + traverse = func(st *Stack) { + if st == nil { + return + } + if st.FuncInfo != nil { + pkg := st.FuncInfo.Pkg + fn := st.FuncInfo.IdentityName + fnMapping := mapping[pkg] + if fnMapping == nil { + fnMapping = make(map[string]*stat) + mapping[pkg] = fnMapping + } + st := fnMapping[fn] + if st == nil { + st = &stat{} + fnMapping[fn] = st + } + st.total++ + } + + for _, st := range st.Children { + traverse(st) + } + } + for _, st := range root.Children { + traverse(st) + } + return mapping +} + type stacks []*Stack func (c stacks) Export(opts *ExportOptions) []*StackExport { @@ -58,6 +126,18 @@ func (c stacks) Export(opts *ExportOptions) []*StackExport { if exportStack == nil { continue } + if exportStack.FuncInfo != nil && opts != nil && opts.stats != nil { + apprLimit := opts.getAppearanceLimit() + if apprLimit > 0 { + fnStat := opts.stats[exportStack.FuncInfo.Pkg][exportStack.FuncInfo.IdentityName] + if fnStat != nil && fnStat.total > apprLimit { + if fnStat.current >= apprLimit { + continue + } + fnStat.current++ + } + } + } list = append(list, exportStack) } return list @@ -67,16 +147,29 @@ func (c *Stack) Export(opts *ExportOptions) *StackExport { if c == nil { return nil } + var errMsg string if c.Error != nil { errMsg = c.Error.Error() } + var args interface{} = c.Args + var results interface{} = c.Results + + sizeLimit := opts.getSizeLimit() + if sizeLimit > 0 { + args = &LimitSize{args, sizeLimit} + results = &LimitSize{results, sizeLimit} + } + if opts == nil || !opts.DisableErrSilent { + args = &ErrSilent{args} + results = &ErrSilent{results} + } stack := &StackExport{ FuncInfo: ExportFuncInfo(c.FuncInfo, opts), Begin: c.Begin, End: c.End, - Args: c.Args, - Results: c.Results, + Args: args, + Results: results, Panic: c.Panic, Error: errMsg, Children: ((stacks)(c.Children)).Export(opts), @@ -111,3 +204,44 @@ func ExportFuncInfo(c *core.FuncInfo, opts *ExportOptions) *FuncInfoExport { Line: c.Line, } } + +// make json err silent +type ErrSilent struct { + Data interface{} +} + +func (c *ErrSilent) MarshalJSON() (data []byte, err error) { + defer func() { + if e := recover(); e != nil { + if pe, ok := e.(error); ok { + err = pe + } else { + err = fmt.Errorf("panic: %v", e) + } + } + if err != nil { + data = []byte(fmt.Sprintf(`{"error":%q}`, err.Error())) + err = nil + } + }() + data, err = json.Marshal(c.Data) + return +} + +// make json err silent +type LimitSize struct { + Data interface{} + Limit int +} + +func (c *LimitSize) MarshalJSON() ([]byte, error) { + data, err := json.Marshal(c.Data) + if err != nil { + return nil, err + } + if c.Limit <= 0 || c.Limit >= len(data) { + return data, nil + } + // shorten + return []byte(fmt.Sprintf(`{"size":%d, "sizeBeforeShrink":%d,"partialData":%q}`, c.Limit, len(data), string(data[:c.Limit]))), nil +} diff --git a/runtime/trace/trace.go b/runtime/trace/trace.go index 4f210e88..92c80df4 100644 --- a/runtime/trace/trace.go +++ b/runtime/trace/trace.go @@ -367,8 +367,8 @@ func fmtStack(root *Root, opts *ExportOptions) (data []byte, err error) { if opts.FilterRoot != nil { exportRoot = opts.FilterRoot(exportRoot) } - if opts.MarshalJSON != nil { - return opts.MarshalJSON(exportRoot) + if opts.MarshalRoot != nil { + return opts.MarshalRoot(exportRoot) } } return MarshalAnyJSON(exportRoot)