github.com/s1s1ty/go@v0.0.0-20180207192209-104445e3140f/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  }