diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f11b75 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea/ diff --git a/main.go b/main.go index b9f4325..1a331af 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "fmt" "log" "os" + "time" "github.com/pyroscope-io/jfr-parser/parser" ) @@ -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 { diff --git a/parser/checkpoint.go b/parser/checkpoint.go index b108841..dfef7d0 100644 --- a/parser/checkpoint.go +++ b/parser/checkpoint.go @@ -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() @@ -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 diff --git a/parser/chunk.go b/parser/chunk.go index 6806be3..320b21e 100644 --- a/parser/chunk.go +++ b/parser/chunk.go @@ -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 @@ -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) } diff --git a/parser/parser_test.go b/parser/parser_test.go new file mode 100644 index 0000000..e8debe7 --- /dev/null +++ b/parser/parser_test.go @@ -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) +} diff --git a/parser/testdata/example.jfr.gz b/parser/testdata/example.jfr.gz new file mode 100644 index 0000000..37dc8a8 Binary files /dev/null and b/parser/testdata/example.jfr.gz differ diff --git a/parser/testdata/example_parsed.json.gz b/parser/testdata/example_parsed.json.gz new file mode 100644 index 0000000..4e40c19 Binary files /dev/null and b/parser/testdata/example_parsed.json.gz differ diff --git a/parser/types.go b/parser/types.go index c3ac15a..2d36b3b 100644 --- a/parser/types.go +++ b/parser/types.go @@ -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 } @@ -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) } }