github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/convert/pprof/profile.go (about) 1 package pprof 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "github.com/pyroscope-io/pyroscope/pkg/convert/pprof/streaming" 8 "github.com/pyroscope-io/pyroscope/pkg/stackbuilder" 9 "io" 10 "mime/multipart" 11 "sync" 12 13 "github.com/pyroscope-io/pyroscope/pkg/ingestion" 14 "github.com/pyroscope-io/pyroscope/pkg/storage" 15 "github.com/pyroscope-io/pyroscope/pkg/storage/tree" 16 "github.com/pyroscope-io/pyroscope/pkg/util/form" 17 ) 18 19 type RawProfile struct { 20 // parser is stateful: it holds parsed previous profile 21 // which is necessary for cumulative profiles that require 22 // two consecutive profiles. 23 parser ParserInterface 24 // References the next profile in the sequence (cumulative type only). 25 next *RawProfile 26 27 m sync.Mutex 28 // Initializes lazily on Bytes, if not present. 29 RawData []byte // Represents raw request body as per ingestion API. 30 FormDataContentType string // Set optionally, if RawData is multipart form. 31 // Initializes lazily on Parse, if not present. 32 Profile []byte // Represents raw pprof data. 33 PreviousProfile []byte // Used for cumulative type only. 34 SkipExemplars bool 35 StreamingParser bool 36 PoolStreamingParser bool 37 ArenasEnabled bool 38 SampleTypeConfig map[string]*tree.SampleTypeConfig 39 } 40 41 func (p *RawProfile) ContentType() string { 42 if p.FormDataContentType == "" { 43 return "binary/octet-stream" 44 } 45 return p.FormDataContentType 46 } 47 48 // Push loads data from profile to RawProfile making it eligible for 49 // Bytes and Parse calls. 50 // 51 // Returned RawProfile should be used at the next Push: the method 52 // established relationship between these two RawProfiles in order 53 // to propagate internal pprof parser state lazily on a successful 54 // Parse call. This is necessary for cumulative profiles that require 55 // two consecutive samples to calculate the diff. If parser is not 56 // present due to a failure, or sequence violation, the profiles will 57 // be re-parsed. 58 func (p *RawProfile) Push(profile []byte, cumulative bool) *RawProfile { 59 p.m.Lock() 60 p.Profile = profile 61 p.RawData = nil 62 n := &RawProfile{ 63 SampleTypeConfig: p.SampleTypeConfig, 64 } 65 if cumulative { 66 // N.B the parser state is only propagated 67 // after successful Parse call. 68 n.PreviousProfile = p.Profile 69 p.next = n 70 } 71 p.m.Unlock() 72 return p.next 73 } 74 75 const ( 76 formFieldProfile, formFileProfile = "profile", "profile.pprof" 77 formFieldPreviousProfile, formFilePreviousProfile = "prev_profile", "profile.pprof" 78 formFieldSampleTypeConfig, formFileSampleTypeConfig = "sample_type_config", "sample_type_config.json" 79 ) 80 81 func (p *RawProfile) Bytes() ([]byte, error) { 82 p.m.Lock() 83 defer p.m.Unlock() 84 if p.RawData != nil { 85 // RawProfile was initialized with RawData or 86 // Bytes has been already called. 87 return p.RawData, nil 88 } 89 // Build multipart form. 90 if len(p.Profile) == 0 && len(p.PreviousProfile) == 0 { 91 return nil, nil 92 } 93 var b bytes.Buffer 94 mw := multipart.NewWriter(&b) 95 ff, err := mw.CreateFormFile(formFieldProfile, formFileProfile) 96 if err != nil { 97 return nil, err 98 } 99 _, _ = io.Copy(ff, bytes.NewReader(p.Profile)) 100 if len(p.PreviousProfile) > 0 { 101 if ff, err = mw.CreateFormFile(formFieldPreviousProfile, formFilePreviousProfile); err != nil { 102 return nil, err 103 } 104 _, _ = io.Copy(ff, bytes.NewReader(p.PreviousProfile)) 105 } 106 if len(p.SampleTypeConfig) > 0 { 107 if ff, err = mw.CreateFormFile(formFieldSampleTypeConfig, formFileSampleTypeConfig); err != nil { 108 return nil, err 109 } 110 _ = json.NewEncoder(ff).Encode(p.SampleTypeConfig) 111 } 112 _ = mw.Close() 113 p.RawData = b.Bytes() 114 p.FormDataContentType = mw.FormDataContentType() 115 return p.RawData, nil 116 } 117 118 func (p *RawProfile) Parse(ctx context.Context, putter storage.Putter, _ storage.MetricsExporter, md ingestion.Metadata) error { 119 p.m.Lock() 120 defer p.m.Unlock() 121 cont, err := p.handleRawData() 122 if err != nil || !cont { 123 return err 124 } 125 126 if p.parser == nil { 127 sampleTypes := p.getSampleTypes() 128 if p.StreamingParser { 129 config := streaming.ParserConfig{ 130 SpyName: md.SpyName, 131 Labels: md.Key.Labels(), 132 Putter: putter, 133 SampleTypes: sampleTypes, 134 Formatter: streaming.StackFrameFormatterForSpyName(md.SpyName), 135 } 136 if p.PoolStreamingParser { 137 parser := streaming.VTStreamingParserFromPool(config) 138 p.parser = parser 139 defer func() { 140 parser.ResetCache() 141 parser.ReturnToPool() 142 p.parser = nil 143 }() 144 } else { 145 if p.ArenasEnabled { 146 config.ArenasEnabled = true 147 } 148 parser := streaming.NewStreamingParser(config) 149 p.parser = parser 150 if p.ArenasEnabled { 151 defer parser.FreeArena() 152 } 153 } 154 } else { 155 p.parser = NewParser(ParserConfig{ 156 SpyName: md.SpyName, 157 Labels: md.Key.Labels(), 158 Putter: putter, 159 SampleTypes: sampleTypes, 160 SkipExemplars: p.SkipExemplars, 161 StackFrameFormatter: StackFrameFormatterForSpyName(md.SpyName), 162 }) 163 } 164 165 if p.PreviousProfile != nil { 166 // Ignore non-cumulative samples from the PreviousProfile 167 // to avoid duplicates: although, presence of PreviousProfile 168 // tells that there are cumulative sample types, it may also 169 // include regular ones. 170 cumulativeOnly := true 171 if err := p.parser.ParsePprof(ctx, md.StartTime, md.EndTime, p.PreviousProfile, cumulativeOnly); err != nil { 172 return err 173 } 174 } 175 } 176 177 if err := p.parser.ParsePprof(ctx, md.StartTime, md.EndTime, p.Profile, false); err != nil { 178 return err 179 } 180 181 // Propagate parser to the next profile, if it is present. 182 if p.next != nil { 183 p.next.m.Lock() 184 p.next.parser = p.parser 185 p.next.m.Unlock() 186 } 187 188 return nil 189 } 190 191 func (p *RawProfile) ParseWithWriteBatch(c context.Context, wb stackbuilder.WriteBatchFactory, md ingestion.Metadata) error { 192 cont, err := p.handleRawData() 193 if err != nil || !cont { 194 return err 195 } 196 parser := streaming.NewStreamingParser(streaming.ParserConfig{ 197 SpyName: md.SpyName, 198 Labels: md.Key.Labels(), 199 SampleTypes: p.getSampleTypes(), 200 Formatter: streaming.StackFrameFormatterForSpyName(md.SpyName), 201 ArenasEnabled: true, 202 }) 203 defer parser.FreeArena() 204 return parser.ParseWithWriteBatch(streaming.ParseWriteBatchInput{ 205 Context: c, 206 StartTime: md.StartTime, EndTime: md.EndTime, 207 Profile: p.Profile, Previous: p.PreviousProfile, 208 WriteBatchFactory: wb, 209 }) 210 } 211 212 func (p *RawProfile) getSampleTypes() map[string]*tree.SampleTypeConfig { 213 sampleTypes := tree.DefaultSampleTypeMapping 214 if p.SampleTypeConfig != nil { 215 sampleTypes = p.SampleTypeConfig 216 } 217 return sampleTypes 218 } 219 220 func (p *RawProfile) handleRawData() (cont bool, err error) { 221 if len(p.Profile) == 0 && len(p.PreviousProfile) == 0 { 222 // Check if RawProfile was initialized with RawData. 223 if p.RawData == nil { 224 // Zero profile, nothing to parse. 225 return false, nil 226 } 227 if p.FormDataContentType != "" { 228 // The profile was ingested as a multipart form. Load parts to 229 // Profile, PreviousProfile, and SampleTypeConfig. 230 if err := p.loadPprofFromForm(); err != nil { 231 return false, err 232 } 233 } else { 234 p.Profile = p.RawData 235 } 236 } 237 if len(p.Profile) == 0 { 238 return false, nil 239 } 240 return true, nil 241 } 242 243 func (p *RawProfile) loadPprofFromForm() error { 244 boundary, err := form.ParseBoundary(p.FormDataContentType) 245 if err != nil { 246 return err 247 } 248 249 f, err := multipart.NewReader(bytes.NewReader(p.RawData), boundary).ReadForm(32 << 20) 250 if err != nil { 251 return err 252 } 253 defer func() { 254 _ = f.RemoveAll() 255 }() 256 257 p.Profile, err = form.ReadField(f, formFieldProfile) 258 if err != nil { 259 return err 260 } 261 p.PreviousProfile, err = form.ReadField(f, formFieldPreviousProfile) 262 if err != nil { 263 return err 264 } 265 266 r, err := form.ReadField(f, formFieldSampleTypeConfig) 267 if err != nil || r == nil { 268 return err 269 } 270 var config map[string]*tree.SampleTypeConfig 271 if err = json.Unmarshal(r, &config); err != nil { 272 return err 273 } 274 p.SampleTypeConfig = config 275 return nil 276 }