github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/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  		// data[2] is sampling rate in Hz. Convert to sampling
   274  		// period in nanoseconds.
   275  		b.period = 1e9 / int64(data[2])
   276  		b.havePeriod = true
   277  		data = data[3:]
   278  	}
   279  
   280  	// Parse CPU samples from the profile.
   281  	// Each sample is 3+n uint64s:
   282  	//	data[0] = 3+n
   283  	//	data[1] = time stamp (ignored)
   284  	//	data[2] = count
   285  	//	data[3:3+n] = stack
   286  	// If the count is 0 and the stack has length 1,
   287  	// that's an overflow record inserted by the runtime
   288  	// to indicate that stack[0] samples were lost.
   289  	// Otherwise the count is usually 1,
   290  	// but in a few special cases like lost non-Go samples
   291  	// there can be larger counts.
   292  	// Because many samples with the same stack arrive,
   293  	// we want to deduplicate immediately, which we do
   294  	// using the b.m profMap.
   295  	for len(data) > 0 {
   296  		if len(data) < 3 || data[0] > uint64(len(data)) {
   297  			return fmt.Errorf("truncated profile")
   298  		}
   299  		if data[0] < 3 || tags != nil && len(tags) < 1 {
   300  			return fmt.Errorf("malformed profile")
   301  		}
   302  		count := data[2]
   303  		stk := data[3:data[0]]
   304  		data = data[data[0]:]
   305  		var tag unsafe.Pointer
   306  		if tags != nil {
   307  			tag = tags[0]
   308  			tags = tags[1:]
   309  		}
   310  
   311  		if count == 0 && len(stk) == 1 {
   312  			// overflow record
   313  			count = uint64(stk[0])
   314  			stk = []uint64{
   315  				uint64(funcPC(lostProfileEvent)),
   316  			}
   317  		}
   318  		b.m.lookup(stk, tag).count += int64(count)
   319  	}
   320  	return nil
   321  }
   322  
   323  // build completes and returns the constructed profile.
   324  func (b *profileBuilder) build() error {
   325  	b.end = time.Now()
   326  
   327  	b.pb.int64Opt(tagProfile_TimeNanos, b.start.UnixNano())
   328  	if b.havePeriod { // must be CPU profile
   329  		b.pbValueType(tagProfile_SampleType, "samples", "count")
   330  		b.pbValueType(tagProfile_SampleType, "cpu", "nanoseconds")
   331  		b.pb.int64Opt(tagProfile_DurationNanos, b.end.Sub(b.start).Nanoseconds())
   332  		b.pbValueType(tagProfile_PeriodType, "cpu", "nanoseconds")
   333  		b.pb.int64Opt(tagProfile_Period, b.period)
   334  	}
   335  
   336  	values := []int64{0, 0}
   337  	var locs []uint64
   338  	for e := b.m.all; e != nil; e = e.nextAll {
   339  		values[0] = e.count
   340  		values[1] = e.count * b.period
   341  
   342  		locs = locs[:0]
   343  		for i, addr := range e.stk {
   344  			// Addresses from stack traces point to the next instruction after
   345  			// each call.  Adjust by -1 to land somewhere on the actual call
   346  			// (except for the leaf, which is not a call).
   347  			if i > 0 {
   348  				addr--
   349  			}
   350  			l := b.locForPC(addr)
   351  			if l == 0 { // runtime.goexit
   352  				continue
   353  			}
   354  			locs = append(locs, l)
   355  		}
   356  		b.pbSample(values, locs, nil)
   357  	}
   358  
   359  	// TODO: Anything for tagProfile_DropFrames?
   360  	// TODO: Anything for tagProfile_KeepFrames?
   361  
   362  	b.pb.strings(tagProfile_StringTable, b.strings)
   363  	b.zw.Write(b.pb.data)
   364  	b.zw.Close()
   365  	return nil
   366  }
   367  
   368  // readMapping reads /proc/self/maps and writes mappings to b.pb.
   369  // It saves the address ranges of the mappings in b.mem for use
   370  // when emitting locations.
   371  func (b *profileBuilder) readMapping() {
   372  	data, _ := ioutil.ReadFile("/proc/self/maps")
   373  	parseProcSelfMaps(data, b.addMapping)
   374  }
   375  
   376  func parseProcSelfMaps(data []byte, addMapping func(lo, hi, offset uint64, file, buildID string)) {
   377  	// $ cat /proc/self/maps
   378  	// 00400000-0040b000 r-xp 00000000 fc:01 787766                             /bin/cat
   379  	// 0060a000-0060b000 r--p 0000a000 fc:01 787766                             /bin/cat
   380  	// 0060b000-0060c000 rw-p 0000b000 fc:01 787766                             /bin/cat
   381  	// 014ab000-014cc000 rw-p 00000000 00:00 0                                  [heap]
   382  	// 7f7d76af8000-7f7d7797c000 r--p 00000000 fc:01 1318064                    /usr/lib/locale/locale-archive
   383  	// 7f7d7797c000-7f7d77b36000 r-xp 00000000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   384  	// 7f7d77b36000-7f7d77d36000 ---p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   385  	// 7f7d77d36000-7f7d77d3a000 r--p 001ba000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   386  	// 7f7d77d3a000-7f7d77d3c000 rw-p 001be000 fc:01 1180226                    /lib/x86_64-linux-gnu/libc-2.19.so
   387  	// 7f7d77d3c000-7f7d77d41000 rw-p 00000000 00:00 0
   388  	// 7f7d77d41000-7f7d77d64000 r-xp 00000000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   389  	// 7f7d77f3f000-7f7d77f42000 rw-p 00000000 00:00 0
   390  	// 7f7d77f61000-7f7d77f63000 rw-p 00000000 00:00 0
   391  	// 7f7d77f63000-7f7d77f64000 r--p 00022000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   392  	// 7f7d77f64000-7f7d77f65000 rw-p 00023000 fc:01 1180217                    /lib/x86_64-linux-gnu/ld-2.19.so
   393  	// 7f7d77f65000-7f7d77f66000 rw-p 00000000 00:00 0
   394  	// 7ffc342a2000-7ffc342c3000 rw-p 00000000 00:00 0                          [stack]
   395  	// 7ffc34343000-7ffc34345000 r-xp 00000000 00:00 0                          [vdso]
   396  	// ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
   397  
   398  	var line []byte
   399  	// next removes and returns the next field in the line.
   400  	// It also removes from line any spaces following the field.
   401  	next := func() []byte {
   402  		j := bytes.IndexByte(line, ' ')
   403  		if j < 0 {
   404  			f := line
   405  			line = nil
   406  			return f
   407  		}
   408  		f := line[:j]
   409  		line = line[j+1:]
   410  		for len(line) > 0 && line[0] == ' ' {
   411  			line = line[1:]
   412  		}
   413  		return f
   414  	}
   415  
   416  	for len(data) > 0 {
   417  		i := bytes.IndexByte(data, '\n')
   418  		if i < 0 {
   419  			line, data = data, nil
   420  		} else {
   421  			line, data = data[:i], data[i+1:]
   422  		}
   423  		addr := next()
   424  		i = bytes.IndexByte(addr, '-')
   425  		if i < 0 {
   426  			continue
   427  		}
   428  		lo, err := strconv.ParseUint(string(addr[:i]), 16, 64)
   429  		if err != nil {
   430  			continue
   431  		}
   432  		hi, err := strconv.ParseUint(string(addr[i+1:]), 16, 64)
   433  		if err != nil {
   434  			continue
   435  		}
   436  		perm := next()
   437  		if len(perm) < 4 || perm[2] != 'x' {
   438  			// Only interested in executable mappings.
   439  			continue
   440  		}
   441  		offset, err := strconv.ParseUint(string(next()), 16, 64)
   442  		if err != nil {
   443  			continue
   444  		}
   445  		next()          // dev
   446  		inode := next() // inode
   447  		if line == nil {
   448  			continue
   449  		}
   450  		file := string(line)
   451  		if len(inode) == 1 && inode[0] == '0' && file == "" {
   452  			// Huge-page text mappings list the initial fragment of
   453  			// mapped but unpopulated memory as being inode 0.
   454  			// Don't report that part.
   455  			// But [vdso] and [vsyscall] are inode 0, so let non-empty file names through.
   456  			continue
   457  		}
   458  
   459  		// TODO: pprof's remapMappingIDs makes two adjustments:
   460  		// 1. If there is an /anon_hugepage mapping first and it is
   461  		// consecutive to a next mapping, drop the /anon_hugepage.
   462  		// 2. If start-offset = 0x400000, change start to 0x400000 and offset to 0.
   463  		// There's no indication why either of these is needed.
   464  		// Let's try not doing these and see what breaks.
   465  		// If we do need them, they would go here, before we
   466  		// enter the mappings into b.mem in the first place.
   467  
   468  		buildID, _ := elfBuildID(file)
   469  		addMapping(lo, hi, offset, file, buildID)
   470  	}
   471  }
   472  
   473  func (b *profileBuilder) addMapping(lo, hi, offset uint64, file, buildID string) {
   474  	b.mem = append(b.mem, memMap{uintptr(lo), uintptr(hi)})
   475  	b.pbMapping(tagProfile_Mapping, uint64(len(b.mem)), lo, hi, offset, file, buildID)
   476  }