podman

Форк
0
805 строк · 19.7 Кб
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
	"math"
26
	"path/filepath"
27
	"regexp"
28
	"sort"
29
	"strings"
30
	"sync"
31
	"time"
32
)
33

34
// Profile is an in-memory representation of profile.proto.
35
type Profile struct {
36
	SampleType        []*ValueType
37
	DefaultSampleType string
38
	Sample            []*Sample
39
	Mapping           []*Mapping
40
	Location          []*Location
41
	Function          []*Function
42
	Comments          []string
43

44
	DropFrames string
45
	KeepFrames string
46

47
	TimeNanos     int64
48
	DurationNanos int64
49
	PeriodType    *ValueType
50
	Period        int64
51

52
	// The following fields are modified during encoding and copying,
53
	// so are protected by a Mutex.
54
	encodeMu sync.Mutex
55

56
	commentX           []int64
57
	dropFramesX        int64
58
	keepFramesX        int64
59
	stringTable        []string
60
	defaultSampleTypeX int64
61
}
62

63
// ValueType corresponds to Profile.ValueType
64
type ValueType struct {
65
	Type string // cpu, wall, inuse_space, etc
66
	Unit string // seconds, nanoseconds, bytes, etc
67

68
	typeX int64
69
	unitX int64
70
}
71

72
// Sample corresponds to Profile.Sample
73
type Sample struct {
74
	Location []*Location
75
	Value    []int64
76
	Label    map[string][]string
77
	NumLabel map[string][]int64
78
	NumUnit  map[string][]string
79

80
	locationIDX []uint64
81
	labelX      []label
82
}
83

84
// label corresponds to Profile.Label
85
type label struct {
86
	keyX int64
87
	// Exactly one of the two following values must be set
88
	strX int64
89
	numX int64 // Integer value for this label
90
	// can be set if numX has value
91
	unitX int64
92
}
93

94
// Mapping corresponds to Profile.Mapping
95
type Mapping struct {
96
	ID              uint64
97
	Start           uint64
98
	Limit           uint64
99
	Offset          uint64
100
	File            string
101
	BuildID         string
102
	HasFunctions    bool
103
	HasFilenames    bool
104
	HasLineNumbers  bool
105
	HasInlineFrames bool
106

107
	fileX    int64
108
	buildIDX int64
109
}
110

111
// Location corresponds to Profile.Location
112
type Location struct {
113
	ID       uint64
114
	Mapping  *Mapping
115
	Address  uint64
116
	Line     []Line
117
	IsFolded bool
118

119
	mappingIDX uint64
120
}
121

122
// Line corresponds to Profile.Line
123
type Line struct {
124
	Function *Function
125
	Line     int64
126

127
	functionIDX uint64
128
}
129

130
// Function corresponds to Profile.Function
131
type Function struct {
132
	ID         uint64
133
	Name       string
134
	SystemName string
135
	Filename   string
136
	StartLine  int64
137

138
	nameX       int64
139
	systemNameX int64
140
	filenameX   int64
141
}
142

143
// Parse parses a profile and checks for its validity. The input
144
// may be a gzip-compressed encoded protobuf or one of many legacy
145
// profile formats which may be unsupported in the future.
146
func Parse(r io.Reader) (*Profile, error) {
147
	data, err := ioutil.ReadAll(r)
148
	if err != nil {
149
		return nil, err
150
	}
151
	return ParseData(data)
152
}
153

154
// ParseData parses a profile from a buffer and checks for its
155
// validity.
156
func ParseData(data []byte) (*Profile, error) {
157
	var p *Profile
158
	var err error
159
	if len(data) >= 2 && data[0] == 0x1f && data[1] == 0x8b {
160
		gz, err := gzip.NewReader(bytes.NewBuffer(data))
161
		if err == nil {
162
			data, err = ioutil.ReadAll(gz)
163
		}
164
		if err != nil {
165
			return nil, fmt.Errorf("decompressing profile: %v", err)
166
		}
167
	}
168
	if p, err = ParseUncompressed(data); err != nil && err != errNoData && err != errConcatProfile {
169
		p, err = parseLegacy(data)
170
	}
171

172
	if err != nil {
173
		return nil, fmt.Errorf("parsing profile: %v", err)
174
	}
175

176
	if err := p.CheckValid(); err != nil {
177
		return nil, fmt.Errorf("malformed profile: %v", err)
178
	}
179
	return p, nil
180
}
181

