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