github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/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[*runtime.Func]int 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 // It may emit to b.pb, so there must be no message encoding in progress. 187 func (b *profileBuilder) locForPC(addr uintptr) uint64 { 188 id := uint64(b.locs[addr]) 189 if id != 0 { 190 return id 191 } 192 f := runtime.FuncForPC(addr) 193 if f != nil && f.Name() == "runtime.goexit" { 194 return 0 195 } 196 funcID, lineno := b.funcForPC(addr) 197 id = uint64(len(b.locs)) + 1 198 b.locs[addr] = int(id) 199 start := b.pb.startMessage() 200 b.pb.uint64Opt(tagLocation_ID, id) 201 b.pb.uint64Opt(tagLocation_Address, uint64(addr)) 202 b.pbLine(tagLocation_Line, funcID, int64(lineno)) 203 if len(b.mem) > 0 { 204 i := sort.Search(len(b.mem), func(i int) bool { 205 return b.mem[i].end > addr 206 }) 207 if i < len(b.mem) && b.mem[i].start <= addr && addr < b.mem[i].end { 208 b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1)) 209 } 210 } 211 b.pb.endMessage(tagProfile_Location, start) 212 b.flush() 213 return id 214 } 215 216 // funcForPC returns the func ID and line number for addr. 217 // It may emit to b.pb, so there must be no message encoding in progress. 218 func (b *profileBuilder) funcForPC(addr uintptr) (funcID uint64, lineno int) { 219 f := runtime.FuncForPC(addr) 220 if f == nil { 221 return 0, 0 222 } 223 file, lineno := f.FileLine(addr) 224 funcID = uint64(b.funcs[f]) 225 if funcID != 0 { 226 return funcID, lineno 227 } 228 229 funcID = uint64(len(b.funcs)) + 1 230 b.funcs[f] = int(funcID) 231 name := f.Name() 232 start := b.pb.startMessage() 233 b.pb.uint64Opt(tagFunction_ID, funcID) 234 b.pb.int64Opt(tagFunction_Name, b.stringIndex(name)) 235 b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(name)) 236 b.pb.int64Opt(tagFunction_Filename, b.stringIndex(file)) 237 b.pb.endMessage(tagProfile_Function, start) 238 b.flush() 239 return funcID, lineno 240 } 241 242 // newProfileBuilder returns a new profileBuilder. 243 // CPU profiling data obtained from the runtime can be added 244 // by calling b.addCPUData, and then the eventual profile 245 // can be obtained by calling b.finish. 246 func newProfileBuilder(w io.Writer) *profileBuilder { 247 zw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed) 248 b := &profileBuilder{ 249 w: w, 250 zw: zw, 251 start: time.Now(), 252 strings: []string{""}, 253 stringMap: map[string]int{"": 0}, 254 locs: map[uintptr]int{}, 255 funcs: map[*runtime.Func]int{}, 256 } 257 b.readMapping() 258 return b 259 } 260 261 // addCPUData adds the CPU profiling data to the profile. 262 // The data must be a whole number of records, 263 // as delivered by the runtime. 264 func (b *profileBuilder) addCPUData(data []uint64, tags []unsafe.Pointer) error { 265 if !b.havePeriod { 266 // first record is period 267 if len(data) < 3 { 268 return fmt.Errorf("truncated profile") 269 } 270 if data[0] != 3 || data[2] == 0 { 271 return fmt.Errorf("malformed profile") 272 } 273 b.period = int64(data[2]) * 1000 274 b.havePeriod = true 275 data = data[3:] 276 } 277 278 // Parse CPU samples from the profile. 279 // Each sample is 3+n uint64s: 280 // data[0] = 3+n 281 // data[1] = time stamp (ignored) 282 // data[2] = count 283 // data[3:3+n] = stack 284 // If the count is 0 and the stack has length 1, 285 // that's an overflow record inserted by the runtime 286 // to indicate that stack[0] samples were lost. 287 // Otherwise the count is usually 1, 288 // but in a few special cases like lost non-Go samples 289 // there can be larger counts. 290 // Because many samples with the same stack arrive, 291 // we want to deduplicate immediately, which we do 292 // using the b.m profMap. 293 for len(data) > 0 { 294 if len(data) < 3 || data[0] > uint64(len(data)) { 295 return fmt.Errorf("truncated profile") 296 } 297 if data[0] < 3 || tags != nil && len(tags) < 1 { 298 return fmt.Errorf("malformed profile") 299 } 300 count := data[2] 301 stk := data[3:data[0]] 302 data = data[data[0]:] 303 var tag unsafe.Pointer 304 if tags != nil { 305 tag = tags[0] 306 tags = tags[1:] 307 } 308 309 if count == 0 && len(stk) == 1 { 310 // overflow record 311 count = uint64(stk[0]) 312 stk = []uint64{ 313 uint64(funcPC(lostProfileEvent)), 314 } 315 } 316 b.m.lookup(stk, tag).count += int64(count) 317 } 318 return nil 319 } 320 321 // build completes and returns the constructed profile. 322 func (b *profileBuilder) build() error { 323 b.end = time.Now() 324 325 b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano()) 326 if b.havePeriod { // must be CPU profile 327 b.pbValueType(tagProfile_SampleType, "samples", "count") 328 b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds") 329 b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds()) 330 b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds") 331 b.pb.int64Opt(tagProfile_Period, b.period) 332 } 333 334 values := []int64{0, 0} 335 var locs []uint64 336 for e := b.m.all; e != nil; e = e.nextAll { 337 values[0] = e.count 338 values[1] = e.count * b.period 339 340 locs = locs[:0] 341 for i, addr := range e.stk { 342 // Addresses from stack traces point to the next instruction after 343 // each call. Adjust by -1 to land somewhere on the actual call 344 // (except for the leaf, which is not a call). 345 if i > 0 { 346 addr-- 347 } 348 l := b.locForPC(addr) 349 if l == 0 { // runtime.goexit 350 continue 351 } 352 locs = append(locs, l) 353 } 354 b.pbSample(values, locs, nil) 355 } 356 357 // TODO: Anything for tagProfile_DropFrames? 358 // TODO: Anything for tagProfile_KeepFrames? 359 360 b.pb.strings(tagProfile_StringTable, b.strings) 361 b.zw.Write(b.pb.data) 362 b.zw.Close() 363 return nil 364 } 365 366 // readMapping reads /proc/self/maps and writes mappings to b.pb. 367 // It saves the address ranges of the mappings in b.mem for use 368 // when emitting locations. 369 func (b *profileBuilder) readMapping() { 370 data, _ := ioutil.ReadFile("/proc/self/maps") 371 372 // $ cat /proc/self/maps 373 // 00400000-0040b000 r-xp 00000000 fc:01 787766 /bin/cat 374 // 0060a000-0060b000 r--p 0000a000 fc:01 787766 /bin/cat 375 // 0060b000-0060c000 rw-p 0000b000 fc:01 787766 /bin/cat 376 // 014ab000-014cc000 rw-p 00000000 00:00 0 [heap] 377 // 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064 /usr/lib/locale/locale-archive 378 // 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 379 // 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 380 // 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 381 // 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226 /lib/x86_64-linux-gnu/libc-2.19.so 382 // 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0 383 // 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 384 // 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0 385 // 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0 386 // 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 387 // 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217 /lib/x86_64-linux-gnu/ld-2.19.so 388 // 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0 389 // 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0 [stack] 390 // 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0 [vdso] 391 // ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] 392 393 var line []byte 394 // next removes and returns the next field in the line. 395 // It also removes from line any spaces following the field. 396 next := func() []byte { 397 j := bytes.IndexByte(line, ' ') 398 if j < 0 { 399 f := line 400 line = nil 401 return f 402 } 403 f := line[:j] 404 line = line[j+1:] 405 for len(line) > 0 && line[0] == ' ' { 406 line = line[1:] 407 } 408 return f 409 } 410 411 for len(data) > 0 { 412 i := bytes.IndexByte(data, '\n') 413 if i < 0 { 414 line, data = data, nil 415 } else { 416 line, data = data[:i], data[i+1:] 417 } 418 addr := next() 419 i = bytes.IndexByte(addr, '-') 420 if i < 0 { 421 continue 422 } 423 lo, err := strconv.ParseUint(string(addr[:i]), 16, 64) 424 if err != nil { 425 continue 426 } 427 hi, err := strconv.ParseUint(string(addr[i+1:]), 16, 64) 428 if err != nil { 429 continue 430 } 431 perm := next() 432 if len(perm) < 4 || perm[2] != 'x' { 433 // Only interested in executable mappings. 434 continue 435 } 436 offset, err := strconv.ParseUint(string(next()), 16, 64) 437 if err != nil { 438 continue 439 } 440 next() // dev 441 next() // inode 442 if line == nil { 443 continue 444 } 445 file := string(line) 446 447 // TODO: pprof's remapMappingIDs makes two adjustments: 448 // 1. If there is an /anon_hugepage mapping first and it is 449 // consecutive to a next mapping, drop the /anon_hugepage. 450 // 2. If start-offset = 0x400000, change start to 0x400000 and offset to 0. 451 // There's no indication why either of these is needed. 452 // Let's try not doing these and see what breaks. 453 // If we do need them, they would go here, before we 454 // enter the mappings into b.mem in the first place. 455 456 buildID, _ := elfBuildID(file) 457 b.mem = append(b.mem, memMap{uintptr(lo), uintptr(hi)}) 458 b.pbMapping(tagProfile_Mapping, uint64(len(b.mem)), lo, hi, offset, file, buildID) 459 } 460 }