Skip to content

Commit

Permalink
jfrparser-cli: add cli that formats json (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomershafir committed Apr 28, 2024
1 parent 8abcb81 commit f984a37
Show file tree
Hide file tree
Showing 10 changed files with 272 additions and 43 deletions.
91 changes: 91 additions & 0 deletions cmd/jfrparser/format/format_json.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package format

import (
"encoding/json"
"fmt"
"io"

"github.com/grafana/jfr-parser/parser"
"github.com/grafana/jfr-parser/parser/types"
)

type formatterJson struct{}

func NewFormatterJson() *formatterJson {
return &formatterJson{}
}

type chunk struct {
Header parser.ChunkHeader
FrameTypes types.FrameTypeList
ThreadStates types.ThreadStateList
Threads types.ThreadList
Classes types.ClassList
Methods types.MethodList
Packages types.PackageList
Symbols types.SymbolList
LogLevels types.LogLevelList
Stacktraces types.StackTraceList
Recordings []any
}

func initChunk(c *chunk, p *parser.Parser) {
c.Header = p.ChunkHeader()
c.FrameTypes = p.FrameTypes
c.ThreadStates = p.ThreadStates
c.Threads = p.Threads
c.Classes = p.Classes
c.Methods = p.Methods
c.Packages = p.Packages
c.Symbols = p.Symbols
c.LogLevels = p.LogLevels
c.Stacktraces = p.Stacktrace
c.Recordings = make([]any, 0)
}

// TODO: support multi-chunk JFR, by exposing new chunk indicator on the parser (a counter), and printing an array of chunks
func (f *formatterJson) Format(buf []byte, dest string) ([]string, [][]byte, error) {
p := parser.NewParser(buf, parser.Options{SymbolProcessor: parser.ProcessSymbols})

ir := make([]chunk, 1)
chunkIdx := 0
newChunk := true
for {
typ, err := p.ParseEvent()
if err != nil {
if err == io.EOF {
break
}
return nil, nil, fmt.Errorf("parser.ParseEvent error: %w", err)
}

if newChunk {
initChunk(&ir[chunkIdx], p)
newChunk = false
}

switch typ {
case p.TypeMap.T_EXECUTION_SAMPLE:
ir[chunkIdx].Recordings = append(ir[chunkIdx].Recordings, p.ExecutionSample)
case p.TypeMap.T_ALLOC_IN_NEW_TLAB:
ir[chunkIdx].Recordings = append(ir[chunkIdx].Recordings, p.ObjectAllocationInNewTLAB)
case p.TypeMap.T_ALLOC_OUTSIDE_TLAB:
ir[chunkIdx].Recordings = append(ir[chunkIdx].Recordings, p.ObjectAllocationOutsideTLAB)
case p.TypeMap.T_MONITOR_ENTER:
ir[chunkIdx].Recordings = append(ir[chunkIdx].Recordings, p.JavaMonitorEnter)
case p.TypeMap.T_THREAD_PARK:
ir[chunkIdx].Recordings = append(ir[chunkIdx].Recordings, p.ThreadPark)
case p.TypeMap.T_LIVE_OBJECT:
ir[chunkIdx].Recordings = append(ir[chunkIdx].Recordings, p.LiveObject)
case p.TypeMap.T_ACTIVE_SETTING:
ir[chunkIdx].Recordings = append(ir[chunkIdx].Recordings, p.ActiveSetting)
}
}

outBuf, err := json.Marshal(ir)
if err != nil {
return nil, nil, fmt.Errorf("json.Marshal error: %w", err)
}
outBuf = append(outBuf, '\n')
return []string{dest}, [][]byte{outBuf}, nil
}
54 changes: 54 additions & 0 deletions cmd/jfrparser/format/format_json_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package format

import (
"bytes"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
)

func loadTestDataGzip(t *testing.T, filename string) []byte {
f, err := os.Open(filename)
assert.NoError(t, err)
defer f.Close()
r, err := gzip.NewReader(f)
assert.NoError(t, err)
defer r.Close()
b, err := io.ReadAll(r)
assert.NoError(t, err)
return b
}

func TestFormatterJson(t *testing.T) {
testDataDir := filepath.Join("..", "..", "..", "parser", "testdata")
fnamePrefix := "cortex-dev-01__kafka-0__cpu__0"
dest := "example"

fmtr := NewFormatterJson()
tests := []struct {
name string
pathJfr string
pathJson string
}{{
"example",
filepath.Join(testDataDir, fmt.Sprintf("%s.jfr.gz", fnamePrefix)),
filepath.Join(testDataDir, fmt.Sprintf("%s.json.gz", fnamePrefix)),
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
in := loadTestDataGzip(t, tt.pathJfr)
expected := loadTestDataGzip(t, tt.pathJson)
dests, data, err := fmtr.Format(in, dest)
assert.NoError(t, err)
assert.Equal(t, 1, len(dests))
assert.True(t, dest == dests[0])
assert.Equal(t, 1, len(data))
assert.Equal(t, 0, bytes.Compare(expected, data[0]))
})
}
}
43 changes: 43 additions & 0 deletions cmd/jfrparser/format/format_pprof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package format