182
var errUnrecognized = fmt.Errorf("unrecognized profile format")
183
var errMalformed = fmt.Errorf("malformed profile format")
184
var errNoData = fmt.Errorf("empty input file")
185
var errConcatProfile = fmt.Errorf("concatenated profiles detected")
186

187
func parseLegacy(data []byte) (*Profile, error) {
188
	parsers := []func([]byte) (*Profile, error){
189
		parseCPU,
190
		parseHeap,
191
		parseGoCount, // goroutine, threadcreate
192
		parseThread,
193
		parseContention,
194
		parseJavaProfile,
195
	}
196

197
	for _, parser := range parsers {
198
		p, err := parser(data)
199
		if err == nil {
200
			p.addLegacyFrameInfo()
201
			return p, nil
202
		}
203
		if err != errUnrecognized {
204
			return nil, err
205
		}
206
	}
207
	return nil, errUnrecognized
208
}
209

210
// ParseUncompressed parses an uncompressed protobuf into a profile.
211
func ParseUncompressed(data []byte) (*Profile, error) {
212
	if len(data) == 0 {
213
		return nil, errNoData
214
	}
215
	p := &Profile{}
216
	if err := unmarshal(data, p); err != nil {
217
		return nil, err
218
	}
219

220
	if err := p.postDecode(); err != nil {
221
		return nil, err
222
	}
223

224
	return p, nil
225
}
226

227
var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
228

229
// massageMappings applies heuristic-based changes to the profile
230
// mappings to account for quirks of some environments.
231
func (p *Profile) massageMappings() {
232
	// Merge adjacent regions with matching names, checking that the offsets match
233
	if len(p.Mapping) > 1 {
234
		mappings := []*Mapping{p.Mapping[0]}
235
		for _, m := range p.Mapping[1:] {
236
			lm := mappings[len(mappings)-1]
237
			if adjacent(lm, m) {
238
				lm.Limit = m.Limit
239
				if m.File != "" {
240
					lm.File = m.File
241
				}
242
				if m.BuildID != "" {
243
					lm.BuildID = m.BuildID
244
				}
245
				p.updateLocationMapping(m, lm)
246
				continue
247
			}
248
			mappings = append(mappings, m)
249
		}
250
		p.Mapping = mappings
251
	}
252

253
	// Use heuristics to identify main binary and move it to the top of the list of mappings
254
	for i, m := range p.Mapping {
255
		file := strings.TrimSpace(strings.Replace(m.File, "(deleted)", "", -1))
256
		if len(file) == 0 {
257
			continue
258
		}
259
		if len(libRx.FindStringSubmatch(file)) > 0 {
260
			continue
261
		}
262
		if file[0] == '[' {
263
			continue
264
		}
265
		// Swap what we guess is main to position 0.
266
		p.Mapping[0], p.Mapping[i] = p.Mapping[i], p.Mapping[0]
267
		break
268
	}
269

270
	// Keep the mapping IDs neatly sorted
271
	for i, m := range p.Mapping {
272
		m.ID = uint64(i + 1)
273
	}
274
}
275

276
// adjacent returns whether two mapping entries represent the same
277
// mapping that has been split into two. Check that their addresses are adjacent,
278
// and if the offsets match, if they are available.
279
func adjacent(m1, m2 *Mapping) bool {
280
	if m1.File != "" && m2.File != "" {
281
		if m1.File != m2.File {
282
			return false
283
		}
284
	}
285
	if m1.BuildID != "" && m2.BuildID != "" {
286
		if m1.BuildID != m2.BuildID {
287
			return false
288
		}
289
	}
290
	if m1.Limit != m2.Start {
291
		return false
292
	}
293
	if m1.Offset != 0 && m2.Offset != 0 {
294
		offset := m1.Offset + (m1.Limit - m1.Start)
295
		if offset != m2.Offset {
296
			return false
297
		}
298
	}
299
	return true
300
}
301

302
func (p *Profile) updateLocationMapping(from, to *Mapping) {
303
	for _, l := range p.Location {
304
		if l.Mapping == from {
305
			l.Mapping = to
306
		}
307
	}
308
}
309

