-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathjfr.go
More file actions
153 lines (134 loc) · 4.38 KB
/
Copy pathjfr.go
File metadata and controls
153 lines (134 loc) · 4.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
// Package analysis — JFR support.
//
// This file adds the ability to read JFR (Java Flight Recorder) files and map
// JFR events directly into the analyzer's neutral ProfileSet. The mapping from
// event names to profile semantics intentionally lives here: prof-correctness is
// the consumer that knows which JDK / Datadog profiler events should satisfy a
// given expected_profile.json assertion.
package analysis
import (
"fmt"
"io"
"strings"
"github.com/grafana/jfr-parser/parser"
"github.com/grafana/jfr-parser/parser/types"
)
// FromJFR builds a ProfileSet directly from Java Flight Recorder events.
func FromJFR(data []byte) (*ProfileSet, error) {
p := parser.NewParser(data, parser.Options{
SymbolProcessor: parser.ProcessSymbols,
})
ps := newProfileSet()
var cpuTotal int64
var durationNanos uint64
seenChunks := map[jfrChunkKey]bool{}
for {
event, err := p.ParseRawEvent()
if err == io.EOF {
break
}
if err != nil {
return nil, fmt.Errorf("jfr ParseRawEvent: %w", err)
}
if event.Type == nil {
continue
}
header := p.ChunkHeader()
chunk := jfrChunkKey{startNanos: header.StartNanos, durationNanos: header.DurationNanos}
if !seenChunks[chunk] {
seenChunks[chunk] = true
durationNanos += header.DurationNanos
}
switch event.Type.Name {
case "jdk.ExecutionSample", "datadog.ExecutionSample":
fields, err := p.DecodeRawEventFields(event)
if err != nil {
return nil, fmt.Errorf("jfr decode %s: %w", event.Type.Name, err)
}
cpuTotal += addJFRCPU(ps, p, fields)
}
}
if cpuTotal > 0 && durationNanos > 0 {
ps.addProfileDuration("cpu", cpuTotal, float64(durationNanos)/1e9)
}
return ps.finalize(), nil
}
type jfrChunkKey struct {
startNanos uint64
durationNanos uint64
}
func addJFRCPU(ps *ProfileSet, p *parser.Parser, fields map[string]parser.RawField) int64 {
stackField, ok := jfrField(fields, "stackTrace")
if !ok {
return 0
}
// Match jfr-parser/pprof's CPU semantics: execution samples from sleeping
// threads do not count as CPU samples. If the state is absent, keep the
// sample rather than silently dropping producer-specific events.
if state, ok := jfrField(fields, "state"); ok {
if ts := p.GetThreadState(types.ThreadStateRef(state.Uint64)); ts != nil && ts.Name == "STATE_SLEEPING" {
return 0
}
}
folded := foldJFRStack(p, types.StackTraceRef(stackField.Uint64))
if folded == "" {
return 0
}
val := int64(1)
if weight, ok := jfrField(fields, "weight"); ok && weight.Uint64 > 0 {
val = int64(weight.Uint64)
}
ps.add("cpu", StackSample{Stack: folded, Val: val, Labels: executionSampleLabels(fields)})
return val
}
func executionSampleLabels(fields map[string]parser.RawField) map[string][]string {
labels := map[string][]string{}
if spanID, ok := jfrField(fields, "spanId"); ok && spanID.Uint64 != 0 {
labels[LabelSpanID] = []string{fmt.Sprintf("%d", spanID.Uint64)}
}
if localRootSpanID, ok := jfrField(fields, "localRootSpanId"); ok && localRootSpanID.Uint64 != 0 {
labels[LabelLocalRootSID] = []string{fmt.Sprintf("%d", localRootSpanID.Uint64)}
}
traceHi, traceHiOK := jfrField(fields, "traceIdHi")
traceLo, traceLoOK := jfrField(fields, "traceIdLo")
if (traceHiOK || traceLoOK) && (traceHi.Uint64 != 0 || traceLo.Uint64 != 0) {
labels[LabelTraceID] = []string{fmt.Sprintf("%016x%016x", traceHi.Uint64, traceLo.Uint64)}
}
return labels
}
func jfrField(fields map[string]parser.RawField, name string) (parser.RawValue, bool) {
field, ok := fields[name]
if !ok {
return parser.RawValue{}, false
}
return field.First()
}
func foldJFRStack(p *parser.Parser, stackRef types.StackTraceRef) string {
st := p.GetStacktrace(stackRef)
if st == nil || len(st.Frames) == 0 {
return ""
}
// JFR frames are leaf-first. The analyzer uses root-first folded stacks,
// matching FromPprof and the historical expected_profile.json captures.
frames := make([]string, 0, len(st.Frames))
for i := len(st.Frames) - 1; i >= 0; i-- {
name := jfrFrameName(p, st.Frames[i].Method)
if name != "" {
frames = append(frames, name)
}
}
return strings.Join(frames, ";")
}
func jfrFrameName(p *parser.Parser, methodRef types.MethodRef) string {
m := p.GetMethod(methodRef)
if m == nil {
return ""
}
methodName := p.GetSymbolString(m.Name)
cls := p.GetClass(m.Type)
if cls == nil {
return methodName
}
clsName := strings.ReplaceAll(p.GetSymbolString(cls.Name), "/", ".")
return clsName + "." + methodName
}