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