Skip to content

Commit

Permalink
Improve constant pool resolving speed (#16)
Browse files Browse the repository at this point in the history
* Add a simple parsing test

* Do not resolve same instances multiple times

* rm unused example.jfr (not gzipped) file
  • Loading branch information
korniltsev authored May 25, 2022
1 parent 6ac8ddf commit 3228ecf
Show file tree
Hide file tree
Showing 8 changed files with 84 additions and 9 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea/
5 changes: 4 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"log"
"os"
"time"

"github.com/pyroscope-io/jfr-parser/parser"
)
Expand All @@ -17,11 +18,13 @@ func main() {
if err != nil {
log.Fatalf("Unable to open file: %s", err)
}
t1 := time.Now()
chunks, err := parser.Parse(f)
if err != nil {
log.Fatalf("Unable to parse: %s", err)
}
log.Printf("Parsed %d chunks", len(chunks))
t2 := time.Now()
log.Printf("Parsed %d chunks in %v", len(chunks), t2.Sub(t1))
events := make(map[string]int)
for _, c := range chunks {
for _, e := range c.Events {
Expand Down
4 changes: 2 additions & 2 deletions parser/checkpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ func (c *CheckpointEvent) Parse(r reader.Reader, classes ClassMap, cpools PoolMa
}
cm, ok := cpools[int(classID)]
if !ok {
cpools[int(classID)] = make(map[int]ParseResolvable)
cpools[int(classID)] = &CPool{pool: make(map[int]ParseResolvable)}
cm = cpools[int(classID)]
}
m, err := r.VarInt()
Expand All @@ -58,7 +58,7 @@ func (c *CheckpointEvent) Parse(r reader.Reader, classes ClassMap, cpools PoolMa
if err != nil {
return fmt.Errorf("unable to parse constant type %d: %w", classID, err)
}
cm[int(idx)] = v
cm.pool[int(idx)] = v
}
}
return nil
Expand Down
12 changes: 10 additions & 2 deletions parser/chunk.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@ import (

var magic = []byte{'F', 'L', 'R', 0}

type CPool struct {
pool map[int]ParseResolvable
resolved bool
}
type ClassMap map[int]ClassMetadata
type PoolMap map[int]map[int]ParseResolvable
type PoolMap map[int]*CPool

type Chunk struct {
Header Header
Expand Down Expand Up @@ -145,7 +149,11 @@ func ResolveConstants(classes ClassMap, cpools PoolMap, classID int) (err error)
// Non-existent constant pool references seem to be used to mark no value
return nil
}
for _, t := range cpool {
if cpool.resolved {
return nil
}
cpool.resolved = true
for _, t := range cpool.pool {
if err := t.Resolve(classes, cpools); err != nil {
return fmt.Errorf("unable to resolve constants: %w", err)
}
Expand Down
58 changes: 58 additions & 0 deletions parser/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package parser

import (
"bytes"
"compress/gzip"
"encoding/json"
"io/ioutil"
"os"
"testing"
)

func TestParse(t *testing.T) {
jfr, err := readGzipFile("./testdata/example.jfr.gz")
if err != nil {
t.Fatalf("Unable to read JFR file: %s", err)
}
expectedJson, err := readGzipFile("./testdata/example_parsed.json.gz")
if err != nil {
t.Fatalf("Unable to read example_parsd.json")
}
chunks, err := Parse(bytes.NewReader(jfr))
if err != nil {
t.Fatalf("Failed to parse JFR: %s", err)
return
}
actualJson, _ := json.Marshal(chunks)
if !bytes.Equal(expectedJson, actualJson) {
t.Fatalf("Failed to parse JFR: %s", err)
return
}
}

func BenchmarkParse(b *testing.B) {
jfr, err := readGzipFile("./testdata/example.jfr.gz")
if err != nil {
b.Fatalf("Unable to read JFR file: %s", err)
}
for i := 0; i < b.N; i++ {
_, err := Parse(bytes.NewReader(jfr))
if err != nil {
b.Fatalf("Unable to parse JFR file: %s", err)
}
}
}

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)
}
Binary file added parser/testdata/example.jfr.gz
Binary file not shown.
Binary file added parser/testdata/example_parsed.json.gz
Binary file not shown.
13 changes: 9 additions & 4 deletions parser/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func parseFields(r reader.Reader, classes ClassMap, cpools PoolMap, class ClassM
if err != nil {
return fmt.Errorf("unable to read constant index")
}
p, ok := cpool[int(i)]
p, ok := cpool.pool[int(i)]
if !ok {
continue
}
Expand Down Expand Up @@ -142,15 +142,20 @@ func resolveConstants(classes ClassMap, cpools PoolMap, constants *[]constant, r
if err := ResolveConstants(classes, cpools, int(c.classID)); err != nil {
return fmt.Errorf("unable to resolve contants: %w", err)
}
p, ok := cpools[int(c.classID)][int(c.index)]
p, ok := cpools[int(c.classID)]
if !ok {
// Non-existent constant pool references seem to be used to mark no value
continue
}
if err := p.Resolve(classes, cpools); err != nil {
it, ok := p.pool[int(c.index)]
if !ok {
// Non-existent constant pool references seem to be used to mark no value
continue
}
if err := it.Resolve(classes, cpools); err != nil {
return err
}
if err := cb(c.field, p); err != nil {
if err := cb(c.field, it); err != nil {
return fmt.Errorf("unable to resolve constants for field %s: %w", c.field, err)
}
}
Expand Down

0 comments on commit 3228ecf

Please sign in to comment.