import (
"fmt"
"path/filepath"
"time"

"github.com/grafana/jfr-parser/pprof"
)

type formatterPprof struct{}

func NewFormatterPprof() *formatterPprof {
return &formatterPprof{}
}

func (f *formatterPprof) Format(buf []byte, dest string) ([]string, [][]byte, error) {
pi := &pprof.ParseInput{
StartTime: time.Now(),
EndTime: time.Now(),
SampleRate: 100,
}
profiles, err := pprof.ParseJFR(buf, pi, nil)
if err != nil {
return nil, nil, err
}

data := make([][]byte, 0)
dests := make([]string, 0)
destDir := filepath.Dir(dest)
destBase := filepath.Base(dest)
for i := 0; i < len(profiles.Profiles); i++ {
filename := fmt.Sprintf("%s.%s", profiles.Profiles[i].Metric, destBase)
dests = append(dests, filepath.Join(destDir, filename))

bs, err := profiles.Profiles[i].Profile.MarshalVT()
if err != nil {
return nil, nil, err
}
data = append(data, bs)
}
return dests, data, nil
}
75 changes: 75 additions & 0 deletions cmd/jfrparser/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"flag"
"fmt"
"os"
"strings"

"github.com/grafana/jfr-parser/cmd/jfrparser/format"
)

type command struct {
// Opts
format string

// Args
src string
dest string
}

func parseCommand(c *command) {
format := flag.String("format", "json", "output format. Supported formats: json, pprof")
flag.Parse()
c.format = strings.ToLower(*format)

args := flag.Args()
c.src = args[0]
if len(args) < 2 {
c.dest = fmt.Sprintf("%s.%s", c.src, c.format)
} else {
c.dest = args[1]
}
}

type formatter interface {
// Formats the given JFR
Format(buf []byte, dest string) ([]string, [][]byte, error)
}

// Usage: ./jfrparser [options] /path/to/jfr [/path/to/dest]
func main() {
c := new(command)
parseCommand(c)

buf, err := os.ReadFile(c.src)
if err != nil {
panic(err)
}

var fmtr formatter = nil
switch c.format {
case "json":
fmtr = format.NewFormatterJson()
case "pprof":
fmtr = format.NewFormatterPprof()
default:
panic("unsupported format")
}

dests, data, err := fmtr.Format(buf, c.dest)
if err != nil {
panic(err)
}

if len(dests) != len(data) {
panic(fmt.Errorf("logic error"))
}

for i := 0; i < len(dests); i++ {
err = os.WriteFile(dests[i], data[i], 0644)
if err != nil {
panic(err)
}
}
}
5 changes: 5 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ func (p *Parser) ParseEvent() (def.TypeID, error) {
}
}
}

func (p *Parser) ChunkHeader() ChunkHeader {
return p.header
}

func (p *Parser) GetStacktrace(stID types2.StackTraceRef) *types2.StackTrace {
idx, ok := p.Stacktrace.IDMap[stID]
if !ok {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pprof
package parser

import (
"testing"
Expand Down
4 changes: 2 additions & 2 deletions pprof/symbols.go → parser/symbols.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package pprof
package parser

import (
"regexp"
Expand Down Expand Up @@ -36,7 +36,7 @@ func mergeJVMGeneratedClasses(frame string) string {
return frame
}

func processSymbols(ref *types.SymbolList) {
func ProcessSymbols(ref *types.SymbolList) {
for i := range ref.Symbol { //todo regex replace inplace
ref.Symbol[i].String = mergeJVMGeneratedClasses(ref.Symbol[i].String)
}
Expand Down
Binary file not shown.
39 changes: 0 additions & 39 deletions pprof/cmd/jfr2pprof/main.go

This file was deleted.

2 changes: 1 addition & 1 deletion pprof/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func ParseJFR(body []byte, pi *ParseInput, jfrLabels *LabelsSnapshot) (res *Prof
}
}()
p := parser.NewParser(body, parser.Options{
SymbolProcessor: processSymbols,
SymbolProcessor: parser.ProcessSymbols,
})
return parse(p, pi, jfrLabels)
}
Expand Down

0 comments on commit f984a37

Please sign in to comment.