1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package profile
19
20 import (
21 "bytes"
22 "fmt"
23 "io"
24 "path/filepath"
25 "regexp"
26 "strconv"
27 "strings"
28 )
29
30 var (
31 attributeRx = regexp.MustCompile(`([\w ]+)=([\w ]+)`)
32 javaSampleRx = regexp.MustCompile(` *(\d+) +(\d+) +@ +([ x0-9a-f]*)`)
33 javaLocationRx = regexp.MustCompile(`^\s*0x([[:xdigit:]]+)\s+(.*)\s*$`)
34 javaLocationFileLineRx = regexp.MustCompile(`^(.*)\s+\((.+):(-?[[:digit:]]+)\)$`)
35 javaLocationPathRx = regexp.MustCompile(`^(.*)\s+\((.*)\)$`)
36 )
37
38
39
40
41
42 func javaCPUProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) {
43 p := &Profile{
44 Period: period * 1000,
45 PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},
46 SampleType: []*ValueType{{Type: "samples", Unit: "count"}, {Type: "cpu", Unit: "nanoseconds"}},
47 }
48 var err error
49 var locs map[uint64]*Location
50 if b, locs, err = parseCPUSamples(b, parse, false, p); err != nil {
51 return nil, err
52 }
53
54 if err = parseJavaLocations(b, locs, p); err != nil {
55 return nil, err
56 }
57
58
59 if err = p.Aggregate(true, true, true, true, false); err != nil {
60 return nil, err
61 }
62
63 return p, nil
64 }
65
66
67
68 func parseJavaProfile(b []byte) (*Profile, error) {
69 h := bytes.SplitAfterN(b, []byte("\n"), 2)
70 if len(h) < 2 {
71 return nil, errUnrecognized
72 }
73
74 p := &Profile{
75 PeriodType: &ValueType{},
76 }
77 header := string(bytes.TrimSpace(h[0]))
78
79 var err error
80 var pType string
81 switch header {
82 case "--- heapz 1 ---":
83 pType = "heap"
84 case "--- contentionz 1 ---":
85 pType = "contention"
86 default:
87 return nil, errUnrecognized
88 }
89
90 if b, err = parseJavaHeader(pType, h[1], p); err != nil {
91 return nil, err
92 }
93 var locs map[uint64]*Location
94 if b, locs, err = parseJavaSamples(pType, b, p); err != nil {
95 return nil, err
96 }
97 if err = parseJavaLocations(b, locs, p); err != nil {
98 return nil, err
99 }
100
101
102 if err = p.Aggregate(true, true, true, true, false); err != nil {
103 return nil, err
104 }
105
106 return p, nil
107 }
108
109
110
111
112 func parseJavaHeader(pType string, b []byte, p *Profile) ([]byte, error) {
113 nextNewLine := bytes.IndexByte(b, byte('\n'))
114 for nextNewLine != -1 {
115 line := string(bytes.TrimSpace(b[0:nextNewLine]))
116 if line != "" {
117 h := attributeRx.FindStringSubmatch(line)
118 if h == nil {
119
120 return b, nil
121 }
122
123 attribute, value := strings.TrimSpace(h[1]), strings.TrimSpace(h[2])
124 var err error
125 switch pType + "/" + attribute {
126 case "heap/format", "cpu/format", "contention/format":
127 if value != "java" {
128 return nil, errUnrecognized
129 }
130 case "heap/resolution":
131 p.SampleType = []*ValueType{
132 {Type: "inuse_objects", Unit: "count"},
133 {Type: "inuse_space", Unit: value},
134 }
135 case "contention/resolution":
136 p.SampleType = []*ValueType{
137 {Type: "contentions", Unit: "count"},
138 {Type: "delay", Unit: value},
139 }
140 case "contention/sampling period":
141 p.PeriodType = &ValueType{
142 Type: "contentions", Unit: "count",
143 }
144 if p.Period, err = strconv.ParseInt(value, 0, 64); err != nil {
145 return nil, fmt.Errorf("failed to parse attribute %s: %v", line, err)
146 }
147 case "contention/ms since reset":
148 millis, err := strconv.ParseInt(value, 0, 64)
149 if err != nil {
150 return nil, fmt.Errorf("failed to parse attribute %s: %v", line, err)
151 }
152 p.DurationNanos = millis * 1000 * 1000
153 default:
154 return nil, errUnrecognized
155 }
156 }
157
158 b = b[nextNewLine+1:]
159 nextNewLine = bytes.IndexByte(b, byte('\n'))
160 }
161 return b, nil
162 }
163
164
165
166
167 func parseJavaSamples(pType string, b []byte, p *Profile) ([]byte, map[uint64]*Location, error) {
168 nextNewLine := bytes.IndexByte(b, byte('\n'))
169 locs := make(map[uint64]*Location)
170 for nextNewLine != -1 {
171 line := string(bytes.TrimSpace(b[0:nextNewLine]))
172 if line != "" {
173 sample := javaSampleRx.FindStringSubmatch(line)
174 if sample == nil {
175
176 return b, locs, nil
177 }
178
179
180
181 var err error
182 value1, value2, value3 := sample[2], sample[1], sample[3]
183 addrs, err := parseHexAddresses(value3)
184 if err != nil {
185 return nil, nil, fmt.Errorf("malformed sample: %s: %v", line, err)
186 }
187
188 var sloc []*Location
189 for _, addr := range addrs {
190 loc := locs[addr]
191 if locs[addr] == nil {
192 loc = &Location{
193 Address: addr,
194 }
195 p.Location = append(p.Location, loc)
196 locs[addr] = loc
197 }
198 sloc = append(sloc, loc)
199 }
200 s := &Sample{
201 Value: make([]int64, 2),
202 Location: sloc,
203 }
204
205 if s.Value[0], err = strconv.ParseInt(value1, 0, 64); err != nil {
206 return nil, nil, fmt.Errorf("parsing sample %s: %v", line, err)
207 }
208 if s.Value[1], err = strconv.ParseInt(value2, 0, 64); err != nil {
209 return nil, nil, fmt.Errorf("parsing sample %s: %v", line, err)
210 }
211
212 switch pType {
213 case "heap":
214 const javaHeapzSamplingRate = 524288
215 if s.Value[0] == 0 {
216 return nil, nil, fmt.Errorf("parsing sample %s: second value must be non-zero", line)
217 }
218 s.NumLabel = map[string][]int64{"bytes": {s.Value[1] / s.Value[0]}}
219 s.Value[0], s.Value[1] = scaleHeapSample(s.Value[0], s.Value[1], javaHeapzSamplingRate)
220 case "contention":
221 if period := p.Period; period != 0 {
222 s.Value[0] = s.Value[0] * p.Period
223 s.Value[1] = s.Value[1] * p.Period
224 }
225 }
226 p.Sample = append(p.Sample, s)
227 }
228
229 b = b[nextNewLine+1:]
230 nextNewLine = bytes.IndexByte(b, byte('\n'))
231 }
232 return b, locs, nil
233 }
234
235
236
237
238
239 func parseJavaLocations(b []byte, locs map[uint64]*Location, p *Profile) error {
240 r := bytes.NewBuffer(b)
241 fns := make(map[string]*Function)
242 for {
243 line, err := r.ReadString('\n')
244 if err != nil {
245 if err != io.EOF {
246 return err
247 }
248 if line == "" {
249 break
250 }
251 }
252
253 if line = strings.TrimSpace(line); line == "" {
254 continue
255 }
256
257 jloc := javaLocationRx.FindStringSubmatch(line)
258 if len(jloc) != 3 {
259 continue
260 }
261 addr, err := strconv.ParseUint(jloc[1], 16, 64)
262 if err != nil {
263 return fmt.Errorf("parsing sample %s: %v", line, err)
264 }
265 loc := locs[addr]
266 if loc == nil {
267
268 continue
269 }
270 var lineFunc, lineFile string
271 var lineNo int64
272
273 if fileLine := javaLocationFileLineRx.FindStringSubmatch(jloc[2]); len(fileLine) == 4 {
274
275 lineFunc, lineFile = fileLine[1], fileLine[2]
276 if n, err := strconv.ParseInt(fileLine[3], 10, 64); err == nil && n > 0 {
277 lineNo = n
278 }
279 } else if filePath := javaLocationPathRx.FindStringSubmatch(jloc[2]); len(filePath) == 3 {
280
281
282 lineFunc, lineFile = filePath[1], filepath.Base(filePath[2])
283 } else if strings.Contains(jloc[2], "generated stub/JIT") {
284 lineFunc = "STUB"
285 } else {
286
287
288 lineFunc = jloc[2]
289 }
290 fn := fns[lineFunc]
291
292 if fn == nil {
293 fn = &Function{
294 Name: lineFunc,
295 SystemName: lineFunc,
296 Filename: lineFile,
297 }
298 fns[lineFunc] = fn
299 p.Function = append(p.Function, fn)
300 }
301 loc.Line = []Line{
302 {
303 Function: fn,
304 Line: lineNo,
305 },
306 }
307 loc.Address = 0
308 }
309
310 p.remapLocationIDs()
311 p.remapFunctionIDs()
312 p.remapMappingIDs()
313
314 return nil
315 }
316
View as plain text