Source file src/runtime/pprof/proto.go
1
2
3
4
5 package pprof
6
7 import (
8 "bytes"
9 "compress/gzip"
10 "fmt"
11 "io"
12 "io/ioutil"
13 "runtime"
14 "strconv"
15 "time"
16 "unsafe"
17 )
18
19
20
21
22 func lostProfileEvent() { lostProfileEvent() }
23
24
25 func funcPC(f interface{}) uintptr {
26 return *(*[2]*uintptr)(unsafe.Pointer(&f))[1]
27 }
28
29
30
31 type profileBuilder struct {
32 start time.Time
33 end time.Time
34 havePeriod bool
35 period int64
36 m profMap
37
38
39 w io.Writer
40 zw *gzip.Writer
41 pb protobuf
42 strings []string
43 stringMap map[string]int
44 locs map[uintptr]int
45 funcs map[string]int
46 mem []memMap
47 }
48
49 type memMap struct {
50
51 start uintptr
52 end uintptr
53 offset uint64
54 file, buildID string
55
56 funcs symbolizeFlag
57 fake bool
58 }
59
60
61
62
63
64 type symbolizeFlag uint8
65
66 const (
67 lookupTried symbolizeFlag = 1 << iota
68 lookupFailed symbolizeFlag = 1 << iota
69 )
70
71 const (
72
73 tagProfile_SampleType = 1
74 tagProfile_Sample = 2
75 tagProfile_Mapping = 3
76 tagProfile_Location = 4
77 tagProfile_Function = 5
78 tagProfile_StringTable = 6
79 tagProfile_DropFrames = 7
80 tagProfile_KeepFrames = 8
81 tagProfile_TimeNanos = 9
82 tagProfile_DurationNanos = 10
83 tagProfile_PeriodType = 11
84 tagProfile_Period = 12
85 tagProfile_Comment = 13
86 tagProfile_DefaultSampleType = 14
87
88
89 tagValueType_Type = 1
90 tagValueType_Unit = 2
91
92
93 tagSample_Location = 1
94 tagSample_Value = 2
95 tagSample_Label = 3
96
97
98 tagLabel_Key = 1
99 tagLabel_Str = 2
100 tagLabel_Num = 3
101
102
103 tagMapping_ID = 1
104 tagMapping_Start = 2
105 tagMapping_Limit = 3
106 tagMapping_Offset = 4
107 tagMapping_Filename = 5
108 tagMapping_BuildID = 6
109 tagMapping_HasFunctions = 7
110 tagMapping_HasFilenames = 8
111 tagMapping_HasLineNumbers = 9
112 tagMapping_HasInlineFrames = 10
113
114
115 tagLocation_ID = 1
116 tagLocation_MappingID = 2
117 tagLocation_Address = 3
118 tagLocation_Line = 4
119
120
121 tagLine_FunctionID = 1
122 tagLine_Line = 2
123
124
125 tagFunction_ID = 1
126 tagFunction_Name = 2
127 tagFunction_SystemName = 3
128 tagFunction_Filename = 4
129 tagFunction_StartLine = 5
130 )
131
132
133
134 func (b *profileBuilder) stringIndex(s string) int64 {
135 id, ok := b.stringMap[s]
136 if !ok {
137 id = len(b.strings)
138 b.strings = append(b.strings, s)
139 b.stringMap[s] = id
140 }
141 return int64(id)
142 }
143
144 func (b *profileBuilder) flush() {
145 const dataFlush = 4096
146 if b.pb.nest == 0 && len(b.pb.data) > dataFlush {
147 b.zw.Write(b.pb.data)
148 b.pb.data = b.pb.data[:0]
149 }
150 }
151
152
153 func (b *profileBuilder) pbValueType(tag int, typ, unit string) {
154 start := b.pb.startMessage()
155 b.pb.int64(tagValueType_Type, b.stringIndex(typ))
156 b.pb.int64(tagValueType_Unit, b.stringIndex(unit))
157 b.pb.endMessage(tag, start)
158 }
159
160
161 func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) {
162 start := b.pb.startMessage()
163 b.pb.int64s(tagSample_Value, values)
164 b.pb.uint64s(tagSample_Location, locs)
165 if labels != nil {
166 labels()
167 }
168 b.pb.endMessage(tagProfile_Sample, start)
169 b.flush()
170 }
171
172
173 func (b *profileBuilder) pbLabel(tag int, key, str string, num int64) {
174 start := b.pb.startMessage()
175 b.pb.int64Opt(tagLabel_Key, b.stringIndex(key))
176 b.pb.int64Opt(tagLabel_Str, b.stringIndex(str))
177 b.pb.int64Opt(tagLabel_Num, num)
178 b.pb.endMessage(tag, start)
179 }
180
181
182 func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) {
183 start := b.pb.startMessage()
184 b.pb.uint64Opt(tagLine_FunctionID, funcID)
185 b.pb.int64Opt(tagLine_Line, line)
186 b.pb.endMessage(tag, start)
187 }
188
189
190 func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string, hasFuncs bool) {
191 start := b.pb.startMessage()
192 b.pb.uint64Opt(tagMapping_ID, id)
193 b.pb.uint64Opt(tagMapping_Start, base)
194 b.pb.uint64Opt(tagMapping_Limit, limit)
195 b.pb.uint64Opt(tagMapping_Offset, offset)
196 b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file))
197 b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID))
198
199
200
201
202
203
204 if hasFuncs {
205 b.pb.bool(tagMapping_HasFunctions, true)
206 }
207 b.pb.endMessage(tag, start)
208 }
209
210
211
212
213 func (b *profileBuilder) locForPC(addr uintptr) uint64 {
214 id := uint64(b.locs[addr])
215 if id != 0 {
216 return id
217 }
218
219
220
221
222
223 frames := runtime.CallersFrames([]uintptr{addr})
224 frame, more := frames.Next()
225 if frame.Function == "runtime.goexit" {
226
227
228 return 0
229 }
230
231 symbolizeResult := lookupTried
232 if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 {
233 symbolizeResult |= lookupFailed
234 }
235
236 if frame.PC == 0 {
237
238
239 frame.PC = addr - 1
240 }
241
242
243
244
245 type newFunc struct {
246 id uint64
247 name, file string
248 }
249 newFuncs := make([]newFunc, 0, 8)
250
251 id = uint64(len(b.locs)) + 1
252 b.locs[addr] = int(id)
253 start := b.pb.startMessage()
254 b.pb.uint64Opt(tagLocation_ID, id)
255 b.pb.uint64Opt(tagLocation_Address, uint64(frame.PC))
256 for frame.Function != "runtime.goexit" {
257
258 funcID := uint64(b.funcs[frame.Function])
259 if funcID == 0 {
260 funcID = uint64(len(b.funcs)) + 1
261 b.funcs[frame.Function] = int(funcID)
262 newFuncs = append(newFuncs, newFunc{funcID, frame.Function, frame.File})
263 }
264 b.pbLine(tagLocation_Line, funcID, int64(frame.Line))
265 if !more {
266 break
267 }
268 frame, more = frames.Next()
269 }
270 for i := range b.mem {
271 if b.mem[i].start <= addr && addr < b.mem[i].end || b.mem[i].fake {
272 b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))
273
274 m := b.mem[i]
275 m.funcs |= symbolizeResult
276 b.mem[i] = m
277 break
278 }
279 }
280 b.pb.endMessage(tagProfile_Location, start)
281
282
283 for _, fn := range newFuncs {
284 start := b.pb.startMessage()
285 b.pb.uint64Opt(tagFunction_ID, fn.id)
286 b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))
287 b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))
288 b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))
289 b.pb.endMessage(tagProfile_Function, start)
290 }
291
292 b.flush()
293 return id
294 }
295
296
297
298
299
300 func newProfileBuilder(w io.Writer) *profileBuilder {
301 zw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed)
302 b := &profileBuilder{
303 w: w,
304 zw: zw,
305 start: time.Now(),
306 strings: []string{""},
307 stringMap: map[string]int{"": 0},
308 locs: map[uintptr]int{},
309 funcs: map[string]int{},
310 }
311 b.readMapping()
312 return b
313 }
314
315
316
317
318 func (b *profileBuilder) addCPUData(data []uint64, tags []unsafe.Pointer) error {
319 if !b.havePeriod {
320
321 if len(data) < 3 {
322 return fmt.Errorf("truncated profile")
323 }
324 if data[0] != 3 || data[2] == 0 {
325 return fmt.Errorf("malformed profile")
326 }
327
328
329 b.period = 1e9 / int64(data[2])
330 b.havePeriod = true
331 data = data[3:]
332 }
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349 for len(data) > 0 {
350 if len(data) < 3 || data[0] > uint64(len(data)) {
351 return fmt.Errorf("truncated profile")
352 }
353 if data[0] < 3 || tags != nil && len(tags) < 1 {
354 return fmt.Errorf("malformed profile")
355 }
356 count := data[2]
357 stk := data[3:data[0]]
358 data = data[data[0]:]
359 var tag unsafe.Pointer
360 if tags != nil {
361 tag = tags[0]
362 tags = tags[1:]
363 }
364
365 if count == 0 && len(stk) == 1 {
366
367 count = uint64(stk[0])
368 stk = []uint64{
369 uint64(funcPC(lostProfileEvent)),
370 }
371 }
372 b.m.lookup(stk, tag).count += int64(count)
373 }
374 return nil
375 }
376
377
378 func (b *profileBuilder) build() {
379 b.end = time.Now()
380
381 b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
382 if b.havePeriod {
383 b.pbValueType(tagProfile_SampleType, "samples", "count")
384 b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds")
385 b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds())
386 b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds")
387 b.pb.int64Opt(tagProfile_Period, b.period)
388 }
389
390 values := []int64{0, 0}
391 var locs []uint64
392 for e := b.m.all; e != nil; e = e.nextAll {
393 values[0] = e.count
394 values[1] = e.count * b.period
395
396 var labels func()
397 if e.tag != nil {
398 labels = func() {
399 for k, v := range *(*labelMap)(e.tag) {
400 b.pbLabel(tagSample_Label, k, v, 0)
401 }
402 }
403 }
404
405 locs = locs[:0]
406 for i, addr := range e.stk {
407
408
409
410
411
412
413 if i == 0 {
414 addr++
415 }
416 l := b.locForPC(addr)
417 if l == 0 {
418 continue
419 }
420 locs = append(locs, l)
421 }
422 b.pbSample(values, locs, labels)
423 }
424
425 for i, m := range b.mem {
426 hasFunctions := m.funcs == lookupTried
427 b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(m.start), uint64(m.end), m.offset, m.file, m.buildID, hasFunctions)
428 }
429
430
431
432
433 b.pb.strings(tagProfile_StringTable, b.strings)
434 b.zw.Write(b.pb.data)
435 b.zw.Close()
436 }
437
438
439
440
441 func (b *profileBuilder) readMapping() {
442 data, _ := ioutil.ReadFile("/proc/self/maps")
443 parseProcSelfMaps(data, b.addMapping)
444 if len(b.mem) == 0 {
445 b.addMappingEntry(0, 0, 0, "", "", true)
446
447
448
449 }
450 }
451
452 func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) {
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474 var line []byte
475
476
477 next := func() []byte {
478 j := bytes.IndexByte(line, ' ')
479 if j < 0 {
480 f := line
481 line = nil
482 return f
483 }
484 f := line[:j]
485 line = line[j+1:]
486 for len(line) > 0 && line[0] == ' ' {
487 line = line[1:]
488 }
489 return f
490 }
491
492 for len(data) > 0 {
493 i := bytes.IndexByte(data, '\n')
494 if i < 0 {
495 line, data = data, nil
496 } else {
497 line, data = data[:i], data[i+1:]
498 }
499 addr := next()
500 i = bytes.IndexByte(addr, '-')
501 if i < 0 {
502 continue
503 }
504 lo, err := strconv.ParseUint(string(addr[:i]), 16, 64)
505 if err != nil {
506 continue
507 }
508 hi, err := strconv.ParseUint(string(addr[i+1:]), 16, 64)
509 if err != nil {
510 continue
511 }
512 perm := next()
513 if len(perm) < 4 || perm[2] != 'x' {
514
515 continue
516 }
517 offset, err := strconv.ParseUint(string(next()), 16, 64)
518 if err != nil {
519 continue
520 }
521 next()
522 inode := next()
523 if line == nil {
524 continue
525 }
526 file := string(line)
527
528
529 deletedStr := " (deleted)"
530 deletedLen := len(deletedStr)
531 if len(file) >= deletedLen && file[len(file)-deletedLen:] == deletedStr {
532 file = file[:len(file)-deletedLen]
533 }
534
535 if len(inode) == 1 && inode[0] == '0' && file == "" {
536
537
538
539
540 continue
541 }
542
543
544
545
546
547
548
549
550
551
552 buildID, _ := elfBuildID(file)
553 addMapping(lo, hi, offset, file, buildID)
554 }
555 }
556
557 func (b *profileBuilder) addMapping(lo, hi, offset uint64, file, buildID string) {
558 b.addMappingEntry(lo, hi, offset, file, buildID, false)
559 }
560
561 func (b *profileBuilder) addMappingEntry(lo, hi, offset uint64, file, buildID string, fake bool) {
562 b.mem = append(b.mem, memMap{
563 start: uintptr(lo),
564 end: uintptr(hi),
565 offset: offset,
566 file: file,
567 buildID: buildID,
568 fake: fake,
569 })
570 }
571
View as plain text