310
func serialize(p *Profile) []byte {
311
	p.encodeMu.Lock()
312
	p.preEncode()
313
	b := marshal(p)
314
	p.encodeMu.Unlock()
315
	return b
316
}
317

318
// Write writes the profile as a gzip-compressed marshaled protobuf.
319
func (p *Profile) Write(w io.Writer) error {
320
	zw := gzip.NewWriter(w)
321
	defer zw.Close()
322
	_, err := zw.Write(serialize(p))
323
	return err
324
}
325

326
// WriteUncompressed writes the profile as a marshaled protobuf.
327
func (p *Profile) WriteUncompressed(w io.Writer) error {
328
	_, err := w.Write(serialize(p))
329
	return err
330
}
331

332
// CheckValid tests whether the profile is valid. Checks include, but are
333
// not limited to:
334
//   - len(Profile.Sample[n].value) == len(Profile.value_unit)
335
//   - Sample.id has a corresponding Profile.Location
336
func (p *Profile) CheckValid() error {
337
	// Check that sample values are consistent
338
	sampleLen := len(p.SampleType)
339
	if sampleLen == 0 && len(p.Sample) != 0 {
340
		return fmt.Errorf("missing sample type information")
341
	}
342
	for _, s := range p.Sample {
343
		if s == nil {
344
			return fmt.Errorf("profile has nil sample")
345
		}
346
		if len(s.Value) != sampleLen {
347
			return fmt.Errorf("mismatch: sample has %d values vs. %d types", len(s.Value), len(p.SampleType))
348
		}
349
		for _, l := range s.Location {
350
			if l == nil {
351
				return fmt.Errorf("sample has nil location")
352
			}
353
		}
354
	}
355

356
	// Check that all mappings/locations/functions are in the tables
357
	// Check that there are no duplicate ids
358
	mappings := make(map[uint64]*Mapping, len(p.Mapping))
359
	for _, m := range p.Mapping {
360
		if m == nil {
361
			return fmt.Errorf("profile has nil mapping")
362
		}
363
		if m.ID == 0 {
364
			return fmt.Errorf("found mapping with reserved ID=0")
365
		}
366
		if mappings[m.ID] != nil {
367
			return fmt.Errorf("multiple mappings with same id: %d", m.ID)
368
		}
369
		mappings[m.ID] = m
370
	}
371
	functions := make(map[uint64]*Function, len(p.Function))
372
	for _, f := range p.Function {
373
		if f == nil {
374
			return fmt.Errorf("profile has nil function")
375
		}
376
		if f.ID == 0 {
377
			return fmt.Errorf("found function with reserved ID=0")
378
		}
379
		if functions[f.ID] != nil {
380
			return fmt.Errorf("multiple functions with same id: %d", f.ID)
381
		}
382
		functions[f.ID] = f
383
	}
384
	locations := make(map[uint64]*Location, len(p.Location))
385
	for _, l := range p.Location {
386
		if l == nil {
387
			return fmt.Errorf("profile has nil location")
388
		}
389
		if l.ID == 0 {
390
			return fmt.Errorf("found location with reserved id=0")
391
		}
392
		if locations[l.ID] != nil {
393
			return fmt.Errorf("multiple locations with same id: %d", l.ID)
394
		}
395
		locations[l.ID] = l
396
		if m := l.Mapping; m != nil {
397
			if m.ID == 0 || mappings[m.ID] != m {
398
				return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
399
			}
400
		}
401
		for _, ln := range l.Line {
402
			f := ln.Function
403
			if f == nil {
404
				return fmt.Errorf("location id: %d has a line with nil function", l.ID)
405
			}
406
			if f.ID == 0 || functions[f.ID] != f {
407
				return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
408
			}
409
		}
410
	}
411
	return nil
412
}
413

414
// Aggregate merges the locations in the profile into equivalence
415
// classes preserving the request attributes. It also updates the
416
// samples to point to the merged locations.
417
func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
418
	for _, m := range p.Mapping {
419
		m.HasInlineFrames = m.HasInlineFrames && inlineFrame
420
		m.HasFunctions = m.HasFunctions && function
421
		m.HasFilenames = m.HasFilenames && filename
422
		m.HasLineNumbers = m.HasLineNumbers && linenumber
423
	}
