github.com/grafana/pyroscope@v1.18.0/pkg/og/storage/tree/pprof.go (about)

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