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