424

425
	// Aggregate functions
426
	if !function || !filename {
427
		for _, f := range p.Function {
428
			if !function {
429
				f.Name = ""
430
				f.SystemName = ""
431
			}
432
			if !filename {
433
				f.Filename = ""
434
			}
435
		}
436
	}
437

438
	// Aggregate locations
439
	if !inlineFrame || !address || !linenumber {
440
		for _, l := range p.Location {
441
			if !inlineFrame && len(l.Line) > 1 {
442
				l.Line = l.Line[len(l.Line)-1:]
443
			}
444
			if !linenumber {
445
				for i := range l.Line {
446
					l.Line[i].Line = 0
447
				}
448
			}
449
			if !address {
450
				l.Address = 0
451
			}
452
		}
453
	}
454

455
	return p.CheckValid()
456
}
457

458
// NumLabelUnits returns a map of numeric label keys to the units
459
// associated with those keys and a map of those keys to any units
460
// that were encountered but not used.
461
// Unit for a given key is the first encountered unit for that key. If multiple
462
// units are encountered for values paired with a particular key, then the first
463
// unit encountered is used and all other units are returned in sorted order
464
// in map of ignored units.
465
// If no units are encountered for a particular key, the unit is then inferred
466
// based on the key.
467
func (p *Profile) NumLabelUnits() (map[string]string, map[string][]string) {
468
	numLabelUnits := map[string]string{}
469
	ignoredUnits := map[string]map[string]bool{}
470
	encounteredKeys := map[string]bool{}
471

472
	// Determine units based on numeric tags for each sample.
473
	for _, s := range p.Sample {
474
		for k := range s.NumLabel {
475
			encounteredKeys[k] = true
476
			for _, unit := range s.NumUnit[k] {
477
				if unit == "" {
478
					continue
479
				}
480
				if wantUnit, ok := numLabelUnits[k]; !ok {
481
					numLabelUnits[k] = unit
482
				} else if wantUnit != unit {
483
					if v, ok := ignoredUnits[k]; ok {
484
						v[unit] = true
485
					} else {
486
						ignoredUnits[k] = map[string]bool{unit: true}
487
					}
488
				}
489
			}
490
		}
491
	}
492
	// Infer units for keys without any units associated with
493
	// numeric tag values.
494
	for key := range encounteredKeys {
495
		unit := numLabelUnits[key]
496
		if unit == "" {
497
			switch key {
498
			case "alignment", "request":
499
				numLabelUnits[key] = "bytes"
500
			default:
501
				numLabelUnits[key] = key
502
			}
503
		}
504
	}
505

506
	// Copy ignored units into more readable format
507
	unitsIgnored := make(map[string][]string, len(ignoredUnits))
508
	for key, values := range ignoredUnits {
509
		units := make([]string, len(values))
510
		i := 0
511
		for unit := range values {
512
			units[i] = unit
513
			i++
514
		}
515
		sort.Strings(units)
516
		unitsIgnored[key] = units
517
	}
518

519
	return numLabelUnits, unitsIgnored
520
}
521

522
// String dumps a text representation of a profile. Intended mainly
523
// for debugging purposes.
524
func (p *Profile) String() string {
525
	ss := make([]string, 0, len(p.Comments)+len(p.Sample)+len(p.Mapping)+len(p.Location))
526
	for _, c := range p.Comments {
527
		ss = append(ss, "Comment: "+c)
528
	}
529
	if pt := p.PeriodType; pt != nil {
530
		ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
531
	}
532
	ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
533
	if p.TimeNanos != 0 {
534
		ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
535
	}
536
	if p.DurationNanos != 0 {
537
		ss = append(ss, fmt.Sprintf("Duration: %.4v", time.Duration(p.DurationNanos)))
538
	}
539

540
	ss = append(ss, "Samples:")
541
	var sh1 string
542
	for _, s := range p.SampleType {
543
		dflt := ""
544
		if s.Type == p.DefaultSampleType {
545
			dflt = "[dflt]"
546
		}
547
		sh1 = sh1 + fmt.Sprintf("%s/%s%s ", s.Type, s.Unit, dflt)
548
	}
549
	ss = append(ss, strings.TrimSpace(sh1))
550
	for _, s := range p.Sample {
551
		ss = append(ss, s.string())
552
	}
553

554
	ss = append(ss, "Locations")
555
	for _, l := range p.Location {
556
		ss = append(ss, l.string())
557
	}
558

559
	ss = append(ss, "Mappings")
560
	for _, m := range p.Mapping {
561
		ss = append(ss, m.string())
562
	}
563

564
	return strings.Join(ss, "\n") + "\n"
565
}
566

