...

Source file src/runtime/pprof/proto.go

     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		"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]int
    45		funcs     map[string]int // Package path-qualified function name to Function.ID
    46		mem       []memMap
    47	}
    48	
    49	type memMap struct {
    50		// initialized as reading mapping
    51		start         uintptr
    52		end           uintptr
    53		offset        uint64
    54		file, buildID string
    55	
    56		funcs symbolizeFlag
    57		fake  bool // map entry was faked; /proc/self/maps wasn't available
    58	}
    59	
    60	// symbolizeFlag keeps track of symbolization result.
    61	//   0                  : no symbol lookup was performed
    62	//   1<<0 (lookupTried) : symbol lookup was performed
    63	//   1<<1 (lookupFailed): symbol lookup was performed but failed
    64	type symbolizeFlag uint8
    65	
    66	const (
    67		lookupTried  symbolizeFlag = 1 << iota
    68		lookupFailed symbolizeFlag = 1 << iota
    69	)
    70	
    71	const (
    72		// message Profile
    73		tagProfile_SampleType        = 1  // repeated ValueType
    74		tagProfile_Sample            = 2  // repeated Sample
    75		tagProfile_Mapping           = 3  // repeated Mapping
    76		tagProfile_Location          = 4  // repeated Location
    77		tagProfile_Function          = 5  // repeated Function
    78		tagProfile_StringTable       = 6  // repeated string
    79		tagProfile_DropFrames        = 7  // int64 (string table index)
    80		tagProfile_KeepFrames        = 8  // int64 (string table index)
    81		tagProfile_TimeNanos         = 9  // int64
    82		tagProfile_DurationNanos     = 10 // int64
    83		tagProfile_PeriodType        = 11 // ValueType (really optional string???)
    84		tagProfile_Period            = 12 // int64
    85		tagProfile_Comment           = 13 // repeated int64
    86		tagProfile_DefaultSampleType = 14 // int64
    87	
    88		// message ValueType
    89		tagValueType_Type = 1 // int64 (string table index)
    90		tagValueType_Unit = 2 // int64 (string table index)
    91	
    92		// message Sample
    93		tagSample_Location = 1 // repeated uint64
    94		tagSample_Value    = 2 // repeated int64
    95		tagSample_Label    = 3 // repeated Label
    96	
    97		// message Label
    98		tagLabel_Key = 1 // int64 (string table index)
    99		tagLabel_Str = 2 // int64 (string table index)
   100		tagLabel_Num = 3 // int64
   101	
   102		// message Mapping
   103		tagMapping_ID              = 1  // uint64
   104		tagMapping_Start           = 2  // uint64
   105		tagMapping_Limit           = 3  // uint64
   106		tagMapping_Offset          = 4  // uint64
   107		tagMapping_Filename        = 5  // int64 (string table index)
   108		tagMapping_BuildID         = 6  // int64 (string table index)
   109		tagMapping_HasFunctions    = 7  // bool
   110		tagMapping_HasFilenames    = 8  // bool
   111		tagMapping_HasLineNumbers  = 9  // bool
   112		tagMapping_HasInlineFrames = 10 // bool
   113	
   114		// message Location
   115		tagLocation_ID        = 1 // uint64
   116		tagLocation_MappingID = 2 // uint64
   117		tagLocation_Address   = 3 // uint64
   118		tagLocation_Line      = 4 // repeated Line
   119	
   120		// message Line
   121		tagLine_FunctionID = 1 // uint64
   122		tagLine_Line       = 2 // int64
   123	
   124		// message Function
   125		tagFunction_ID         = 1 // uint64
   126		tagFunction_Name       = 2 // int64 (string table index)
   127		tagFunction_SystemName = 3 // int64 (string table index)
   128		tagFunction_Filename   = 4 // int64 (string table index)
   129		tagFunction_StartLine  = 5 // int64
   130	)
   131	
   132	// stringIndex adds s to the string table if not already present
   133	// and returns the index of s in the string table.
   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	// pbValueType encodes a ValueType message to b.pb.
   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	// pbSample encodes a Sample message to b.pb.
   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	// pbLabel encodes a Label message to b.pb.
   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	// pbLine encodes a Line message to b.pb.
   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	// pbMapping encodes a Mapping message to b.pb.
   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		// TODO: we set HasFunctions if all symbols from samples were symbolized (hasFuncs).
   199		// Decide what to do about HasInlineFrames and HasLineNumbers.
   200		// Also, another approach to handle the mapping entry with
   201		// incomplete symbolization results is to dupliace the mapping
   202		// entry (but with different Has* fields values) and use
   203		// different entries for symbolized locations and unsymbolized locations.
   204		if hasFuncs {
   205			b.pb.bool(tagMapping_HasFunctions, true)
   206		}
   207		b.pb.endMessage(tag, start)
   208	}
   209	
   210	// locForPC returns the location ID for addr.
   211	// addr must a return PC or 1 + the PC of an inline marker. This returns the location of the corresponding call.
   212	// It may emit to b.pb, so there must be no message encoding in progress.
   213	func (b *profileBuilder) locForPC(addr uintptr) uint64 {
   214		id := uint64(b.locs[addr])
   215		if id != 0 {
   216			return id
   217		}
   218	
   219		// Expand this one address using CallersFrames so we can cache
   220		// each expansion. In general, CallersFrames takes a whole
   221		// stack, but in this case we know there will be no skips in
   222		// the stack and we have return PCs anyway.
   223		frames := runtime.CallersFrames([]uintptr{addr})
   224		frame, more := frames.Next()
   225		if frame.Function == "runtime.goexit" {
   226			// Short-circuit if we see runtime.goexit so the loop
   227			// below doesn't allocate a useless empty location.
   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			// If we failed to resolve the frame, at least make up
   238			// a reasonable call PC. This mostly happens in tests.
   239			frame.PC = addr - 1
   240		}
   241	
   242		// We can't write out functions while in the middle of the
   243		// Location message, so record new functions we encounter and
   244		// write them out after the Location.
   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			// Write out each line in frame expansion.
   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		// Write out functions we found during frame expansion.
   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	// newProfileBuilder returns a new profileBuilder.
   297	// CPU profiling data obtained from the runtime can be added
   298	// by calling b.addCPUData, and then the eventual profile
   299	// can be obtained by calling b.finish.
   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	// addCPUData adds the CPU profiling data to the profile.
   316	// The data must be a whole number of records,
   317	// as delivered by the runtime.
   318	func (b *profileBuilder) addCPUData(data []uint64, tags []unsafe.Pointer) error {
   319		if !b.havePeriod {
   320			// first record is period
   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			// data[2] is sampling rate in Hz. Convert to sampling
   328			// period in nanoseconds.
   329			b.period = 1e9 / int64(data[2])
   330			b.havePeriod = true
   331			data = data[3:]
   332		}
   333	
   334		// Parse CPU samples from the profile.
   335		// Each sample is 3+n uint64s:
   336		//	data[0] = 3+n
   337		//	data[1] = time stamp (ignored)
   338		//	data[2] = count
   339		//	data[3:3+n] = stack
   340		// If the count is 0 and the stack has length 1,
   341		// that's an overflow record inserted by the runtime
   342		// to indicate that stack[0] samples were lost.
   343		// Otherwise the count is usually 1,
   344		// but in a few special cases like lost non-Go samples
   345		// there can be larger counts.
   346		// Because many samples with the same stack arrive,
   347		// we want to deduplicate immediately, which we do
   348		// using the b.m profMap.
   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				// overflow record
   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	// build completes and returns the constructed profile.
   378	func (b *profileBuilder) build() {
   379		b.end = time.Now()
   380	
   381		b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
   382		if b.havePeriod { // must be CPU profile
   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				// Addresses from stack traces point to the
   408				// next instruction after each call, except
   409				// for the leaf, which points to where the
   410				// signal occurred. locForPC expects return
   411				// PCs, so increment the leaf address to look
   412				// like a return PC.
   413				if i == 0 {
   414					addr++
   415				}
   416				l := b.locForPC(addr)
   417				if l == 0 { // runtime.goexit
   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 // lookupTried but not lookupFailed
   427			b.pbMapping(tagProfile_Mapping, uint64(i+1), uint64(m.start), uint64(m.end), m.offset, m.file, m.buildID, hasFunctions)
   428		}
   429	
   430		// TODO: Anything for tagProfile_DropFrames?
   431		// TODO: Anything for tagProfile_KeepFrames?
   432	
   433		b.pb.strings(tagProfile_StringTable, b.strings)
   434		b.zw.Write(b.pb.data)
   435		b.zw.Close()
   436	}
   437	
   438	// readMapping reads /proc/self/maps and writes mappings to b.pb.
   439	// It saves the address ranges of the mappings in b.mem for use
   440	// when emitting locations.
   441	func (b *profileBuilder) readMapping() {
   442		data, _ := ioutil.ReadFile("/proc/self/maps")
   443		parseProcSelfMaps(data, b.addMapping)
   444		if len(b.mem) == 0 { // pprof expects a map entry, so fake one.
   445			b.addMappingEntry(0, 0, 0, "", "", true)
   446			// TODO(hyangah): make addMapping return *memMap or
   447			// take a memMap struct, and get rid of addMappingEntry
   448			// that takes a bunch of positional arguments.
   449		}
   450	}
   451	
   452	func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) {
   453		// $ cat /proc/self/maps
   454		// 00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat
   455		// 0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat
   456		// 0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat
   457		// 014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap]
   458		// 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive
   459		// 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   460		// 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   461		// 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   462		// 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   463		// 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
   464		// 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   465		// 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
   466		// 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
   467		// 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   468		// 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   469		// 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
   470		// 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack]
   471		// 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso]
   472		// ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
   473	
   474		var line []byte
   475		// next removes and returns the next field in the line.
   476		// It also removes from line any spaces following the field.
   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				// Only interested in executable mappings.
   515				continue
   516			}
   517			offset, err := strconv.ParseUint(string(next()), 16, 64)
   518			if err != nil {
   519				continue
   520			}
   521			next()          // dev
   522			inode := next() // inode
   523			if line == nil {
   524				continue
   525			}
   526			file := string(line)
   527	
   528			// Trim deleted file marker.
   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				// Huge-page text mappings list the initial fragment of
   537				// mapped but unpopulated memory as being inode 0.
   538				// Don't report that part.
   539				// But [vdso] and [vsyscall] are inode 0, so let non-empty file names through.
   540				continue
   541			}
   542	
   543			// TODO: pprof's remapMappingIDs makes two adjustments:
   544			// 1. If there is an /anon_hugepage mapping first and it is
   545			// consecutive to a next mapping, drop the /anon_hugepage.
   546			// 2. If start-offset = 0x400000, change start to 0x400000 and offset to 0.
   547			// There's no indication why either of these is needed.
   548			// Let's try not doing these and see what breaks.
   549			// If we do need them, they would go here, before we
   550			// enter the mappings into b.mem in the first place.
   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