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