567
// string dumps a text representation of a mapping. Intended mainly
568
// for debugging purposes.
569
func (m *Mapping) string() string {
570
	bits := ""
571
	if m.HasFunctions {
572
		bits = bits + "[FN]"
573
	}
574
	if m.HasFilenames {
575
		bits = bits + "[FL]"
576
	}
577
	if m.HasLineNumbers {
578
		bits = bits + "[LN]"
579
	}
580
	if m.HasInlineFrames {
581
		bits = bits + "[IN]"
582
	}
583
	return fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
584
		m.ID,
585
		m.Start, m.Limit, m.Offset,
586
		m.File,
587
		m.BuildID,
588
		bits)
589
}
590

591
// string dumps a text representation of a location. Intended mainly
592
// for debugging purposes.
593
func (l *Location) string() string {
594
	ss := []string{}
595
	locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
596
	if m := l.Mapping; m != nil {
597
		locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
598
	}
599
	if l.IsFolded {
600
		locStr = locStr + "[F] "
601
	}
602
	if len(l.Line) == 0 {
603
		ss = append(ss, locStr)
604
	}
605
	for li := range l.Line {
606
		lnStr := "??"
607
		if fn := l.Line[li].Function; fn != nil {
608
			lnStr = fmt.Sprintf("%s %s:%d s=%d",
609
				fn.Name,
610
				fn.Filename,
611
				l.Line[li].Line,
612
				fn.StartLine)
613
			if fn.Name != fn.SystemName {
614
				lnStr = lnStr + "(" + fn.SystemName + ")"
615
			}
616
		}
617
		ss = append(ss, locStr+lnStr)
618
		// Do not print location details past the first line
619
		locStr = "             "
620
	}
621
	return strings.Join(ss, "\n")
622
}
623

624
// string dumps a text representation of a sample. Intended mainly
625
// for debugging purposes.
626
func (s *Sample) string() string {
627
	ss := []string{}
628
	var sv string
629
	for _, v := range s.Value {
630
		sv = fmt.Sprintf("%s %10d", sv, v)
631
	}
632
	sv = sv + ": "
633
	for _, l := range s.Location {
634
		sv = sv + fmt.Sprintf("%d ", l.ID)
635
	}
636
	ss = append(ss, sv)
637
	const labelHeader = "                "
638
	if len(s.Label) > 0 {
639
		ss = append(ss, labelHeader+labelsToString(s.Label))
640
	}
641
	if len(s.NumLabel) > 0 {
642
		ss = append(ss, labelHeader+numLabelsToString(s.NumLabel, s.NumUnit))
643
	}
644
	return strings.Join(ss, "\n")
645
}
646

647
// labelsToString returns a string representation of a
648
// map representing labels.
649
func labelsToString(labels map[string][]string) string {
650
	ls := []string{}
651
	for k, v := range labels {
652
		ls = append(ls, fmt.Sprintf("%s:%v", k, v))
653
	}
654
	sort.Strings(ls)
655
	return strings.Join(ls, " ")
656
}
657

658
// numLabelsToString returns a string representation of a map
659
// representing numeric labels.
660
func numLabelsToString(numLabels map[string][]int64, numUnits map[string][]string) string {
661
	ls := []string{}
662
	for k, v := range numLabels {
663
		units := numUnits[k]
664
		var labelString string
665
		if len(units) == len(v) {
666
			values := make([]string, len(v))
667
			for i, vv := range v {
668
				values[i] = fmt.Sprintf("%d %s", vv, units[i])
669
			}
670
			labelString = fmt.Sprintf("%s:%v", k, values)
671
		} else {
672
			labelString = fmt.Sprintf("%s:%v", k, v)
673
		}
674
		ls = append(ls, labelString)
675
	}
676
	sort.Strings(ls)
677
	return strings.Join(ls, " ")
678
}
679

