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