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 }