github.com/razvanm/vanadium-go-1.3@v0.0.0-20160721203343-4a65068e5915/src/cmd/pprof/internal/profile/legacy_profile.go (about)

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