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 }