1
2
3
4
5 package pprof
6
7 import (
8 "bytes"
9 "compress/gzip"
10 "fmt"
11 "io"
12 "os"
13 "runtime"
14 "strconv"
15 "time"
16 "unsafe"
17 )
18
19
20
21
22 func lostProfileEvent() { lostProfileEvent() }
23
24
25 func funcPC(f interface{}) uintptr {
26 return *(*[2]*uintptr)(unsafe.Pointer(&f))[1]
27 }
28
29
30
31 type profileBuilder struct {
32 start time.Time
33 end time.Time
34 havePeriod bool
35 period int64
36 m profMap
37
38
39 w io.Writer
40 zw *gzip.Writer
41 pb protobuf
42 strings []string
43 stringMap map[string]int
44 locs map[uintptr]locInfo
45 funcs map[string]int
46 mem []memMap
47 deck pcDeck
48 }
49
50 type memMap struct {
51
52 start uintptr
53 end uintptr
54 offset uint64
55 file, buildID string
56
57 funcs symbolizeFlag
58 fake bool
59 }
60
61
62
63
64
65 type symbolizeFlag uint8
66
67 const (
68 lookupTried symbolizeFlag = 1 << iota
69 lookupFailed symbolizeFlag = 1 << iota
70 )
71
72 const (
73
74 tagProfile_SampleType = 1
75 tagProfile_Sample = 2
76 tagProfile_Mapping = 3
77 tagProfile_Location = 4
78 tagProfile_Function = 5
79 tagProfile_StringTable = 6
80 tagProfile_DropFrames = 7
81 tagProfile_KeepFrames = 8
82 tagProfile_TimeNanos = 9
83 tagProfile_DurationNanos = 10
84 tagProfile_PeriodType = 11
85 tagProfile_Period = 12
86 tagProfile_Comment = 13
87 tagProfile_DefaultSampleType = 14
88
89
90 tagValueType_Type = 1
91 tagValueType_Unit = 2
92
93
94 tagSample_Location = 1
95 tagSample_Value = 2
96 tagSample_Label = 3
97
98
99 tagLabel_Key = 1
100 tagLabel_Str = 2
101 tagLabel_Num = 3
102
103
104 tagMapping_ID = 1
105 tagMapping_Start = 2
106 tagMapping_Limit = 3
107 tagMapping_Offset = 4
108 tagMapping_Filename = 5
109 tagMapping_BuildID = 6
110 tagMapping_HasFunctions = 7
111 tagMapping_HasFilenames = 8
112 tagMapping_HasLineNumbers = 9
113 tagMapping_HasInlineFrames = 10
114
115
116 tagLocation_ID = 1
117 tagLocation_MappingID = 2
118 tagLocation_Address = 3
119 tagLocation_Line = 4
120
121
122 tagLine_FunctionID = 1
123 tagLine_Line = 2
124
125
126 tagFunction_ID = 1
127 tagFunction_Name = 2
128 tagFunction_SystemName = 3
129 tagFunction_Filename = 4
130 tagFunction_StartLine = 5
131 )
132
133
134
135 func (b *profileBuilder) stringIndex(s string) int64 {
136 id, ok := b.stringMap[s]
137 if !ok {
138 id = len(b.strings)
139 b.strings = append(b.strings, s)
140 b.stringMap[s] = id
141 }
142 return int64(id)
143 }
144
145 func (b *profileBuilder) flush() {
146 const dataFlush = 4096
147 if b.pb.nest == 0 && len(b.pb.data) > dataFlush {
148 b.zw.Write(b.pb.data)
149 b.pb.data = b.pb.data[:0]
150 }
151 }
152
153
154 func (b *profileBuilder) pbValueType(tag int, typ, unit string) {
155 start := b.pb.startMessage()
156 b.pb.int64(tagValueType_Type, b.stringIndex(typ))
157 b.pb.int64(tagValueType_Unit, b.stringIndex(unit))
158 b.pb.endMessage(tag, start)
159 }
160
161
162 func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) {
163 start := b.pb.startMessage()
164 b.pb.int64s(tagSample_Value, values)
165 b.pb.uint64s(tagSample_Location, locs)
166 if labels != nil {
167 labels()
168 }
169 b.pb.endMessage(tagProfile_Sample, start)
170 b.flush()
171 }
172
173
174 func (b *profileBuilder) pbLabel(tag int, key, str string, num int64) {
175 start := b.pb.startMessage()
176 b.pb.int64Opt(tagLabel_Key, b.stringIndex(key))
177 b.pb.int64Opt(tagLabel_Str, b.stringIndex(str))
178 b.pb.int64Opt(tagLabel_Num, num)
179 b.pb.endMessage(tag, start)
180 }
181
182
183 func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) {
184 start := b.pb.startMessage()
185 b.pb.uint64Opt(tagLine_FunctionID, funcID)
186 b.pb.int64Opt(tagLine_Line, line)
187 b.pb.endMessage(tag, start)
188 }
189
190
191 func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string, hasFuncs bool) {
192 start := b.pb.startMessage()
193 b.pb.uint64Opt(tagMapping_ID, id)
194 b.pb.uint64Opt(tagMapping_Start, base)
195 b.pb.uint64Opt(tagMapping_Limit, limit)
196 b.pb.uint64Opt(tagMapping_Offset, offset)
197 b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file))
198 b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID))
199
200
201
202
203
204
205 if hasFuncs {
206 b.pb.bool(tagMapping_HasFunctions, true)
207 }
208 b.pb.endMessage(tag, start)
209 }
210
211 func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) {
212
213
214
215
216 frames := runtime.CallersFrames([]uintptr{addr})
217 frame, more := frames.Next()
218 if frame.Function == "runtime.goexit" {
219
220
221 return nil, 0
222 }
223
224 symbolizeResult := lookupTried
225 if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 {
226 symbolizeResult |= lookupFailed
227 }
228
229 if frame.PC == 0 {
230
231
232 frame.PC = addr - 1
233 }
234 ret := []runtime.Frame{frame}
235 for frame.Function != "runtime.goexit" && more == true {
236 frame, more = frames.Next()
237 ret = append(ret, frame)
238 }
239 return ret, symbolizeResult
240 }
241
242 type locInfo struct {
243
244 id uint64
245
246
247
248
249 pcs []uintptr
250 }
251
252
253
254
255
256 func newProfileBuilder(w io.Writer) *profileBuilder {
257 zw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed)
258 b := &profileBuilder{
259 w: w,
260 zw: zw,
261 start: time.Now(),
262 strings: []string{""},
263 stringMap: map[string]int{"": 0},
264 locs: map[uintptr]locInfo{},
265 funcs: map[string]int{},
266 }
267 b.readMapping()
268 return b
269 }
270
271
272
273
274 func (b *profileBuilder) addCPUData(data []uint64, tags []unsafe.Pointer) error {
275 if !b.havePeriod {
276
277 if len(data) < 3 {
278 return fmt.Errorf("truncated profile")
279 }
280 if data[0] != 3 || data[2] == 0 {
281 return fmt.Errorf("malformed profile")
282 }
283
284
285 b.period = 1e9 / int64(data[2])
286 b.havePeriod = true
287 data = data[3:]
288 }
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305 for len(data) > 0 {
306 if len(data) < 3 || data[0] > uint64(len(data)) {
307 return fmt.Errorf("truncated profile")
308 }
309 if data[0] < 3 || tags != nil && len(tags) < 1 {
310 return fmt.Errorf("malformed profile")
311 }
312 count := data[2]
313 stk := data[3:data[0]]
314 data = data[data[0]:]
315 var tag unsafe.Pointer
316 if tags != nil {
317 tag = tags[0]
318 tags = tags[1:]
319 }
320
321 if count == 0 && len(stk) == 1 {
322
323 count = uint64(stk[0])
324 stk = []uint64{
325
326
327
328 uint64(funcPC(lostProfileEvent) + 1),
329 }
330 }
331 b.m.lookup(stk, tag).count += int64(count)
332 }
333 return nil
334 }
335
336
337 func (b *profileBuilder) build() {
338 b.end = time.Now()
339
340 b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
341 if b.havePeriod {
342 b.pbValueType(tagProfile_SampleType, "samples", "count")
343 b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds")
344 b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds())
345 b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds")
346 b.pb.int64Opt(tagProfile_Period, b.period)
347 }
348
349 values := []int64{0, 0}
350 var locs []uint64
351
352 for e := b.m.all; e != nil; e = e.nextAll {
353 values[0] = e.count
354 values[1] = e.count * b.period
355
356 var labels func()
357 if e.tag != nil {
358 labels = func() {
359 for k, v := range *(*labelMap)(e.tag) {
360 b.pbLabel(tagSample_Label, k, v, 0)
361 }
362 }
363 }
364
365 locs = b.appendLocsForStack(locs[:0], e.stk)
366
367 b.pbSample(values, locs, labels)
368 }
369
370 for i, m := range b.mem {
371 hasFunctions := m.funcs == lookupTried
372 b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(m.start), uint64(m.end), m.offset, m.file, m.buildID, hasFunctions)
373 }
374
375
376
377
378 b.pb.strings(tagProfile_StringTable, b.strings)
379 b.zw.Write(b.pb.data)
380 b.zw.Close()
381 }
382
383
384
385
386
387
388 func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLocs []uint64) {
389 b.deck.reset()
390
391
392 stk = runtime_expandFinalInlineFrame(stk)
393
394 for len(stk) > 0 {
395 addr := stk[0]
396 if l, ok := b.locs[addr]; ok {
397
398 if id := b.emitLocation(); id > 0 {
399 locs = append(locs, id)
400 }
401
402
403 locs = append(locs, l.id)
404
405
406
407
408
409
410 stk = stk[len(l.pcs):]
411 continue
412 }
413
414 frames, symbolizeResult := allFrames(addr)
415 if len(frames) == 0 {
416 if id := b.emitLocation(); id > 0 {
417 locs = append(locs, id)
418 }
419 stk = stk[1:]
420 continue
421 }
422
423 if added := b.deck.tryAdd(addr, frames, symbolizeResult); added {
424 stk = stk[1:]
425 continue
426 }
427
428
429
430 if id := b.emitLocation(); id > 0 {
431 locs = append(locs, id)
432 }
433
434
435 if l, ok := b.locs[addr]; ok {
436 locs = append(locs, l.id)
437 stk = stk[len(l.pcs):]
438 } else {
439 b.deck.tryAdd(addr, frames, symbolizeResult)
440 stk = stk[1:]
441 }
442 }
443 if id := b.emitLocation(); id > 0 {
444 locs = append(locs, id)
445 }
446 return locs
447 }
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469 type pcDeck struct {
470 pcs []uintptr
471 frames []runtime.Frame
472 symbolizeResult symbolizeFlag
473 }
474
475 func (d *pcDeck) reset() {
476 d.pcs = d.pcs[:0]
477 d.frames = d.frames[:0]
478 d.symbolizeResult = 0
479 }
480
481
482
483
484 func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) (success bool) {
485 if existing := len(d.pcs); existing > 0 {
486
487
488 newFrame := frames[0]
489 last := d.frames[existing-1]
490 if last.Func != nil {
491 return false
492 }
493 if last.Entry == 0 || newFrame.Entry == 0 {
494 return false
495 }
496
497 if last.Entry != newFrame.Entry {
498 return false
499 }
500 if last.Function == newFrame.Function {
501 return false
502 }
503 }
504 d.pcs = append(d.pcs, pc)
505 d.frames = append(d.frames, frames...)
506 d.symbolizeResult |= symbolizeResult
507 return true
508 }
509
510
511
512
513
514 func (b *profileBuilder) emitLocation() uint64 {
515 if len(b.deck.pcs) == 0 {
516 return 0
517 }
518 defer b.deck.reset()
519
520 addr := b.deck.pcs[0]
521 firstFrame := b.deck.frames[0]
522
523
524
525
526 type newFunc struct {
527 id uint64
528 name, file string
529 }
530 newFuncs := make([]newFunc, 0, 8)
531
532 id := uint64(len(b.locs)) + 1
533 b.locs[addr] = locInfo{id: id, pcs: append([]uintptr{}, b.deck.pcs...)}
534
535 start := b.pb.startMessage()
536 b.pb.uint64Opt(tagLocation_ID, id)
537 b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))
538 for _, frame := range b.deck.frames {
539
540 funcID := uint64(b.funcs[frame.Function])
541 if funcID == 0 {
542 funcID = uint64(len(b.funcs)) + 1
543 b.funcs[frame.Function] = int(funcID)
544 newFuncs = append(newFuncs, newFunc{funcID, frame.Function, frame.File})
545 }
546 b.pbLine(tagLocation_Line, funcID, int64(frame.Line))
547 }
548 for i := range b.mem {
549 if b.mem[i].start <= addr && addr < b.mem[i].end || b.mem[i].fake {
550 b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))
551
552 m := b.mem[i]
553 m.funcs |= b.deck.symbolizeResult
554 b.mem[i] = m
555 break
556 }
557 }
558 b.pb.endMessage(tagProfile_Location, start)
559
560
561 for _, fn := range newFuncs {
562 start := b.pb.startMessage()
563 b.pb.uint64Opt(tagFunction_ID, fn.id)
564 b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))
565 b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))
566 b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))
567 b.pb.endMessage(tagProfile_Function, start)
568 }
569
570 b.flush()
571 return id
572 }
573
574
575
576
577 func (b *profileBuilder) readMapping() {
578 data, _ := os.ReadFile("/proc/self/maps")
579 parseProcSelfMaps(data, b.addMapping)
580 if len(b.mem) == 0 {
581 b.addMappingEntry(0, 0, 0, "", "", true)
582
583
584
585 }
586 }
587
588 func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) {
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610 var line []byte
611
612
613 next := func() []byte {
614 j := bytes.IndexByte(line, ' ')
615 if j < 0 {
616 f := line
617 line = nil
618 return f
619 }
620 f := line[:j]
621 line = line[j+1:]
622 for len(line) > 0 && line[0] == ' ' {
623 line = line[1:]
624 }
625 return f
626 }
627
628 for len(data) > 0 {
629 i := bytes.IndexByte(data, '\n')
630 if i < 0 {
631 line, data = data, nil
632 } else {
633 line, data = data[:i], data[i+1:]
634 }
635 addr := next()
636 i = bytes.IndexByte(addr, '-')
637 if i < 0 {
638 continue
639 }
640 lo, err := strconv.ParseUint(string(addr[:i]), 16, 64)
641 if err != nil {
642 continue
643 }
644 hi, err := strconv.ParseUint(string(addr[i+1:]), 16, 64)
645 if err != nil {
646 continue
647 }
648 perm := next()
649 if len(perm) < 4 || perm[2] != 'x' {
650
651 continue
652 }
653 offset, err := strconv.ParseUint(string(next()), 16, 64)
654 if err != nil {
655 continue
656 }
657 next()
658 inode := next()
659 if line == nil {
660 continue
661 }
662 file := string(line)
663
664
665 deletedStr := " (deleted)"
666 deletedLen := len(deletedStr)
667 if len(file) >= deletedLen && file[len(file)-deletedLen:] == deletedStr {
668 file = file[:len(file)-deletedLen]
669 }
670
671 if len(inode) == 1 && inode[0] == '0' && file == "" {
672
673
674
675
676 continue
677 }
678
679
680
681
682
683
684
685
686
687
688 buildID, _ := elfBuildID(file)
689 addMapping(lo, hi, offset, file, buildID)
690 }
691 }
692
693 func (b *profileBuilder) addMapping(lo, hi, offset uint64, file, buildID string) {
694 b.addMappingEntry(lo, hi, offset, file, buildID, false)
695 }
696
697 func (b *profileBuilder) addMappingEntry(lo, hi, offset uint64, file, buildID string, fake bool) {
698 b.mem = append(b.mem, memMap{
699 start: uintptr(lo),
700 end: uintptr(hi),
701 offset: offset,
702 file: file,
703 buildID: buildID,
704 fake: fake,
705 })
706 }
707
View as plain text