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