...

Source file src/pkg/cmd/vendor/github.com/google/pprof/profile/profile.go

     1	// Copyright 2014 Google Inc. All Rights Reserved.
     2	//
     3	// Licensed under the Apache License, Version 2.0 (the "License");
     4	// you may not use this file except in compliance with the License.
     5	// You may obtain a copy of the License at
     6	//
     7	//     http://www.apache.org/licenses/LICENSE-2.0
     8	//
     9	// Unless required by applicable law or agreed to in writing, software
    10	// distributed under the License is distributed on an "AS IS" BASIS,
    11	// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12	// See the License for the specific language governing permissions and
    13	// limitations under the License.
    14	
    15	// Package profile provides a representation of profile.proto and
    16	// methods to encode/decode profiles in this format.
    17	package profile
    18	
    19	import (
    20		"bytes"
    21		"compress/gzip"
    22		"fmt"
    23		"io"
    24		"io/ioutil"
    25		"path/filepath"
    26		"regexp"
    27		"sort"
    28		"strings"
    29		"sync"
    30		"time"
    31	)
    32	
    33	// Profile is an in-memory representation of profile.proto.
    34	type Profile struct {
    35		SampleType        []*ValueType
    36		DefaultSampleType string
    37		Sample            []*Sample
    38		Mapping           []*Mapping
    39		Location          []*Location
    40		Function          []*Function
    41		Comments          []string
    42	
    43		DropFrames string
    44		KeepFrames string
    45	
    46		TimeNanos     int64
    47		DurationNanos int64
    48		PeriodType    *ValueType
    49		Period        int64
    50	
    51		// The following fields are modified during encoding and copying,
    52		// so are protected by a Mutex.
    53		encodeMu sync.Mutex
    54	
    55		commentX           []int64
    56		dropFramesX        int64
    57		keepFramesX        int64
    58		stringTable        []string
    59		defaultSampleTypeX int64
    60	}
    61	
    62	// ValueType corresponds to Profile.ValueType
    63	type ValueType struct {
    64		Type string // cpu, wall, inuse_space, etc
    65		Unit string // seconds, nanoseconds, bytes, etc
    66	
    67		typeX int64
    68		unitX int64
    69	}
    70	
    71	// Sample corresponds to Profile.Sample
    72	type Sample struct {
    73		Location []*Location
    74		Value    []int64
    75		Label    map[string][]string
    76		NumLabel map[string][]int64
    77		NumUnit  map[string][]string
    78	
    79		locationIDX []uint64
    80		labelX      []label
    81	}
    82	
    83	// label corresponds to Profile.Label
    84	type label struct {
    85		keyX int64
    86		// Exactly one of the two following values must be set
    87		strX int64
    88		numX int64 // Integer value for this label
    89		// can be set if numX has value
    90		unitX int64
    91	}
    92	
    93	// Mapping corresponds to Profile.Mapping
    94	type Mapping struct {
    95		ID              uint64
    96		Start           uint64
    97		Limit           uint64
    98		Offset          uint64
    99		File            string
   100		BuildID         string
   101		HasFunctions    bool
   102		HasFilenames    bool
   103		HasLineNumbers  bool
   104		HasInlineFrames bool
   105	
   106		fileX    int64
   107		buildIDX int64
   108	}
   109	
   110	// Location corresponds to Profile.Location
   111	type Location struct {
   112		ID       uint64
   113		Mapping  *Mapping
   114		Address  uint64
   115		Line     []Line
   116		IsFolded bool
   117	
   118		mappingIDX uint64
   119	}
   120	
   121	// Line corresponds to Profile.Line
   122	type Line struct {
   123		Function *Function
   124		Line     int64
   125	
   126		functionIDX uint64
   127	}
   128	
   129	// Function corresponds to Profile.Function
   130	type Function struct {
   131		ID         uint64
   132		Name       string
   133		SystemName string
   134		Filename   string
   135		StartLine  int64
   136	
   137		nameX       int64
   138		systemNameX int64
   139		filenameX   int64
   140	}
   141	
   142	// Parse parses a profile and checks for its validity. The input
   143	// may be a gzip-compressed encoded protobuf or one of many legacy
   144	// profile formats which may be unsupported in the future.
   145	func Parse(r io.Reader) (*Profile, error) {
   146		data, err := ioutil.ReadAll(r)
   147		if err != nil {
   148			return nil, err
   149		}
   150		return ParseData(data)
   151	}
   152	
   153	// ParseData parses a profile from a buffer and checks for its
   154	// validity.
   155	func ParseData(data []byte) (*Profile, error) {
   156		var p *Profile
   157		var err error
   158		if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
   159			gz, err := gzip.NewReader(bytes.NewBuffer(data))
   160			if err == nil {
   161				data, err = ioutil.ReadAll(gz)
   162			}
   163			if err != nil {
   164				return nil, fmt.Errorf("decompressing profile: %v", err)
   165			}
   166		}
   167		if p, err = ParseUncompressed(data); err != nil && err != errNoData && err != errConcatProfile {
   168			p, err = parseLegacy(data)
   169		}
   170	
   171		if err != nil {
   172			return nil, fmt.Errorf("parsing profile: %v", err)
   173		}
   174	
   175		if err := p.CheckValid(); err != nil {
   176			return nil, fmt.Errorf("malformed profile: %v", err)
   177		}
   178		return p, nil
   179	}
   180	
   181	var errUnrecognized = fmt.Errorf("unrecognized profile format")
   182	var errMalformed = fmt.Errorf("malformed profile format")
   183	var errNoData = fmt.Errorf("empty input file")
   184	var errConcatProfile = fmt.Errorf("concatenated profiles detected")
   185	
   186	func parseLegacy(data []byte) (*Profile, error) {
   187		parsers := []func([]byte) (*Profile, error){
   188			parseCPU,
   189			parseHeap,
   190			parseGoCount, // goroutine, threadcreate
   191			parseThread,
   192			parseContention,
   193			parseJavaProfile,
   194		}
   195	
   196		for _, parser := range parsers {
   197			p, err := parser(data)
   198			if err == nil {
   199				p.addLegacyFrameInfo()
   200				return p, nil
   201			}
   202			if err != errUnrecognized {
   203				return nil, err
   204			}
   205		}
   206		return nil, errUnrecognized
   207	}
   208	
   209	// ParseUncompressed parses an uncompressed protobuf into a profile.
   210	func ParseUncompressed(data []byte) (*Profile, error) {
   211		if len(data) == 0 {
   212			return nil, errNoData
   213		}
   214		p := &Profile{}
   215		if err := unmarshal(data, p); err != nil {
   216			return nil, err
   217		}
   218	
   219		if err := p.postDecode(); err != nil {
   220			return nil, err
   221		}
   222	
   223		return p, nil
   224	}
   225	
   226	var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
   227	
   228	// massageMappings applies heuristic-based changes to the profile
   229	// mappings to account for quirks of some environments.
   230	func (p *Profile) massageMappings() {
   231		// Merge adjacent regions with matching names, checking that the offsets match
   232		if len(p.Mapping) > 1 {
   233			mappings := []*Mapping{p.Mapping[0]}
   234			for _, m := range p.Mapping[1:] {
   235				lm := mappings[len(mappings)-1]
   236				if adjacent(lm, m) {
   237					lm.Limit = m.Limit
   238					if m.File != "" {
   239						lm.File = m.File
   240					}
   241					if m.BuildID != "" {
   242						lm.BuildID = m.BuildID
   243					}
   244					p.updateLocationMapping(m, lm)
   245					continue
   246				}
   247				mappings = append(mappings, m)
   248			}
   249			p.Mapping = mappings
   250		}
   251	
   252		// Use heuristics to identify main binary and move it to the top of the list of mappings
   253		for i, m := range p.Mapping {
   254			file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1))
   255			if len(file) == 0 {
   256				continue
   257			}
   258			if len(libRx.FindStringSubmatch(file)) > 0 {
   259				continue
   260			}
   261			if file[0] == '[' {
   262				continue
   263			}
   264			// Swap what we guess is main to position 0.
   265			p.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0]
   266			break
   267		}
   268	
   269		// Keep the mapping IDs neatly sorted
   270		for i, m := range p.Mapping {
   271			m.ID = uint64(i + 1)
   272		}
   273	}
   274	
   275	// adjacent returns whether two mapping entries represent the same
   276	// mapping that has been split into two. Check that their addresses are adjacent,
   277	// and if the offsets match, if they are available.
   278	func adjacent(m1, m2 *Mapping) bool {
   279		if m1.File != "" && m2.File != "" {
   280			if m1.File != m2.File {
   281				return false
   282			}
   283		}
   284		if m1.BuildID != "" && m2.BuildID != "" {
   285			if m1.BuildID != m2.BuildID {
   286				return false
   287			}
   288		}
   289		if m1.Limit != m2.Start {
   290			return false
   291		}
   292		if m1.Offset != 0 && m2.Offset != 0 {
   293			offset := m1.Offset + (m1.Limit - m1.Start)
   294			if offset != m2.Offset {
   295				return false
   296			}
   297		}
   298		return true
   299	}
   300	
   301	func (p *Profile) updateLocationMapping(from, to *Mapping) {
   302		for _, l := range p.Location {
   303			if l.Mapping == from {
   304				l.Mapping = to
   305			}
   306		}
   307	}
   308	
   309	func serialize(p *Profile) []byte {
   310		p.encodeMu.Lock()
   311		p.preEncode()
   312		b := marshal(p)
   313		p.encodeMu.Unlock()
   314		return b
   315	}
   316	
   317	// Write writes the profile as a gzip-compressed marshaled protobuf.
   318	func (p *Profile) Write(w io.Writer) error {
   319		zw := gzip.NewWriter(w)
   320		defer zw.Close()
   321		_, err := zw.Write(serialize(p))
   322		return err
   323	}
   324	
   325	// WriteUncompressed writes the profile as a marshaled protobuf.
   326	func (p *Profile) WriteUncompressed(w io.Writer) error {
   327		_, err := w.Write(serialize(p))
   328		return err
   329	}
   330	
   331	// CheckValid tests whether the profile is valid. Checks include, but are
   332	// not limited to:
   333	//   - len(Profile.Sample[n].value) == len(Profile.value_unit)
   334	//   - Sample.id has a corresponding Profile.Location
   335	func (p *Profile) CheckValid() error {
   336		// Check that sample values are consistent
   337		sampleLen := len(p.SampleType)
   338		if sampleLen == 0 && len(p.Sample) != 0 {
   339			return fmt.Errorf("missing sample type information")
   340		}
   341		for _, s := range p.Sample {
   342			if s == nil {
   343				return fmt.Errorf("profile has nil sample")
   344			}
   345			if len(s.Value) != sampleLen {
   346				return fmt.Errorf("mismatch: sample has %d values vs. %d types", len(s.Value), len(p.SampleType))
   347			}
   348			for _, l := range s.Location {
   349				if l == nil {
   350					return fmt.Errorf("sample has nil location")
   351				}
   352			}
   353		}
   354	
   355		// Check that all mappings/locations/functions are in the tables
   356		// Check that there are no duplicate ids
   357		mappings := make(map[uint64]*Mapping, len(p.Mapping))
   358		for _, m := range p.Mapping {
   359			if m == nil {
   360				return fmt.Errorf("profile has nil mapping")
   361			}
   362			if m.ID == 0 {
   363				return fmt.Errorf("found mapping with reserved ID=0")
   364			}
   365			if mappings[m.ID] != nil {
   366				return fmt.Errorf("multiple mappings with same id: %d", m.ID)
   367			}
   368			mappings[m.ID] = m
   369		}
   370		functions := make(map[uint64]*Function, len(p.Function))
   371		for _, f := range p.Function {
   372			if f == nil {
   373				return fmt.Errorf("profile has nil function")
   374			}
   375			if f.ID == 0 {
   376				return fmt.Errorf("found function with reserved ID=0")
   377			}
   378			if functions[f.ID] != nil {
   379				return fmt.Errorf("multiple functions with same id: %d", f.ID)
   380			}
   381			functions[f.ID] = f
   382		}
   383		locations := make(map[uint64]*Location, len(p.Location))
   384		for _, l := range p.Location {
   385			if l == nil {
   386				return fmt.Errorf("profile has nil location")
   387			}
   388			if l.ID == 0 {
   389				return fmt.Errorf("found location with reserved id=0")
   390			}
   391			if locations[l.ID] != nil {
   392				return fmt.Errorf("multiple locations with same id: %d", l.ID)
   393			}
   394			locations[l.ID] = l
   395			if m := l.Mapping; m != nil {
   396				if m.ID == 0 || mappings[m.ID] != m {
   397					return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
   398				}
   399			}
   400			for _, ln := range l.Line {
   401				if f := ln.Function; f != nil {
   402					if f.ID == 0 || functions[f.ID] != f {
   403						return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
   404					}
   405				}
   406			}
   407		}
   408		return nil
   409	}
   410	
   411	// Aggregate merges the locations in the profile into equivalence
   412	// classes preserving the request attributes. It also updates the
   413	// samples to point to the merged locations.
   414	func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
   415		for _, m := range p.Mapping {
   416			m.HasInlineFrames = m.HasInlineFrames && inlineFrame
   417			m.HasFunctions = m.HasFunctions && function
   418			m.HasFilenames = m.HasFilenames && filename
   419			m.HasLineNumbers = m.HasLineNumbers && linenumber
   420		}
   421	
   422		// Aggregate functions
   423		if !function || !filename {
   424			for _, f := range p.Function {
   425				if !function {
   426					f.Name = ""
   427					f.SystemName = ""
   428				}
   429				if !filename {
   430					f.Filename = ""
   431				}
   432			}
   433		}
   434	
   435		// Aggregate locations
   436		if !inlineFrame || !address || !linenumber {
   437			for _, l := range p.Location {
   438				if !inlineFrame && len(l.Line) > 1 {
   439					l.Line = l.Line[len(l.Line)-1:]
   440				}
   441				if !linenumber {
   442					for i := range l.Line {
   443						l.Line[i].Line = 0
   444					}
   445				}
   446				if !address {
   447					l.Address = 0
   448				}
   449			}
   450		}
   451	
   452		return p.CheckValid()
   453	}
   454	
   455	// NumLabelUnits returns a map of numeric label keys to the units
   456	// associated with those keys and a map of those keys to any units
   457	// that were encountered but not used.
   458	// Unit for a given key is the first encountered unit for that key. If multiple
   459	// units are encountered for values paired with a particular key, then the first
   460	// unit encountered is used and all other units are returned in sorted order
   461	// in map of ignored units.
   462	// If no units are encountered for a particular key, the unit is then inferred
   463	// based on the key.
   464	func (p *Profile) NumLabelUnits() (map[string]string, map[string][]string) {
   465		numLabelUnits := map[string]string{}
   466		ignoredUnits := map[string]map[string]bool{}
   467		encounteredKeys := map[string]bool{}
   468	
   469		// Determine units based on numeric tags for each sample.
   470		for _, s := range p.Sample {
   471			for k := range s.NumLabel {
   472				encounteredKeys[k] = true
   473				for _, unit := range s.NumUnit[k] {
   474					if unit == "" {
   475						continue
   476					}
   477					if wantUnit, ok := numLabelUnits[k]; !ok {
   478						numLabelUnits[k] = unit
   479					} else if wantUnit != unit {
   480						if v, ok := ignoredUnits[k]; ok {
   481							v[unit] = true
   482						} else {
   483							ignoredUnits[k] = map[string]bool{unit: true}
   484						}
   485					}
   486				}
   487			}
   488		}
   489		// Infer units for keys without any units associated with
   490		// numeric tag values.
   491		for key := range encounteredKeys {
   492			unit := numLabelUnits[key]
   493			if unit == "" {
   494				switch key {
   495				case "alignment", "request":
   496					numLabelUnits[key] = "bytes"
   497				default:
   498					numLabelUnits[key] = key
   499				}
   500			}
   501		}
   502	
   503		// Copy ignored units into more readable format
   504		unitsIgnored := make(map[string][]string, len(ignoredUnits))
   505		for key, values := range ignoredUnits {
   506			units := make([]string, len(values))
   507			i := 0
   508			for unit := range values {
   509				units[i] = unit
   510				i++
   511			}
   512			sort.Strings(units)
   513			unitsIgnored[key] = units
   514		}
   515	
   516		return numLabelUnits, unitsIgnored
   517	}
   518	
   519	// String dumps a text representation of a profile. Intended mainly
   520	// for debugging purposes.
   521	func (p *Profile) String() string {
   522		ss := make([]string, 0, len(p.Comments)+len(p.Sample)+len(p.Mapping)+len(p.Location))
   523		for _, c := range p.Comments {
   524			ss = append(ss, "Comment: "+c)
   525		}
   526		if pt := p.PeriodType; pt != nil {
   527			ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
   528		}
   529		ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
   530		if p.TimeNanos != 0 {
   531			ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
   532		}
   533		if p.DurationNanos != 0 {
   534			ss = append(ss, fmt.Sprintf("Duration: %.4v", time.Duration(p.DurationNanos)))
   535		}
   536	
   537		ss = append(ss, "Samples:")
   538		var sh1 string
   539		for _, s := range p.SampleType {
   540			dflt := ""
   541			if s.Type == p.DefaultSampleType {
   542				dflt = "[dflt]"
   543			}
   544			sh1 = sh1 + fmt.Sprintf("%s/%s%s ", s.Type, s.Unit, dflt)
   545		}
   546		ss = append(ss, strings.TrimSpace(sh1))
   547		for _, s := range p.Sample {
   548			ss = append(ss, s.string())
   549		}
   550	
   551		ss = append(ss, "Locations")
   552		for _, l := range p.Location {
   553			ss = append(ss, l.string())
   554		}
   555	
   556		ss = append(ss, "Mappings")
   557		for _, m := range p.Mapping {
   558			ss = append(ss, m.string())
   559		}
   560	
   561		return strings.Join(ss, "\n") + "\n"
   562	}
   563	
   564	// string dumps a text representation of a mapping. Intended mainly
   565	// for debugging purposes.
   566	func (m *Mapping) string() string {
   567		bits := ""
   568		if m.HasFunctions {
   569			bits = bits + "[FN]"
   570		}
   571		if m.HasFilenames {
   572			bits = bits + "[FL]"
   573		}
   574		if m.HasLineNumbers {
   575			bits = bits + "[LN]"
   576		}
   577		if m.HasInlineFrames {
   578			bits = bits + "[IN]"
   579		}
   580		return fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
   581			m.ID,
   582			m.Start, m.Limit, m.Offset,
   583			m.File,
   584			m.BuildID,
   585			bits)
   586	}
   587	
   588	// string dumps a text representation of a location. Intended mainly
   589	// for debugging purposes.
   590	func (l *Location) string() string {
   591		ss := []string{}
   592		locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
   593		if m := l.Mapping; m != nil {
   594			locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
   595		}
   596		if l.IsFolded {
   597			locStr = locStr + "[F] "
   598		}
   599		if len(l.Line) == 0 {
   600			ss = append(ss, locStr)
   601		}
   602		for li := range l.Line {
   603			lnStr := "??"
   604			if fn := l.Line[li].Function; fn != nil {
   605				lnStr = fmt.Sprintf("%s %s:%d s=%d",
   606					fn.Name,
   607					fn.Filename,
   608					l.Line[li].Line,
   609					fn.StartLine)
   610				if fn.Name != fn.SystemName {
   611					lnStr = lnStr + "(" + fn.SystemName + ")"
   612				}
   613			}
   614			ss = append(ss, locStr+lnStr)
   615			// Do not print location details past the first line
   616			locStr = "             "
   617		}
   618		return strings.Join(ss, "\n")
   619	}
   620	
   621	// string dumps a text representation of a sample. Intended mainly
   622	// for debugging purposes.
   623	func (s *Sample) string() string {
   624		ss := []string{}
   625		var sv string
   626		for _, v := range s.Value {
   627			sv = fmt.Sprintf("%s %10d", sv, v)
   628		}
   629		sv = sv + ": "
   630		for _, l := range s.Location {
   631			sv = sv + fmt.Sprintf("%d ", l.ID)
   632		}
   633		ss = append(ss, sv)
   634		const labelHeader = "                "
   635		if len(s.Label) > 0 {
   636			ss = append(ss, labelHeader+labelsToString(s.Label))
   637		}
   638		if len(s.NumLabel) > 0 {
   639			ss = append(ss, labelHeader+numLabelsToString(s.NumLabel, s.NumUnit))
   640		}
   641		return strings.Join(ss, "\n")
   642	}
   643	
   644	// labelsToString returns a string representation of a
   645	// map representing labels.
   646	func labelsToString(labels map[string][]string) string {
   647		ls := []string{}
   648		for k, v := range labels {
   649			ls = append(ls, fmt.Sprintf("%s:%v", k, v))
   650		}
   651		sort.Strings(ls)
   652		return strings.Join(ls, " ")
   653	}
   654	
   655	// numLabelsToString returns a string representation of a map
   656	// representing numeric labels.
   657	func numLabelsToString(numLabels map[string][]int64, numUnits map[string][]string) string {
   658		ls := []string{}
   659		for k, v := range numLabels {
   660			units := numUnits[k]
   661			var labelString string
   662			if len(units) == len(v) {
   663				values := make([]string, len(v))
   664				for i, vv := range v {
   665					values[i] = fmt.Sprintf("%d %s", vv, units[i])
   666				}
   667				labelString = fmt.Sprintf("%s:%v", k, values)
   668			} else {
   669				labelString = fmt.Sprintf("%s:%v", k, v)
   670			}
   671			ls = append(ls, labelString)
   672		}
   673		sort.Strings(ls)
   674		return strings.Join(ls, " ")
   675	}
   676	
   677	// SetLabel sets the specified key to the specified value for all samples in the
   678	// profile.
   679	func (p *Profile) SetLabel(key string, value []string) {
   680		for _, sample := range p.Sample {
   681			if sample.Label == nil {
   682				sample.Label = map[string][]string{key: value}
   683			} else {
   684				sample.Label[key] = value
   685			}
   686		}
   687	}
   688	
   689	// RemoveLabel removes all labels associated with the specified key for all
   690	// samples in the profile.
   691	func (p *Profile) RemoveLabel(key string) {
   692		for _, sample := range p.Sample {
   693			delete(sample.Label, key)
   694		}
   695	}
   696	
   697	// HasLabel returns true if a sample has a label with indicated key and value.
   698	func (s *Sample) HasLabel(key, value string) bool {
   699		for _, v := range s.Label[key] {
   700			if v == value {
   701				return true
   702			}
   703		}
   704		return false
   705	}
   706	
   707	// DiffBaseSample returns true if a sample belongs to the diff base and false
   708	// otherwise.
   709	func (s *Sample) DiffBaseSample() bool {
   710		return s.HasLabel("pprof::base", "true")
   711	}
   712	
   713	// Scale multiplies all sample values in a profile by a constant.
   714	func (p *Profile) Scale(ratio float64) {
   715		if ratio == 1 {
   716			return
   717		}
   718		ratios := make([]float64, len(p.SampleType))
   719		for i := range p.SampleType {
   720			ratios[i] = ratio
   721		}
   722		p.ScaleN(ratios)
   723	}
   724	
   725	// ScaleN multiplies each sample values in a sample by a different amount.
   726	func (p *Profile) ScaleN(ratios []float64) error {
   727		if len(p.SampleType) != len(ratios) {
   728			return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
   729		}
   730		allOnes := true
   731		for _, r := range ratios {
   732			if r != 1 {
   733				allOnes = false
   734				break
   735			}
   736		}
   737		if allOnes {
   738			return nil
   739		}
   740		for _, s := range p.Sample {
   741			for i, v := range s.Value {
   742				if ratios[i] != 1 {
   743					s.Value[i] = int64(float64(v) * ratios[i])
   744				}
   745			}
   746		}
   747		return nil
   748	}
   749	
   750	// HasFunctions determines if all locations in this profile have
   751	// symbolized function information.
   752	func (p *Profile) HasFunctions() bool {
   753		for _, l := range p.Location {
   754			if l.Mapping != nil && !l.Mapping.HasFunctions {
   755				return false
   756			}
   757		}
   758		return true
   759	}
   760	
   761	// HasFileLines determines if all locations in this profile have
   762	// symbolized file and line number information.
   763	func (p *Profile) HasFileLines() bool {
   764		for _, l := range p.Location {
   765			if l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
   766				return false
   767			}
   768		}
   769		return true
   770	}
   771	
   772	// Unsymbolizable returns true if a mapping points to a binary for which
   773	// locations can't be symbolized in principle, at least now. Examples are
   774	// "[vdso]", [vsyscall]" and some others, see the code.
   775	func (m *Mapping) Unsymbolizable() bool {
   776		name := filepath.Base(m.File)
   777		return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/")
   778	}
   779	
   780	// Copy makes a fully independent copy of a profile.
   781	func (p *Profile) Copy() *Profile {
   782		pp := &Profile{}
   783		if err := unmarshal(serialize(p), pp); err != nil {
   784			panic(err)
   785		}
   786		if err := pp.postDecode(); err != nil {
   787			panic(err)
   788		}
   789	
   790		return pp
   791	}
   792	

View as plain text