1 // Copyright 2016 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
20 // lostProfileEvent is the function to which lost profiling
21 // events are attributed.
22 // (The name shows up in the pprof graphs.)
23 func lostProfileEvent() { lostProfileEvent() }
25 // funcPC returns the PC for the func value f.
26 func funcPC(f
interface{}) uintptr {
31 i
:= (*iface
)(unsafe
.Pointer(&f
))
32 return **(**uintptr)(i
.data
)
35 // A profileBuilder writes a profile incrementally from a
36 // stream of profile samples delivered by the runtime.
37 type profileBuilder
struct {
49 stringMap
map[string]int
51 funcs
map[string]int // Package path-qualified function name to Function.ID
62 tagProfile_SampleType
= 1 // repeated ValueType
63 tagProfile_Sample
= 2 // repeated Sample
64 tagProfile_Mapping
= 3 // repeated Mapping
65 tagProfile_Location
= 4 // repeated Location
66 tagProfile_Function
= 5 // repeated Function
67 tagProfile_StringTable
= 6 // repeated string
68 tagProfile_DropFrames
= 7 // int64 (string table index)
69 tagProfile_KeepFrames
= 8 // int64 (string table index)
70 tagProfile_TimeNanos
= 9 // int64
71 tagProfile_DurationNanos
= 10 // int64
72 tagProfile_PeriodType
= 11 // ValueType (really optional string???)
73 tagProfile_Period
= 12 // int64
76 tagValueType_Type
= 1 // int64 (string table index)
77 tagValueType_Unit
= 2 // int64 (string table index)
80 tagSample_Location
= 1 // repeated uint64
81 tagSample_Value
= 2 // repeated int64
82 tagSample_Label
= 3 // repeated Label
85 tagLabel_Key
= 1 // int64 (string table index)
86 tagLabel_Str
= 2 // int64 (string table index)
87 tagLabel_Num
= 3 // int64
90 tagMapping_ID
= 1 // uint64
91 tagMapping_Start
= 2 // uint64
92 tagMapping_Limit
= 3 // uint64
93 tagMapping_Offset
= 4 // uint64
94 tagMapping_Filename
= 5 // int64 (string table index)
95 tagMapping_BuildID
= 6 // int64 (string table index)
96 tagMapping_HasFunctions
= 7 // bool
97 tagMapping_HasFilenames
= 8 // bool
98 tagMapping_HasLineNumbers
= 9 // bool
99 tagMapping_HasInlineFrames
= 10 // bool
102 tagLocation_ID
= 1 // uint64
103 tagLocation_MappingID
= 2 // uint64
104 tagLocation_Address
= 3 // uint64
105 tagLocation_Line
= 4 // repeated Line
108 tagLine_FunctionID
= 1 // uint64
109 tagLine_Line
= 2 // int64
112 tagFunction_ID
= 1 // uint64
113 tagFunction_Name
= 2 // int64 (string table index)
114 tagFunction_SystemName
= 3 // int64 (string table index)
115 tagFunction_Filename
= 4 // int64 (string table index)
116 tagFunction_StartLine
= 5 // int64
119 // stringIndex adds s to the string table if not already present
120 // and returns the index of s in the string table.
121 func (b
*profileBuilder
) stringIndex(s
string) int64 {
122 id
, ok
:= b
.stringMap
[s
]
125 b
.strings
= append(b
.strings
, s
)
131 func (b
*profileBuilder
) flush() {
132 const dataFlush
= 4096
133 if b
.pb
.nest
== 0 && len(b
.pb
.data
) > dataFlush
{
134 b
.zw
.Write(b
.pb
.data
)
135 b
.pb
.data
= b
.pb
.data
[:0]
139 // pbValueType encodes a ValueType message to b.pb.
140 func (b
*profileBuilder
) pbValueType(tag
int, typ
, unit
string) {
141 start
:= b
.pb
.startMessage()
142 b
.pb
.int64(tagValueType_Type
, b
.stringIndex(typ
))
143 b
.pb
.int64(tagValueType_Unit
, b
.stringIndex(unit
))
144 b
.pb
.endMessage(tag
, start
)
147 // pbSample encodes a Sample message to b.pb.
148 func (b
*profileBuilder
) pbSample(values
[]int64, locs
[]uint64, labels
func()) {
149 start
:= b
.pb
.startMessage()
150 b
.pb
.int64s(tagSample_Value
, values
)
151 b
.pb
.uint64s(tagSample_Location
, locs
)
155 b
.pb
.endMessage(tagProfile_Sample
, start
)
159 // pbLabel encodes a Label message to b.pb.
160 func (b
*profileBuilder
) pbLabel(tag
int, key
, str
string, num
int64) {
161 start
:= b
.pb
.startMessage()
162 b
.pb
.int64Opt(tagLabel_Key
, b
.stringIndex(key
))
163 b
.pb
.int64Opt(tagLabel_Str
, b
.stringIndex(str
))
164 b
.pb
.int64Opt(tagLabel_Num
, num
)
165 b
.pb
.endMessage(tag
, start
)
168 // pbLine encodes a Line message to b.pb.
169 func (b
*profileBuilder
) pbLine(tag
int, funcID
uint64, line
int64) {
170 start
:= b
.pb
.startMessage()
171 b
.pb
.uint64Opt(tagLine_FunctionID
, funcID
)
172 b
.pb
.int64Opt(tagLine_Line
, line
)
173 b
.pb
.endMessage(tag
, start
)
176 // pbMapping encodes a Mapping message to b.pb.
177 func (b
*profileBuilder
) pbMapping(tag
int, id
, base
, limit
, offset
uint64, file
, buildID
string) {
178 start
:= b
.pb
.startMessage()
179 b
.pb
.uint64Opt(tagMapping_ID
, id
)
180 b
.pb
.uint64Opt(tagMapping_Start
, base
)
181 b
.pb
.uint64Opt(tagMapping_Limit
, limit
)
182 b
.pb
.uint64Opt(tagMapping_Offset
, offset
)
183 b
.pb
.int64Opt(tagMapping_Filename
, b
.stringIndex(file
))
184 b
.pb
.int64Opt(tagMapping_BuildID
, b
.stringIndex(buildID
))
185 // TODO: Set any of HasInlineFrames, HasFunctions, HasFilenames, HasLineNumbers?
186 // It seems like they should all be true, but they've never been set.
187 b
.pb
.endMessage(tag
, start
)
190 // locForPC returns the location ID for addr.
191 // addr must be a return PC. This returns the location of the call.
192 // It may emit to b.pb, so there must be no message encoding in progress.
193 func (b
*profileBuilder
) locForPC(addr
uintptr) uint64 {
194 id
:= uint64(b
.locs
[addr
])
199 // Expand this one address using CallersFrames so we can cache
200 // each expansion. In general, CallersFrames takes a whole
201 // stack, but in this case we know there will be no skips in
202 // the stack and we have return PCs anyway.
203 frames
:= runtime
.CallersFrames([]uintptr{addr
})
204 frame
, more
:= frames
.Next()
205 if frame
.Function
== "runtime.goexit" || frame
.Function
== "runtime.kickoff" {
206 // Short-circuit if we see runtime.goexit so the loop
207 // below doesn't allocate a useless empty location.
212 // If we failed to resolve the frame, at least make up
213 // a reasonable call PC. This mostly happens in tests.
217 // We can't write out functions while in the middle of the
218 // Location message, so record new functions we encounter and
219 // write them out after the Location.
220 type newFunc
struct {
224 newFuncs
:= make([]newFunc
, 0, 8)
226 id
= uint64(len(b
.locs
)) + 1
227 b
.locs
[addr
] = int(id
)
228 start
:= b
.pb
.startMessage()
229 b
.pb
.uint64Opt(tagLocation_ID
, id
)
230 b
.pb
.uint64Opt(tagLocation_Address
, uint64(frame
.PC
))
231 for frame
.Function
!= "runtime.goexit" && frame
.Function
!= "runtime.kickoff" {
232 // Write out each line in frame expansion.
233 funcID
:= uint64(b
.funcs
[frame
.Function
])
235 funcID
= uint64(len(b
.funcs
)) + 1
236 b
.funcs
[frame
.Function
] = int(funcID
)
237 newFuncs
= append(newFuncs
, newFunc
{funcID
, frame
.Function
, frame
.File
})
239 b
.pbLine(tagLocation_Line
, funcID
, int64(frame
.Line
))
243 frame
, more
= frames
.Next()
246 i
:= sort
.Search(len(b
.mem
), func(i
int) bool {
247 return b
.mem
[i
].end
> addr
249 if i
< len(b
.mem
) && b
.mem
[i
].start
<= addr
&& addr
< b
.mem
[i
].end
{
250 b
.pb
.uint64Opt(tagLocation_MappingID
, uint64(i
+1))
253 b
.pb
.endMessage(tagProfile_Location
, start
)
255 // Write out functions we found during frame expansion.
256 for _
, fn
:= range newFuncs
{
257 start
:= b
.pb
.startMessage()
258 b
.pb
.uint64Opt(tagFunction_ID
, fn
.id
)
259 b
.pb
.int64Opt(tagFunction_Name
, b
.stringIndex(fn
.name
))
260 b
.pb
.int64Opt(tagFunction_SystemName
, b
.stringIndex(fn
.name
))
261 b
.pb
.int64Opt(tagFunction_Filename
, b
.stringIndex(fn
.file
))
262 b
.pb
.endMessage(tagProfile_Function
, start
)
269 // newProfileBuilder returns a new profileBuilder.
270 // CPU profiling data obtained from the runtime can be added
271 // by calling b.addCPUData, and then the eventual profile
272 // can be obtained by calling b.finish.
273 func newProfileBuilder(w io
.Writer
) *profileBuilder
{
274 zw
, _
:= gzip
.NewWriterLevel(w
, gzip
.BestSpeed
)
275 b
:= &profileBuilder
{
279 strings
: []string{""},
280 stringMap
: map[string]int{"": 0},
281 locs
: map[uintptr]int{},
282 funcs
: map[string]int{},
288 // addCPUData adds the CPU profiling data to the profile.
289 // The data must be a whole number of records,
290 // as delivered by the runtime.
291 func (b
*profileBuilder
) addCPUData(data
[]uint64, tags
[]unsafe
.Pointer
) error
{
293 // first record is period
295 return fmt
.Errorf("truncated profile")
297 if data
[0] != 3 || data
[2] == 0 {
298 return fmt
.Errorf("malformed profile")
300 // data[2] is sampling rate in Hz. Convert to sampling
301 // period in nanoseconds.
302 b
.period
= 1e9
/ int64(data
[2])
307 // Parse CPU samples from the profile.
308 // Each sample is 3+n uint64s:
310 // data[1] = time stamp (ignored)
312 // data[3:3+n] = stack
313 // If the count is 0 and the stack has length 1,
314 // that's an overflow record inserted by the runtime
315 // to indicate that stack[0] samples were lost.
316 // Otherwise the count is usually 1,
317 // but in a few special cases like lost non-Go samples
318 // there can be larger counts.
319 // Because many samples with the same stack arrive,
320 // we want to deduplicate immediately, which we do
321 // using the b.m profMap.
323 if len(data
) < 3 || data
[0] > uint64(len(data
)) {
324 return fmt
.Errorf("truncated profile")
326 if data
[0] < 3 || tags
!= nil && len(tags
) < 1 {
327 return fmt
.Errorf("malformed profile")
330 stk
:= data
[3:data
[0]]
331 data
= data
[data
[0]:]
332 var tag unsafe
.Pointer
338 if count
== 0 && len(stk
) == 1 {
340 count
= uint64(stk
[0])
342 uint64(funcPC(lostProfileEvent
)),
345 b
.m
.lookup(stk
, tag
).count
+= int64(count
)
350 // build completes and returns the constructed profile.
351 func (b
*profileBuilder
) build() error
{
354 b
.pb
.int64Opt(tagProfile_TimeNanos
, b
.start
.UnixNano())
355 if b
.havePeriod
{ // must be CPU profile
356 b
.pbValueType(tagProfile_SampleType
, "samples", "count")
357 b
.pbValueType(tagProfile_SampleType
, "cpu", "nanoseconds")
358 b
.pb
.int64Opt(tagProfile_DurationNanos
, b
.end
.Sub(b
.start
).Nanoseconds())
359 b
.pbValueType(tagProfile_PeriodType
, "cpu", "nanoseconds")
360 b
.pb
.int64Opt(tagProfile_Period
, b
.period
)
363 values
:= []int64{0, 0}
365 for e
:= b
.m
.all
; e
!= nil; e
= e
.nextAll
{
367 values
[1] = e
.count
* b
.period
372 for k
, v
:= range *(*labelMap
)(e
.tag
) {
373 b
.pbLabel(tagSample_Label
, k
, v
, 0)
379 for i
, addr
:= range e
.stk
{
380 // Addresses from stack traces point to the
381 // next instruction after each call, except
382 // for the leaf, which points to where the
383 // signal occurred. locForPC expects return
384 // PCs, so increment the leaf address to look
389 l
:= b
.locForPC(addr
)
390 if l
== 0 { // runtime.goexit
393 locs
= append(locs
, l
)
395 b
.pbSample(values
, locs
, labels
)
398 // TODO: Anything for tagProfile_DropFrames?
399 // TODO: Anything for tagProfile_KeepFrames?
401 b
.pb
.strings(tagProfile_StringTable
, b
.strings
)
402 b
.zw
.Write(b
.pb
.data
)
407 // readMapping reads /proc/self/maps and writes mappings to b.pb.
408 // It saves the address ranges of the mappings in b.mem for use
409 // when emitting locations.
410 func (b
*profileBuilder
) readMapping() {
411 data
, _
:= ioutil
.ReadFile("/proc/self/maps")
412 parseProcSelfMaps(data
, b
.addMapping
)
415 func parseProcSelfMaps(data
[]byte, addMapping
func(lo
, hi
, offset
uint64, file
, buildID
string)) {
416 // $ cat /proc/self/maps
417 // 00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat
418 // 0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat
419 // 0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat
420 // 014ab000-014cc000 rw-p 00000000 00:00 0 [heap]
421 // 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive
422 // 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
423 // 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
424 // 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
425 // 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so
426 // 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
427 // 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
428 // 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
429 // 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
430 // 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
431 // 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so
432 // 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
433 // 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack]
434 // 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso]
435 // ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
438 // next removes and returns the next field in the line.
439 // It also removes from line any spaces following the field.
440 next
:= func() []byte {
441 j
:= bytes
.IndexByte(line
, ' ')
449 for len(line
) > 0 && line
[0] == ' ' {
456 i
:= bytes
.IndexByte(data
, '\n')
458 line
, data
= data
, nil
460 line
, data
= data
[:i
], data
[i
+1:]
463 i
= bytes
.IndexByte(addr
, '-')
467 lo
, err
:= strconv
.ParseUint(string(addr
[:i
]), 16, 64)
471 hi
, err
:= strconv
.ParseUint(string(addr
[i
+1:]), 16, 64)
476 if len(perm
) < 4 || perm
[2] != 'x' {
477 // Only interested in executable mappings.
480 offset
, err
:= strconv
.ParseUint(string(next()), 16, 64)
485 inode
:= next() // inode
490 if len(inode
) == 1 && inode
[0] == '0' && file
== "" {
491 // Huge-page text mappings list the initial fragment of
492 // mapped but unpopulated memory as being inode 0.
493 // Don't report that part.
494 // But [vdso] and [vsyscall] are inode 0, so let non-empty file names through.
498 // TODO: pprof's remapMappingIDs makes two adjustments:
499 // 1. If there is an /anon_hugepage mapping first and it is
500 // consecutive to a next mapping, drop the /anon_hugepage.
501 // 2. If start-offset = 0x400000, change start to 0x400000 and offset to 0.
502 // There's no indication why either of these is needed.
503 // Let's try not doing these and see what breaks.
504 // If we do need them, they would go here, before we
505 // enter the mappings into b.mem in the first place.
507 buildID
, _
:= elfBuildID(file
)
508 addMapping(lo
, hi
, offset
, file
, buildID
)
512 func (b
*profileBuilder
) addMapping(lo
, hi
, offset
uint64, file
, buildID
string) {
513 b
.mem
= append(b
.mem
, memMap
{uintptr(lo
), uintptr(hi
)})
514 b
.pbMapping(tagProfile_Mapping
, uint64(len(b
.mem
)), lo
, hi
, offset
, file
, buildID
)