...

Source file src/runtime/pprof/proto.go

Documentation: runtime/pprof

		 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  	"os"
		13  	"runtime"
		14  	"strconv"
		15  	"time"
		16  	"unsafe"
		17  )
		18  
		19  // lostProfileEvent is the function to which lost profiling
		20  // events are attributed.
		21  // (The name shows up in the pprof graphs.)
		22  func lostProfileEvent() { lostProfileEvent() }
		23  
		24  // funcPC returns the PC for the func value f.
		25  func funcPC(f interface{}) uintptr {
		26  	return *(*[2]*uintptr)(unsafe.Pointer(&f))[1]
		27  }
		28  
		29  // A profileBuilder writes a profile incrementally from a
		30  // stream of profile samples delivered by the runtime.
		31  type profileBuilder struct {
		32  	start			time.Time
		33  	end				time.Time
		34  	havePeriod bool
		35  	period		 int64
		36  	m					profMap
		37  
		38  	// encoding state
		39  	w				 io.Writer
		40  	zw				*gzip.Writer
		41  	pb				protobuf
		42  	strings	 []string
		43  	stringMap map[string]int
		44  	locs			map[uintptr]locInfo // list of locInfo starting with the given PC.
		45  	funcs		 map[string]int			// Package path-qualified function name to Function.ID
		46  	mem			 []memMap
		47  	deck			pcDeck
		48  }
		49  
		50  type memMap struct {
		51  	// initialized as reading mapping
		52  	start				 uintptr
		53  	end					 uintptr
		54  	offset				uint64
		55  	file, buildID string
		56  
		57  	funcs symbolizeFlag
		58  	fake	bool // map entry was faked; /proc/self/maps wasn't available
		59  }
		60  
		61  // symbolizeFlag keeps track of symbolization result.
		62  //	 0									: no symbol lookup was performed
		63  //	 1<<0 (lookupTried) : symbol lookup was performed
		64  //	 1<<1 (lookupFailed): symbol lookup was performed but failed
		65  type symbolizeFlag uint8
		66  
		67  const (
		68  	lookupTried	symbolizeFlag = 1 << iota
		69  	lookupFailed symbolizeFlag = 1 << iota
		70  )
		71  
		72  const (
		73  	// message Profile
		74  	tagProfile_SampleType				= 1	// repeated ValueType
		75  	tagProfile_Sample						= 2	// repeated Sample
		76  	tagProfile_Mapping					 = 3	// repeated Mapping
		77  	tagProfile_Location					= 4	// repeated Location
		78  	tagProfile_Function					= 5	// repeated Function
		79  	tagProfile_StringTable			 = 6	// repeated string
		80  	tagProfile_DropFrames				= 7	// int64 (string table index)
		81  	tagProfile_KeepFrames				= 8	// int64 (string table index)
		82  	tagProfile_TimeNanos				 = 9	// int64
		83  	tagProfile_DurationNanos		 = 10 // int64
		84  	tagProfile_PeriodType				= 11 // ValueType (really optional string???)
		85  	tagProfile_Period						= 12 // int64
		86  	tagProfile_Comment					 = 13 // repeated int64
		87  	tagProfile_DefaultSampleType = 14 // int64
		88  
		89  	// message ValueType
		90  	tagValueType_Type = 1 // int64 (string table index)
		91  	tagValueType_Unit = 2 // int64 (string table index)
		92  
		93  	// message Sample
		94  	tagSample_Location = 1 // repeated uint64
		95  	tagSample_Value		= 2 // repeated int64
		96  	tagSample_Label		= 3 // repeated Label
		97  
		98  	// message Label
		99  	tagLabel_Key = 1 // int64 (string table index)
	 100  	tagLabel_Str = 2 // int64 (string table index)
	 101  	tagLabel_Num = 3 // int64
	 102  
	 103  	// message Mapping
	 104  	tagMapping_ID							= 1	// uint64
	 105  	tagMapping_Start					 = 2	// uint64
	 106  	tagMapping_Limit					 = 3	// uint64
	 107  	tagMapping_Offset					= 4	// uint64
	 108  	tagMapping_Filename				= 5	// int64 (string table index)
	 109  	tagMapping_BuildID				 = 6	// int64 (string table index)
	 110  	tagMapping_HasFunctions		= 7	// bool
	 111  	tagMapping_HasFilenames		= 8	// bool
	 112  	tagMapping_HasLineNumbers	= 9	// bool
	 113  	tagMapping_HasInlineFrames = 10 // bool
	 114  
	 115  	// message Location
	 116  	tagLocation_ID				= 1 // uint64
	 117  	tagLocation_MappingID = 2 // uint64
	 118  	tagLocation_Address	 = 3 // uint64
	 119  	tagLocation_Line			= 4 // repeated Line
	 120  
	 121  	// message Line
	 122  	tagLine_FunctionID = 1 // uint64
	 123  	tagLine_Line			 = 2 // int64
	 124  
	 125  	// message Function
	 126  	tagFunction_ID				 = 1 // uint64
	 127  	tagFunction_Name			 = 2 // int64 (string table index)
	 128  	tagFunction_SystemName = 3 // int64 (string table index)
	 129  	tagFunction_Filename	 = 4 // int64 (string table index)
	 130  	tagFunction_StartLine	= 5 // int64
	 131  )
	 132  
	 133  // stringIndex adds s to the string table if not already present
	 134  // and returns the index of s in the string table.
	 135  func (b *profileBuilder) stringIndex(s string) int64 {
	 136  	id, ok := b.stringMap[s]
	 137  	if !ok {
	 138  		id = len(b.strings)
	 139  		b.strings = append(b.strings, s)
	 140  		b.stringMap[s] = id
	 141  	}
	 142  	return int64(id)
	 143  }
	 144  
	 145  func (b *profileBuilder) flush() {
	 146  	const dataFlush = 4096
	 147  	if b.pb.nest == 0 && len(b.pb.data) > dataFlush {
	 148  		b.zw.Write(b.pb.data)
	 149  		b.pb.data = b.pb.data[:0]
	 150  	}
	 151  }
	 152  
	 153  // pbValueType encodes a ValueType message to b.pb.
	 154  func (b *profileBuilder) pbValueType(tag int, typ, unit string) {
	 155  	start := b.pb.startMessage()
	 156  	b.pb.int64(tagValueType_Type, b.stringIndex(typ))
	 157  	b.pb.int64(tagValueType_Unit, b.stringIndex(unit))
	 158  	b.pb.endMessage(tag, start)
	 159  }
	 160  
	 161  // pbSample encodes a Sample message to b.pb.
	 162  func (b *profileBuilder) pbSample(values []int64, locs []uint64, labels func()) {
	 163  	start := b.pb.startMessage()
	 164  	b.pb.int64s(tagSample_Value, values)
	 165  	b.pb.uint64s(tagSample_Location, locs)
	 166  	if labels != nil {
	 167  		labels()
	 168  	}
	 169  	b.pb.endMessage(tagProfile_Sample, start)
	 170  	b.flush()
	 171  }
	 172  
	 173  // pbLabel encodes a Label message to b.pb.
	 174  func (b *profileBuilder) pbLabel(tag int, key, str string, num int64) {
	 175  	start := b.pb.startMessage()
	 176  	b.pb.int64Opt(tagLabel_Key, b.stringIndex(key))
	 177  	b.pb.int64Opt(tagLabel_Str, b.stringIndex(str))
	 178  	b.pb.int64Opt(tagLabel_Num, num)
	 179  	b.pb.endMessage(tag, start)
	 180  }
	 181  
	 182  // pbLine encodes a Line message to b.pb.
	 183  func (b *profileBuilder) pbLine(tag int, funcID uint64, line int64) {
	 184  	start := b.pb.startMessage()
	 185  	b.pb.uint64Opt(tagLine_FunctionID, funcID)
	 186  	b.pb.int64Opt(tagLine_Line, line)
	 187  	b.pb.endMessage(tag, start)
	 188  }
	 189  
	 190  // pbMapping encodes a Mapping message to b.pb.
	 191  func (b *profileBuilder) pbMapping(tag int, id, base, limit, offset uint64, file, buildID string, hasFuncs bool) {
	 192  	start := b.pb.startMessage()
	 193  	b.pb.uint64Opt(tagMapping_ID, id)
	 194  	b.pb.uint64Opt(tagMapping_Start, base)
	 195  	b.pb.uint64Opt(tagMapping_Limit, limit)
	 196  	b.pb.uint64Opt(tagMapping_Offset, offset)
	 197  	b.pb.int64Opt(tagMapping_Filename, b.stringIndex(file))
	 198  	b.pb.int64Opt(tagMapping_BuildID, b.stringIndex(buildID))
	 199  	// TODO: we set HasFunctions if all symbols from samples were symbolized (hasFuncs).
	 200  	// Decide what to do about HasInlineFrames and HasLineNumbers.
	 201  	// Also, another approach to handle the mapping entry with
	 202  	// incomplete symbolization results is to dupliace the mapping
	 203  	// entry (but with different Has* fields values) and use
	 204  	// different entries for symbolized locations and unsymbolized locations.
	 205  	if hasFuncs {
	 206  		b.pb.bool(tagMapping_HasFunctions, true)
	 207  	}
	 208  	b.pb.endMessage(tag, start)
	 209  }
	 210  
	 211  func allFrames(addr uintptr) ([]runtime.Frame, symbolizeFlag) {
	 212  	// Expand this one address using CallersFrames so we can cache
	 213  	// each expansion. In general, CallersFrames takes a whole
	 214  	// stack, but in this case we know there will be no skips in
	 215  	// the stack and we have return PCs anyway.
	 216  	frames := runtime.CallersFrames([]uintptr{addr})
	 217  	frame, more := frames.Next()
	 218  	if frame.Function == "runtime.goexit" {
	 219  		// Short-circuit if we see runtime.goexit so the loop
	 220  		// below doesn't allocate a useless empty location.
	 221  		return nil, 0
	 222  	}
	 223  
	 224  	symbolizeResult := lookupTried
	 225  	if frame.PC == 0 || frame.Function == "" || frame.File == "" || frame.Line == 0 {
	 226  		symbolizeResult |= lookupFailed
	 227  	}
	 228  
	 229  	if frame.PC == 0 {
	 230  		// If we failed to resolve the frame, at least make up
	 231  		// a reasonable call PC. This mostly happens in tests.
	 232  		frame.PC = addr - 1
	 233  	}
	 234  	ret := []runtime.Frame{frame}
	 235  	for frame.Function != "runtime.goexit" && more == true {
	 236  		frame, more = frames.Next()
	 237  		ret = append(ret, frame)
	 238  	}
	 239  	return ret, symbolizeResult
	 240  }
	 241  
	 242  type locInfo struct {
	 243  	// location id assigned by the profileBuilder
	 244  	id uint64
	 245  
	 246  	// sequence of PCs, including the fake PCs returned by the traceback
	 247  	// to represent inlined functions
	 248  	// https://github.com/golang/go/blob/d6f2f833c93a41ec1c68e49804b8387a06b131c5/src/runtime/traceback.go#L347-L368
	 249  	pcs []uintptr
	 250  }
	 251  
	 252  // newProfileBuilder returns a new profileBuilder.
	 253  // CPU profiling data obtained from the runtime can be added
	 254  // by calling b.addCPUData, and then the eventual profile
	 255  // can be obtained by calling b.finish.
	 256  func newProfileBuilder(w io.Writer) *profileBuilder {
	 257  	zw, _ := gzip.NewWriterLevel(w, gzip.BestSpeed)
	 258  	b := &profileBuilder{
	 259  		w:				 w,
	 260  		zw:				zw,
	 261  		start:		 time.Now(),
	 262  		strings:	 []string{""},
	 263  		stringMap: map[string]int{"": 0},
	 264  		locs:			map[uintptr]locInfo{},
	 265  		funcs:		 map[string]int{},
	 266  	}
	 267  	b.readMapping()
	 268  	return b
	 269  }
	 270  
	 271  // addCPUData adds the CPU profiling data to the profile.
	 272  // The data must be a whole number of records,
	 273  // as delivered by the runtime.
	 274  func (b *profileBuilder) addCPUData(data []uint64, tags []unsafe.Pointer) error {
	 275  	if !b.havePeriod {
	 276  		// first record is period
	 277  		if len(data) < 3 {
	 278  			return fmt.Errorf("truncated profile")
	 279  		}
	 280  		if data[0] != 3 || data[2] == 0 {
	 281  			return fmt.Errorf("malformed profile")
	 282  		}
	 283  		// data[2] is sampling rate in Hz. Convert to sampling
	 284  		// period in nanoseconds.
	 285  		b.period = 1e9 / int64(data[2])
	 286  		b.havePeriod = true
	 287  		data = data[3:]
	 288  	}
	 289  
	 290  	// Parse CPU samples from the profile.
	 291  	// Each sample is 3+n uint64s:
	 292  	//	data[0] = 3+n
	 293  	//	data[1] = time stamp (ignored)
	 294  	//	data[2] = count
	 295  	//	data[3:3+n] = stack
	 296  	// If the count is 0 and the stack has length 1,
	 297  	// that's an overflow record inserted by the runtime
	 298  	// to indicate that stack[0] samples were lost.
	 299  	// Otherwise the count is usually 1,
	 300  	// but in a few special cases like lost non-Go samples
	 301  	// there can be larger counts.
	 302  	// Because many samples with the same stack arrive,
	 303  	// we want to deduplicate immediately, which we do
	 304  	// using the b.m profMap.
	 305  	for len(data) > 0 {
	 306  		if len(data) < 3 || data[0] > uint64(len(data)) {
	 307  			return fmt.Errorf("truncated profile")
	 308  		}
	 309  		if data[0] < 3 || tags != nil && len(tags) < 1 {
	 310  			return fmt.Errorf("malformed profile")
	 311  		}
	 312  		count := data[2]
	 313  		stk := data[3:data[0]]
	 314  		data = data[data[0]:]
	 315  		var tag unsafe.Pointer
	 316  		if tags != nil {
	 317  			tag = tags[0]
	 318  			tags = tags[1:]
	 319  		}
	 320  
	 321  		if count == 0 && len(stk) == 1 {
	 322  			// overflow record
	 323  			count = uint64(stk[0])
	 324  			stk = []uint64{
	 325  				// gentraceback guarantees that PCs in the
	 326  				// stack can be unconditionally decremented and
	 327  				// still be valid, so we must do the same.
	 328  				uint64(funcPC(lostProfileEvent) + 1),
	 329  			}
	 330  		}
	 331  		b.m.lookup(stk, tag).count += int64(count)
	 332  	}
	 333  	return nil
	 334  }
	 335  
	 336  // build completes and returns the constructed profile.
	 337  func (b *profileBuilder) build() {
	 338  	b.end = time.Now()
	 339  
	 340  	b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
	 341  	if b.havePeriod { // must be CPU profile
	 342  		b.pbValueType(tagProfile_SampleType, "samples", "count")
	 343  		b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds")
	 344  		b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds())
	 345  		b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds")
	 346  		b.pb.int64Opt(tagProfile_Period, b.period)
	 347  	}
	 348  
	 349  	values := []int64{0, 0}
	 350  	var locs []uint64
	 351  
	 352  	for e := b.m.all; e != nil; e = e.nextAll {
	 353  		values[0] = e.count
	 354  		values[1] = e.count * b.period
	 355  
	 356  		var labels func()
	 357  		if e.tag != nil {
	 358  			labels = func() {
	 359  				for k, v := range *(*labelMap)(e.tag) {
	 360  					b.pbLabel(tagSample_Label, k, v, 0)
	 361  				}
	 362  			}
	 363  		}
	 364  
	 365  		locs = b.appendLocsForStack(locs[:0], e.stk)
	 366  
	 367  		b.pbSample(values, locs, labels)
	 368  	}
	 369  
	 370  	for i, m := range b.mem {
	 371  		hasFunctions := m.funcs == lookupTried // lookupTried but not lookupFailed
	 372  		b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(m.start), uint64(m.end), m.offset, m.file, m.buildID, hasFunctions)
	 373  	}
	 374  
	 375  	// TODO: Anything for tagProfile_DropFrames?
	 376  	// TODO: Anything for tagProfile_KeepFrames?
	 377  
	 378  	b.pb.strings(tagProfile_StringTable, b.strings)
	 379  	b.zw.Write(b.pb.data)
	 380  	b.zw.Close()
	 381  }
	 382  
	 383  // appendLocsForStack appends the location IDs for the given stack trace to the given
	 384  // location ID slice, locs. The addresses in the stack are return PCs or 1 + the PC of
	 385  // an inline marker as the runtime traceback function returns.
	 386  //
	 387  // It may emit to b.pb, so there must be no message encoding in progress.
	 388  func (b *profileBuilder) appendLocsForStack(locs []uint64, stk []uintptr) (newLocs []uint64) {
	 389  	b.deck.reset()
	 390  
	 391  	// The last frame might be truncated. Recover lost inline frames.
	 392  	stk = runtime_expandFinalInlineFrame(stk)
	 393  
	 394  	for len(stk) > 0 {
	 395  		addr := stk[0]
	 396  		if l, ok := b.locs[addr]; ok {
	 397  			// first record the location if there is any pending accumulated info.
	 398  			if id := b.emitLocation(); id > 0 {
	 399  				locs = append(locs, id)
	 400  			}
	 401  
	 402  			// then, record the cached location.
	 403  			locs = append(locs, l.id)
	 404  
	 405  			// Skip the matching pcs.
	 406  			//
	 407  			// Even if stk was truncated due to the stack depth
	 408  			// limit, expandFinalInlineFrame above has already
	 409  			// fixed the truncation, ensuring it is long enough.
	 410  			stk = stk[len(l.pcs):]
	 411  			continue
	 412  		}
	 413  
	 414  		frames, symbolizeResult := allFrames(addr)
	 415  		if len(frames) == 0 { // runtime.goexit.
	 416  			if id := b.emitLocation(); id > 0 {
	 417  				locs = append(locs, id)
	 418  			}
	 419  			stk = stk[1:]
	 420  			continue
	 421  		}
	 422  
	 423  		if added := b.deck.tryAdd(addr, frames, symbolizeResult); added {
	 424  			stk = stk[1:]
	 425  			continue
	 426  		}
	 427  		// add failed because this addr is not inlined with the
	 428  		// existing PCs in the deck. Flush the deck and retry handling
	 429  		// this pc.
	 430  		if id := b.emitLocation(); id > 0 {
	 431  			locs = append(locs, id)
	 432  		}
	 433  
	 434  		// check cache again - previous emitLocation added a new entry
	 435  		if l, ok := b.locs[addr]; ok {
	 436  			locs = append(locs, l.id)
	 437  			stk = stk[len(l.pcs):] // skip the matching pcs.
	 438  		} else {
	 439  			b.deck.tryAdd(addr, frames, symbolizeResult) // must succeed.
	 440  			stk = stk[1:]
	 441  		}
	 442  	}
	 443  	if id := b.emitLocation(); id > 0 { // emit remaining location.
	 444  		locs = append(locs, id)
	 445  	}
	 446  	return locs
	 447  }
	 448  
	 449  // pcDeck is a helper to detect a sequence of inlined functions from
	 450  // a stack trace returned by the runtime.
	 451  //
	 452  // The stack traces returned by runtime's trackback functions are fully
	 453  // expanded (at least for Go functions) and include the fake pcs representing
	 454  // inlined functions. The profile proto expects the inlined functions to be
	 455  // encoded in one Location message.
	 456  // https://github.com/google/pprof/blob/5e965273ee43930341d897407202dd5e10e952cb/proto/profile.proto#L177-L184
	 457  //
	 458  // Runtime does not directly expose whether a frame is for an inlined function
	 459  // and looking up debug info is not ideal, so we use a heuristic to filter
	 460  // the fake pcs and restore the inlined and entry functions. Inlined functions
	 461  // have the following properties:
	 462  //	 Frame's Func is nil (note: also true for non-Go functions), and
	 463  //	 Frame's Entry matches its entry function frame's Entry (note: could also be true for recursive calls and non-Go functions), and
	 464  //	 Frame's Name does not match its entry function frame's name (note: inlined functions cannot be directly recursive).
	 465  //
	 466  // As reading and processing the pcs in a stack trace one by one (from leaf to the root),
	 467  // we use pcDeck to temporarily hold the observed pcs and their expanded frames
	 468  // until we observe the entry function frame.
	 469  type pcDeck struct {
	 470  	pcs						 []uintptr
	 471  	frames					[]runtime.Frame
	 472  	symbolizeResult symbolizeFlag
	 473  }
	 474  
	 475  func (d *pcDeck) reset() {
	 476  	d.pcs = d.pcs[:0]
	 477  	d.frames = d.frames[:0]
	 478  	d.symbolizeResult = 0
	 479  }
	 480  
	 481  // tryAdd tries to add the pc and Frames expanded from it (most likely one,
	 482  // since the stack trace is already fully expanded) and the symbolizeResult
	 483  // to the deck. If it fails the caller needs to flush the deck and retry.
	 484  func (d *pcDeck) tryAdd(pc uintptr, frames []runtime.Frame, symbolizeResult symbolizeFlag) (success bool) {
	 485  	if existing := len(d.pcs); existing > 0 {
	 486  		// 'd.frames' are all expanded from one 'pc' and represent all
	 487  		// inlined functions so we check only the last one.
	 488  		newFrame := frames[0]
	 489  		last := d.frames[existing-1]
	 490  		if last.Func != nil { // the last frame can't be inlined. Flush.
	 491  			return false
	 492  		}
	 493  		if last.Entry == 0 || newFrame.Entry == 0 { // Possibly not a Go function. Don't try to merge.
	 494  			return false
	 495  		}
	 496  
	 497  		if last.Entry != newFrame.Entry { // newFrame is for a different function.
	 498  			return false
	 499  		}
	 500  		if last.Function == newFrame.Function { // maybe recursion.
	 501  			return false
	 502  		}
	 503  	}
	 504  	d.pcs = append(d.pcs, pc)
	 505  	d.frames = append(d.frames, frames...)
	 506  	d.symbolizeResult |= symbolizeResult
	 507  	return true
	 508  }
	 509  
	 510  // emitLocation emits the new location and function information recorded in the deck
	 511  // and returns the location ID encoded in the profile protobuf.
	 512  // It emits to b.pb, so there must be no message encoding in progress.
	 513  // It resets the deck.
	 514  func (b *profileBuilder) emitLocation() uint64 {
	 515  	if len(b.deck.pcs) == 0 {
	 516  		return 0
	 517  	}
	 518  	defer b.deck.reset()
	 519  
	 520  	addr := b.deck.pcs[0]
	 521  	firstFrame := b.deck.frames[0]
	 522  
	 523  	// We can't write out functions while in the middle of the
	 524  	// Location message, so record new functions we encounter and
	 525  	// write them out after the Location.
	 526  	type newFunc struct {
	 527  		id				 uint64
	 528  		name, file string
	 529  	}
	 530  	newFuncs := make([]newFunc, 0, 8)
	 531  
	 532  	id := uint64(len(b.locs)) + 1
	 533  	b.locs[addr] = locInfo{id: id, pcs: append([]uintptr{}, b.deck.pcs...)}
	 534  
	 535  	start := b.pb.startMessage()
	 536  	b.pb.uint64Opt(tagLocation_ID, id)
	 537  	b.pb.uint64Opt(tagLocation_Address, uint64(firstFrame.PC))
	 538  	for _, frame := range b.deck.frames {
	 539  		// Write out each line in frame expansion.
	 540  		funcID := uint64(b.funcs[frame.Function])
	 541  		if funcID == 0 {
	 542  			funcID = uint64(len(b.funcs)) + 1
	 543  			b.funcs[frame.Function] = int(funcID)
	 544  			newFuncs = append(newFuncs, newFunc{funcID, frame.Function, frame.File})
	 545  		}
	 546  		b.pbLine(tagLocation_Line, funcID, int64(frame.Line))
	 547  	}
	 548  	for i := range b.mem {
	 549  		if b.mem[i].start <= addr && addr < b.mem[i].end || b.mem[i].fake {
	 550  			b.pb.uint64Opt(tagLocation_MappingID, uint64(i+1))
	 551  
	 552  			m := b.mem[i]
	 553  			m.funcs |= b.deck.symbolizeResult
	 554  			b.mem[i] = m
	 555  			break
	 556  		}
	 557  	}
	 558  	b.pb.endMessage(tagProfile_Location, start)
	 559  
	 560  	// Write out functions we found during frame expansion.
	 561  	for _, fn := range newFuncs {
	 562  		start := b.pb.startMessage()
	 563  		b.pb.uint64Opt(tagFunction_ID, fn.id)
	 564  		b.pb.int64Opt(tagFunction_Name, b.stringIndex(fn.name))
	 565  		b.pb.int64Opt(tagFunction_SystemName, b.stringIndex(fn.name))
	 566  		b.pb.int64Opt(tagFunction_Filename, b.stringIndex(fn.file))
	 567  		b.pb.endMessage(tagProfile_Function, start)
	 568  	}
	 569  
	 570  	b.flush()
	 571  	return id
	 572  }
	 573  
	 574  // readMapping reads /proc/self/maps and writes mappings to b.pb.
	 575  // It saves the address ranges of the mappings in b.mem for use
	 576  // when emitting locations.
	 577  func (b *profileBuilder) readMapping() {
	 578  	data, _ := os.ReadFile("/proc/self/maps")
	 579  	parseProcSelfMaps(data, b.addMapping)
	 580  	if len(b.mem) == 0 { // pprof expects a map entry, so fake one.
	 581  		b.addMappingEntry(0, 0, 0, "", "", true)
	 582  		// TODO(hyangah): make addMapping return *memMap or
	 583  		// take a memMap struct, and get rid of addMappingEntry
	 584  		// that takes a bunch of positional arguments.
	 585  	}
	 586  }
	 587  
	 588  func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) {
	 589  	// $ cat /proc/self/maps
	 590  	// 00400000-0040b000 r-xp 00000000 fc:01 787766														 /bin/cat
	 591  	// 0060a000-0060b000 r--p 0000a000 fc:01 787766														 /bin/cat
	 592  	// 0060b000-0060c000 rw-p 0000b000 fc:01 787766														 /bin/cat
	 593  	// 014ab000-014cc000 rw-p 00000000 00:00 0																	[heap]
	 594  	// 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064										/usr/lib/locale/locale-archive
	 595  	// 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226										/lib/x86_64-linux-gnu/libc-2.19.so
	 596  	// 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226										/lib/x86_64-linux-gnu/libc-2.19.so
	 597  	// 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226										/lib/x86_64-linux-gnu/libc-2.19.so
	 598  	// 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226										/lib/x86_64-linux-gnu/libc-2.19.so
	 599  	// 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
	 600  	// 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217										/lib/x86_64-linux-gnu/ld-2.19.so
	 601  	// 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
	 602  	// 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
	 603  	// 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217										/lib/x86_64-linux-gnu/ld-2.19.so
	 604  	// 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217										/lib/x86_64-linux-gnu/ld-2.19.so
	 605  	// 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
	 606  	// 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0													[stack]
	 607  	// 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0													[vdso]
	 608  	// ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0									[vsyscall]
	 609  
	 610  	var line []byte
	 611  	// next removes and returns the next field in the line.
	 612  	// It also removes from line any spaces following the field.
	 613  	next := func() []byte {
	 614  		j := bytes.IndexByte(line, ' ')
	 615  		if j < 0 {
	 616  			f := line
	 617  			line = nil
	 618  			return f
	 619  		}
	 620  		f := line[:j]
	 621  		line = line[j+1:]
	 622  		for len(line) > 0 && line[0] == ' ' {
	 623  			line = line[1:]
	 624  		}
	 625  		return f
	 626  	}
	 627  
	 628  	for len(data) > 0 {
	 629  		i := bytes.IndexByte(data, '\n')
	 630  		if i < 0 {
	 631  			line, data = data, nil
	 632  		} else {
	 633  			line, data = data[:i], data[i+1:]
	 634  		}
	 635  		addr := next()
	 636  		i = bytes.IndexByte(addr, '-')
	 637  		if i < 0 {
	 638  			continue
	 639  		}
	 640  		lo, err := strconv.ParseUint(string(addr[:i]), 16, 64)
	 641  		if err != nil {
	 642  			continue
	 643  		}
	 644  		hi, err := strconv.ParseUint(string(addr[i+1:]), 16, 64)
	 645  		if err != nil {
	 646  			continue
	 647  		}
	 648  		perm := next()
	 649  		if len(perm) < 4 || perm[2] != 'x' {
	 650  			// Only interested in executable mappings.
	 651  			continue
	 652  		}
	 653  		offset, err := strconv.ParseUint(string(next()), 16, 64)
	 654  		if err != nil {
	 655  			continue
	 656  		}
	 657  		next()					// dev
	 658  		inode := next() // inode
	 659  		if line == nil {
	 660  			continue
	 661  		}
	 662  		file := string(line)
	 663  
	 664  		// Trim deleted file marker.
	 665  		deletedStr := " (deleted)"
	 666  		deletedLen := len(deletedStr)
	 667  		if len(file) >= deletedLen && file[len(file)-deletedLen:] == deletedStr {
	 668  			file = file[:len(file)-deletedLen]
	 669  		}
	 670  
	 671  		if len(inode) == 1 && inode[0] == '0' && file == "" {
	 672  			// Huge-page text mappings list the initial fragment of
	 673  			// mapped but unpopulated memory as being inode 0.
	 674  			// Don't report that part.
	 675  			// But [vdso] and [vsyscall] are inode 0, so let non-empty file names through.
	 676  			continue
	 677  		}
	 678  
	 679  		// TODO: pprof's remapMappingIDs makes two adjustments:
	 680  		// 1. If there is an /anon_hugepage mapping first and it is
	 681  		// consecutive to a next mapping, drop the /anon_hugepage.
	 682  		// 2. If start-offset = 0x400000, change start to 0x400000 and offset to 0.
	 683  		// There's no indication why either of these is needed.
	 684  		// Let's try not doing these and see what breaks.
	 685  		// If we do need them, they would go here, before we
	 686  		// enter the mappings into b.mem in the first place.
	 687  
	 688  		buildID, _ := elfBuildID(file)
	 689  		addMapping(lo, hi, offset, file, buildID)
	 690  	}
	 691  }
	 692  
	 693  func (b *profileBuilder) addMapping(lo, hi, offset uint64, file, buildID string) {
	 694  	b.addMappingEntry(lo, hi, offset, file, buildID, false)
	 695  }
	 696  
	 697  func (b *profileBuilder) addMappingEntry(lo, hi, offset uint64, file, buildID string, fake bool) {
	 698  	b.mem = append(b.mem, memMap{
	 699  		start:	 uintptr(lo),
	 700  		end:		 uintptr(hi),
	 701  		offset:	offset,
	 702  		file:		file,
	 703  		buildID: buildID,
	 704  		fake:		fake,
	 705  	})
	 706  }
	 707  

View as plain text