Source file src/internal/profile/legacy_profile.go

     1  // Copyright 2014 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  // This file implements parsers to convert legacy profiles into the
     6  // profile.proto format.
     7  
     8  package profile
     9  
    10  import (
    11  	"bufio"
    12  	"bytes"
    13  	"fmt"
    14  	"internal/lazyregexp"
    15  	"io"
    16  	"math"
    17  	"strconv"
    18  	"strings"
    19  )
    20  
    21  var (
    22  	countStartRE = lazyregexp.New(`\A(\w+) profile: total \d+\n\z`)
    23  	countRE      = lazyregexp.New(`\A(\d+) @(( 0x[0-9a-f]+)+)\n\z`)
    24  
    25  	heapHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] *@ *(heap[_a-z0-9]*)/?(\d*)`)
    26  	heapSampleRE = lazyregexp.New(`(-?\d+): *(-?\d+) *\[ *(\d+): *(\d+) *] @([ x0-9a-f]*)`)
    27  
    28  	contentionSampleRE = lazyregexp.New(`(\d+) *(\d+) @([ x0-9a-f]*)`)
    29  
    30  	hexNumberRE = lazyregexp.New(`0x[0-9a-f]+`)
    31  
    32  	growthHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ growthz`)
    33  
    34  	fragmentationHeaderRE = lazyregexp.New(`heap profile: *(\d+): *(\d+) *\[ *(\d+): *(\d+) *\] @ fragmentationz`)
    35  
    36  	threadzStartRE = lazyregexp.New(`--- threadz \d+ ---`)
    37  	threadStartRE  = lazyregexp.New(`--- Thread ([[:xdigit:]]+) \(name: (.*)/(\d+)\) stack: ---`)
    38  
    39  	procMapsRE = lazyregexp.New(`([[:xdigit:]]+)-([[:xdigit:]]+)\s+([-rwxp]+)\s+([[:xdigit:]]+)\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:digit:]]+)\s*(\S+)?`)
    40  
    41  	briefMapsRE = lazyregexp.New(`\s*([[:xdigit:]]+)-([[:xdigit:]]+):\s*(\S+)(\s.*@)?([[:xdigit:]]+)?`)
    42  
    43  	// LegacyHeapAllocated instructs the heapz parsers to use the
    44  	// allocated memory stats instead of the default in-use memory. Note
    45  	// that tcmalloc doesn't provide all allocated memory, only in-use
    46  	// stats.
    47  	LegacyHeapAllocated bool
    48  )
    49  
    50  func isSpaceOrComment(line string) bool {
    51  	trimmed := strings.TrimSpace(line)
    52  	return len(trimmed) == 0 || trimmed[0] == '#'
    53  }
    54  
    55  // parseGoCount parses a Go count profile (e.g., threadcreate or
    56  // goroutine) and returns a new Profile.
    57  func parseGoCount(b []byte) (*Profile, error) {
    58  	r := bytes.NewBuffer(b)
    59  
    60  	var line string
    61  	var err error
    62  	for {
    63  		// Skip past comments and empty lines seeking a real header.
    64  		line, err = r.ReadString('\n')
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  		if !isSpaceOrComment(line) {
    69  			break
    70  		}
    71  	}
    72  
    73  	m := countStartRE.FindStringSubmatch(line)
    74  	if m == nil {
    75  		return nil, errUnrecognized
    76  	}
    77  	profileType := m[1]
    78  	p := &Profile{
    79  		PeriodType: &ValueType{Type: profileType, Unit: "count"},
    80  		Period:     1,
    81  		SampleType: []*ValueType{{Type: profileType, Unit: "count"}},
    82  	}
    83  	locations := make(map[uint64]*Location)
    84  	for {
    85  		line, err = r.ReadString('\n')
    86  		if err != nil {
    87  			if err == io.EOF {
    88  				break
    89  			}
    90  			return nil, err
    91  		}
    92  		if isSpaceOrComment(line) {
    93  			continue
    94  		}
    95  		if strings.HasPrefix(line, "---") {
    96  			break
    97  		}
    98  		m := countRE.FindStringSubmatch(line)
    99  		if m == nil {
   100  			return nil, errMalformed
   101  		}
   102  		n, err := strconv.ParseInt(m[1], 0, 64)
   103  		if err != nil {
   104  			return nil, errMalformed
   105  		}
   106  		fields := strings.Fields(m[2])
   107  		locs := make([]*Location, 0, len(fields))
   108  		for _, stk := range fields {
   109  			addr, err := strconv.ParseUint(stk, 0, 64)
   110  			if err != nil {
   111  				return nil, errMalformed
   112  			}
   113  			// Adjust all frames by -1 to land on the call instruction.
   114  			addr--
   115  			loc := locations[addr]
   116  			if loc == nil {
   117  				loc = &Location{
   118  					Address: addr,
   119  				}
   120  				locations[addr] = loc
   121  				p.Location = append(p.Location, loc)
   122  			}
   123  			locs = append(locs, loc)
   124  		}
   125  		p.Sample = append(p.Sample, &Sample{
   126  			Location: locs,
   127  			Value:    []int64{n},
   128  		})
   129  	}
   130  
   131  	if err = parseAdditionalSections(strings.TrimSpace(line), r, p); err != nil {
   132  		return nil, err
   133  	}
   134  	return p, nil
   135  }
   136  
   137  // remapLocationIDs ensures there is a location for each address
   138  // referenced by a sample, and remaps the samples to point to the new
   139  // location ids.
   140  func (p *Profile) remapLocationIDs() {
   141  	seen := make(map[*Location]bool, len(p.Location))
   142  	var locs []*Location
   143  
   144  	for _, s := range p.Sample {
   145  		for _, l := range s.Location {
   146  			if seen[l] {
   147  				continue
   148  			}
   149  			l.ID = uint64(len(locs) + 1)
   150  			locs = append(locs, l)
   151  			seen[l] = true
   152  		}
   153  	}
   154  	p.Location = locs
   155  }
   156  
   157  func (p *Profile) remapFunctionIDs() {
   158  	seen := make(map[*Function]bool, len(p.Function))
   159  	var fns []*Function
   160  
   161  	for _, l := range p.Location {
   162  		for _, ln := range l.Line {
   163  			fn := ln.Function
   164  			if fn == nil || seen[fn] {
   165  				continue
   166  			}
   167  			fn.ID = uint64(len(fns) + 1)
   168  			fns = append(fns, fn)
   169  			seen[fn] = true
   170  		}
   171  	}
   172  	p.Function = fns
   173  }
   174  
   175  // remapMappingIDs matches location addresses with existing mappings
   176  // and updates them appropriately. This is O(N*M), if this ever shows
   177  // up as a bottleneck, evaluate sorting the mappings and doing a
   178  // binary search, which would make it O(N*log(M)).
   179  func (p *Profile) remapMappingIDs() {
   180  	if len(p.Mapping) == 0 {
   181  		return
   182  	}
   183  
   184  	// Some profile handlers will incorrectly set regions for the main
   185  	// executable if its section is remapped. Fix them through heuristics.
   186  
   187  	// Remove the initial mapping if named '/anon_hugepage' and has a
   188  	// consecutive adjacent mapping.
   189  	if m := p.Mapping[0]; strings.HasPrefix(m.File, "/anon_hugepage") {
   190  		if len(p.Mapping) > 1 && m.Limit == p.Mapping[1].Start {
   191  			p.Mapping = p.Mapping[1:]
   192  		}
   193  	}
   194  
   195  	for _, l := range p.Location {
   196  		if a := l.Address; a != 0 {
   197  			for _, m := range p.Mapping {
   198  				if m.Start <= a && a < m.Limit {
   199  					l.Mapping = m
   200  					break
   201  				}
   202  			}
   203  		}
   204  	}
   205  
   206  	// Reset all mapping IDs.
   207  	for i, m := range p.Mapping {
   208  		m.ID = uint64(i + 1)
   209  	}
   210  }
   211  
   212  var cpuInts = []func([]byte) (uint64, []byte){
   213  	get32l,
   214  	get32b,
   215  	get64l,
   216  	get64b,
   217  }
   218  
   219  func get32l(b []byte) (uint64, []byte) {
   220  	if len(b) < 4 {
   221  		return 0, nil
   222  	}
   223  	return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24, b[4:]
   224  }
   225  
   226  func get32b(b []byte) (uint64, []byte) {
   227  	if len(b) < 4 {
   228  		return 0, nil
   229  	}
   230  	return uint64(b[3]) | uint64(b[2])<<8 | uint64(b[1])<<16 | uint64(b[0])<<24, b[4:]
   231  }
   232  
   233  func get64l(b []byte) (uint64, []byte) {
   234  	if len(b) < 8 {
   235  		return 0, nil
   236  	}
   237  	return uint64(b[0]) | uint64(b[1])<<8 | uint64(b[2])<<16 | uint64(b[3])<<24 | uint64(b[4])<<32 | uint64(b[5])<<40 | uint64(b[6])<<48 | uint64(b[7])<<56, b[8:]
   238  }
   239  
   240  func get64b(b []byte) (uint64, []byte) {
   241  	if len(b) < 8 {
   242  		return 0, nil
   243  	}
   244  	return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 | uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56, b[8:]
   245  }
   246  
   247  // ParseTracebacks parses a set of tracebacks and returns a newly
   248  // populated profile. It will accept any text file and generate a
   249  // Profile out of it with any hex addresses it can identify, including
   250  // a process map if it can recognize one. Each sample will include a
   251  // tag "source" with the addresses recognized in string format.
   252  func ParseTracebacks(b []byte) (*Profile, error) {
   253  	r := bytes.NewBuffer(b)
   254  
   255  	p := &Profile{
   256  		PeriodType: &ValueType{Type: "trace", Unit: "count"},
   257  		Period:     1,
   258  		SampleType: []*ValueType{
   259  			{Type: "trace", Unit: "count"},
   260  		},
   261  	}
   262  
   263  	var sources []string
   264  	var sloc []*Location
   265  
   266  	locs := make(map[uint64]*Location)
   267  	for {
   268  		l, err := r.ReadString('\n')
   269  		if err != nil {
   270  			if err != io.EOF {
   271  				return nil, err
   272  			}
   273  			if l == "" {
   274  				break
   275  			}
   276  		}
   277  		if sectionTrigger(l) == memoryMapSection {
   278  			break
   279  		}
   280  		if s, addrs := extractHexAddresses(l); len(s) > 0 {
   281  			for _, addr := range addrs {
   282  				// Addresses from stack traces point to the next instruction after
   283  				// each call. Adjust by -1 to land somewhere on the actual call.
   284  				addr--
   285  				loc := locs[addr]
   286  				if locs[addr] == nil {
   287  					loc = &Location{
   288  						Address: addr,
   289  					}
   290  					p.Location = append(p.Location, loc)
   291  					locs[addr] = loc
   292  				}
   293  				sloc = append(sloc, loc)
   294  			}
   295  
   296  			sources = append(sources, s...)
   297  		} else {
   298  			if len(sources) > 0 || len(sloc) > 0 {
   299  				addTracebackSample(sloc, sources, p)
   300  				sloc, sources = nil, nil
   301  			}
   302  		}
   303  	}
   304  
   305  	// Add final sample to save any leftover data.
   306  	if len(sources) > 0 || len(sloc) > 0 {
   307  		addTracebackSample(sloc, sources, p)
   308  	}
   309  
   310  	if err := p.ParseMemoryMap(r); err != nil {
   311  		return nil, err
   312  	}
   313  	return p, nil
   314  }
   315  
   316  func addTracebackSample(l []*Location, s []string, p *Profile) {
   317  	p.Sample = append(p.Sample,
   318  		&Sample{
   319  			Value:    []int64{1},
   320  			Location: l,
   321  			Label:    map[string][]string{"source": s},
   322  		})
   323  }
   324  
   325  // parseCPU parses a profilez legacy profile and returns a newly
   326  // populated Profile.
   327  //
   328  // The general format for profilez samples is a sequence of words in
   329  // binary format. The first words are a header with the following data:
   330  //
   331  //	1st word -- 0
   332  //	2nd word -- 3
   333  //	3rd word -- 0 if a c++ application, 1 if a java application.
   334  //	4th word -- Sampling period (in microseconds).
   335  //	5th word -- Padding.
   336  func parseCPU(b []byte) (*Profile, error) {
   337  	var parse func([]byte) (uint64, []byte)
   338  	var n1, n2, n3, n4, n5 uint64
   339  	for _, parse = range cpuInts {
   340  		var tmp []byte
   341  		n1, tmp = parse(b)
   342  		n2, tmp = parse(tmp)
   343  		n3, tmp = parse(tmp)
   344  		n4, tmp = parse(tmp)
   345  		n5, tmp = parse(tmp)
   346  
   347  		if tmp != nil && n1 == 0 && n2 == 3 && n3 == 0 && n4 > 0 && n5 == 0 {
   348  			b = tmp
   349  			return cpuProfile(b, int64(n4), parse)
   350  		}
   351  	}
   352  	return nil, errUnrecognized
   353  }
   354  
   355  // cpuProfile returns a new Profile from C++ profilez data.
   356  // b is the profile bytes after the header, period is the profiling
   357  // period, and parse is a function to parse 8-byte chunks from the
   358  // profile in its native endianness.
   359  func cpuProfile(b []byte, period int64, parse func(b []byte) (uint64, []byte)) (*Profile, error) {
   360  	p := &Profile{
   361  		Period:     period * 1000,
   362  		PeriodType: &ValueType{Type: "cpu", Unit: "nanoseconds"},
   363  		SampleType: []*ValueType{
   364  			{Type: "samples", Unit: "count"},
   365  			{Type: "cpu", Unit: "nanoseconds"},
   366  		},
   367  	}
   368  	var err error
   369  	if b, _, err = parseCPUSamples(b, parse, true, p); err != nil {
   370  		return nil, err
   371  	}
   372  
   373  	// If all samples have the same second-to-the-bottom frame, it
   374  	// strongly suggests that it is an uninteresting artifact of
   375  	// measurement -- a stack frame pushed by the signal handler. The
   376  	// bottom frame is always correct as it is picked up from the signal
   377  	// structure, not the stack. Check if this is the case and if so,
   378  	// remove.
   379  	if len(p.Sample) > 1 && len(p.Sample[0].Location) > 1 {
   380  		allSame := true
   381  		id1 := p.Sample[0].Location[1].Address
   382  		for _, s := range p.Sample {
   383  			if len(s.Location) < 2 || id1 != s.Location[1].Address {
   384  				allSame = false
   385  				break
   386  			}
   387  		}
   388  		if allSame {
   389  			for _, s := range p.Sample {
   390  				s.Location = append(s.Location[:1], s.Location[2:]...)
   391  			}
   392  		}
   393  	}
   394  
   395  	if err := p.ParseMemoryMap(bytes.NewBuffer(b)); err != nil {
   396  		return nil, err
   397  	}
   398  	return p, nil
   399  }
   400  
   401  // parseCPUSamples parses a collection of profilez samples from a
   402  // profile.
   403  //
   404  // profilez samples are a repeated sequence of stack frames of the
   405  // form:
   406  //
   407  //	1st word -- The number of times this stack was encountered.
   408  //	2nd word -- The size of the stack (StackSize).
   409  //	3rd word -- The first address on the stack.
   410  //	...
   411  //	StackSize + 2 -- The last address on the stack
   412  //
   413  // The last stack trace is of the form:
   414  //
   415  //	1st word -- 0
   416  //	2nd word -- 1
   417  //	3rd word -- 0
   418  //
   419  // Addresses from stack traces may point to the next instruction after
   420  // each call. Optionally adjust by -1 to land somewhere on the actual
   421  // call (except for the leaf, which is not a call).
   422  func parseCPUSamples(b []byte, parse func(b []byte) (uint64, []byte), adjust bool, p *Profile) ([]byte, map[uint64]*Location, error) {
   423  	locs := make(map[uint64]*Location)
   424  	for len(b) > 0 {
   425  		var count, nstk uint64
   426  		count, b = parse(b)
   427  		nstk, b = parse(b)
   428  		if b == nil || nstk > uint64(len(b)/4) {
   429  			return nil, nil, errUnrecognized
   430  		}
   431  		var sloc []*Location
   432  		addrs := make([]uint64, nstk)
   433  		for i := 0; i < int(nstk); i++ {
   434  			addrs[i], b = parse(b)
   435  		}
   436  
   437  		if count == 0 && nstk == 1 && addrs[0] == 0 {
   438  			// End of data marker
   439  			break
   440  		}
   441  		for i, addr := range addrs {
   442  			if adjust && i > 0 {
   443  				addr--
   444  			}
   445  			loc := locs[addr]
   446  			if loc == nil {
   447  				loc = &Location{
   448  					Address: addr,
   449  				}
   450  				locs[addr] = loc
   451  				p.Location = append(p.Location, loc)
   452  			}
   453  			sloc = append(sloc, loc)
   454  		}
   455  		p.Sample = append(p.Sample,
   456  			&Sample{
   457  				Value:    []int64{int64(count), int64(count) * p.Period},
   458  				Location: sloc,
   459  			})
   460  	}
   461  	// Reached the end without finding the EOD marker.
   462  	return b, locs, nil
   463  }
   464  
   465  // parseHeap parses a heapz legacy or a growthz profile and
   466  // returns a newly populated Profile.
   467  func parseHeap(b []byte) (p *Profile, err error) {
   468  	r := bytes.NewBuffer(b)
   469  	l, err := r.ReadString('\n')
   470  	if err != nil {
   471  		return nil, errUnrecognized
   472  	}
   473  
   474  	sampling := ""
   475  
   476  	if header := heapHeaderRE.FindStringSubmatch(l); header != nil {
   477  		p = &Profile{
   478  			SampleType: []*ValueType{
   479  				{Type: "objects", Unit: "count"},
   480  				{Type: "space", Unit: "bytes"},
   481  			},
   482  			PeriodType: &ValueType{Type: "objects", Unit: "bytes"},
   483  		}
   484  
   485  		var period int64
   486  		if len(header[6]) > 0 {
   487  			if period, err = strconv.ParseInt(header[6], 10, 64); err != nil {
   488  				return nil, errUnrecognized
   489  			}
   490  		}
   491  
   492  		switch header[5] {
   493  		case "heapz_v2", "heap_v2":
   494  			sampling, p.Period = "v2", period
   495  		case "heapprofile":
   496  			sampling, p.Period = "", 1
   497  		case "heap":
   498  			sampling, p.Period = "v2", period/2
   499  		default:
   500  			return nil, errUnrecognized
   501  		}
   502  	} else if header = growthHeaderRE.FindStringSubmatch(l); header != nil {
   503  		p = &Profile{
   504  			SampleType: []*ValueType{
   505  				{Type: "objects", Unit: "count"},
   506  				{Type: "space", Unit: "bytes"},
   507  			},
   508  			PeriodType: &ValueType{Type: "heapgrowth", Unit: "count"},
   509  			Period:     1,
   510  		}
   511  	} else if header = fragmentationHeaderRE.FindStringSubmatch(l); header != nil {
   512  		p = &Profile{
   513  			SampleType: []*ValueType{
   514  				{Type: "objects", Unit: "count"},
   515  				{Type: "space", Unit: "bytes"},
   516  			},
   517  			PeriodType: &ValueType{Type: "allocations", Unit: "count"},
   518  			Period:     1,
   519  		}
   520  	} else {
   521  		return nil, errUnrecognized
   522  	}
   523  
   524  	if LegacyHeapAllocated {
   525  		for _, st := range p.SampleType {
   526  			st.Type = "alloc_" + st.Type
   527  		}
   528  	} else {
   529  		for _, st := range p.SampleType {
   530  			st.Type = "inuse_" + st.Type
   531  		}
   532  	}
   533  
   534  	locs := make(map[uint64]*Location)
   535  	for {
   536  		l, err = r.ReadString('\n')
   537  		if err != nil {
   538  			if err != io.EOF {
   539  				return nil, err
   540  			}
   541  
   542  			if l == "" {
   543  				break
   544  			}
   545  		}
   546  
   547  		if isSpaceOrComment(l) {
   548  			continue
   549  		}
   550  		l = strings.TrimSpace(l)
   551  
   552  		if sectionTrigger(l) != unrecognizedSection {
   553  			break
   554  		}
   555  
   556  		value, blocksize, addrs, err := parseHeapSample(l, p.Period, sampling)
   557  		if err != nil {
   558  			return nil, err
   559  		}
   560  		var sloc []*Location
   561  		for _, addr := range addrs {
   562  			// Addresses from stack traces point to the next instruction after
   563  			// each call. Adjust by -1 to land somewhere on the actual call.
   564  			addr--
   565  			loc := locs[addr]
   566  			if locs[addr] == nil {
   567  				loc = &Location{
   568  					Address: addr,
   569  				}
   570  				p.Location = append(p.Location, loc)
   571  				locs[addr] = loc
   572  			}
   573  			sloc = append(sloc, loc)
   574  		}
   575  
   576  		p.Sample = append(p.Sample, &Sample{
   577  			Value:    value,
   578  			Location: sloc,
   579  			NumLabel: map[string][]int64{"bytes": {blocksize}},
   580  		})
   581  	}
   582  
   583  	if err = parseAdditionalSections(l, r, p); err != nil {
   584  		return nil, err
   585  	}
   586  	return p, nil
   587  }
   588  
   589  // parseHeapSample parses a single row from a heap profile into a new Sample.
   590  func parseHeapSample(line string, rate int64, sampling string) (value []int64, blocksize int64, addrs []uint64, err error) {
   591  	sampleData := heapSampleRE.FindStringSubmatch(line)
   592  	if len(sampleData) != 6 {
   593  		return value, blocksize, addrs, fmt.Errorf("unexpected number of sample values: got %d, want 6", len(sampleData))
   594  	}
   595  
   596  	// Use first two values by default; tcmalloc sampling generates the
   597  	// same value for both, only the older heap-profile collect separate
   598  	// stats for in-use and allocated objects.
   599  	valueIndex := 1
   600  	if LegacyHeapAllocated {
   601  		valueIndex = 3
   602  	}
   603  
   604  	var v1, v2 int64
   605  	if v1, err = strconv.ParseInt(sampleData[valueIndex], 10, 64); err != nil {
   606  		return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
   607  	}
   608  	if v2, err = strconv.ParseInt(sampleData[valueIndex+1], 10, 64); err != nil {
   609  		return value, blocksize, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
   610  	}
   611  
   612  	if v1 == 0 {
   613  		if v2 != 0 {
   614  			return value, blocksize, addrs, fmt.Errorf("allocation count was 0 but allocation bytes was %d", v2)
   615  		}
   616  	} else {
   617  		blocksize = v2 / v1
   618  		if sampling == "v2" {
   619  			v1, v2 = scaleHeapSample(v1, v2, rate)
   620  		}
   621  	}
   622  
   623  	value = []int64{v1, v2}
   624  	addrs = parseHexAddresses(sampleData[5])
   625  
   626  	return value, blocksize, addrs, nil
   627  }
   628  
   629  // extractHexAddresses extracts hex numbers from a string and returns
   630  // them, together with their numeric value, in a slice.
   631  func extractHexAddresses(s string) ([]string, []uint64) {
   632  	hexStrings := hexNumberRE.FindAllString(s, -1)
   633  	var ids []uint64
   634  	for _, s := range hexStrings {
   635  		if id, err := strconv.ParseUint(s, 0, 64); err == nil {
   636  			ids = append(ids, id)
   637  		} else {
   638  			// Do not expect any parsing failures due to the regexp matching.
   639  			panic("failed to parse hex value:" + s)
   640  		}
   641  	}
   642  	return hexStrings, ids
   643  }
   644  
   645  // parseHexAddresses parses hex numbers from a string and returns them
   646  // in a slice.
   647  func parseHexAddresses(s string) []uint64 {
   648  	_, ids := extractHexAddresses(s)
   649  	return ids
   650  }
   651  
   652  // scaleHeapSample adjusts the data from a heapz Sample to
   653  // account for its probability of appearing in the collected
   654  // data. heapz profiles are a sampling of the memory allocations
   655  // requests in a program. We estimate the unsampled value by dividing
   656  // each collected sample by its probability of appearing in the
   657  // profile. heapz v2 profiles rely on a poisson process to determine
   658  // which samples to collect, based on the desired average collection
   659  // rate R. The probability of a sample of size S to appear in that
   660  // profile is 1-exp(-S/R).
   661  func scaleHeapSample(count, size, rate int64) (int64, int64) {
   662  	if count == 0 || size == 0 {
   663  		return 0, 0
   664  	}
   665  
   666  	if rate <= 1 {
   667  		// if rate==1 all samples were collected so no adjustment is needed.
   668  		// if rate<1 treat as unknown and skip scaling.
   669  		return count, size
   670  	}
   671  
   672  	avgSize := float64(size) / float64(count)
   673  	scale := 1 / (1 - math.Exp(-avgSize/float64(rate)))
   674  
   675  	return int64(float64(count) * scale), int64(float64(size) * scale)
   676  }
   677  
   678  // parseContention parses a mutex or contention profile. There are 2 cases:
   679  // "--- contentionz " for legacy C++ profiles (and backwards compatibility)
   680  // "--- mutex:" or "--- contention:" for profiles generated by the Go runtime.
   681  // This code converts the text output from runtime into a *Profile. (In the future
   682  // the runtime might write a serialized Profile directly making this unnecessary.)
   683  func parseContention(b []byte) (*Profile, error) {
   684  	r := bytes.NewBuffer(b)
   685  	var l string
   686  	var err error
   687  	for {
   688  		// Skip past comments and empty lines seeking a real header.
   689  		l, err = r.ReadString('\n')
   690  		if err != nil {
   691  			return nil, err
   692  		}
   693  		if !isSpaceOrComment(l) {
   694  			break
   695  		}
   696  	}
   697  
   698  	if strings.HasPrefix(l, "--- contentionz ") {
   699  		return parseCppContention(r)
   700  	} else if strings.HasPrefix(l, "--- mutex:") {
   701  		return parseCppContention(r)
   702  	} else if strings.HasPrefix(l, "--- contention:") {
   703  		return parseCppContention(r)
   704  	}
   705  	return nil, errUnrecognized
   706  }
   707  
   708  // parseCppContention parses the output from synchronization_profiling.cc
   709  // for backward compatibility, and the compatible (non-debug) block profile
   710  // output from the Go runtime.
   711  func parseCppContention(r *bytes.Buffer) (*Profile, error) {
   712  	p := &Profile{
   713  		PeriodType: &ValueType{Type: "contentions", Unit: "count"},
   714  		Period:     1,
   715  		SampleType: []*ValueType{
   716  			{Type: "contentions", Unit: "count"},
   717  			{Type: "delay", Unit: "nanoseconds"},
   718  		},
   719  	}
   720  
   721  	var cpuHz int64
   722  	var l string
   723  	var err error
   724  	// Parse text of the form "attribute = value" before the samples.
   725  	const delimiter = '='
   726  	for {
   727  		l, err = r.ReadString('\n')
   728  		if err != nil {
   729  			if err != io.EOF {
   730  				return nil, err
   731  			}
   732  
   733  			if l == "" {
   734  				break
   735  			}
   736  		}
   737  		if isSpaceOrComment(l) {
   738  			continue
   739  		}
   740  
   741  		if l = strings.TrimSpace(l); l == "" {
   742  			continue
   743  		}
   744  
   745  		if strings.HasPrefix(l, "---") {
   746  			break
   747  		}
   748  
   749  		index := strings.IndexByte(l, delimiter)
   750  		if index < 0 {
   751  			break
   752  		}
   753  		key := l[:index]
   754  		val := l[index+1:]
   755  
   756  		key, val = strings.TrimSpace(key), strings.TrimSpace(val)
   757  		var err error
   758  		switch key {
   759  		case "cycles/second":
   760  			if cpuHz, err = strconv.ParseInt(val, 0, 64); err != nil {
   761  				return nil, errUnrecognized
   762  			}
   763  		case "sampling period":
   764  			if p.Period, err = strconv.ParseInt(val, 0, 64); err != nil {
   765  				return nil, errUnrecognized
   766  			}
   767  		case "ms since reset":
   768  			ms, err := strconv.ParseInt(val, 0, 64)
   769  			if err != nil {
   770  				return nil, errUnrecognized
   771  			}
   772  			p.DurationNanos = ms * 1000 * 1000
   773  		case "format":
   774  			// CPP contentionz profiles don't have format.
   775  			return nil, errUnrecognized
   776  		case "resolution":
   777  			// CPP contentionz profiles don't have resolution.
   778  			return nil, errUnrecognized
   779  		case "discarded samples":
   780  		default:
   781  			return nil, errUnrecognized
   782  		}
   783  	}
   784  
   785  	locs := make(map[uint64]*Location)
   786  	for {
   787  		if !isSpaceOrComment(l) {
   788  			if l = strings.TrimSpace(l); strings.HasPrefix(l, "---") {
   789  				break
   790  			}
   791  			value, addrs, err := parseContentionSample(l, p.Period, cpuHz)
   792  			if err != nil {
   793  				return nil, err
   794  			}
   795  			var sloc []*Location
   796  			for _, addr := range addrs {
   797  				// Addresses from stack traces point to the next instruction after
   798  				// each call. Adjust by -1 to land somewhere on the actual call.
   799  				addr--
   800  				loc := locs[addr]
   801  				if locs[addr] == nil {
   802  					loc = &Location{
   803  						Address: addr,
   804  					}
   805  					p.Location = append(p.Location, loc)
   806  					locs[addr] = loc
   807  				}
   808  				sloc = append(sloc, loc)
   809  			}
   810  			p.Sample = append(p.Sample, &Sample{
   811  				Value:    value,
   812  				Location: sloc,
   813  			})
   814  		}
   815  
   816  		if l, err = r.ReadString('\n'); err != nil {
   817  			if err != io.EOF {
   818  				return nil, err
   819  			}
   820  			if l == "" {
   821  				break
   822  			}
   823  		}
   824  	}
   825  
   826  	if err = parseAdditionalSections(l, r, p); err != nil {
   827  		return nil, err
   828  	}
   829  
   830  	return p, nil
   831  }
   832  
   833  // parseContentionSample parses a single row from a contention profile
   834  // into a new Sample.
   835  func parseContentionSample(line string, period, cpuHz int64) (value []int64, addrs []uint64, err error) {
   836  	sampleData := contentionSampleRE.FindStringSubmatch(line)
   837  	if sampleData == nil {
   838  		return value, addrs, errUnrecognized
   839  	}
   840  
   841  	v1, err := strconv.ParseInt(sampleData[1], 10, 64)
   842  	if err != nil {
   843  		return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
   844  	}
   845  	v2, err := strconv.ParseInt(sampleData[2], 10, 64)
   846  	if err != nil {
   847  		return value, addrs, fmt.Errorf("malformed sample: %s: %v", line, err)
   848  	}
   849  
   850  	// Unsample values if period and cpuHz are available.
   851  	// - Delays are scaled to cycles and then to nanoseconds.
   852  	// - Contentions are scaled to cycles.
   853  	if period > 0 {
   854  		if cpuHz > 0 {
   855  			cpuGHz := float64(cpuHz) / 1e9
   856  			v1 = int64(float64(v1) * float64(period) / cpuGHz)
   857  		}
   858  		v2 = v2 * period
   859  	}
   860  
   861  	value = []int64{v2, v1}
   862  	addrs = parseHexAddresses(sampleData[3])
   863  
   864  	return value, addrs, nil
   865  }
   866  
   867  // parseThread parses a Threadz profile and returns a new Profile.
   868  func parseThread(b []byte) (*Profile, error) {
   869  	r := bytes.NewBuffer(b)
   870  
   871  	var line string
   872  	var err error
   873  	for {
   874  		// Skip past comments and empty lines seeking a real header.
   875  		line, err = r.ReadString('\n')
   876  		if err != nil {
   877  			return nil, err
   878  		}
   879  		if !isSpaceOrComment(line) {
   880  			break
   881  		}
   882  	}
   883  
   884  	if m := threadzStartRE.FindStringSubmatch(line); m != nil {
   885  		// Advance over initial comments until first stack trace.
   886  		for {
   887  			line, err = r.ReadString('\n')
   888  			if err != nil {
   889  				if err != io.EOF {
   890  					return nil, err
   891  				}
   892  
   893  				if line == "" {
   894  					break
   895  				}
   896  			}
   897  			if sectionTrigger(line) != unrecognizedSection || line[0] == '-' {
   898  				break
   899  			}
   900  		}
   901  	} else if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {
   902  		return nil, errUnrecognized
   903  	}
   904  
   905  	p := &Profile{
   906  		SampleType: []*ValueType{{Type: "thread", Unit: "count"}},
   907  		PeriodType: &ValueType{Type: "thread", Unit: "count"},
   908  		Period:     1,
   909  	}
   910  
   911  	locs := make(map[uint64]*Location)
   912  	// Recognize each thread and populate profile samples.
   913  	for sectionTrigger(line) == unrecognizedSection {
   914  		if strings.HasPrefix(line, "---- no stack trace for") {
   915  			line = ""
   916  			break
   917  		}
   918  		if t := threadStartRE.FindStringSubmatch(line); len(t) != 4 {
   919  			return nil, errUnrecognized
   920  		}
   921  
   922  		var addrs []uint64
   923  		line, addrs, err = parseThreadSample(r)
   924  		if err != nil {
   925  			return nil, errUnrecognized
   926  		}
   927  		if len(addrs) == 0 {
   928  			// We got a --same as previous threads--. Bump counters.
   929  			if len(p.Sample) > 0 {
   930  				s := p.Sample[len(p.Sample)-1]
   931  				s.Value[0]++
   932  			}
   933  			continue
   934  		}
   935  
   936  		var sloc []*Location
   937  		for _, addr := range addrs {
   938  			// Addresses from stack traces point to the next instruction after
   939  			// each call. Adjust by -1 to land somewhere on the actual call.
   940  			addr--
   941  			loc := locs[addr]
   942  			if locs[addr] == nil {
   943  				loc = &Location{
   944  					Address: addr,
   945  				}
   946  				p.Location = append(p.Location, loc)
   947  				locs[addr] = loc
   948  			}
   949  			sloc = append(sloc, loc)
   950  		}
   951  
   952  		p.Sample = append(p.Sample, &Sample{
   953  			Value:    []int64{1},
   954  			Location: sloc,
   955  		})
   956  	}
   957  
   958  	if err = parseAdditionalSections(line, r, p); err != nil {
   959  		return nil, err
   960  	}
   961  
   962  	return p, nil
   963  }
   964  
   965  // parseThreadSample parses a symbolized or unsymbolized stack trace.
   966  // Returns the first line after the traceback, the sample (or nil if
   967  // it hits a 'same-as-previous' marker) and an error.
   968  func parseThreadSample(b *bytes.Buffer) (nextl string, addrs []uint64, err error) {
   969  	var l string
   970  	sameAsPrevious := false
   971  	for {
   972  		if l, err = b.ReadString('\n'); err != nil {
   973  			if err != io.EOF {
   974  				return "", nil, err
   975  			}
   976  			if l == "" {
   977  				break
   978  			}
   979  		}
   980  		if l = strings.TrimSpace(l); l == "" {
   981  			continue
   982  		}
   983  
   984  		if strings.HasPrefix(l, "---") {
   985  			break
   986  		}
   987  		if strings.Contains(l, "same as previous thread") {
   988  			sameAsPrevious = true
   989  			continue
   990  		}
   991  
   992  		addrs = append(addrs, parseHexAddresses(l)...)
   993  	}
   994  
   995  	if sameAsPrevious {
   996  		return l, nil, nil
   997  	}
   998  	return l, addrs, nil
   999  }
  1000  
  1001  // parseAdditionalSections parses any additional sections in the
  1002  // profile, ignoring any unrecognized sections.
  1003  func parseAdditionalSections(l string, b *bytes.Buffer, p *Profile) (err error) {
  1004  	for {
  1005  		if sectionTrigger(l) == memoryMapSection {
  1006  			break
  1007  		}
  1008  		// Ignore any unrecognized sections.
  1009  		if l, err := b.ReadString('\n'); err != nil {
  1010  			if err != io.EOF {
  1011  				return err
  1012  			}
  1013  			if l == "" {
  1014  				break
  1015  			}
  1016  		}
  1017  	}
  1018  	return p.ParseMemoryMap(b)
  1019  }
  1020  
  1021  // ParseMemoryMap parses a memory map in the format of
  1022  // /proc/self/maps, and overrides the mappings in the current profile.
  1023  // It renumbers the samples and locations in the profile correspondingly.
  1024  func (p *Profile) ParseMemoryMap(rd io.Reader) error {
  1025  	b := bufio.NewReader(rd)
  1026  
  1027  	var attrs []string
  1028  	var r *strings.Replacer
  1029  	const delimiter = '='
  1030  	for {
  1031  		l, err := b.ReadString('\n')
  1032  		if err != nil {
  1033  			if err != io.EOF {
  1034  				return err
  1035  			}
  1036  			if l == "" {
  1037  				break
  1038  			}
  1039  		}
  1040  		if l = strings.TrimSpace(l); l == "" {
  1041  			continue
  1042  		}
  1043  
  1044  		if r != nil {
  1045  			l = r.Replace(l)
  1046  		}
  1047  		m, err := parseMappingEntry(l)
  1048  		if err != nil {
  1049  			if err == errUnrecognized {
  1050  				// Recognize assignments of the form: attr=value, and replace
  1051  				// $attr with value on subsequent mappings.
  1052  				idx := strings.IndexByte(l, delimiter)
  1053  				if idx >= 0 {
  1054  					attr := l[:idx]
  1055  					value := l[idx+1:]
  1056  					attrs = append(attrs, "$"+strings.TrimSpace(attr), strings.TrimSpace(value))
  1057  					r = strings.NewReplacer(attrs...)
  1058  				}
  1059  				// Ignore any unrecognized entries
  1060  				continue
  1061  			}
  1062  			return err
  1063  		}
  1064  		if m == nil || (m.File == "" && len(p.Mapping) != 0) {
  1065  			// In some cases the first entry may include the address range
  1066  			// but not the name of the file. It should be followed by
  1067  			// another entry with the name.
  1068  			continue
  1069  		}
  1070  		if len(p.Mapping) == 1 && p.Mapping[0].File == "" {
  1071  			// Update the name if this is the entry following that empty one.
  1072  			p.Mapping[0].File = m.File
  1073  			continue
  1074  		}
  1075  		p.Mapping = append(p.Mapping, m)
  1076  	}
  1077  	p.remapLocationIDs()
  1078  	p.remapFunctionIDs()
  1079  	p.remapMappingIDs()
  1080  	return nil
  1081  }
  1082  
  1083  func parseMappingEntry(l string) (*Mapping, error) {
  1084  	mapping := &Mapping{}
  1085  	var err error
  1086  	if me := procMapsRE.FindStringSubmatch(l); len(me) == 9 {
  1087  		if !strings.Contains(me[3], "x") {
  1088  			// Skip non-executable entries.
  1089  			return nil, nil
  1090  		}
  1091  		if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil {
  1092  			return nil, errUnrecognized
  1093  		}
  1094  		if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil {
  1095  			return nil, errUnrecognized
  1096  		}
  1097  		if me[4] != "" {
  1098  			if mapping.Offset, err = strconv.ParseUint(me[4], 16, 64); err != nil {
  1099  				return nil, errUnrecognized
  1100  			}
  1101  		}
  1102  		mapping.File = me[8]
  1103  		return mapping, nil
  1104  	}
  1105  
  1106  	if me := briefMapsRE.FindStringSubmatch(l); len(me) == 6 {
  1107  		if mapping.Start, err = strconv.ParseUint(me[1], 16, 64); err != nil {
  1108  			return nil, errUnrecognized
  1109  		}
  1110  		if mapping.Limit, err = strconv.ParseUint(me[2], 16, 64); err != nil {
  1111  			return nil, errUnrecognized
  1112  		}
  1113  		mapping.File = me[3]
  1114  		if me[5] != "" {
  1115  			if mapping.Offset, err = strconv.ParseUint(me[5], 16, 64); err != nil {
  1116  				return nil, errUnrecognized
  1117  			}
  1118  		}
  1119  		return mapping, nil
  1120  	}
  1121  
  1122  	return nil, errUnrecognized
  1123  }
  1124  
  1125  type sectionType int
  1126  
  1127  const (
  1128  	unrecognizedSection sectionType = iota
  1129  	memoryMapSection
  1130  )
  1131  
  1132  var memoryMapTriggers = []string{
  1133  	"--- Memory map: ---",
  1134  	"MAPPED_LIBRARIES:",
  1135  }
  1136  
  1137  func sectionTrigger(line string) sectionType {
  1138  	for _, trigger := range memoryMapTriggers {
  1139  		if strings.Contains(line, trigger) {
  1140  			return memoryMapSection
  1141  		}
  1142  	}
  1143  	return unrecognizedSection
  1144  }
  1145  
  1146  func (p *Profile) addLegacyFrameInfo() {
  1147  	switch {
  1148  	case isProfileType(p, heapzSampleTypes) ||
  1149  		isProfileType(p, heapzInUseSampleTypes) ||
  1150  		isProfileType(p, heapzAllocSampleTypes):
  1151  		p.DropFrames, p.KeepFrames = allocRxStr, allocSkipRxStr
  1152  	case isProfileType(p, contentionzSampleTypes):
  1153  		p.DropFrames, p.KeepFrames = lockRxStr, ""
  1154  	default:
  1155  		p.DropFrames, p.KeepFrames = cpuProfilerRxStr, ""
  1156  	}
  1157  }
  1158  
  1159  var heapzSampleTypes = []string{"allocations", "size"} // early Go pprof profiles
  1160  var heapzInUseSampleTypes = []string{"inuse_objects", "inuse_space"}
  1161  var heapzAllocSampleTypes = []string{"alloc_objects", "alloc_space"}
  1162  var contentionzSampleTypes = []string{"contentions", "delay"}
  1163  
  1164  func isProfileType(p *Profile, t []string) bool {
  1165  	st := p.SampleType
  1166  	if len(st) != len(t) {
  1167  		return false
  1168  	}
  1169  
  1170  	for i := range st {
  1171  		if st[i].Type != t[i] {
  1172  			return false
  1173  		}
  1174  	}
  1175  	return true
  1176  }
  1177  
  1178  var allocRxStr = strings.Join([]string{
  1179  	// POSIX entry points.
  1180  	`calloc`,
  1181  	`cfree`,
  1182  	`malloc`,
  1183  	`free`,
  1184  	`memalign`,
  1185  	`do_memalign`,
  1186  	`(__)?posix_memalign`,
  1187  	`pvalloc`,
  1188  	`valloc`,
  1189  	`realloc`,
  1190  
  1191  	// TC malloc.
  1192  	`tcmalloc::.*`,
  1193  	`tc_calloc`,
  1194  	`tc_cfree`,
  1195  	`tc_malloc`,
  1196  	`tc_free`,
  1197  	`tc_memalign`,
  1198  	`tc_posix_memalign`,
  1199  	`tc_pvalloc`,
  1200  	`tc_valloc`,
  1201  	`tc_realloc`,
  1202  	`tc_new`,
  1203  	`tc_delete`,
  1204  	`tc_newarray`,
  1205  	`tc_deletearray`,
  1206  	`tc_new_nothrow`,
  1207  	`tc_newarray_nothrow`,
  1208  
  1209  	// Memory-allocation routines on OS X.
  1210  	`malloc_zone_malloc`,
  1211  	`malloc_zone_calloc`,
  1212  	`malloc_zone_valloc`,
  1213  	`malloc_zone_realloc`,
  1214  	`malloc_zone_memalign`,
  1215  	`malloc_zone_free`,
  1216  
  1217  	// Go runtime
  1218  	`runtime\..*`,
  1219  
  1220  	// Other misc. memory allocation routines
  1221  	`BaseArena::.*`,
  1222  	`(::)?do_malloc_no_errno`,
  1223  	`(::)?do_malloc_pages`,
  1224  	`(::)?do_malloc`,
  1225  	`DoSampledAllocation`,
  1226  	`MallocedMemBlock::MallocedMemBlock`,
  1227  	`_M_allocate`,
  1228  	`__builtin_(vec_)?delete`,
  1229  	`__builtin_(vec_)?new`,
  1230  	`__gnu_cxx::new_allocator::allocate`,
  1231  	`__libc_malloc`,
  1232  	`__malloc_alloc_template::allocate`,
  1233  	`allocate`,
  1234  	`cpp_alloc`,
  1235  	`operator new(\[\])?`,
  1236  	`simple_alloc::allocate`,
  1237  }, `|`)
  1238  
  1239  var allocSkipRxStr = strings.Join([]string{
  1240  	// Preserve Go runtime frames that appear in the middle/bottom of
  1241  	// the stack.
  1242  	`runtime\.panic`,
  1243  	`runtime\.reflectcall`,
  1244  	`runtime\.call[0-9]*`,
  1245  }, `|`)
  1246  
  1247  var cpuProfilerRxStr = strings.Join([]string{
  1248  	`ProfileData::Add`,
  1249  	`ProfileData::prof_handler`,
  1250  	`CpuProfiler::prof_handler`,
  1251  	`__pthread_sighandler`,
  1252  	`__restore`,
  1253  }, `|`)
  1254  
  1255  var lockRxStr = strings.Join([]string{
  1256  	`RecordLockProfileData`,
  1257  	`(base::)?RecordLockProfileData.*`,
  1258  	`(base::)?SubmitMutexProfileData.*`,
  1259  	`(base::)?SubmitSpinLockProfileData.*`,
  1260  	`(Mutex::)?AwaitCommon.*`,
  1261  	`(Mutex::)?Unlock.*`,
  1262  	`(Mutex::)?UnlockSlow.*`,
  1263  	`(Mutex::)?ReaderUnlock.*`,
  1264  	`(MutexLock::)?~MutexLock.*`,
  1265  	`(SpinLock::)?Unlock.*`,
  1266  	`(SpinLock::)?SlowUnlock.*`,
  1267  	`(SpinLockHolder::)?~SpinLockHolder.*`,
  1268  }, `|`)
  1269  

View as plain text