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  }