1
2
3
4
5 package pprof
6
7 import (
8 "bytes"
9 "compress/gzip"
10 "fmt"
11 "internal/abi"
12 "io"
13 "runtime"
14 "strconv"
15 "strings"
16 "time"
17 "unsafe"
18 )
19
20
21
22
23 func lostProfileEvent() { lostProfileEvent() }
24
25
26
27 type profileBuilder struct {
28 start time.Time
29 end time.Time
30 havePeriod bool
31 period int64
32 m profMap
33
34
35 w io.Writer
36 zw *gzip.Writer
37 pb protobuf
38 strings []string
39 stringMap map[string]int
40 locs map[uintptr]locInfo
41 funcs map[string]int
42 mem []memMap
43 deck pcDeck
44 }
45
46 type memMap struct {
47
48 start uintptr
49 end uintptr
50 offset uint64
51 file string
52 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 {
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() error {
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 _, lbl := range (*labelMap)(e.tag).list {
371 b.pbLabel(tagSample_Label, lbl.key, lbl.value, 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 _, err := b.zw.Write(b.pb.data)
391 if err != nil {
392 return err
393 }
394 return b.zw.Close()
395 }
396
397
398
399
400
401
402
403
404
405
406 func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLocs []uint64) {
407 b.deck.reset()
408
409
410 origStk := stk
411 stk = runtime_expandFinalInlineFrame(stk)
412
413 for len(stk) > 0 {
414 addr := stk[0]
415 if l, ok := b.locs[addr]; ok {
416
417
418
419
420
421
422
423
424
425
426
427 if len(b.deck.pcs) > 0 {
428 if added := b.deck.tryAdd(addr, l.firstPCFrames, l.firstPCSymbolizeResult); 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 locs = append(locs, l.id)
441
442
443
444
445
446
447 if len(l.pcs) > len(stk) {
448 panic(fmt.Sprintf("stack too short to match cached location; stk = %#x, l.pcs = %#x, original stk = %#x", stk, l.pcs, origStk))
449 }
450 stk = stk[len(l.pcs):]
451 continue
452 }
453
454 frames, symbolizeResult := allFrames(addr)
455 if len(frames) == 0 {
456 if id := b.emitLocation(); id > 0 {
457 locs = append(locs, id)
458 }
459 stk = stk[1:]
460 continue
461 }
462
463 if added := b.deck.tryAdd(addr, frames, symbolizeResult); added {
464 stk = stk[1:]
465 continue
466 }
467
468
469
470 if id := b.emitLocation(); id > 0 {
471 locs = append(locs, id)
472 }
473
474
475 if l, ok := b.locs[addr]; ok {
476 locs = append(locs, l.id)
477 stk = stk[len(l.pcs):]
478 } else {
479 b.deck.tryAdd(addr, frames, symbolizeResult)
480 stk = stk[1:]
481 }
482 }
483 if id := b.emitLocation(); id > 0 {
484 locs = append(locs, id)
485 }
486 return locs
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
521
522
523
524
525
526
527
528
529
530
531 type pcDeck struct {
532 pcs []uintptr
533 frames []runtime.Frame
534 symbolizeResult symbolizeFlag
535
536
537
538 firstPCFrames int
539
540
541 firstPCSymbolizeResult symbolizeFlag
542 }
543
544 func (d *pcDeck) reset() {
545 d.pcs = d.pcs[:0]
546 d.frames = d.frames[:0]
547 d.symbolizeResult = 0
548 d.firstPCFrames = 0
549 d.firstPCSymbolizeResult = 0
550 }
551
552
553
554
555 func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) (success bool) {
556 if existing := len(d.frames); existing > 0 {
557
558
559 newFrame := frames[0]
560 last := d.frames[existing-1]
561 if last.Func != nil {
562 return false
563 }
564 if last.Entry == 0 || newFrame.Entry == 0 {
565 return false
566 }
567
568 if last.Entry != newFrame.Entry {
569 return false
570 }
571 if runtime_FrameSymbolName(&last) == runtime_FrameSymbolName(&newFrame) {
572 return false
573 }
574 }
575 d.pcs = append(d.pcs, pc)
576 d.frames = append(d.frames, frames...)
577 d.symbolizeResult |= symbolizeResult
578 if len(d.pcs) == 1 {
579 d.firstPCFrames = len(d.frames)
580 d.firstPCSymbolizeResult = symbolizeResult
581 }
582 return true
583 }
584
585
586
587
588
589 func (b *profileBuilder) emitLocation() uint64 {
590 if len(b.deck.pcs) == 0 {
591 return 0
592 }
593 defer b.deck.reset()
594
595 addr := b.deck.pcs[0]
596 firstFrame := b.deck.frames[0]
597
598
599
600
601 type newFunc struct {
602 id uint64
603 name, file string
604 startLine int64
605 }
606 newFuncs := make([]newFunc, 0, 8)
607
608 id := uint64(len(b.locs)) + 1
609 b.locs[addr] = locInfo{
610 id: id,
611 pcs: append([]uintptr{}, b.deck.pcs...),
612 firstPCSymbolizeResult: b.deck.firstPCSymbolizeResult,
613 firstPCFrames: append([]runtime.Frame{}, b.deck.frames[:b.deck.firstPCFrames]...),
614 }
615
616 start := b.pb.startMessage()
617 b.pb.uint64Opt(tagLocation_ID, id)
618 b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))
619 for _, frame := range b.deck.frames {
620
621 funcName := runtime_FrameSymbolName(&frame)
622 funcID := uint64(b.funcs[funcName])
623 if funcID == 0 {
624 funcID = uint64(len(b.funcs)) + 1
625 b.funcs[funcName] = int(funcID)
626 newFuncs = append(newFuncs, newFunc{
627 id: funcID,
628 name: funcName,
629 file: frame.File,
630 startLine: int64(runtime_FrameStartLine(&frame)),
631 })
632 }
633 b.pbLine(tagLocation_Line, funcID, int64(frame.Line))
634 }
635 for i := range b.mem {
636 if b.mem[i].start <= addr && addr < b.mem[i].end || b.mem[i].fake {
637 b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))
638
639 m := b.mem[i]
640 m.funcs |= b.deck.symbolizeResult
641 b.mem[i] = m
642 break
643 }
644 }
645 b.pb.endMessage(tagProfile_Location, start)
646
647
648 for _, fn := range newFuncs {
649 start := b.pb.startMessage()
650 b.pb.uint64Opt(tagFunction_ID, fn.id)
651 b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))
652 b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))
653 b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))
654 b.pb.int64Opt(tagFunction_StartLine, fn.startLine)
655 b.pb.endMessage(tagProfile_Function, start)
656 }
657
658 b.flush()
659 return id
660 }
661
662 var space = []byte(" ")
663 var newline = []byte("\n")
664
665 func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) {
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687 var line []byte
688
689
690 next := func() []byte {
691 var f []byte
692 f, line, _ = bytes.Cut(line, space)
693 line = bytes.TrimLeft(line, " ")
694 return f
695 }
696
697 for len(data) > 0 {
698 line, data, _ = bytes.Cut(data, newline)
699 addr := next()
700 loStr, hiStr, ok := strings.Cut(string(addr), "-")
701 if !ok {
702 continue
703 }
704 lo, err := strconv.ParseUint(loStr, 16, 64)
705 if err != nil {
706 continue
707 }
708 hi, err := strconv.ParseUint(hiStr, 16, 64)
709 if err != nil {
710 continue
711 }
712 perm := next()
713 if len(perm) < 4 || perm[2] != 'x' {
714
715 continue
716 }
717 offset, err := strconv.ParseUint(string(next()), 16, 64)
718 if err != nil {
719 continue
720 }
721 next()
722 inode := next()
723 if line == nil {
724 continue
725 }
726 file := string(line)
727
728
729 deletedStr := " (deleted)"
730 deletedLen := len(deletedStr)
731 if len(file) >= deletedLen && file[len(file)-deletedLen:] == deletedStr {
732 file = file[:len(file)-deletedLen]
733 }
734
735 if len(inode) == 1 && inode[0] == '0' && file == "" {
736
737
738
739
740 continue
741 }
742
743
744
745
746
747
748
749
750
751 buildID, _ := elfBuildID(file)
752 addMapping(lo, hi, offset, file, buildID)
753 }
754 }
755
756 func (b *profileBuilder) addMapping(lo, hi, offset uint64, file, buildID string) {
757 b.addMappingEntry(lo, hi, offset, file, buildID, false)
758 }
759
760 func (b *profileBuilder) addMappingEntry(lo, hi, offset uint64, file, buildID string, fake bool) {
761 b.mem = append(b.mem, memMap{
762 start: uintptr(lo),
763 end: uintptr(hi),
764 offset: offset,
765 file: file,
766 buildID: buildID,
767 fake: fake,
768 })
769 }
770
View as plain text