github.com/eun/go@v0.0.0-20170811110501-92cfd07a6cfd/src/runtime/pprof/proto.go (about) 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. 4 5 package pprof 6 7 import ( 8 "bytes" 9 "compress/gzip" 10 "fmt" 11 "io" 12 "io/ioutil" 13 "runtime" 14 "sort" 15 "strconv" 16 "time" 17 "unsafe" 18 ) 19 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() } 24 25 // funcPC returns the PC for the func value f. 26 func funcPC(f interface{}) uintptr { 27 return *(*[2]*uintptr)(unsafe.Pointer(&f))[1] 28 } 29 30 // A profileBuilder writes a profile incrementally from a 31 // stream of profile samples delivered by the runtime. 32 type profileBuilder struct { 33 start time.Time 34 end time.Time 35 havePeriod bool 36 period int64 37 m profMap 38 39 // encoding state 40 w io.Writer 41 zw *gzip.Writer 42 pb protobuf 43 strings []string 44 stringMap map[string]int 45 locs map[uintptr]int 46 funcs map[string]int // Package path-qualified function name to Function.ID 47 mem []memMap 48 } 49 50 type memMap struct { 51 start uintptr 52 end uintptr 53 } 54 55 const ( 56 // message Profile 57 tagProfile_SampleType = 1 // repeated ValueType 58 tagProfile_Sample = 2 // repeated Sample 59 tagProfile_Mapping = 3 // repeated Mapping 60 tagProfile_Location = 4 // repeated Location 61 tagProfile_Function = 5 // repeated Function 62 tagProfile_StringTable = 6 // repeated string 63 tagProfile_DropFrames = 7 // int64 (string table index) 64 tagProfile_KeepFrames = 8 // int64 (string table index) 65 tagProfile_TimeNanos = 9 // int64 66 tagProfile_DurationNanos = 10 // int64 67 tagProfile_PeriodType = 11 // ValueType (really optional string???) 68 tagProfile_Period = 12 // int64 69 70 // message ValueType 71 tagValueType_Type = 1 // int64 (string table index) 72 tagValueType_Unit = 2 // int64 (string table index) 73 74 // message Sample 75 tagSample_Location = 1 // repeated uint64 76 tagSample_Value = 2 // repeated int64 77 tagSample_Label = 3 // repeated Label 78 79 // message Label 80 tagLabel_Key = 1 // int64 (string table index) 81 tagLabel_Str = 2 // int64 (string table index) 82 tagLabel_Num = 3 // int64 83 84 // message Mapping 85 tagMapping_ID = 1 // uint64 86 tagMapping_Start = 2 // uint64 87 tagMapping_Limit = 3 // uint64 88 tagMapping_Offset = 4 // uint64 89 tagMapping_Filename = 5 // int64 (string table index) 90 tagMapping_BuildID = 6 // int64 (string table index) 91 tagMapping_HasFunctions = 7 // bool 92 tagMapping_HasFilenames = 8 // bool 93 tagMapping_HasLineNumbers = 9 // bool 94 tagMapping_HasInlineFrames = 10 // bool 95 96 // message Location 97 tagLocation_ID = 1 // uint64 98 tagLocation_MappingID = 2 // uint64 99 tagLocation_Address = 3 // uint64 100 tagLocation_Line = 4 // repeated Line 101 102 // message Line 103 tagLine_FunctionID = 1 // uint64 104 tagLine_Line = 2 // int64 105 106 // message Function 107 tagFunction_ID = 1 // uint64 108 tagFunction_Name = 2 // int64 (string table index) 109 tagFunction_SystemName = 3 // int64 (string table index) 110 tagFunction_Filename = 4 // int64 (string table index) 111 tagFunction_StartLine = 5 // int64 112 ) 113 114 // stringIndex adds s to the string table if not already present 115 // and returns the index of s in the string table. 116 func (b *profileBuilder) stringIndex(s string) int64 { 117 id, ok := b.stringMap[s] 118 if !ok { 119 id = len(b.strings) 120 b.strings = append(b.strings, s) 121 b.stringMap[s] = id 122 } 123 return int64(id) 124 } 125 126 func (b *profileBuilder) flush() { 127 const dataFlush = 4096 128 if b.pb.nest == 0 && len(b.pb.data) > dataFlush { 129 b.zw.Write(b.pb.data) 130 b.pb.data = b.pb.data[:0] 131 } 132 } 133 134 // pbValueType encodes a ValueType message to b.pb. 135 func (b *profileBuilder) pbValueType(tag int, typ, unit string) { 136 start := b.pb.startMessage() 137 b.pb.int64(tagValueType_Type, b.stringIndex(typ)) 138 b.pb.int64(tagValueType_Unit, b.stringIndex(unit)) 139 b.pb.endMessage(tag, start) 140 } 141 142 // pbSample encodes a Sample message to b.pb. 143 func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) { 144 start := b.pb.startMessage() 145 b.pb.int64s(tagSample_Value, values) 146 b.pb.uint64s(tagSample_Location, locs) 147 if labels != nil { 148 labels() 149 } 150 b.pb.endMessage(tagProfile_Sample, start) 151 b.flush() 152 } 153 154 // pbLabel encodes a Label message to b.pb. 155 func (b *profileBuilder) pbLabel(tag int, key, str string, num int64) { 156 start := b.pb.startMessage() 157 b.pb.int64Opt(tagLabel_Key, b.stringIndex(key)) 158 b.pb.int64Opt(tagLabel_Str, b.stringIndex(str)) 159 b.pb.int64Opt(tagLabel_Num, num) 160 b.pb.endMessage(tag, start) 161 } 162 163 // pbLine encodes a Line message to b.pb. 164 func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) { 165 start := b.pb.startMessage() 166 b.pb.uint64Opt(tagLine_FunctionID, funcID) 167 b.pb.int64Opt(tagLine_Line, line) 168 b.pb.endMessage(tag, start) 169 } 170 171 // pbMapping encodes a Mapping message to b.pb. 172 func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string) { 173 start := b.pb.startMessage() 174 b.pb.uint64Opt(tagMapping_ID, id) 175 b.pb.uint64Opt(tagMapping_Start, base) 176 b.pb.uint64Opt(tagMapping_Limit, limit) 177 b.pb.uint64Opt(tagMapping_Offset, offset) 178 b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file)) 179 b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID)) 180 // TODO: Set any of HasInlineFrames, HasFunctions, HasFilenames, HasLineNumbers? 181 // It seems like they should all be true, but they've never been set. 182 b.pb.endMessage(tag, start) 183 } 184 185 // locForPC returns the location ID for addr. 186 // addr must be a return PC. This returns the location of the call. 187 // It may emit to b.pb, so there must be no message encoding in progress. 188 func (b *profileBuilder) locForPC(addr uintptr) uint64 { 189 id := uint64(b.locs[addr]) 190 if id != 0 { 191 return id 192 } 193 194 // Expand this one address using CallersFrames so we can cache 195 // each expansion. In general, CallersFrames takes a whole 196 // stack, but in this case we know there will be no skips in 197 // the stack and we have return PCs anyway. 198 frames := runtime.CallersFrames([]uintptr{addr}) 199 frame, more := frames.Next() 200 if frame.Function == "runtime.goexit" { 201 // Short-circuit if we see runtime.goexit so the loop 202 // below doesn't allocate a useless empty location. 203 return 0 204 } 205 206 if frame.PC == 0 { 207 // If we failed to resolve the frame, at least make up 208 // a reasonable call PC. This mostly happens in tests. 209 frame.PC = addr - 1 210 } 211 212 // We can't write out functions while in the middle of the 213 // Location message, so record new functions we encounter and 214 // write them out after the Location. 215 type newFunc struct { 216 id uint64 217 name, file string 218 } 219 newFuncs := make([]newFunc, 0, 8) 220 221 id = uint64(len(b.locs)) + 1 222 b.locs[addr] = int(id) 223 start := b.pb.startMessage() 224 b.pb.uint64Opt(tagLocation_ID, id) 225 b.pb.uint64Opt(tagLocation_Address, uint64(frame.PC)) 226 for frame.Function != "runtime.goexit" { 227 // Write out each line in frame expansion. 228 funcID := uint64(b.funcs[frame.Function]) 229 if funcID == 0 { 230 funcID = uint64(len(b.funcs)) + 1 231 b.funcs[frame.Function] = int(funcID) 232 newFuncs = append(newFuncs, newFunc{funcID, frame.Function, frame.File}) 233 } 234 b.pbLine(tagLocation_Line, funcID, int64(frame.Line)) 235 if !more { 236 break 237 } 238 frame, more = frames.Next() 239 } 240 if len(b.mem) > 0 { 241 i := sort.Search(len(b.mem), func(i int) bool { 242 return b.mem[i].end > addr 243 }) 244 if i < len(b.mem) && b.mem[i].start <= addr && addr < b.mem[i].end { 245 b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1)) 246 } 247 } 248 b.pb.endMessage(tagProfile_Location, start) 249 250 // Write out functions we found during frame expansion. 251 for _, fn := range newFuncs { 252 start := b.pb.startMessage() 253 b.pb.uint64Opt(tagFunction_ID, fn.id) 254 b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name)) 255 b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name)) 256 b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file)) 257 b.pb.endMessage(tagProfile_Function, start) 258 } 259 260 b.flush() 261 return id 262 } 263 264 // newProfileBuilder returns a new profileBuilder. 265 // CPU profiling data obtained from the runtime can be added 266 // by calling b.addCPUData, and then the eventual profile 267 // can be obtained by calling b.finish. 268 func newProfileBuilder(w io.Writer) *profileBuilder { 269 zw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed) 270 b := &profileBuilder{ 271 w: w, 272 zw: zw, 273 start: time.Now(), 274 strings: []string{""}, 275 stringMap: map[string]int{"": 0}, 276 locs: map[uintptr]int{}, 277 funcs: map[string]int{}, 278 } 279 b.readMapping() 280 return b 281 } 282 283 // addCPUData adds the CPU profiling data to the profile. 284 // The data must be a whole number of records, 285 // as delivered by the runtime. 286 func (b *profileBuilder) addCPUData(data []uint64, tags []unsafe.Pointer) error { 287 if !b.havePeriod { 288 // first record is period 289 if len(data) < 3 { 290 return fmt.Errorf("truncated profile") 291 } 292 if data[0] != 3 || data[2] == 0 { 293 return fmt.Errorf("malformed profile") 294 } 295 // data[2] is sampling rate in Hz. Convert to sampling 296 // period in nanoseconds. 297 b.period = 1e9 / int64(data[2]) 298 b.havePeriod = true 299 data = data[3:] 300 } 301 302 // Parse CPU samples from the profile. 303 // Each sample is 3+n uint64s: 304 // data[0] = 3+n 305 // data[1] = time stamp (ignored) 306 // data[2] = count 307 // data[3:3+n] = stack 308 // If the count is 0 and the stack has length 1, 309 // that's an overflow record inserted by the runtime 310 // to indicate that stack[0] samples were lost. 311 // Otherwise the count is usually 1, 312 // but in a few special cases like lost non-Go samples 313 // there can be larger counts. 314 // Because many samples with the same stack arrive, 315 // we want to deduplicate immediately, which we do 316 // using the b.m profMap. 317 for len(data) > 0 { 318 if len(data) < 3 || data[0] > uint64(len(data)) { 319 return fmt.Errorf("truncated profile") 320 } 321 if data[0] < 3 || tags != nil && len(tags) < 1 { 322 return fmt.Errorf("malformed profile") 323 } 324 count := data[2] 325 stk := data[3:data[0]] 326 data = data[data[0]:] 327 var tag unsafe.Pointer 328 if tags != nil { 329 tag = tags[0] 330 tags = tags[1:] 331 } 332 333 if count == 0 && len(stk) == 1 { 334 // overflow record 335 count = uint64(stk[0]) 336 stk = []uint64{ 337 uint64(funcPC(lostProfileEvent)), 338 } 339 } 340 b.m.lookup(stk, tag).count += int64(count) 341 } 342 return nil 343 } 344 345 // build completes and returns the constructed profile. 346 func (b *profileBuilder) build() error { 347 b.end = time.Now() 348 349 b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano()) 350 if b.havePeriod { // must be CPU profile 351 b.pbValueType(tagProfile_SampleType, "samples", "count") 352 b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds") 353 b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds()) 354 b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds") 355 b.pb.int64Opt(tagProfile_Period, b.period) 356 } 357 358 values := []int64{0, 0} 359 var locs []uint64 360 for e := b.m.all; e != nil; e = e.nextAll { 361 values[0] = e.count 362 values[1] = e.count * b.period 363 364 var labels func() 365 if e.tag != nil { 366 labels = func() { 367 for k, v := range *(*labelMap)(e.tag) { 368 b.pbLabel(tagSample_Label, k, v, 0) 369 } 370 } 371 } 372 373 locs = locs[:0] 374 for i, addr := range e.stk { 375 // Addresses from stack traces point to the 376 // next instruction after each call, except 377 // for the leaf, which points to where the 378 // signal occurred. locForPC expects return 379 // PCs, so increment the leaf address to look 380 // like a return PC. 381 if i == 0 { 382 addr++ 383 } 384 l := b.locForPC(addr) 385 if l == 0 { // runtime.goexit 386 continue 387 } 388 locs = append(locs, l) 389 } 390 b.pbSample(values, locs, labels) 391 } 392 393 // TODO: Anything for tagProfile_DropFrames? 394 // TODO: Anything for tagProfile_KeepFrames? 395 396 b.pb.strings(tagProfile_StringTable, b.strings) 397 b.zw.Write(b.pb.data) 398 b.zw.Close() 399 return nil 400 } 401 402 // readMapping reads /proc/self/maps and writes mappings to b.pb. 403 // It saves the address ranges of the mappings in b.mem for use 404 // when emitting locations. 405 func (b *profileBuilder) readMapping() { 406 data, _ := ioutil.ReadFile("/proc/self/maps") 407 parseProcSelfMaps(data, b.addMapping) 408 } 409 410 func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) { 411 // $ cat /proc/self/maps 412 // 00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat 413 // 0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat 414 // 0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat 415 // 014ab000-014cc000 rw-p 00000000 00:00 0 [heap] 416 // 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive 417 // 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 418 // 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 419 // 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 420 // 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 421 // 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0 422 // 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 423 // 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0 424 // 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0 425 // 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 426 // 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 427 // 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0 428 // 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack] 429 // 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso] 430 // ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 431 432 var line []byte 433 // next removes and returns the next field in the line. 434 // It also removes from line any spaces following the field. 435 next := func() []byte { 436 j := bytes.IndexByte(line, ' ') 437 if j < 0 { 438 f := line 439 line = nil 440 return f 441 } 442 f := line[:j] 443 line = line[j+1:] 444 for len(line) > 0 && line[0] == ' ' { 445 line = line[1:] 446 } 447 return f 448 } 449 450 for len(data) > 0 { 451 i := bytes.IndexByte(data, '\n') 452 if i < 0 { 453 line, data = data, nil 454 } else { 455 line, data = data[:i], data[i+1:] 456 } 457 addr := next() 458 i = bytes.IndexByte(addr, '-') 459 if i < 0 { 460 continue 461 } 462 lo, err := strconv.ParseUint(string(addr[:i]), 16, 64) 463 if err != nil { 464 continue 465 } 466 hi, err := strconv.ParseUint(string(addr[i+1:]), 16, 64) 467 if err != nil { 468 continue 469 } 470 perm := next() 471 if len(perm) < 4 || perm[2] != 'x' { 472 // Only interested in executable mappings. 473 continue 474 } 475 offset, err := strconv.ParseUint(string(next()), 16, 64) 476 if err != nil { 477 continue 478 } 479 next() // dev 480 inode := next() // inode 481 if line == nil { 482 continue 483 } 484 file := string(line) 485 if len(inode) == 1 && inode[0] == '0' && file == "" { 486 // Huge-page text mappings list the initial fragment of 487 // mapped but unpopulated memory as being inode 0. 488 // Don't report that part. 489 // But [vdso] and [vsyscall] are inode 0, so let non-empty file names through. 490 continue 491 } 492 493 // TODO: pprof's remapMappingIDs makes two adjustments: 494 // 1. If there is an /anon_hugepage mapping first and it is 495 // consecutive to a next mapping, drop the /anon_hugepage. 496 // 2. If start-offset = 0x400000, change start to 0x400000 and offset to 0. 497 // There's no indication why either of these is needed. 498 // Let's try not doing these and see what breaks. 499 // If we do need them, they would go here, before we 500 // enter the mappings into b.mem in the first place. 501 502 buildID, _ := elfBuildID(file) 503 addMapping(lo, hi, offset, file, buildID) 504 } 505 } 506 507 func (b *profileBuilder) addMapping(lo, hi, offset uint64, file, buildID string) { 508 b.mem = append(b.mem, memMap{uintptr(lo), uintptr(hi)}) 509 b.pbMapping(tagProfile_Mapping, uint64(len(b.mem)), lo, hi, offset, file, buildID) 510 }