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  }