Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplify trace output #57

Merged
merged 3 commits into from
Apr 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/xgo/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package main
import "fmt"

const VERSION = "1.0.22"
const REVISION = "f174b18f76bff4bd8acddffd03835b760dad03d8+1"
const NUMBER = 175
const REVISION = "b95076921b299cab29e79a1642abf005462fb732+1"
const NUMBER = 177

func getRevision() string {
revSuffix := ""
Expand Down
4 changes: 2 additions & 2 deletions runtime/core/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

const VERSION = "1.0.22"
const REVISION = "f174b18f76bff4bd8acddffd03835b760dad03d8+1"
const NUMBER = 175
const REVISION = "b95076921b299cab29e79a1642abf005462fb732+1"
const NUMBER = 177

// these fields will be filled by compiler
const XGO_VERSION = ""
Expand Down
2 changes: 1 addition & 1 deletion runtime/test/trace/trace_mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
177 changes: 177 additions & 0 deletions runtime/test/trace_marshal/decyclic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
package trace_marshal

import (
"reflect"
"unsafe"
)

// cyclic are often caused by tree like data structures
//
// NOTE: this file is a temporiray backup
// decyclic seems has signaficant memory consumption
// making it slow to decyclic when encounters large data
//
// Problems with modifying decyclic:
//
// may cause data race because value is being
// used in another goroutine
type decyclicer struct {
seen map[uintptr]struct{}
}

func Decyclic(v interface{}) interface{} {
if v == nil {
return nil
}
d := &decyclicer{
seen: map[uintptr]struct{}{},
}
arr := []interface{}{v}
d.clear(reflect.ValueOf(arr), func(r reflect.Value) {
panic("should not call slice's set")
})
return arr[0]
}

// func makeAddrable(v reflect.Value, set func(r reflect.Value)) reflect.Value {
// if v.CanAddr() {
// return v
// }
// if false {
// p := reflect.New(v.Type())
// p.Elem().Set(v)
// x := p.Elem()
// set(x)
// return x
// }
// panic("not addressable")
// }

func (c *decyclicer) clear(v reflect.Value, set func(r reflect.Value)) {
// fmt.Printf("clear: %v\n", v.Type())
switch v.Kind() {
case reflect.Ptr:
if v.IsNil() {
return
}
// if !v.CanAddr() {
// return
// }

// only pointer can create cyclic
ptr := v.Pointer()
if ptr == 0 {
return
}
if _, ok := c.seen[ptr]; ok {
// fmt.Printf("found : 0x%x -> %v\n", ptr, v.Interface())
set(reflect.Zero(v.Type()))
return
}
c.seen[ptr] = struct{}{}
defer delete(c.seen, ptr)

c.clear(v.Elem(), func(r reflect.Value) {
v.Elem().Set(r)
})
case reflect.Interface:
if v.IsNil() {
return
}
// if !v.CanAddr() {
// return
// }
c.clear(v.Elem(), func(r reflect.Value) {
// NOTE: interface{} is special
// we can directly can call v.Set
// instead of v.Elem().Set()
v.Set(r)
if v.Elem().Kind() == reflect.Ptr && v.Elem().IsNil() {
// fmt.Printf("found isNil\n")
// avoid {nil-value,non-nil type}
set(reflect.Zero(v.Type()))
}
})
case reflect.Array, reflect.Slice:
switch v.Type().Elem().Kind() {
case reflect.Int64, reflect.Int, reflect.Int32, reflect.Int16,
reflect.Uint64, reflect.Uint, reflect.Uint32, reflect.Uint16,
reflect.Float64, reflect.Float32,
reflect.String,
reflect.Bool:
return
case reflect.Int8, reflect.Uint8:
// []byte -> Uint8
// ignore some: 10K JSON
n := v.Len()
if v.Kind() == reflect.Slice && n > 10*1024 {
// reserve first 16 and last 16
// S[:16] ... + S[len(S)-16:]
const reserve = 16
const ellipse = 3
const totalLen = reserve*2 + ellipse
newSlice := reflect.MakeSlice(v.Type(), totalLen, totalLen)
for i := 0; i < reserve; i++ {
newSlice.Index(i).Set(v.Index(i))
}
for i := 0; i < ellipse; i++ {
if v.Kind() == reflect.Uint8 {
newSlice.Index(reserve + i).SetUint('.')
} else if v.Kind() == reflect.Int8 {
newSlice.Index(reserve + i).SetInt('.')
}
}
for i := 0; i < reserve; i++ {
newSlice.Index(reserve + ellipse + i).Set(v.Index(n - reserve + i))
}
set(newSlice)
}
return
}
for i := 0; i < v.Len(); i++ {
e := v.Index(i)
c.clear(e, func(r reflect.Value) {
e.Set(r)
})
}
case reflect.Map:
if !v.CanAddr() {
return
}
iter := v.MapRange()
// sets := [][2]reflect.Value{}
for iter.Next() {
vi := v.MapIndex(iter.Key())
c.clear(vi, func(r reflect.Value) {
v.SetMapIndex(iter.Key(), r)
})
}
case reflect.Struct:
if !v.CanAddr() {
return
}
// fmt.Printf("struct \n")
// make struct addrable
// v = makeAddrable(v, set)
// fmt.Printf("struct can addr: %v\n", v.CanAddr())
for i := 0; i < v.NumField(); i++ {
field := v.Field(i)
// fmt.Printf("field: %v %v\n", field, field.CanAddr())
if field.CanSet() && false {
c.clear(field, func(r reflect.Value) {
field.Set(r)
})
} else {
e := reflect.NewAt(field.Type(), unsafe.Pointer(field.UnsafeAddr()))
c.clear(e.Elem(), func(r reflect.Value) {
e.Elem().Set(r)
})
// panic(fmt.Errorf("cannot set: %v", field))
}
}
case reflect.Chan, reflect.Func:
// ignore
default:
// int
}
}
100 changes: 100 additions & 0 deletions runtime/test/trace_marshal/decyclic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package trace_marshal

import (
"fmt"
"reflect"
"testing"
)

type _struct struct {
self *_struct
}

func TestDecyclicUnexported(t *testing.T) {
s := &_struct{}
s.self = s

vs := Decyclic(s)
if vs.(*_struct).self != nil {
t.Fatalf("expect vs.self to be nil")
}
}

type _structMap struct {
m map[string]*_structMap
}

func TestDecyclicMapValue(t *testing.T) {
s := &_structMap{
m: make(map[string]*_structMap),
}
s.m["self"] = s

v1 := reflect.ValueOf(s).Interface()
if v1.(*_structMap).m["self"] == nil {
t.Fatalf("expect v1.m['self'] to be non nil")
}

vs := Decyclic(s)
if vs.(*_structMap).m["self"] != nil {
t.Fatalf("expect vs.m['self'] to be nil")
}
}

type _structWithIntf struct {
self interface{}
}

func TestDecyclicInterface(t *testing.T) {
s := &_structWithIntf{}
s.self = s

vs := Decyclic(s)
if vs.(*_structWithIntf).self != nil {
t.Fatalf("expect vs.self to be nil")
}
}

type _valueStruct struct {
A int
}

func TestDecyclicValueStruct(t *testing.T) {
s := _valueStruct{
A: 123,
}

vs := Decyclic(s)
vsA := vs.(_valueStruct).A
if vsA != 123 {
t.Fatalf("vs.A: %v", vsA)
}
}

func TestDecyclicMapInterface(t *testing.T) {
m := make(map[string]interface{})
m["A"] = 123

vs := Decyclic(m)
vsA := vs.(map[string]interface{})["A"]
if fmt.Sprint(vsA) != "123" {
t.Fatalf("vs.A: %v", vsA)
}
}

type byteSliceHolder struct {
slice []byte
}

func TestDecyclicLargeByteSlice(t *testing.T) {
s := &byteSliceHolder{
slice: make([]byte, 10*1024+1),
}
vs := Decyclic(s)
vslice := vs.(*byteSliceHolder).slice

// 16 + ... + 16
if len(vslice) != 35 {
t.Logf("expect len to be %d, actual: %v", 35, len(vslice))
}
}
22 changes: 22 additions & 0 deletions runtime/test/trace_marshal/marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,28 @@ func TestMarshalAnyJSON(t *testing.T) {
}
}

