diff --git a/gen/main.go b/gen/main.go index 981de3a..d057104 100644 --- a/gen/main.go +++ b/gen/main.go @@ -18,7 +18,7 @@ func main() { })) write("types/stackframe.go", generate(&Type_jdk_types_StackFrame, options{ skipFields: []string{ - "lineNumber", "bytecodeIndex", "type", + "bytecodeIndex", "type", }, cpool: false, })) @@ -42,7 +42,6 @@ func main() { write("types/method.go", generate(&Type_jdk_types_Method, options{ cpool: true, sortedIDs: true, - Scratch: true, skipFields: []string{ "hidden", "descriptor", @@ -91,7 +90,6 @@ func write(dst, s string) { type options struct { cpool bool sortedIDs bool - Scratch bool doNotKeepData bool skipFields []string //todo make skip fields runtime option, but still saving memory - explode struct to fields } @@ -157,9 +155,7 @@ func generate(typ *def.Class, opt options) string { } } } - if opt.Scratch { - res += fmt.Sprintf(" Scratch []byte\n") - } + res += fmt.Sprintf("}\n\n") res += fmt.Sprintf("\n") @@ -506,6 +502,7 @@ func emitString(depth int) string { res += pad(depth) + "pos++\n" res += pad(depth) + "switch b_ {\n" res += pad(depth) + "case 0:\n" + res += pad(depth) + " break\n" res += pad(depth) + "case 1:\n" res += pad(depth) + " break\n" res += pad(depth) + "case 3:\n" diff --git a/go.work b/go.work index 0a51e47..948da90 100644 --- a/go.work +++ b/go.work @@ -1,6 +1,6 @@ go 1.21 use ( -. -./pprof -) \ No newline at end of file + . + ./pprof +) diff --git a/go.work.sum b/go.work.sum index c98c7d3..a82f991 100644 --- a/go.work.sum +++ b/go.work.sum @@ -1,16 +1,39 @@ +connectrpc.com/connect v1.14.0 h1:PDS+J7uoz5Oui2VEOMcfz6Qft7opQM9hPiKvtGC01pA= +connectrpc.com/connect v1.14.0/go.mod h1:uoAq5bmhhn43TwhaKdGKN/bZcGtzPW1v+ngDTn5u+8s= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg= github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89 h1:aPflPkRFkVwbW6dmcVqfgwp1i+UWGFH6VgR1Jim5Ygc= +github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs= +github.com/chromedp/chromedp v0.9.2 h1:dKtNz4kApb06KuSXoTQIyUC2TrA0fhGDwNZf3bcgfKw= +github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs= +github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= +github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= +github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= +github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk= +github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= +github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= +github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= +github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.2.1 h1:F2aeBZrm2NDsc7vbovKrWSogd4wvfAxg0FQ89/iqOTk= +github.com/gobwas/ws v1.2.1/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/KY= github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= +github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= @@ -23,18 +46,27 @@ github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwa github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY= github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= -golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= -golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= +golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a h1:fwgW9j3vHirt4ObdHoYNwuO24BEZjSzbh+zPaNWoiY8= google.golang.org/genproto v0.0.0-20231012201019-e917dd12ba7a/go.mod h1:EMfReVxb80Dq1hhioy0sOsY9jCE46YDgHlJ7fWVUWRE= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17 h1:wpZ8pe2x1Q3f2KyT5f8oP/fa9rHAKgFPr/HZdNuS+PQ= +google.golang.org/genproto v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:J7XzRzVy1+IPwWHZUzoD0IccYZIrXILAQpc+Qy9CMhY= google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97 h1:W18sezcAYs+3tDZX4F80yctqa12jcP1PUS2gQu1zTPU= google.golang.org/genproto/googleapis/api v0.0.0-20231002182017-d307bd883b97/go.mod h1:iargEX0SFPm3xcfMI0d1domjg0ZF4Aa0p2awqyxhvF0= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17 h1:JpwMPBpFN3uKhdaekDpiNlImDdkUAyiJ6ez/uxGaUSo= +google.golang.org/genproto/googleapis/api v0.0.0-20231106174013-bbf56f31fb17/go.mod h1:0xJLfVdJqpAPl8tDg1ujOCGzx6LFLttXT5NhllGOXY4= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f h1:ultW7fxlIvee4HYrtnaRPon9HpEgFk5zYpmfMgtKB5I= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231120223509-83a465c0220f/go.mod h1:L9KNLi232K1/xB6f7AlSX692koaRnKaWSR0stBki0Yc= google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= diff --git a/parser/cpool.go b/parser/cpool.go index 10cdf6d..f62950d 100644 --- a/parser/cpool.go +++ b/parser/cpool.go @@ -8,61 +8,68 @@ import ( ) func (p *Parser) readConstantPool(pos int) error { - if err := p.seek(pos); err != nil { - return err - } - sz, err := p.varInt() - if err != nil { - return err - } - typ, err := p.varInt() - if err != nil { - return err - } - startTimeTicks, err := p.varLong() - if err != nil { - return err - } - duration, err := p.varInt() - if err != nil { - return err - } - delta, err := p.varInt() - if err != nil { - return err - } - typeMask, err := p.varInt() - if err != nil { - return err - } - n, err := p.varInt() - if err != nil { - return err - } - //fmt.Printf("ConstantPool: size %d type %d startTimeTicks %d duration %d delta %d typeMask %d n %d\n", sz, typ, startTimeTicks, duration, delta, typeMask, n) - _ = startTimeTicks - _ = duration - _ = delta - _ = sz - - if typeMask != 1 { - return fmt.Errorf("expected ConstantPool typeMask 1, got %d", typeMask) - } - //if n != 9 { - // return fmt.Errorf("expected ConstantPool n 9, got %d", n) - //} - for i := 0; i < int(n); i++ { - typ, err = p.varInt() + for { + if err := p.seek(pos); err != nil { + return err + } + sz, err := p.varLong() if err != nil { return err } - c := p.TypeMap.IDMap[def.TypeID(typ)] - if c == nil { - return fmt.Errorf("unknown type %d", def.TypeID(typ)) + typ, err := p.varLong() + if err != nil { + return err + } + startTimeTicks, err := p.varLong() + if err != nil { + return err + } + duration, err := p.varLong() + if err != nil { + return err + } + delta, err := p.varLong() + if err != nil { + return err } - err = p.readConstants(c) + typeMask, err := p.varInt() // boolean flush if err != nil { - return fmt.Errorf("error reading %+v %w", c, err) + return err + } + n, err := p.varInt() + if err != nil { + return err + } + _ = startTimeTicks + _ = duration + _ = delta + _ = sz + _ = typeMask + _ = typ + + id := int(int64(delta)) + + for i := 0; i < int(n); i++ { + typ, err := p.varLong() + if err != nil { + return err + } + c := p.TypeMap.IDMap[def.TypeID(typ)] + if c == nil { + return fmt.Errorf("unknown type %d", def.TypeID(typ)) + } + err = p.readConstants(c) + if err != nil { + return fmt.Errorf("error reading %+v %w", c, err) + } + } + if delta == 0 { + break + } else { + pos += id + if pos <= 0 { + break + } } } return nil @@ -70,6 +77,9 @@ func (p *Parser) readConstantPool(pos int) error { func (p *Parser) readConstants(c *def.Class) error { switch c.Name { + case "jdk.types.ChunkHeader": + p.pos += chunkHeaderSize + return nil case "jdk.types.FrameType": o, err := p.FrameTypes.Parse(p.buf[p.pos:], p.bindFrameType, &p.TypeMap) p.pos += o @@ -99,6 +109,9 @@ func (p *Parser) readConstants(c *def.Class) error { p.pos += o return err case "profiler.types.LogLevel": + if p.bindLogLevel == nil { + return fmt.Errorf("no \"profiler.types.LogLevel\"") + } o, err := p.LogLevels.Parse(p.buf[p.pos:], p.bindLogLevel, &p.TypeMap) p.pos += o return err diff --git a/parser/header.go b/parser/header.go index 5f91ab3..d36c621 100644 --- a/parser/header.go +++ b/parser/header.go @@ -38,12 +38,7 @@ func (p *Parser) readChunkHeader(pos int) error { if p.options.ChunkSizeLimit > 0 && h.Size > p.options.ChunkSizeLimit { return fmt.Errorf("chunk size %d exceeds limit %d", h.Size, p.options.ChunkSizeLimit) } - if h.Features != 1 { - return fmt.Errorf("unsupported features %d", h.Features) - } p.header = h p.chunkEnd = pos + h.Size - //fmt.Printf("Chunk header: %s\n", h.String()) - return nil } diff --git a/parser/parser.go b/parser/parser.go index 0aa956b..c90488d 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -105,14 +105,14 @@ func (p *Parser) ParseEvent() (def.TypeID, error) { } } pp := p.pos - size, err := p.varInt() + size, err := p.varLong() if err != nil { return 0, err } if size == 0 { return 0, def.ErrIntOverflow } - typ, err := p.varInt() + typ, err := p.varLong() if err != nil { return 0, err } @@ -272,13 +272,13 @@ func (p *Parser) readChunk(pos int) error { return fmt.Errorf("error reading metadata: %w", err) } if err := p.readConstantPool(pos + p.header.OffsetConstantPool); err != nil { - return fmt.Errorf("error reading CP: %w", err) + return fmt.Errorf("error reading CP: %w @ %d", err, pos+p.header.OffsetConstantPool) } pp := p.options.SymbolProcessor if pp != nil { pp(&p.Symbols) } - p.pos = pos + chunkHeaderSize + int(p.metaSize) + p.pos = pos + chunkHeaderSize return nil } @@ -344,7 +344,7 @@ func (p *Parser) string() (string, error) { } b := p.buf[p.pos] p.pos++ - switch b { + switch b { //todo implement 2 case 0: return "", nil //todo this should be nil case 1: @@ -356,12 +356,35 @@ func (p *Parser) string() (string, error) { } str := *(*string)(unsafe.Pointer(&bs)) return str, nil + case 4: + return p.charArrayString() default: return "", fmt.Errorf("unknown string type %d", b) } } +func (p *Parser) charArrayString() (string, error) { + l, err := p.varInt() + if err != nil { + return "", err + } + if l < 0 { + return "", def.ErrIntOverflow + } + buf := make([]rune, int(l)) + for i := 0; i < int(l); i++ { + c, err := p.varInt() + if err != nil { + return "", err + } + buf[i] = rune(c) + } + + res := string(buf) + return res, nil +} + func (p *Parser) bytes() ([]byte, error) { l, err := p.varInt() if err != nil { @@ -439,9 +462,6 @@ func (p *Parser) checkTypes() error { if typeCPSymbol == nil { return fmt.Errorf("missing \"jdk.types.Symbol\"") } - if typeCPLogLevel == nil { - return fmt.Errorf("missing \"profiler.types.LogLevel\"") - } if typeCPStackTrace == nil { return fmt.Errorf("missing \"jdk.types.StackTrace\"") } @@ -455,7 +475,11 @@ func (p *Parser) checkTypes() error { p.TypeMap.T_METHOD = typeCPMethod.ID p.TypeMap.T_PACKAGE = typeCPPackage.ID p.TypeMap.T_SYMBOL = typeCPSymbol.ID - p.TypeMap.T_LOG_LEVEL = typeCPLogLevel.ID + if typeCPLogLevel != nil { + p.TypeMap.T_LOG_LEVEL = typeCPLogLevel.ID + } else { + p.TypeMap.T_LOG_LEVEL = 0 + } p.TypeMap.T_STACK_TRACE = typeCPStackTrace.ID p.TypeMap.T_CLASS_LOADER = typeCPClassLoader.ID @@ -473,7 +497,11 @@ func (p *Parser) checkTypes() error { p.bindMethod = types2.NewBindMethod(typeCPMethod, &p.TypeMap) p.bindPackage = types2.NewBindPackage(typeCPPackage, &p.TypeMap) p.bindSymbol = types2.NewBindSymbol(typeCPSymbol, &p.TypeMap) - p.bindLogLevel = types2.NewBindLogLevel(typeCPLogLevel, &p.TypeMap) + if typeCPLogLevel != nil { + p.bindLogLevel = types2.NewBindLogLevel(typeCPLogLevel, &p.TypeMap) + } else { + p.bindLogLevel = nil + } p.bindStackTrace = types2.NewBindStackTrace(typeCPStackTrace, &p.TypeMap) p.bindStackFrame = types2.NewBindStackFrame(typeStackFrame, &p.TypeMap) diff --git a/parser/parser_test.go b/parser/parser_test.go deleted file mode 100644 index 122ff3e..0000000 --- a/parser/parser_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package parser - -import ( - "bytes" - "compress/gzip" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "testing" - "time" - - "github.com/grafana/jfr-parser/parser/types" -) - -var testfiles = []string{ - "example", - "async-profiler", // -e cpu -i 10ms --alloc 512k --wall 200ms --lock 10ms -d 60 (async-profiler 2.10) - "cortex-dev-01__kafka-0__cpu_lock0_alloc0__0", - "goland", - "goland-multichunk", -} - -type ExpectedActiveSetting struct { - Key, Value string -} -type ExpectedStacktrace struct { - Frames []string - ContextID int64 -} -type Expected struct { - ExecutionSample []ExpectedStacktrace - AllocInTLAB []ExpectedStacktrace - AllocOutsideTLAB []ExpectedStacktrace - MonitorEnter []ExpectedStacktrace - ThreadPark []ExpectedStacktrace - LiveObject []ExpectedStacktrace - ActiveSetting []ExpectedActiveSetting -} - -func TestParse(t *testing.T) { - for _, testfile := range testfiles { - t.Run(testfile, func(t *testing.T) { - jfrfile := "./testdata/" + testfile + ".jfr.gz" - jsonfile := "./testdata/" + testfile + "_parsed.json.gz" - jfr, err := readGzipFile(jfrfile) - if err != nil { - t.Fatalf("Unable to read JFR file: %s", err) - } - expectedJson, err := readGzipFile(jsonfile) - if err != nil { - t.Fatalf("Unable to read example_parsd.json") - } - t1 := time.Now() - parser := NewParser(jfr, Options{}) - - e := new(Expected) - stacktraceToFrames := func(stacktrace types.StackTraceRef) []string { - st := parser.GetStacktrace(stacktrace) - if st == nil { - t.Fatalf("stacktrace not found: %d\n", stacktrace) - } - frames := make([]string, len(st.Frames)) - for i, frame := range st.Frames { - m := parser.GetMethod(frame.Method) - if m == nil { - t.Fatalf("method not found: %d\n", frame.Method) - } - if m.Scratch == nil { - cls := parser.GetClass(m.Type) - if cls == nil { - t.Fatalf("class not found: %d\n", m.Type) - } - symbolString := parser.GetSymbolString(cls.Name) - getSymbolString := parser.GetSymbolString(m.Name) - m.Scratch = []byte(symbolString + "." + getSymbolString) - } - frames[i] = string(m.Scratch) - } - return frames - } - - for { - typ, err := parser.ParseEvent() - if err != nil { - if errors.Is(err, io.EOF) { - break - } - t.Fatalf("Failed to parse JFR: %s", err) - } - - switch typ { - case parser.TypeMap.T_EXECUTION_SAMPLE: - e.ExecutionSample = append(e.ExecutionSample, ExpectedStacktrace{ - Frames: stacktraceToFrames(parser.ExecutionSample.StackTrace), - ContextID: int64(parser.ExecutionSample.ContextId), - }) - case parser.TypeMap.T_ALLOC_IN_NEW_TLAB: - e.AllocInTLAB = append(e.AllocInTLAB, ExpectedStacktrace{ - Frames: stacktraceToFrames(parser.ObjectAllocationInNewTLAB.StackTrace), - ContextID: int64(parser.ObjectAllocationInNewTLAB.ContextId), - }) - case parser.TypeMap.T_ALLOC_OUTSIDE_TLAB: - e.AllocOutsideTLAB = append(e.AllocOutsideTLAB, ExpectedStacktrace{ - Frames: stacktraceToFrames(parser.ObjectAllocationOutsideTLAB.StackTrace), - ContextID: int64(parser.ObjectAllocationOutsideTLAB.ContextId), - }) - case parser.TypeMap.T_MONITOR_ENTER: - e.MonitorEnter = append(e.MonitorEnter, ExpectedStacktrace{ - Frames: stacktraceToFrames(parser.JavaMonitorEnter.StackTrace), - ContextID: int64(parser.JavaMonitorEnter.ContextId), - }) - case parser.TypeMap.T_THREAD_PARK: - e.ThreadPark = append(e.ThreadPark, ExpectedStacktrace{ - Frames: stacktraceToFrames(parser.ThreadPark.StackTrace), - ContextID: int64(parser.ThreadPark.ContextId), - }) - case parser.TypeMap.T_LIVE_OBJECT: - e.LiveObject = append(e.LiveObject, ExpectedStacktrace{ - Frames: stacktraceToFrames(parser.LiveObject.StackTrace), - ContextID: 0, - }) - case parser.TypeMap.T_ACTIVE_SETTING: - e.ActiveSetting = append(e.ActiveSetting, ExpectedActiveSetting{ - Key: parser.ActiveSetting.Name, - Value: parser.ActiveSetting.Value, - }) - - } - } - t2 := time.Now() - fmt.Println(t2.Sub(t1)) - actualJson, _ := json.Marshal(e) - //os.WriteFile("./testdata/"+testfile+"_parsed.json", actualJson, 0644) - if !bytes.Equal(expectedJson, actualJson) { - os.WriteFile("./"+testfile+"_actual.json", actualJson, 0644) - os.WriteFile("./"+testfile+"_expected.json", expectedJson, 0644) - - t.Fatalf("Failed to parse JFR: %s", err) - return - } - }) - } -} - -func BenchmarkParse(b *testing.B) { - marker := []byte("marker") - for _, testfile := range testfiles { - b.Run(testfile, func(b *testing.B) { - jfr, err := readGzipFile("./testdata/" + testfile + ".jfr.gz") - if err != nil { - b.Fatalf("Unable to read JFR file: %s", err) - } - b.ResetTimer() - b.ReportAllocs() - for i := 0; i < b.N; i++ { - parser := NewParser(jfr, Options{}) - - stacktraceToFrames := func(stacktrace types.StackTraceRef) []string { - st := parser.GetStacktrace(stacktrace) - if st == nil { - b.Fatalf("stacktrace not found: %d\n", stacktrace) - } - //frames := make([]string, len(st.Frames)) - for _, frame := range st.Frames { - m := parser.GetMethod(frame.Method) - if m == nil { - b.Fatalf("method not found: %d\n", frame.Method) - } - if m.Scratch == nil { - cls := parser.GetClass(m.Type) - if cls == nil { - b.Fatalf("class not found: %d\n", m.Type) - } - symbolString := parser.GetSymbolString(cls.Name) - getSymbolString := parser.GetSymbolString(m.Name) - _ = symbolString - _ = getSymbolString - //m.Scratch = symbolString + "." + getSymbolString - m.Scratch = marker - } - //frames[i] = m.Scratch - //return ni - } - return nil - } - - for { - typ, err := parser.ParseEvent() - if err != nil { - if errors.Is(err, io.EOF) { - break - } - b.Fatalf("Unable to parse JFR file: %s", err) - } - - switch typ { - case parser.TypeMap.T_EXECUTION_SAMPLE: - _ = stacktraceToFrames(parser.ExecutionSample.StackTrace) - case parser.TypeMap.T_ALLOC_IN_NEW_TLAB: - _ = stacktraceToFrames(parser.ObjectAllocationInNewTLAB.StackTrace) - case parser.TypeMap.T_ALLOC_OUTSIDE_TLAB: - _ = stacktraceToFrames(parser.ObjectAllocationOutsideTLAB.StackTrace) - case parser.TypeMap.T_MONITOR_ENTER: - _ = stacktraceToFrames(parser.JavaMonitorEnter.StackTrace) - case parser.TypeMap.T_THREAD_PARK: - _ = stacktraceToFrames(parser.ThreadPark.StackTrace) - case parser.TypeMap.T_LIVE_OBJECT: - _ = stacktraceToFrames(parser.LiveObject.StackTrace) - - case parser.TypeMap.T_ACTIVE_SETTING: - - } - - } - } - }) - } -} - -func readGzipFile(fname string) ([]byte, error) { - f, err := os.Open(fname) - if err != nil { - return nil, err - } - defer f.Close() - r, err := gzip.NewReader(f) - if err != nil { - return nil, err - } - defer r.Close() - return ioutil.ReadAll(r) -} diff --git a/parser/testdata/FastSlow_2024_01_16_180855.jfr.gz b/parser/testdata/FastSlow_2024_01_16_180855.jfr.gz new file mode 100644 index 0000000..f4b6edc Binary files /dev/null and b/parser/testdata/FastSlow_2024_01_16_180855.jfr.gz differ diff --git a/parser/testdata/FastSlow_2024_01_16_180855_0_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/FastSlow_2024_01_16_180855_0_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..6715719 Binary files /dev/null and b/parser/testdata/FastSlow_2024_01_16_180855_0_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/FastSlow_2024_01_16_180855_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/FastSlow_2024_01_16_180855_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..80fce48 Binary files /dev/null and b/parser/testdata/FastSlow_2024_01_16_180855_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/FastSlow_2024_01_16_180855_1_wall_wall__nanoseconds_expected.txt.gz b/parser/testdata/FastSlow_2024_01_16_180855_1_wall_wall__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..01821fa Binary files /dev/null and b/parser/testdata/FastSlow_2024_01_16_180855_1_wall_wall__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/FastSlow_2024_01_16_180855_1_wall_wall__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/FastSlow_2024_01_16_180855_1_wall_wall__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..80fce48 Binary files /dev/null and b/parser/testdata/FastSlow_2024_01_16_180855_1_wall_wall__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/async-profiler_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/async-profiler_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..f1a4775 Binary files /dev/null and b/parser/testdata/async-profiler_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/async-profiler_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/async-profiler_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..5f55dd4 Binary files /dev/null and b/parser/testdata/async-profiler_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/async-profiler_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/async-profiler_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..981e638 Binary files /dev/null and b/parser/testdata/async-profiler_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/async-profiler_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/async-profiler_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..0dc39c8 Binary files /dev/null and b/parser/testdata/async-profiler_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/async-profiler_2_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/async-profiler_2_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..2007c99 Binary files /dev/null and b/parser/testdata/async-profiler_2_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/async-profiler_2_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/async-profiler_2_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..9733fc4 Binary files /dev/null and b/parser/testdata/async-profiler_2_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/async-profiler_parsed.json.gz b/parser/testdata/async-profiler_parsed.json.gz deleted file mode 100644 index e183f63..0000000 Binary files a/parser/testdata/async-profiler_parsed.json.gz and /dev/null differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__0.jfr.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__0.jfr.gz new file mode 100644 index 0000000..4913ced Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__0.jfr.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__0_0_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__0_0_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..6ab00e1 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__0_0_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__0_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__0_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..dc4f595 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__0_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__1.jfr.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__1.jfr.gz new file mode 100644 index 0000000..8510f1a Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__1.jfr.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__1_0_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__1_0_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..7642d6c Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__1_0_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__1_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__1_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..68735d4 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__1_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__2.jfr.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__2.jfr.gz new file mode 100644 index 0000000..ca86237 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__2.jfr.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__2_0_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__2_0_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..9cef6c1 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__2_0_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__2_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__2_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..8dde0f7 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__2_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__3.jfr.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__3.jfr.gz new file mode 100644 index 0000000..6c66089 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__3.jfr.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__3_0_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__3_0_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..80ef804 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__3_0_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu__3_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu__3_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..a949f00 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu__3_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_0_block_contentions__count delay__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_0_block_contentions__count delay__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..2b0c252 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_0_block_contentions__count delay__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..e8ed770 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..89cacd9 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..9156103 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..cec4cb4 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..2c1cea3 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..2b39d84 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..3546053 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_4_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_4_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..342e92f Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_4_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..953908b Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_parsed.json.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_parsed.json.gz deleted file mode 100644 index a4555f9..0000000 Binary files a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock0_alloc0__0_parsed.json.gz and /dev/null differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0.jfr.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0.jfr.gz new file mode 100644 index 0000000..0031a52 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0.jfr.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..6db2e8a Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..60d3a16 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_1_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_1_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..88372be Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_1_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..283b761 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__0_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1.jfr.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1.jfr.gz new file mode 100644 index 0000000..a4abf9b Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1.jfr.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..89b0406 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..b4547fd Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_1_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_1_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..cc01125 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_1_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..9f7a0a1 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__1_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2.jfr.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2.jfr.gz new file mode 100644 index 0000000..668fcda Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2.jfr.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..d7f2025 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..afcb29b Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_1_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_1_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..cb1fd73 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_1_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..ad25f73 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__2_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3.jfr.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3.jfr.gz new file mode 100644 index 0000000..288d048 Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3.jfr.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..e79a91c Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..623ecdf Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_1_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_1_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..39485ff Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_1_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..53251ac Binary files /dev/null and b/parser/testdata/cortex-dev-01__kafka-0__cpu_lock_alloc__3_1_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/dump1.jfr.gz b/parser/testdata/dump1.jfr.gz new file mode 100644 index 0000000..efa7fbb Binary files /dev/null and b/parser/testdata/dump1.jfr.gz differ diff --git a/parser/testdata/dump1.labels.pb.gz b/parser/testdata/dump1.labels.pb.gz new file mode 100644 index 0000000..ef21c1f Binary files /dev/null and b/parser/testdata/dump1.labels.pb.gz differ diff --git a/parser/testdata/dump1_0_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/dump1_0_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..50bdfbd Binary files /dev/null and b/parser/testdata/dump1_0_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/dump1_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/dump1_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..ce00ed6 Binary files /dev/null and b/parser/testdata/dump1_0_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/dump2.jfr.gz b/parser/testdata/dump2.jfr.gz new file mode 100644 index 0000000..0d29917 Binary files /dev/null and b/parser/testdata/dump2.jfr.gz differ diff --git a/parser/testdata/dump2.labels.pb.gz b/parser/testdata/dump2.labels.pb.gz new file mode 100644 index 0000000..4203bb4 Binary files /dev/null and b/parser/testdata/dump2.labels.pb.gz differ diff --git a/parser/testdata/dump2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/dump2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..3498dfa Binary files /dev/null and b/parser/testdata/dump2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/dump2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/dump2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..b9618dd Binary files /dev/null and b/parser/testdata/dump2_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/dump2_1_memory_live__count_expected.txt.gz b/parser/testdata/dump2_1_memory_live__count_expected.txt.gz new file mode 100644 index 0000000..d00e9eb Binary files /dev/null and b/parser/testdata/dump2_1_memory_live__count_expected.txt.gz differ diff --git a/parser/testdata/dump2_1_memory_live__count_expected_collapsed.txt.gz b/parser/testdata/dump2_1_memory_live__count_expected_collapsed.txt.gz new file mode 100644 index 0000000..41d7966 Binary files /dev/null and b/parser/testdata/dump2_1_memory_live__count_expected_collapsed.txt.gz differ diff --git a/parser/testdata/dump2_2_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/dump2_2_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..16ff79b Binary files /dev/null and b/parser/testdata/dump2_2_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/dump2_2_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/dump2_2_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..f2619b5 Binary files /dev/null and b/parser/testdata/dump2_2_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/dump2_3_wall_wall__nanoseconds_expected.txt.gz b/parser/testdata/dump2_3_wall_wall__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..4a14c88 Binary files /dev/null and b/parser/testdata/dump2_3_wall_wall__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/dump2_3_wall_wall__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/dump2_3_wall_wall__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..f47b68c Binary files /dev/null and b/parser/testdata/dump2_3_wall_wall__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/example_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/example_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..c6ab7f3 Binary files /dev/null and b/parser/testdata/example_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/example_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/example_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..7ada57e Binary files /dev/null and b/parser/testdata/example_0_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/example_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/example_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..1114d3c Binary files /dev/null and b/parser/testdata/example_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/example_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/example_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..979d925 Binary files /dev/null and b/parser/testdata/example_1_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/example_2_mutex_contentions__count delay__nanoseconds_expected.txt.gz b/parser/testdata/example_2_mutex_contentions__count delay__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..d058bce Binary files /dev/null and b/parser/testdata/example_2_mutex_contentions__count delay__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/example_2_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/example_2_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..a0240b0 Binary files /dev/null and b/parser/testdata/example_2_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/example_3_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/example_3_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..20bd7c4 Binary files /dev/null and b/parser/testdata/example_3_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/example_3_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/example_3_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..8c8543c Binary files /dev/null and b/parser/testdata/example_3_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/example_parsed.json.gz b/parser/testdata/example_parsed.json.gz deleted file mode 100644 index d06ead0..0000000 Binary files a/parser/testdata/example_parsed.json.gz and /dev/null differ diff --git a/parser/testdata/goland-multichunk_0_block_contentions__count delay__nanoseconds_expected.txt.gz b/parser/testdata/goland-multichunk_0_block_contentions__count delay__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..20fc351 Binary files /dev/null and b/parser/testdata/goland-multichunk_0_block_contentions__count delay__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/goland-multichunk_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/goland-multichunk_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..7f16ae8 Binary files /dev/null and b/parser/testdata/goland-multichunk_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland-multichunk_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/goland-multichunk_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..c48a2a8 Binary files /dev/null and b/parser/testdata/goland-multichunk_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/goland-multichunk_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/goland-multichunk_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..909695f Binary files /dev/null and b/parser/testdata/goland-multichunk_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland-multichunk_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/goland-multichunk_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..43f91b7 Binary files /dev/null and b/parser/testdata/goland-multichunk_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/goland-multichunk_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/goland-multichunk_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..5dca3fc Binary files /dev/null and b/parser/testdata/goland-multichunk_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland-multichunk_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz b/parser/testdata/goland-multichunk_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..12a25f9 Binary files /dev/null and b/parser/testdata/goland-multichunk_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/goland-multichunk_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/goland-multichunk_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..9e4716d Binary files /dev/null and b/parser/testdata/goland-multichunk_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland-multichunk_4_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/goland-multichunk_4_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..9d2adac Binary files /dev/null and b/parser/testdata/goland-multichunk_4_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/goland-multichunk_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/goland-multichunk_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..7660103 Binary files /dev/null and b/parser/testdata/goland-multichunk_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland-multichunk_parsed.json.gz b/parser/testdata/goland-multichunk_parsed.json.gz deleted file mode 100644 index 1a06fe3..0000000 Binary files a/parser/testdata/goland-multichunk_parsed.json.gz and /dev/null differ diff --git a/parser/testdata/goland_0_block_contentions__count delay__nanoseconds_expected.txt.gz b/parser/testdata/goland_0_block_contentions__count delay__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..e791d50 Binary files /dev/null and b/parser/testdata/goland_0_block_contentions__count delay__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/goland_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/goland_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..fd500fd Binary files /dev/null and b/parser/testdata/goland_0_block_contentions__count delay__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/goland_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..b9e2d9c Binary files /dev/null and b/parser/testdata/goland_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/goland_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/goland_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..62a8f46 Binary files /dev/null and b/parser/testdata/goland_1_memory_alloc_in_new_tlab_objects__count alloc_in_new_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz b/parser/testdata/goland_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz new file mode 100644 index 0000000..65f5dcc Binary files /dev/null and b/parser/testdata/goland_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected.txt.gz differ diff --git a/parser/testdata/goland_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz b/parser/testdata/goland_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz new file mode 100644 index 0000000..c8815e0 Binary files /dev/null and b/parser/testdata/goland_2_memory_alloc_outside_tlab_objects__count alloc_outside_tlab_bytes__bytes_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz b/parser/testdata/goland_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..209b249 Binary files /dev/null and b/parser/testdata/goland_3_mutex_contentions__count delay__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/goland_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/goland_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..a5e2b53 Binary files /dev/null and b/parser/testdata/goland_3_mutex_contentions__count delay__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland_4_process_cpu_cpu__nanoseconds_expected.txt.gz b/parser/testdata/goland_4_process_cpu_cpu__nanoseconds_expected.txt.gz new file mode 100644 index 0000000..b601399 Binary files /dev/null and b/parser/testdata/goland_4_process_cpu_cpu__nanoseconds_expected.txt.gz differ diff --git a/parser/testdata/goland_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz b/parser/testdata/goland_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz new file mode 100644 index 0000000..66628a1 Binary files /dev/null and b/parser/testdata/goland_4_process_cpu_cpu__nanoseconds_expected_collapsed.txt.gz differ diff --git a/parser/testdata/goland_parsed.json.gz b/parser/testdata/goland_parsed.json.gz deleted file mode 100644 index 8b6d00d..0000000 Binary files a/parser/testdata/goland_parsed.json.gz and /dev/null differ diff --git a/parser/types/active_settings.go b/parser/types/active_settings.go index 72c6000..fdfbea5 100644 --- a/parser/types/active_settings.go +++ b/parser/types/active_settings.go @@ -157,6 +157,7 @@ func (this *ActiveSetting) Parse(data []byte, bind *BindActiveSetting, typeMap * pos++ switch b_ { case 0: + break case 1: break case 3: @@ -301,6 +302,7 @@ func (this *ActiveSetting) Parse(data []byte, bind *BindActiveSetting, typeMap * pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/allocation_in_new_tlab.go b/parser/types/allocation_in_new_tlab.go index 663f7e4..b50358a 100644 --- a/parser/types/allocation_in_new_tlab.go +++ b/parser/types/allocation_in_new_tlab.go @@ -161,6 +161,7 @@ func (this *ObjectAllocationInNewTLAB) Parse(data []byte, bind *BindObjectAlloca pos++ switch b_ { case 0: + break case 1: break case 3: @@ -303,6 +304,7 @@ func (this *ObjectAllocationInNewTLAB) Parse(data []byte, bind *BindObjectAlloca pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/allocation_outside_tlab.go b/parser/types/allocation_outside_tlab.go index 51bd47d..3ff5288 100644 --- a/parser/types/allocation_outside_tlab.go +++ b/parser/types/allocation_outside_tlab.go @@ -154,6 +154,7 @@ func (this *ObjectAllocationOutsideTLAB) Parse(data []byte, bind *BindObjectAllo pos++ switch b_ { case 0: + break case 1: break case 3: @@ -296,6 +297,7 @@ func (this *ObjectAllocationOutsideTLAB) Parse(data []byte, bind *BindObjectAllo pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/class.go b/parser/types/class.go index 48b9c7c..5280af6 100644 --- a/parser/types/class.go +++ b/parser/types/class.go @@ -169,6 +169,7 @@ func (this *ClassList) Parse(data []byte, bind *BindClass, typeMap *def.TypeMap) pos++ switch b_ { case 0: + break case 1: break case 3: @@ -311,6 +312,7 @@ func (this *ClassList) Parse(data []byte, bind *BindClass, typeMap *def.TypeMap) pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/classloader.go b/parser/types/classloader.go index 6f6a497..187434e 100644 --- a/parser/types/classloader.go +++ b/parser/types/classloader.go @@ -161,6 +161,7 @@ func (this *ClassLoaderList) Parse(data []byte, bind *BindClassLoader, typeMap * pos++ switch b_ { case 0: + break case 1: break case 3: @@ -301,6 +302,7 @@ func (this *ClassLoaderList) Parse(data []byte, bind *BindClassLoader, typeMap * pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/def/types.go b/parser/types/def/types.go index d2a6f3c..545648f 100644 --- a/parser/types/def/types.go +++ b/parser/types/def/types.go @@ -1,6 +1,6 @@ package def -type TypeID uint32 +type TypeID uint64 type TypeMap struct { IDMap map[TypeID]*Class diff --git a/parser/types/execution_sample.go b/parser/types/execution_sample.go index 7228587..914c09d 100644 --- a/parser/types/execution_sample.go +++ b/parser/types/execution_sample.go @@ -147,6 +147,7 @@ func (this *ExecutionSample) Parse(data []byte, bind *BindExecutionSample, typeM pos++ switch b_ { case 0: + break case 1: break case 3: @@ -289,6 +290,7 @@ func (this *ExecutionSample) Parse(data []byte, bind *BindExecutionSample, typeM pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/frametype.go b/parser/types/frametype.go index 0198ac1..2bcf85e 100644 --- a/parser/types/frametype.go +++ b/parser/types/frametype.go @@ -143,6 +143,7 @@ func (this *FrameTypeList) Parse(data []byte, bind *BindFrameType, typeMap *def. pos++ switch b_ { case 0: + break case 1: break case 3: @@ -285,6 +286,7 @@ func (this *FrameTypeList) Parse(data []byte, bind *BindFrameType, typeMap *def. pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/live_object.go b/parser/types/live_object.go index 2258fda..dd0d866 100644 --- a/parser/types/live_object.go +++ b/parser/types/live_object.go @@ -154,6 +154,7 @@ func (this *LiveObject) Parse(data []byte, bind *BindLiveObject, typeMap *def.Ty pos++ switch b_ { case 0: + break case 1: break case 3: @@ -296,6 +297,7 @@ func (this *LiveObject) Parse(data []byte, bind *BindLiveObject, typeMap *def.Ty pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/loglevel.go b/parser/types/loglevel.go index 4e7e279..c3b22c5 100644 --- a/parser/types/loglevel.go +++ b/parser/types/loglevel.go @@ -143,6 +143,7 @@ func (this *LogLevelList) Parse(data []byte, bind *BindLogLevel, typeMap *def.Ty pos++ switch b_ { case 0: + break case 1: break case 3: @@ -285,6 +286,7 @@ func (this *LogLevelList) Parse(data []byte, bind *BindLogLevel, typeMap *def.Ty pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/method.go b/parser/types/method.go index 67d27e2..64ce853 100644 --- a/parser/types/method.go +++ b/parser/types/method.go @@ -64,7 +64,6 @@ type Method struct { // skip descriptor // skip modifiers // skip hidden - Scratch []byte } func (this *MethodList) Parse(data []byte, bind *BindMethod, typeMap *def.TypeMap) (pos int, err error) { @@ -173,6 +172,7 @@ func (this *MethodList) Parse(data []byte, bind *BindMethod, typeMap *def.TypeMa pos++ switch b_ { case 0: + break case 1: break case 3: @@ -317,6 +317,7 @@ func (this *MethodList) Parse(data []byte, bind *BindMethod, typeMap *def.TypeMa pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/monitor_enter.go b/parser/types/monitor_enter.go index fa33d2e..cf47e4f 100644 --- a/parser/types/monitor_enter.go +++ b/parser/types/monitor_enter.go @@ -168,6 +168,7 @@ func (this *JavaMonitorEnter) Parse(data []byte, bind *BindJavaMonitorEnter, typ pos++ switch b_ { case 0: + break case 1: break case 3: @@ -310,6 +311,7 @@ func (this *JavaMonitorEnter) Parse(data []byte, bind *BindJavaMonitorEnter, typ pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/package.go b/parser/types/package.go index f9a0db3..69a35e6 100644 --- a/parser/types/package.go +++ b/parser/types/package.go @@ -149,6 +149,7 @@ func (this *PackageList) Parse(data []byte, bind *BindPackage, typeMap *def.Type pos++ switch b_ { case 0: + break case 1: break case 3: @@ -289,6 +290,7 @@ func (this *PackageList) Parse(data []byte, bind *BindPackage, typeMap *def.Type pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/skipper.go b/parser/types/skipper.go index 2a228e4..a49fbd1 100644 --- a/parser/types/skipper.go +++ b/parser/types/skipper.go @@ -130,6 +130,7 @@ func (this *SkipConstantPoolList) Parse(data []byte, bind *BindSkipConstantPool, pos++ switch b_ { case 0: + break case 1: break case 3: @@ -270,6 +271,7 @@ func (this *SkipConstantPoolList) Parse(data []byte, bind *BindSkipConstantPool, pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/stackframe.go b/parser/types/stackframe.go index 61fc195..9a05fd9 100644 --- a/parser/types/stackframe.go +++ b/parser/types/stackframe.go @@ -33,7 +33,11 @@ func NewBindStackFrame(typ *def.Class, typeMap *def.TypeMap) *BindStackFrame { res.Fields = append(res.Fields, BindFieldStackFrame{Field: &typ.Fields[i]}) // skip changed field } case "lineNumber": - res.Fields = append(res.Fields, BindFieldStackFrame{Field: &typ.Fields[i]}) // skip to save mem + if typ.Fields[i].Equals(&def.Field{Name: "lineNumber", Type: typeMap.T_INT, ConstantPool: false, Array: false}) { + res.Fields = append(res.Fields, BindFieldStackFrame{Field: &typ.Fields[i], uint32: &res.Temp.LineNumber}) + } else { + res.Fields = append(res.Fields, BindFieldStackFrame{Field: &typ.Fields[i]}) // skip changed field + } case "bytecodeIndex": res.Fields = append(res.Fields, BindFieldStackFrame{Field: &typ.Fields[i]}) // skip to save mem case "type": @@ -46,8 +50,8 @@ func NewBindStackFrame(typ *def.Class, typeMap *def.TypeMap) *BindStackFrame { } type StackFrame struct { - Method MethodRef - // skip lineNumber + Method MethodRef + LineNumber uint32 // skip bytecodeIndex // skip type } @@ -123,6 +127,7 @@ func (this *StackFrame) Parse(data []byte, bind *BindStackFrame, typeMap *def.Ty pos++ switch b_ { case 0: + break case 1: break case 3: @@ -265,6 +270,7 @@ func (this *StackFrame) Parse(data []byte, bind *BindStackFrame, typeMap *def.Ty pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/stacktrace.go b/parser/types/stacktrace.go index de316c4..aac1f84 100644 --- a/parser/types/stacktrace.go +++ b/parser/types/stacktrace.go @@ -154,6 +154,7 @@ func (this *StackTraceList) Parse(data []byte, bind *BindStackTrace, bindStackFr pos++ switch b_ { case 0: + break case 1: break case 3: @@ -304,6 +305,7 @@ func (this *StackTraceList) Parse(data []byte, bind *BindStackTrace, bindStackFr pos++ switch b_ { case 0: + break case 1: break case 3: @@ -446,6 +448,7 @@ func (this *StackTraceList) Parse(data []byte, bind *BindStackTrace, bindStackFr pos++ switch b_ { case 0: + break case 1: break case 3: @@ -593,6 +596,7 @@ func (this *StackTraceList) Parse(data []byte, bind *BindStackTrace, bindStackFr pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/symbol.go b/parser/types/symbol.go index 58ad4b9..4f6f5cf 100644 --- a/parser/types/symbol.go +++ b/parser/types/symbol.go @@ -143,6 +143,7 @@ func (this *SymbolList) Parse(data []byte, bind *BindSymbol, typeMap *def.TypeMa pos++ switch b_ { case 0: + break case 1: break case 3: @@ -285,6 +286,7 @@ func (this *SymbolList) Parse(data []byte, bind *BindSymbol, typeMap *def.TypeMa pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/thread.go b/parser/types/thread.go index 9ad2486..78ba708 100644 --- a/parser/types/thread.go +++ b/parser/types/thread.go @@ -165,6 +165,7 @@ func (this *ThreadList) Parse(data []byte, bind *BindThread, typeMap *def.TypeMa pos++ switch b_ { case 0: + break case 1: break case 3: @@ -309,6 +310,7 @@ func (this *ThreadList) Parse(data []byte, bind *BindThread, typeMap *def.TypeMa pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/thread_park.go b/parser/types/thread_park.go index 1e49072..492335e 100644 --- a/parser/types/thread_park.go +++ b/parser/types/thread_park.go @@ -175,6 +175,7 @@ func (this *ThreadPark) Parse(data []byte, bind *BindThreadPark, typeMap *def.Ty pos++ switch b_ { case 0: + break case 1: break case 3: @@ -317,6 +318,7 @@ func (this *ThreadPark) Parse(data []byte, bind *BindThreadPark, typeMap *def.Ty pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/parser/types/threadstate.go b/parser/types/threadstate.go index 4ceb771..1bfa512 100644 --- a/parser/types/threadstate.go +++ b/parser/types/threadstate.go @@ -143,6 +143,7 @@ func (this *ThreadStateList) Parse(data []byte, bind *BindThreadState, typeMap * pos++ switch b_ { case 0: + break case 1: break case 3: @@ -285,6 +286,7 @@ func (this *ThreadStateList) Parse(data []byte, bind *BindThreadState, typeMap * pos++ switch b_ { case 0: + break case 1: break case 3: diff --git a/pprof/cmd/jfr2pprof/main.go b/pprof/cmd/jfr2pprof/main.go new file mode 100644 index 0000000..ecac271 --- /dev/null +++ b/pprof/cmd/jfr2pprof/main.go @@ -0,0 +1,39 @@ +package main + +import ( + "fmt" + "os" + "path/filepath" + "time" + + "github.com/grafana/jfr-parser/pprof" +) + +func main() { + jfrFile, err := os.ReadFile(os.Args[1]) + if err != nil { + panic(err) + } + dstFile := os.Args[2] + pi := &pprof.ParseInput{ + StartTime: time.Now(), + EndTime: time.Now(), + SampleRate: 100, + } + profiles, err := pprof.ParseJFR(jfrFile, pi, nil) + if err != nil { + panic(err) + } + for _, profile := range profiles.Profiles { + bs, err := profile.Profile.MarshalVT() + if err != nil { + panic(err) + } + dir := filepath.Dir(dstFile) + filename := fmt.Sprintf("%s.%s", profile.Metric, filepath.Base(dstFile)) + err = os.WriteFile(filepath.Join(dir, filename), bs, 0644) + if err != nil { + panic(err) + } + } +} diff --git a/pprof/go.mod b/pprof/go.mod index 4227790..991a7f5 100644 --- a/pprof/go.mod +++ b/pprof/go.mod @@ -3,14 +3,20 @@ module github.com/grafana/jfr-parser/pprof go 1.18 require ( + github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 github.com/grafana/jfr-parser v0.7.2-0.20230831140626-08fa3a941bf8 - github.com/grafana/pyroscope/api v0.3.0 + github.com/grafana/pyroscope/api v0.4.0 + github.com/k0kubun/pp/v3 v3.2.0 github.com/stretchr/testify v1.8.4 google.golang.org/protobuf v1.32.0 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + golang.org/x/sys v0.15.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pprof/go.sum b/pprof/go.sum index 521688f..bd81675 100644 --- a/pprof/go.sum +++ b/pprof/go.sum @@ -1,15 +1,27 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/pprof v0.0.0-20240117000934-35fc243c5815 h1:WzfWbQz/Ze8v6l++GGbGNFZnUShVpP/0xffCPLL+ax8= +github.com/google/pprof v0.0.0-20240117000934-35fc243c5815/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik= github.com/grafana/jfr-parser v0.7.2-0.20230831140626-08fa3a941bf8 h1:Cod+QZWJXGLoCfKfuH66J3iSQ/WWw+R03R6QCwm8IB8= github.com/grafana/jfr-parser v0.7.2-0.20230831140626-08fa3a941bf8/go.mod h1:M5u1ux34Qo47ZBWksbMYVk40s7dvU3WMVYpxweEu4R0= -github.com/grafana/pyroscope/api v0.3.0 h1:WcVKNZ8JlriJnD28wTkZray0wGo8dGkizSJXnbG7Gd8= -github.com/grafana/pyroscope/api v0.3.0/go.mod h1:JggA80ToAAUACYGfwL49XoFk5aN5ecHp4pNIZhlk9Uc= +github.com/grafana/pyroscope/api v0.4.0 h1:J86DxoNeLOvtJhB1Cn65JMZkXe682D+RqeoIUiYc/eo= +github.com/grafana/pyroscope/api v0.4.0/go.mod h1:MFnZNeUM4RDsDOnbgKW3GWoLSBpLzMMT9nkvhHHo81o= +github.com/k0kubun/pp/v3 v3.2.0 h1:h33hNTZ9nVFNP3u2Fsgz8JXiF5JINoZfFq4SvKJwNcs= +github.com/k0kubun/pp/v3 v3.2.0/go.mod h1:ODtJQbQcIRfAD3N+theGCV1m/CBxweERz2dapdz1EwA= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/pprof/parser_test.go b/pprof/parser_test.go new file mode 100644 index 0000000..414f611 --- /dev/null +++ b/pprof/parser_test.go @@ -0,0 +1,267 @@ +package pprof + +import ( + "compress/gzip" + "fmt" + "io/ioutil" + "os" + "slices" + "strings" + "testing" + + gpprof "github.com/google/pprof/profile" + profilev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1" + "github.com/k0kubun/pp/v3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const testdataDir = "../parser/testdata/" +const doDump = false + +type testdata struct { + jfr, labels string + expectedCount int +} + +var testfiles = []testdata{ + {"example", "", 4}, + {"async-profiler", "", 3}, // -e cpu -i 10ms --alloc 512k --wall 200ms --lock 10ms -d 60 (async-profiler 2.10) + {"goland", "", 5}, + {"goland-multichunk", "", 5}, + {"FastSlow_2024_01_16_180855", "", 2}, // from IJ Ultimate, multichunk, chunked CP + {"cortex-dev-01__kafka-0__cpu__0", "", 1}, + {"cortex-dev-01__kafka-0__cpu__1", "", 1}, + {"cortex-dev-01__kafka-0__cpu__2", "", 1}, + {"cortex-dev-01__kafka-0__cpu__3", "", 1}, + {"cortex-dev-01__kafka-0__cpu_lock0_alloc0__0", "", 5}, + {"cortex-dev-01__kafka-0__cpu_lock_alloc__0", "", 2}, + {"cortex-dev-01__kafka-0__cpu_lock_alloc__1", "", 2}, + {"cortex-dev-01__kafka-0__cpu_lock_alloc__2", "", 2}, + {"cortex-dev-01__kafka-0__cpu_lock_alloc__3", "", 2}, + {"dump1", "dump1.labels.pb.gz", 1}, + {"dump2", "dump2.labels.pb.gz", 4}, +} + +type gprofile struct { + profile *gpprof.Profile + proto *profilev1.Profile + metric string +} + +func TestDoDump(t *testing.T) { + assert.False(t, doDump) +} + +var parseInput = &ParseInput{ + SampleRate: 100, +} + +func TestParse(t *testing.T) { + for _, testfile := range testfiles { + mypp := pp.New() + mypp.SetColoringEnabled(false) + mypp.SetExportedOnly(true) + t.Run(testfile.jfr, func(t *testing.T) { + jfrFile := testdataDir + testfile.jfr + ".jfr.gz" + jfr := readGzipFile(t, jfrFile) + ls := readLabels(t, testfile) + + profiles, err := ParseJFR(jfr, parseInput, ls) + require.NoError(t, err) + + gprofiles := toGoogleProfiles(t, profiles.Profiles) + profiles = nil + + slices.SortFunc(gprofiles, func(i, j gprofile) int { + return strings.Compare(i.metric, j.metric) + }) + assert.Equal(t, testfile.expectedCount, len(gprofiles)) + + for i, profile := range gprofiles { + actual := profile.profile.String() + actualCollapsed := stackCollapseProto(profile.proto, true) + expectedFile := fmt.Sprintf("%s%s_%d_%s_expected.txt.gz", testdataDir, testfile.jfr, i, profile.metric) + expectedCollapsedFile := fmt.Sprintf("%s%s_%d_%s_expected_collapsed.txt.gz", testdataDir, testfile.jfr, i, profile.metric) + assert.NotEmpty(t, actual) + assert.NotEmpty(t, actualCollapsed) + if doDump { + writeGzipFile(t, expectedFile, []byte(actual)) + writeGzipFile(t, expectedCollapsedFile, []byte(actualCollapsed)) + } else { + expected := readGzipFile(t, expectedFile) + require.NoError(t, err) + expectedCollapsed := readGzipFile(t, expectedCollapsedFile) + require.NoError(t, err) + + assert.Equal(t, string(expected), actual) + assert.Equal(t, string(expectedCollapsed), actualCollapsed) + + if string(expected) != actual { + os.WriteFile("actual.txt", []byte(actual), 0644) + os.WriteFile("expected.txt", expected, 0644) + } + + if string(expectedCollapsed) != actualCollapsed { + os.WriteFile("actual_collapsed.txt", []byte(actualCollapsed), 0644) + os.WriteFile("expected_collapsed.txt", expectedCollapsed, 0644) + } + } + } + }) + } +} + +func readLabels(t testing.TB, td testdata) *LabelsSnapshot { + ls := new(LabelsSnapshot) + if td.labels != "" { + labelsBytes := readGzipFile(t, testdataDir+td.labels) + err := ls.UnmarshalVT(labelsBytes) + require.NoError(t, err) + } + return ls +} + +func BenchmarkParse(b *testing.B) { + for _, testfile := range testfiles { + b.Run(testfile.jfr, func(b *testing.B) { + jfr := readGzipFile(b, testdataDir+testfile.jfr+".jfr.gz") + ls := readLabels(b, testfile) + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + profiles, err := ParseJFR(jfr, parseInput, ls) + if err != nil { + b.Fatalf("Unable to parse JFR file: %s", err) + } + if len(profiles.Profiles) == 0 { + b.Fatalf("No profiles found") + } + } + }) + } +} + +func toGoogleProfiles(t *testing.T, profiles []Profile) []gprofile { + res := make([]gprofile, 0, len(profiles)) + for _, profile := range profiles { + bs, err := profile.Profile.MarshalVT() + require.NoError(t, err) + p, err := gpprof.ParseData(bs) + require.NoError(t, err) + + res = append(res, gprofile{p, profile.Profile, fmt.Sprintf("%s_%s", profile.Metric, sampleTypesToString(p))}) + } + return res +} + +func sampleTypesToString(p *gpprof.Profile) string { + var sh1 string + for _, s := range p.SampleType { + dflt := "" + sh1 = sh1 + fmt.Sprintf("%s__%s%s ", s.Type, s.Unit, dflt) + } + return strings.TrimSpace(sh1) +} + +func readGzipFile(t testing.TB, fname string) []byte { + f, err := os.Open(fname) + require.NoError(t, err) + defer f.Close() + r, err := gzip.NewReader(f) + require.NoError(t, err) + defer r.Close() + bs, err := ioutil.ReadAll(r) + require.NoError(t, err) + return bs +} + +func writeGzipFile(t *testing.T, f string, data []byte) { + fd, err := os.OpenFile(f, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) + require.NoError(t, err) + defer fd.Close() + g := gzip.NewWriter(fd) + _, err = g.Write(data) + require.NoError(t, err) + err = g.Close() + require.NoError(t, err) +} + +func stackCollapseProto(p *profilev1.Profile, lineNumbers bool) string { + allZeros := func(a []int64) bool { + for _, v := range a { + if v != 0 { + return false + } + } + return true + } + addValues := func(a, b []int64) { + for i := range a { + a[i] += b[i] + } + } + + type stack struct { + funcs string + value []int64 + } + locMap := make(map[int64]*profilev1.Location) + funcMap := make(map[int64]*profilev1.Function) + for _, l := range p.Location { + locMap[int64(l.Id)] = l + } + for _, f := range p.Function { + funcMap[int64(f.Id)] = f + } + + var ret []stack + for _, s := range p.Sample { + var funcs []string + for i := range s.LocationId { + locID := s.LocationId[len(s.LocationId)-1-i] + loc := locMap[int64(locID)] + for _, line := range loc.Line { + f := funcMap[int64(line.FunctionId)] + fname := p.StringTable[f.Name] + if lineNumbers { + fname = fmt.Sprintf("%s:%d", fname, line.Line) + } + funcs = append(funcs, fname) + } + } + + vv := make([]int64, len(s.Value)) + copy(vv, s.Value) + ret = append(ret, stack{ + funcs: strings.Join(funcs, ";"), + value: vv, + }) + } + slices.SortFunc(ret, func(i, j stack) int { + return strings.Compare(i.funcs, j.funcs) + }) + var unique []stack + for _, s := range ret { + if allZeros(s.value) { + continue + } + if len(unique) == 0 { + unique = append(unique, s) + continue + } + + if unique[len(unique)-1].funcs == s.funcs { + addValues(unique[len(unique)-1].value, s.value) + continue + } + unique = append(unique, s) + + } + + res := make([]string, 0, len(unique)) + for _, s := range unique { + res = append(res, fmt.Sprintf("%s %v", s.funcs, s.value)) + } + return strings.Join(res, "\n") +} diff --git a/pprof/pprof.go b/pprof/pprof.go index 456e0a9..d1eff52 100644 --- a/pprof/pprof.go +++ b/pprof/pprof.go @@ -3,7 +3,6 @@ package pprof import ( "github.com/grafana/jfr-parser/parser" "github.com/grafana/jfr-parser/parser/types" - v1 "github.com/grafana/pyroscope/api/gen/proto/go/types/v1" ) const ( @@ -72,28 +71,40 @@ func (b *jfrPprofBuilders) addStacktrace(sampleType int64, contextID uint64, ref locations := make([]uint64, 0, len(st.Frames)) for i := 0; i < len(st.Frames); i++ { f := st.Frames[i] - loc, found := p.FindLocationByExternalID(uint32(f.Method)) + extLocID := ExternalLocationID{ + ExternalFunctionID: ExternalFunctionID(f.Method), + Line: f.LineNumber, + } + loc, found := p.FindLocationByExternalID(extLocID) if found { - locations = append(locations, loc) + locations = append(locations, uint64(loc)) continue } m := b.parser.GetMethod(f.Method) if m != nil { - cls := b.parser.GetClass(m.Type) - if cls != nil { + pprofFuncID, found := p.FindFunctionByExternalID(extLocID.ExternalFunctionID) + if found { + // add new location with old function + } else { + cls := b.parser.GetClass(m.Type) + if cls == nil { + continue + } clsName := b.parser.GetSymbolString(cls.Name) methodName := b.parser.GetSymbolString(m.Name) frame := clsName + "." + methodName - loc = p.AddExternalFunction(frame, uint32(f.Method)) - locations = append(locations, loc) + pprofFuncID = p.AddExternalFunction(frame, extLocID.ExternalFunctionID) } + loc = p.AddExternalLocation(extLocID, pprofFuncID) + locations = append(locations, uint64(loc)) + //todo remove Scratch field from the Method } } vs := make([]int64, len(values)) addValues(vs) - p.AddExternalSampleWithLabels(locations, vs, contextLabels(contextID, b.jfrLabels), uint64(ref), contextID) + p.AddExternalSampleWithLabels(locations, vs, b.contextLabels(contextID), b.jfrLabels, uint64(ref), contextID) } func (b *jfrPprofBuilders) profileBuilderForSampleType(sampleType int64) *ProfileBuilder { @@ -142,22 +153,11 @@ func (b *jfrPprofBuilders) profileBuilderForSampleType(sampleType int64) *Profil return builder } -func contextLabels(contextID uint64, jfrLabels *LabelsSnapshot) Labels { - if jfrLabels == nil { - return nil - } - ctx, ok := jfrLabels.Contexts[int64(contextID)] - if !ok { +func (b *jfrPprofBuilders) contextLabels(contextID uint64) *Context { + if b.jfrLabels == nil { return nil } - labels := make(Labels, 0, len(ctx.Labels)) - for k, v := range ctx.Labels { - labels = append(labels, &v1.LabelPair{ - Name: jfrLabels.Strings[k], - Value: jfrLabels.Strings[v], - }) - } - return labels + return b.jfrLabels.Contexts[int64(contextID)] } func (b *jfrPprofBuilders) build(jfrEvent string) *Profiles { diff --git a/pprof/profile_builder.go b/pprof/profile_builder.go index cfa40b3..0652cdd 100644 --- a/pprof/profile_builder.go +++ b/pprof/profile_builder.go @@ -7,7 +7,8 @@ import ( type ProfileBuilder struct { *profilev1.Profile strings map[string]int - externalFunctionID2LocationId map[uint32]uint64 + externalLocationID2LocationID map[ExternalLocationID]PPROFLocationID + externalFunctionID2FunctionID map[ExternalFunctionID]PPROFFunctionID externalSampleID2SampleIndex map[sampleID]uint32 metricName string } @@ -27,12 +28,21 @@ func NewProfileBuilderWithLabels(ts int64) *ProfileBuilder { p := &ProfileBuilder{ Profile: profile, strings: map[string]int{}, - externalFunctionID2LocationId: map[uint32]uint64{}, + externalFunctionID2FunctionID: map[ExternalFunctionID]PPROFFunctionID{}, + externalLocationID2LocationID: map[ExternalLocationID]PPROFLocationID{}, } p.addString("") return p } +type ExternalFunctionID uint32 +type ExternalLocationID struct { + ExternalFunctionID ExternalFunctionID + Line uint32 +} +type PPROFFunctionID uint64 +type PPROFLocationID uint64 + func (m *ProfileBuilder) AddSampleType(typ, unit string) { m.Profile.SampleType = append(m.Profile.SampleType, &profilev1.ValueType{ Type: m.addString(typ), @@ -61,37 +71,50 @@ func (m *ProfileBuilder) addString(s string) int64 { return int64(i) } -func (m *ProfileBuilder) FindLocationByExternalID(externalID uint32) (uint64, bool) { - loc, ok := m.externalFunctionID2LocationId[externalID] +func (m *ProfileBuilder) FindLocationByExternalID(externalLocationID ExternalLocationID) (PPROFLocationID, bool) { + loc, ok := m.externalLocationID2LocationID[externalLocationID] + return loc, ok +} + +func (m *ProfileBuilder) FindFunctionByExternalID(externalFunctionID ExternalFunctionID) (PPROFFunctionID, bool) { + loc, ok := m.externalFunctionID2FunctionID[externalFunctionID] return loc, ok } -func (m *ProfileBuilder) AddExternalFunction(frame string, externalFunctionID uint32) uint64 { +func (m *ProfileBuilder) AddExternalFunction(frame string, id ExternalFunctionID) PPROFFunctionID { fname := m.addString(frame) funcID := uint64(len(m.Function)) + 1 m.Function = append(m.Function, &profilev1.Function{ Id: funcID, Name: fname, }) + ret := PPROFFunctionID(funcID) + m.externalFunctionID2FunctionID[id] = ret + return ret +} + +func (m *ProfileBuilder) AddExternalLocation(id ExternalLocationID, pprofFunctionID PPROFFunctionID) PPROFLocationID { locID := uint64(len(m.Location)) + 1 m.Location = append(m.Location, &profilev1.Location{ Id: locID, MappingId: uint64(1), - Line: []*profilev1.Line{{FunctionId: funcID}}, + Line: []*profilev1.Line{{FunctionId: uint64(pprofFunctionID), Line: int64(id.Line)}}, }) - m.externalFunctionID2LocationId[externalFunctionID] = locID - return locID + ret := PPROFLocationID(locID) + m.externalLocationID2LocationID[id] = ret + return ret + } func (m *ProfileBuilder) AddExternalSample(locs []uint64, values []int64, externalSampleID uint32) { - m.AddExternalSampleWithLabels(locs, values, nil, uint64(externalSampleID), 0) + m.AddExternalSampleWithLabels(locs, values, nil, nil, uint64(externalSampleID), 0) } func (m *ProfileBuilder) FindExternalSample(externalSampleID uint32) *profilev1.Sample { return m.FindExternalSampleWithLabels(uint64(externalSampleID), 0) } -func (m *ProfileBuilder) AddExternalSampleWithLabels(locs []uint64, values []int64, labels Labels, locationsID, labelsID uint64) { +func (m *ProfileBuilder) AddExternalSampleWithLabels(locs []uint64, values []int64, labelsCtx *Context, labelsSnapshot *LabelsSnapshot, locationsID, labelsID uint64) { sample := &profilev1.Sample{ LocationId: locs, Value: values, @@ -101,12 +124,12 @@ func (m *ProfileBuilder) AddExternalSampleWithLabels(locs []uint64, values []int } m.externalSampleID2SampleIndex[sampleID{locationsID: locationsID, labelsID: labelsID}] = uint32(len(m.Profile.Sample)) m.Profile.Sample = append(m.Profile.Sample, sample) - if len(labels) > 0 { - sample.Label = make([]*profilev1.Label, 0, len(labels)) - for _, label := range labels { + if labelsCtx != nil && labelsSnapshot != nil { + sample.Label = make([]*profilev1.Label, 0, len(labelsCtx.Labels)) + for k, v := range labelsCtx.Labels { //todo iterating over map is not deterministic, this can break tests and maybe even affect performance sample.Label = append(sample.Label, &profilev1.Label{ - Key: m.addString(label.Name), - Str: m.addString(label.Value), + Key: m.addString(labelsSnapshot.Strings[k]), + Str: m.addString(labelsSnapshot.Strings[v]), }) } }