680
// SetLabel sets the specified key to the specified value for all samples in the
681
// profile.
682
func (p *Profile) SetLabel(key string, value []string) {
683
	for _, sample := range p.Sample {
684
		if sample.Label == nil {
685
			sample.Label = map[string][]string{key: value}
686
		} else {
687
			sample.Label[key] = value
688
		}
689
	}
690
}
691

692
// RemoveLabel removes all labels associated with the specified key for all
693
// samples in the profile.
694
func (p *Profile) RemoveLabel(key string) {
695
	for _, sample := range p.Sample {
696
		delete(sample.Label, key)
697
	}
698
}
699

700
// HasLabel returns true if a sample has a label with indicated key and value.
701
func (s *Sample) HasLabel(key, value string) bool {
702
	for _, v := range s.Label[key] {
703
		if v == value {
704
			return true
705
		}
706
	}
707
	return false
708
}
709

710
// DiffBaseSample returns true if a sample belongs to the diff base and false
711
// otherwise.
712
func (s *Sample) DiffBaseSample() bool {
713
	return s.HasLabel("pprof::base", "true")
714
}
715

716
// Scale multiplies all sample values in a profile by a constant and keeps
717
// only samples that have at least one non-zero value.
718
func (p *Profile) Scale(ratio float64) {
719
	if ratio == 1 {
720
		return
721
	}
722
	ratios := make([]float64, len(p.SampleType))
723
	for i := range p.SampleType {
724
		ratios[i] = ratio
725
	}
726
	p.ScaleN(ratios)
727
}
728

729
// ScaleN multiplies each sample values in a sample by a different amount
730
// and keeps only samples that have at least one non-zero value.
731
func (p *Profile) ScaleN(ratios []float64) error {
732
	if len(p.SampleType) != len(ratios) {
733
		return fmt.Errorf("mismatched scale ratios, got %d, want %d", len(ratios), len(p.SampleType))
734
	}
735
	allOnes := true
736
	for _, r := range ratios {
737
		if r != 1 {
738
			allOnes = false
739
			break
740
		}
741
	}
742
	if allOnes {
743
		return nil
744
	}
745
	fillIdx := 0
746
	for _, s := range p.Sample {
747
		keepSample := false
748
		for i, v := range s.Value {
749
			if ratios[i] != 1 {
750
				val := int64(math.Round(float64(v) * ratios[i]))
751
				s.Value[i] = val
752
				keepSample = keepSample || val != 0
753
			}
754
		}
755
		if keepSample {
756
			p.Sample[fillIdx] = s
757
			fillIdx++
758
		}
759
	}
760
	p.Sample = p.Sample[:fillIdx]
761
	return nil
762
}
763

764
// HasFunctions determines if all locations in this profile have
765
// symbolized function information.
766
func (p *Profile) HasFunctions() bool {
767
	for _, l := range p.Location {
768
		if l.Mapping != nil && !l.Mapping.HasFunctions {
769
			return false
770
		}
771
	}
772
	return true
773
}
774

775
// HasFileLines determines if all locations in this profile have
776
// symbolized file and line number information.
777
func (p *Profile) HasFileLines() bool {
778
	for _, l := range p.Location {
779
		if l.Mapping != nil && (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
780
			return false
781
		}
782
	}
783
	return true
784
}
785

786
// Unsymbolizable returns true if a mapping points to a binary for which
787
// locations can't be symbolized in principle, at least now. Examples are
788
// "[vdso]", [vsyscall]" and some others, see the code.
789
func (m *Mapping) Unsymbolizable() bool {
790
	name := filepath.Base(m.File)
791
	return strings.HasPrefix(name, "[") || strings.HasPrefix(name, "linux-vdso") || strings.HasPrefix(m.File, "/dev/dri/")
792
}
793

794
// Copy makes a fully independent copy of a profile.
795
func (p *Profile) Copy() *Profile {
796
	pp := &Profile{}
797
	if err := unmarshal(serialize(p), pp); err != nil {
798
		panic(err)
799
	}
800
	if err := pp.postDecode(); err != nil {
801
		panic(err)
802
	}
803

804
	return pp
805
}
806

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.