type cyclic struct {
Self *cyclic
Name string
}

func TestMarshalCyclicJSONShouldError(t *testing.T) {
c := &cyclic{
Name: "cyclic",
}
c.Self = c

//
_, err := trace.MarshalAnyJSON(c)
if err == nil {
t.Fatalf("expect marshal err")
}
expectErrMsg := "encountered a cycle via *trace_marshal.cyclic"
if !strings.Contains(err.Error(), expectErrMsg) {
t.Fatalf("expect err: %q, actual: %q", expectErrMsg, err.Error())
}
}

func exampleReturnFunc() context.CancelFunc {
_, f := context.WithTimeout(context.TODO(), 10*time.Millisecond)
return f
Expand Down
2 changes: 1 addition & 1 deletion runtime/test/trace_panic_peek/trace_panic_peek_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestTracePanicPeek(t *testing.T) {
var traceData []byte
trace.Options().OnComplete(func(root *trace.Root) {
var err error
traceData, err = trace.MarshalAnyJSON(root.Export())
traceData, err = trace.MarshalAnyJSON(root.Export(nil))
if err != nil {
t.Fatal(err)
}
Expand Down
16 changes: 10 additions & 6 deletions runtime/trace/marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,37 @@ import (
// to JSON, when it encounters unmarshalable values
// like func, chan, it will bypass these values.
func MarshalAnyJSON(v interface{}) ([]byte, error) {
funcInfo := functab.Info("encoding/json", "newTypeEncoder")
if funcInfo == nil {
newTypeEncoder := functab.Info("encoding/json", "newTypeEncoder")
if newTypeEncoder == nil {
fmt.Fprintf(os.Stderr, "WARNING: encoding/json.newTypeEncoder not trapped(requires xgo).\n")
return json.Marshal(v)
}

// get the unmarshalable function
unmarshalable := getMarshaler(funcInfo.Func, reflect.TypeOf(unmarshalableFunc))
unmarshalable := getMarshaler(newTypeEncoder.Func, reflect.TypeOf(unmarshalableFunc))
var data []byte
var err error
// mock the encoding json
trap.WithFuncOverride(funcInfo, &trap.Interceptor{
trap.WithFuncOverride(newTypeEncoder, &trap.Interceptor{
// 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 != funcInfo {
if f != newTypeEncoder {
return nil
}
resField := result.GetFieldIndex(0)

// if unmarshalable, replace with an empty struct
if reflect.ValueOf(resField.Value()).Pointer() == reflect.ValueOf(unmarshalable).Pointer() {
resField.Set(getMarshaler(funcInfo.Func, reflect.TypeOf(struct{}{})))
resField.Set(getMarshaler(newTypeEncoder.Func, reflect.TypeOf(struct{}{})))
}
return nil
},
}, func() {
data, err = json.Marshal(v)
})

return data, err
}

Expand Down
Loading
Loading