Skip to content

Commit

Permalink
feat(jfr): Split wall events into both CPU and Wall profile types. (#…
Browse files Browse the repository at this point in the history
…1002)

* feat(jfr): Split wall events into both CPU and Wall profile types.

When wall is used for CPU profiling, the thread state can be used to
differentiate between runnable and sleeping threads.
Using this information, we can extract two different profile types:
- wall profile type, with all the execution samples.
- cpu profile type, with only the runnable threads.

In order to do so, jfr-parser version has been upgraded, as thread
state was not being properly retrieved.

* chore: move chunk parsing to its own function.

That should make the linter happy.
  • Loading branch information
abeaumont committed Apr 6, 2022
1 parent b11d044 commit 06dabcf
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 78 deletions.
2 changes: 1 addition & 1 deletion go.mod
Expand Up @@ -47,7 +47,7 @@ require (
github.com/pyroscope-io/client v0.0.0-20211206204731-3fd0a4b8239c
github.com/pyroscope-io/dotnetdiag v1.2.1
github.com/pyroscope-io/goldga v0.4.2-0.20220218190441-817afcc3a7f1
github.com/pyroscope-io/jfr-parser v0.2.0
github.com/pyroscope-io/jfr-parser v0.3.0
github.com/rlmcpherson/s3gof3r v0.5.0
github.com/shirou/gopsutil v3.21.4+incompatible
github.com/sirupsen/logrus v1.8.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Expand Up @@ -618,8 +618,8 @@ github.com/pyroscope-io/dotnetdiag v1.2.1 h1:3XEMrfFJnZ87BiEhozyQKmCUAuMd/Spq7KC
github.com/pyroscope-io/dotnetdiag v1.2.1/go.mod h1:eFUEHCp4eD1TgcXMlJihC+R4MrqGf7nTRdWxNADbDHA=
github.com/pyroscope-io/goldga v0.4.2-0.20220218190441-817afcc3a7f1 h1:T1fDdt3E3UpaGZ7tRF2IYrUFwNyuPlIWGeCOjfINp1s=
github.com/pyroscope-io/goldga v0.4.2-0.20220218190441-817afcc3a7f1/go.mod h1:PbX5bxlj/WxyKIEAxYgNMNWUpXP4rt9GqtjfvTf8m+I=
github.com/pyroscope-io/jfr-parser v0.2.0 h1:vwUTqn8CKKNh3zDAauIJwR9Gzqqb1VXP4Zy3Q/jYBOI=
github.com/pyroscope-io/jfr-parser v0.2.0/go.mod h1:ZMcbJjfDkOwElEK8CvUJbpetztRWRXszCmf5WU0erV8=
github.com/pyroscope-io/jfr-parser v0.3.0 h1:d37SB6tW5h45kFpglfT/YRflWXDZTo9wjWKoSLdspGM=
github.com/pyroscope-io/jfr-parser v0.3.0/go.mod h1:ZMcbJjfDkOwElEK8CvUJbpetztRWRXszCmf5WU0erV8=
github.com/pyroscope-io/revive v1.0.6-0.20210330033039-4a71146f9dc1 h1:0v9lBNgdmVtpyyk9PP/DfpJlOHkXriu5YgNlrhQw5YE=
github.com/pyroscope-io/revive v1.0.6-0.20210330033039-4a71146f9dc1/go.mod h1:tSw34BaGZ0iF+oVKDOjq1/LuxGifgW7shaJ6+dBYFXg=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
Expand Down
175 changes: 100 additions & 75 deletions pkg/convert/jfr/parser.go
Expand Up @@ -19,89 +19,114 @@ func ParseJFR(ctx context.Context, r io.Reader, s storage.Putter, pi *storage.Pu
return fmt.Errorf("unable to parse JFR format: %w", err)
}
for _, c := range chunks {
var event, alloc string
cpu := tree.New()
inTLABObjects := tree.New()
inTLABBytes := tree.New()
outTLABObjects := tree.New()
outTLABBytes := tree.New()
for _, e := range c.Events {
switch e.(type) {
case *parser.ExecutionSample:
es := e.(*parser.ExecutionSample)
if fs := frames(es.StackTrace); fs != nil {
if pErr := parse(ctx, c, s, pi); pErr != nil {
err = multierror.Append(err, pErr)
}
}
pi.Val = nil
return err
}

func parse(ctx context.Context, c parser.Chunk, s storage.Putter, pi *storage.PutInput) (err error) {
var event, alloc string
cpu := tree.New()
wall := tree.New()
inTLABObjects := tree.New()
inTLABBytes := tree.New()
outTLABObjects := tree.New()
outTLABBytes := tree.New()
for _, e := range c.Events {
switch e.(type) {
case *parser.ExecutionSample:
es := e.(*parser.ExecutionSample)
if fs := frames(es.StackTrace); fs != nil {
if es.State.Name == "STATE_RUNNABLE" {
cpu.InsertStackString(fs, 1)
}
case *parser.ObjectAllocationInNewTLAB:
oa := e.(*parser.ObjectAllocationInNewTLAB)
if fs := frames(oa.StackTrace); fs != nil {
inTLABObjects.InsertStackString(fs, 1)
inTLABBytes.InsertStackString(fs, uint64(oa.TLABSize))
}
case *parser.ObjectAllocationOutsideTLAB:
oa := e.(*parser.ObjectAllocationOutsideTLAB)
if fs := frames(oa.StackTrace); fs != nil {
outTLABObjects.InsertStackString(fs, 1)
outTLABBytes.InsertStackString(fs, uint64(oa.AllocationSize))
}
case *parser.ActiveSetting:
as := e.(*parser.ActiveSetting)
switch as.Name {
case "event":
event = as.Value
case "alloc":
alloc = as.Value
}
}
}
labels := pi.Key.Labels()
prefix := labels["__name__"]
if event == "cpu" || event == "itimer" || event == "wall" {
labels["__name__"] = prefix + "." + event
pi.Key = segment.NewKey(labels)
pi.Val = cpu
pi.Units = "samples"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
}
}
if alloc != "" {
labels["__name__"] = prefix + ".alloc_in_new_tlab_objects"
pi.Key = segment.NewKey(labels)
pi.Val = inTLABObjects
pi.Units = "objects"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
wall.InsertStackString(fs, 1)
}
labels["__name__"] = prefix + ".alloc_in_new_tlab_bytes"
pi.Key = segment.NewKey(labels)
pi.Val = inTLABObjects
pi.Units = "bytes"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
case *parser.ObjectAllocationInNewTLAB:
oa := e.(*parser.ObjectAllocationInNewTLAB)
if fs := frames(oa.StackTrace); fs != nil {
inTLABObjects.InsertStackString(fs, 1)
inTLABBytes.InsertStackString(fs, uint64(oa.TLABSize))
}
labels["__name__"] = prefix + ".alloc_outside_tlab_objects"
pi.Key = segment.NewKey(labels)
pi.Val = inTLABObjects
pi.Units = "objects"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
case *parser.ObjectAllocationOutsideTLAB:
oa := e.(*parser.ObjectAllocationOutsideTLAB)
if fs := frames(oa.StackTrace); fs != nil {
outTLABObjects.InsertStackString(fs, 1)
outTLABBytes.InsertStackString(fs, uint64(oa.AllocationSize))
}
labels["__name__"] = prefix + ".alloc_outside_tlab_bytes"
pi.Key = segment.NewKey(labels)
pi.Val = inTLABObjects
pi.Units = "bytes"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
case *parser.ActiveSetting:
as := e.(*parser.ActiveSetting)
switch as.Name {
case "event":
event = as.Value
case "alloc":
alloc = as.Value
}
}
}
pi.Val = nil
labels := pi.Key.Labels()
prefix := labels["__name__"]
if event == "cpu" || event == "itimer" || event == "wall" {
profile := event
if event == "wall" {
profile = "cpu"
}
labels["__name__"] = prefix + "." + profile
pi.Key = segment.NewKey(labels)
pi.Val = cpu
pi.Units = "samples"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
}
}
if event == "wall" {
labels["__name__"] = prefix + "." + event
pi.Key = segment.NewKey(labels)
pi.Val = wall
pi.Units = "samples"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
}
}
if alloc != "" {
labels["__name__"] = prefix + ".alloc_in_new_tlab_objects"
pi.Key = segment.NewKey(labels)
pi.Val = inTLABObjects
pi.Units = "objects"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
}
labels["__name__"] = prefix + ".alloc_in_new_tlab_bytes"
pi.Key = segment.NewKey(labels)
pi.Val = inTLABObjects
pi.Units = "bytes"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
}
labels["__name__"] = prefix + ".alloc_outside_tlab_objects"
pi.Key = segment.NewKey(labels)
pi.Val = inTLABObjects
pi.Units = "objects"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
}
labels["__name__"] = prefix + ".alloc_outside_tlab_bytes"
pi.Key = segment.NewKey(labels)
pi.Val = inTLABObjects
pi.Units = "bytes"
pi.AggregationType = "sum"
if putErr := s.Put(ctx, pi); err != nil {
err = multierror.Append(err, putErr)
}
}
return err
}

Expand Down

0 comments on commit 06dabcf

Please sign in to comment.