Home | History | Annotate | Download | only in profile
      1 // Copyright 2014 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // Package profile provides a representation of profile.proto and
      6 // methods to encode/decode profiles in this format.
      7 //
      8 // This package is only for testing runtime/pprof.
      9 // It is not used by production Go programs.
     10 package profile
     11 
     12 import (
     13 	"bytes"
     14 	"compress/gzip"
     15 	"fmt"
     16 	"io"
     17 	"io/ioutil"
     18 	"regexp"
     19 	"strings"
     20 	"time"
     21 )
     22 
     23 // Profile is an in-memory representation of profile.proto.
     24 type Profile struct {
     25 	SampleType []*ValueType
     26 	Sample     []*Sample
     27 	Mapping    []*Mapping
     28 	Location   []*Location
     29 	Function   []*Function
     30 
     31 	DropFrames string
     32 	KeepFrames string
     33 
     34 	TimeNanos     int64
     35 	DurationNanos int64
     36 	PeriodType    *ValueType
     37 	Period        int64
     38 
     39 	dropFramesX int64
     40 	keepFramesX int64
     41 	stringTable []string
     42 }
     43 
     44 // ValueType corresponds to Profile.ValueType
     45 type ValueType struct {
     46 	Type string // cpu, wall, inuse_space, etc
     47 	Unit string // seconds, nanoseconds, bytes, etc
     48 
     49 	typeX int64
     50 	unitX int64
     51 }
     52 
     53 // Sample corresponds to Profile.Sample
     54 type Sample struct {
     55 	Location []*Location
     56 	Value    []int64
     57 	Label    map[string][]string
     58 	NumLabel map[string][]int64
     59 
     60 	locationIDX []uint64
     61 	labelX      []Label
     62 }
     63 
     64 // Label corresponds to Profile.Label
     65 type Label struct {
     66 	keyX int64
     67 	// Exactly one of the two following values must be set
     68 	strX int64
     69 	numX int64 // Integer value for this label
     70 }
     71 
     72 // Mapping corresponds to Profile.Mapping
     73 type Mapping struct {
     74 	ID              uint64
     75 	Start           uint64
     76 	Limit           uint64
     77 	Offset          uint64
     78 	File            string
     79 	BuildID         string
     80 	HasFunctions    bool
     81 	HasFilenames    bool
     82 	HasLineNumbers  bool
     83 	HasInlineFrames bool
     84 
     85 	fileX    int64
     86 	buildIDX int64
     87 }
     88 
     89 // Location corresponds to Profile.Location
     90 type Location struct {
     91 	ID      uint64
     92 	Mapping *Mapping
     93 	Address uint64
     94 	Line    []Line
     95 
     96 	mappingIDX uint64
     97 }
     98 
     99 // Line corresponds to Profile.Line
    100 type Line struct {
    101 	Function *Function
    102 	Line     int64
    103 
    104 	functionIDX uint64
    105 }
    106 
    107 // Function corresponds to Profile.Function
    108 type Function struct {
    109 	ID         uint64
    110 	Name       string
    111 	SystemName string
    112 	Filename   string
    113 	StartLine  int64
    114 
    115 	nameX       int64
    116 	systemNameX int64
    117 	filenameX   int64
    118 }
    119 
    120 // Parse parses a profile and checks for its validity. The input
    121 // may be a gzip-compressed encoded protobuf or one of many legacy
    122 // profile formats which may be unsupported in the future.
    123 func Parse(r io.Reader) (*Profile, error) {
    124 	orig, err := ioutil.ReadAll(r)
    125 	if err != nil {
    126 		return nil, err
    127 	}
    128 
    129 	var p *Profile
    130 	if len(orig) >= 2 && orig[0] == 0x1f && orig[1] == 0x8b {
    131 		gz, err := gzip.NewReader(bytes.NewBuffer(orig))
    132 		if err != nil {
    133 			return nil, fmt.Errorf("decompressing profile: %v", err)
    134 		}
    135 		data, err := ioutil.ReadAll(gz)
    136 		if err != nil {
    137 			return nil, fmt.Errorf("decompressing profile: %v", err)
    138 		}
    139 		orig = data
    140 	}
    141 	if p, err = parseUncompressed(orig); err != nil {
    142 		if p, err = parseLegacy(orig); err != nil {
    143 			return nil, fmt.Errorf("parsing profile: %v", err)
    144 		}
    145 	}
    146 
    147 	if err := p.CheckValid(); err != nil {
    148 		return nil, fmt.Errorf("malformed profile: %v", err)
    149 	}
    150 	return p, nil
    151 }
    152 
    153 var errUnrecognized = fmt.Errorf("unrecognized profile format")
    154 var errMalformed = fmt.Errorf("malformed profile format")
    155 
    156 func parseLegacy(data []byte) (*Profile, error) {
    157 	parsers := []func([]byte) (*Profile, error){
    158 		parseCPU,
    159 		parseHeap,
    160 		parseGoCount, // goroutine, threadcreate
    161 		parseThread,
    162 		parseContention,
    163 	}
    164 
    165 	for _, parser := range parsers {
    166 		p, err := parser(data)
    167 		if err == nil {
    168 			p.setMain()
    169 			p.addLegacyFrameInfo()
    170 			return p, nil
    171 		}
    172 		if err != errUnrecognized {
    173 			return nil, err
    174 		}
    175 	}
    176 	return nil, errUnrecognized
    177 }
    178 
    179 func parseUncompressed(data []byte) (*Profile, error) {
    180 	p := &Profile{}
    181 	if err := unmarshal(data, p); err != nil {
    182 		return nil, err
    183 	}
    184 
    185 	if err := p.postDecode(); err != nil {
    186 		return nil, err
    187 	}
    188 
    189 	return p, nil
    190 }
    191 
    192 var libRx = regexp.MustCompile(`([.]so$|[.]so[._][0-9]+)`)
    193 
    194 // setMain scans Mapping entries and guesses which entry is main
    195 // because legacy profiles don't obey the convention of putting main
    196 // first.
    197 func (p *Profile) setMain() {
    198 	for i := 0; i < len(p.Mapping); i++ {
    199 		file := strings.TrimSpace(strings.Replace(p.Mapping[i].File, "(deleted)", "", -1))
    200 		if len(file) == 0 {
    201 			continue
    202 		}
    203 		if len(libRx.FindStringSubmatch(file)) > 0 {
    204 			continue
    205 		}
    206 		if strings.HasPrefix(file, "[") {
    207 			continue
    208 		}
    209 		// Swap what we guess is main to position 0.
    210 		tmp := p.Mapping[i]
    211 		p.Mapping[i] = p.Mapping[0]
    212 		p.Mapping[0] = tmp
    213 		break
    214 	}
    215 }
    216 
    217 // Write writes the profile as a gzip-compressed marshaled protobuf.
    218 func (p *Profile) Write(w io.Writer) error {
    219 	p.preEncode()
    220 	b := marshal(p)
    221 	zw := gzip.NewWriter(w)
    222 	defer zw.Close()
    223 	_, err := zw.Write(b)
    224 	return err
    225 }
    226 
    227 // CheckValid tests whether the profile is valid. Checks include, but are
    228 // not limited to:
    229 //   - len(Profile.Sample[n].value) == len(Profile.value_unit)
    230 //   - Sample.id has a corresponding Profile.Location
    231 func (p *Profile) CheckValid() error {
    232 	// Check that sample values are consistent
    233 	sampleLen := len(p.SampleType)
    234 	if sampleLen == 0 && len(p.Sample) != 0 {
    235 		return fmt.Errorf("missing sample type information")
    236 	}
    237 	for _, s := range p.Sample {
    238 		if len(s.Value) != sampleLen {
    239 			return fmt.Errorf("mismatch: sample has: %d values vs. %d types", len(s.Value), len(p.SampleType))
    240 		}
    241 	}
    242 
    243 	// Check that all mappings/locations/functions are in the tables
    244 	// Check that there are no duplicate ids
    245 	mappings := make(map[uint64]*Mapping, len(p.Mapping))
    246 	for _, m := range p.Mapping {
    247 		if m.ID == 0 {
    248 			return fmt.Errorf("found mapping with reserved ID=0")
    249 		}
    250 		if mappings[m.ID] != nil {
    251 			return fmt.Errorf("multiple mappings with same id: %d", m.ID)
    252 		}
    253 		mappings[m.ID] = m
    254 	}
    255 	functions := make(map[uint64]*Function, len(p.Function))
    256 	for _, f := range p.Function {
    257 		if f.ID == 0 {
    258 			return fmt.Errorf("found function with reserved ID=0")
    259 		}
    260 		if functions[f.ID] != nil {
    261 			return fmt.Errorf("multiple functions with same id: %d", f.ID)
    262 		}
    263 		functions[f.ID] = f
    264 	}
    265 	locations := make(map[uint64]*Location, len(p.Location))
    266 	for _, l := range p.Location {
    267 		if l.ID == 0 {
    268 			return fmt.Errorf("found location with reserved id=0")
    269 		}
    270 		if locations[l.ID] != nil {
    271 			return fmt.Errorf("multiple locations with same id: %d", l.ID)
    272 		}
    273 		locations[l.ID] = l
    274 		if m := l.Mapping; m != nil {
    275 			if m.ID == 0 || mappings[m.ID] != m {
    276 				return fmt.Errorf("inconsistent mapping %p: %d", m, m.ID)
    277 			}
    278 		}
    279 		for _, ln := range l.Line {
    280 			if f := ln.Function; f != nil {
    281 				if f.ID == 0 || functions[f.ID] != f {
    282 					return fmt.Errorf("inconsistent function %p: %d", f, f.ID)
    283 				}
    284 			}
    285 		}
    286 	}
    287 	return nil
    288 }
    289 
    290 // Aggregate merges the locations in the profile into equivalence
    291 // classes preserving the request attributes. It also updates the
    292 // samples to point to the merged locations.
    293 func (p *Profile) Aggregate(inlineFrame, function, filename, linenumber, address bool) error {
    294 	for _, m := range p.Mapping {
    295 		m.HasInlineFrames = m.HasInlineFrames && inlineFrame
    296 		m.HasFunctions = m.HasFunctions && function
    297 		m.HasFilenames = m.HasFilenames && filename
    298 		m.HasLineNumbers = m.HasLineNumbers && linenumber
    299 	}
    300 
    301 	// Aggregate functions
    302 	if !function || !filename {
    303 		for _, f := range p.Function {
    304 			if !function {
    305 				f.Name = ""
    306 				f.SystemName = ""
    307 			}
    308 			if !filename {
    309 				f.Filename = ""
    310 			}
    311 		}
    312 	}
    313 
    314 	// Aggregate locations
    315 	if !inlineFrame || !address || !linenumber {
    316 		for _, l := range p.Location {
    317 			if !inlineFrame && len(l.Line) > 1 {
    318 				l.Line = l.Line[len(l.Line)-1:]
    319 			}
    320 			if !linenumber {
    321 				for i := range l.Line {
    322 					l.Line[i].Line = 0
    323 				}
    324 			}
    325 			if !address {
    326 				l.Address = 0
    327 			}
    328 		}
    329 	}
    330 
    331 	return p.CheckValid()
    332 }
    333 
    334 // Print dumps a text representation of a profile. Intended mainly
    335 // for debugging purposes.
    336 func (p *Profile) String() string {
    337 
    338 	ss := make([]string, 0, len(p.Sample)+len(p.Mapping)+len(p.Location))
    339 	if pt := p.PeriodType; pt != nil {
    340 		ss = append(ss, fmt.Sprintf("PeriodType: %s %s", pt.Type, pt.Unit))
    341 	}
    342 	ss = append(ss, fmt.Sprintf("Period: %d", p.Period))
    343 	if p.TimeNanos != 0 {
    344 		ss = append(ss, fmt.Sprintf("Time: %v", time.Unix(0, p.TimeNanos)))
    345 	}
    346 	if p.DurationNanos != 0 {
    347 		ss = append(ss, fmt.Sprintf("Duration: %v", time.Duration(p.DurationNanos)))
    348 	}
    349 
    350 	ss = append(ss, "Samples:")
    351 	var sh1 string
    352 	for _, s := range p.SampleType {
    353 		sh1 = sh1 + fmt.Sprintf("%s/%s ", s.Type, s.Unit)
    354 	}
    355 	ss = append(ss, strings.TrimSpace(sh1))
    356 	for _, s := range p.Sample {
    357 		var sv string
    358 		for _, v := range s.Value {
    359 			sv = fmt.Sprintf("%s %10d", sv, v)
    360 		}
    361 		sv = sv + ": "
    362 		for _, l := range s.Location {
    363 			sv = sv + fmt.Sprintf("%d ", l.ID)
    364 		}
    365 		ss = append(ss, sv)
    366 		const labelHeader = "                "
    367 		if len(s.Label) > 0 {
    368 			ls := labelHeader
    369 			for k, v := range s.Label {
    370 				ls = ls + fmt.Sprintf("%s:%v ", k, v)
    371 			}
    372 			ss = append(ss, ls)
    373 		}
    374 		if len(s.NumLabel) > 0 {
    375 			ls := labelHeader
    376 			for k, v := range s.NumLabel {
    377 				ls = ls + fmt.Sprintf("%s:%v ", k, v)
    378 			}
    379 			ss = append(ss, ls)
    380 		}
    381 	}
    382 
    383 	ss = append(ss, "Locations")
    384 	for _, l := range p.Location {
    385 		locStr := fmt.Sprintf("%6d: %#x ", l.ID, l.Address)
    386 		if m := l.Mapping; m != nil {
    387 			locStr = locStr + fmt.Sprintf("M=%d ", m.ID)
    388 		}
    389 		if len(l.Line) == 0 {
    390 			ss = append(ss, locStr)
    391 		}
    392 		for li := range l.Line {
    393 			lnStr := "??"
    394 			if fn := l.Line[li].Function; fn != nil {
    395 				lnStr = fmt.Sprintf("%s %s:%d s=%d",
    396 					fn.Name,
    397 					fn.Filename,
    398 					l.Line[li].Line,
    399 					fn.StartLine)
    400 				if fn.Name != fn.SystemName {
    401 					lnStr = lnStr + "(" + fn.SystemName + ")"
    402 				}
    403 			}
    404 			ss = append(ss, locStr+lnStr)
    405 			// Do not print location details past the first line
    406 			locStr = "             "
    407 		}
    408 	}
    409 
    410 	ss = append(ss, "Mappings")
    411 	for _, m := range p.Mapping {
    412 		bits := ""
    413 		if m.HasFunctions {
    414 			bits = bits + "[FN]"
    415 		}
    416 		if m.HasFilenames {
    417 			bits = bits + "[FL]"
    418 		}
    419 		if m.HasLineNumbers {
    420 			bits = bits + "[LN]"
    421 		}
    422 		if m.HasInlineFrames {
    423 			bits = bits + "[IN]"
    424 		}
    425 		ss = append(ss, fmt.Sprintf("%d: %#x/%#x/%#x %s %s %s",
    426 			m.ID,
    427 			m.Start, m.Limit, m.Offset,
    428 			m.File,
    429 			m.BuildID,
    430 			bits))
    431 	}
    432 
    433 	return strings.Join(ss, "\n") + "\n"
    434 }
    435 
    436 // Merge adds profile p adjusted by ratio r into profile p. Profiles
    437 // must be compatible (same Type and SampleType).
    438 // TODO(rsilvera): consider normalizing the profiles based on the
    439 // total samples collected.
    440 func (p *Profile) Merge(pb *Profile, r float64) error {
    441 	if err := p.Compatible(pb); err != nil {
    442 		return err
    443 	}
    444 
    445 	pb = pb.Copy()
    446 
    447 	// Keep the largest of the two periods.
    448 	if pb.Period > p.Period {
    449 		p.Period = pb.Period
    450 	}
    451 
    452 	p.DurationNanos += pb.DurationNanos
    453 
    454 	p.Mapping = append(p.Mapping, pb.Mapping...)
    455 	for i, m := range p.Mapping {
    456 		m.ID = uint64(i + 1)
    457 	}
    458 	p.Location = append(p.Location, pb.Location...)
    459 	for i, l := range p.Location {
    460 		l.ID = uint64(i + 1)
    461 	}
    462 	p.Function = append(p.Function, pb.Function...)
    463 	for i, f := range p.Function {
    464 		f.ID = uint64(i + 1)
    465 	}
    466 
    467 	if r != 1.0 {
    468 		for _, s := range pb.Sample {
    469 			for i, v := range s.Value {
    470 				s.Value[i] = int64((float64(v) * r))
    471 			}
    472 		}
    473 	}
    474 	p.Sample = append(p.Sample, pb.Sample...)
    475 	return p.CheckValid()
    476 }
    477 
    478 // Compatible determines if two profiles can be compared/merged.
    479 // returns nil if the profiles are compatible; otherwise an error with
    480 // details on the incompatibility.
    481 func (p *Profile) Compatible(pb *Profile) error {
    482 	if !compatibleValueTypes(p.PeriodType, pb.PeriodType) {
    483 		return fmt.Errorf("incompatible period types %v and %v", p.PeriodType, pb.PeriodType)
    484 	}
    485 
    486 	if len(p.SampleType) != len(pb.SampleType) {
    487 		return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
    488 	}
    489 
    490 	for i := range p.SampleType {
    491 		if !compatibleValueTypes(p.SampleType[i], pb.SampleType[i]) {
    492 			return fmt.Errorf("incompatible sample types %v and %v", p.SampleType, pb.SampleType)
    493 		}
    494 	}
    495 
    496 	return nil
    497 }
    498 
    499 // HasFunctions determines if all locations in this profile have
    500 // symbolized function information.
    501 func (p *Profile) HasFunctions() bool {
    502 	for _, l := range p.Location {
    503 		if l.Mapping == nil || !l.Mapping.HasFunctions {
    504 			return false
    505 		}
    506 	}
    507 	return true
    508 }
    509 
    510 // HasFileLines determines if all locations in this profile have
    511 // symbolized file and line number information.
    512 func (p *Profile) HasFileLines() bool {
    513 	for _, l := range p.Location {
    514 		if l.Mapping == nil || (!l.Mapping.HasFilenames || !l.Mapping.HasLineNumbers) {
    515 			return false
    516 		}
    517 	}
    518 	return true
    519 }
    520 
    521 func compatibleValueTypes(v1, v2 *ValueType) bool {
    522 	if v1 == nil || v2 == nil {
    523 		return true // No grounds to disqualify.
    524 	}
    525 	return v1.Type == v2.Type && v1.Unit == v2.Unit
    526 }
    527 
    528 // Copy makes a fully independent copy of a profile.
    529 func (p *Profile) Copy() *Profile {
    530 	p.preEncode()
    531 	b := marshal(p)
    532 
    533 	pp := &Profile{}
    534 	if err := unmarshal(b, pp); err != nil {
    535 		panic(err)
    536 	}
    537 	if err := pp.postDecode(); err != nil {
    538 		panic(err)
    539 	}
    540 
    541 	return pp
    542 }
    543 
    544 // Demangler maps symbol names to a human-readable form. This may
    545 // include C++ demangling and additional simplification. Names that
    546 // are not demangled may be missing from the resulting map.
    547 type Demangler func(name []string) (map[string]string, error)
    548 
    549 // Demangle attempts to demangle and optionally simplify any function
    550 // names referenced in the profile. It works on a best-effort basis:
    551 // it will silently preserve the original names in case of any errors.
    552 func (p *Profile) Demangle(d Demangler) error {
    553 	// Collect names to demangle.
    554 	var names []string
    555 	for _, fn := range p.Function {
    556 		names = append(names, fn.SystemName)
    557 	}
    558 
    559 	// Update profile with demangled names.
    560 	demangled, err := d(names)
    561 	if err != nil {
    562 		return err
    563 	}
    564 	for _, fn := range p.Function {
    565 		if dd, ok := demangled[fn.SystemName]; ok {
    566 			fn.Name = dd
    567 		}
    568 	}
    569 	return nil
    570 }
    571 
    572 // Empty returns true if the profile contains no samples.
    573 func (p *Profile) Empty() bool {
    574 	return len(p.Sample) == 0
    575 }
    576