github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/storage/tree/pprof.go (about)

     1  package tree
     2  
     3  import (
     4  	"time"
     5  
     6  	"github.com/pyroscope-io/pyroscope/pkg/storage/metadata"
     7  )
     8  
     9  type SampleTypeConfig struct {
    10  	Units       metadata.Units           `json:"units,omitempty" yaml:"units,omitempty"`
    11  	DisplayName string                   `json:"display-name,omitempty" yaml:"display-name,omitempty"`
    12  	Aggregation metadata.AggregationType `json:"aggregation,omitempty" yaml:"aggregation,omitempty"`
    13  	Cumulative  bool                     `json:"cumulative,omitempty" yaml:"cumulative,omitempty"`
    14  	Sampled     bool                     `json:"sampled,omitempty" yaml:"sampled,omitempty"`
    15  }
    16  
    17  // DefaultSampleTypeMapping contains default settings for every
    18  // supported pprof sample type. These settings are required to build
    19  // a proper storage.PutInput payload.
    20  //
    21  // TODO(kolesnikovae): We should find a way to eliminate collisions.
    22  //
    23  //  For example, both Go 'block' and 'mutex' profiles have
    24  //  'contentions' and 'delay' sample types - this means we can't
    25  //  override display name of the profile types and they would
    26  //  be indistinguishable for the server.
    27  //
    28  //  The keys should have the following structure:
    29  //  	{origin}.{profile_type}.{sample_type}
    30  //
    31  //  Example names (can be a reserved label, e.g __type__):
    32  //    * go.cpu.samples
    33  //    * go.block.delay
    34  //    * go.mutex.delay
    35  //    * nodejs.heap.objects
    36  //
    37  // Another problem is that in pull mode we don't have spy-name,
    38  // therefore we should solve this problem first.
    39  var DefaultSampleTypeMapping = map[string]*SampleTypeConfig{
    40  	// Sample types specific to Go.
    41  	"samples": {
    42  		DisplayName: "cpu",
    43  		Units:       metadata.SamplesUnits,
    44  		Sampled:     true,
    45  	},
    46  	"inuse_objects": {
    47  		Units:       metadata.ObjectsUnits,
    48  		Aggregation: metadata.AverageAggregationType,
    49  	},
    50  	"alloc_objects": {
    51  		Units:      metadata.ObjectsUnits,
    52  		Cumulative: true,
    53  	},
    54  	"inuse_space": {
    55  		Units:       metadata.BytesUnits,
    56  		Aggregation: metadata.AverageAggregationType,
    57  	},
    58  	"alloc_space": {
    59  		Units:      metadata.BytesUnits,
    60  		Cumulative: true,
    61  	},
    62  	"goroutine": {
    63  		DisplayName: "goroutines",
    64  		Units:       metadata.GoroutinesUnits,
    65  		Aggregation: metadata.AverageAggregationType,
    66  	},
    67  	"contentions": {
    68  		// TODO(petethepig): technically block profiles have the same name
    69  		//   so this might be a block profile, need better heuristic
    70  		DisplayName: "mutex_count",
    71  		Units:       metadata.LockSamplesUnits,
    72  		Cumulative:  true,
    73  	},
    74  	"delay": {
    75  		// TODO(petethepig): technically block profiles have the same name
    76  		//   so this might be a block profile, need better heuristic
    77  		DisplayName: "mutex_duration",
    78  		Units:       metadata.LockNanosecondsUnits,
    79  		Cumulative:  true,
    80  	},
    81  }
    82  
    83  type pprof struct {
    84  	locations map[string]uint64
    85  	functions map[string]uint64
    86  	strings   map[string]int64
    87  	profile   *Profile
    88  }
    89  
    90  type PprofMetadata struct {
    91  	Type       string
    92  	Unit       string
    93  	PeriodType string
    94  	PeriodUnit string
    95  	Period     int64
    96  	StartTime  time.Time
    97  	Duration   time.Duration
    98  }
    99  
   100  func (t *Tree) Pprof(mdata *PprofMetadata) *Profile {
   101  	t.RLock()
   102  	defer t.RUnlock()
   103  
   104  	p := &pprof{
   105  		locations: make(map[string]uint64),
   106  		functions: make(map[string]uint64),
   107  		strings:   make(map[string]int64),
   108  		profile: &Profile{
   109  			StringTable: []string{""},
   110  		},
   111  	}
   112  	p.profile.Mapping = []*Mapping{{Id: 0}} // a fake mapping
   113  	p.profile.SampleType = []*ValueType{{Type: p.newString(mdata.Type), Unit: p.newString(mdata.Unit)}}
   114  	p.profile.TimeNanos = mdata.StartTime.UnixNano()
   115  	p.profile.DurationNanos = mdata.Duration.Nanoseconds()
   116  	if mdata.Period != 0 && mdata.PeriodType != "" && mdata.PeriodUnit != "" {
   117  		p.profile.Period = mdata.Period
   118  		p.profile.PeriodType = &ValueType{
   119  			Type: p.newString(mdata.PeriodType),
   120  			Unit: p.newString(mdata.PeriodUnit),
   121  		}
   122  	}
   123  	t.IterateStacks(func(name string, self uint64, stack []string) {
   124  		value := []int64{int64(self)}
   125  		loc := []uint64{}
   126  		for _, l := range stack {
   127  			loc = append(loc, p.newLocation(l))
   128  		}
   129  		sample := &Sample{LocationId: loc, Value: value}
   130  		p.profile.Sample = append(p.profile.Sample, sample)
   131  	})
   132  
   133  	return p.profile
   134  }
   135  
   136  func (p *pprof) newString(value string) int64 {
   137  	id, ok := p.strings[value]
   138  	if !ok {
   139  		id = int64(len(p.profile.StringTable))
   140  		p.profile.StringTable = append(p.profile.StringTable, value)
   141  		p.strings[value] = id
   142  	}
   143  	return id
   144  }
   145  
   146  func (p *pprof) newLocation(location string) uint64 {
   147  	id, ok := p.locations[location]
   148  	if !ok {
   149  		id = uint64(len(p.profile.Location) + 1)
   150  		newLoc := &Location{
   151  			Id:   id,
   152  			Line: []*Line{{FunctionId: p.newFunction(location)}},
   153  		}
   154  		p.profile.Location = append(p.profile.Location, newLoc)
   155  		p.locations[location] = newLoc.Id
   156  	}
   157  	return id
   158  }
   159  
   160  func (p *pprof) newFunction(function string) uint64 {
   161  	id, ok := p.functions[function]
   162  	if !ok {
   163  		id = uint64(len(p.profile.Function) + 1)
   164  		name := p.newString(function)
   165  		newFn := &Function{
   166  			Id:         id,
   167  			Name:       name,
   168  			SystemName: name,
   169  		}
   170  		p.functions[function] = id
   171  		p.profile.Function = append(p.profile.Function, newFn)
   172  	}
   173  	return id
   174  }