github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/server/ingest.go (about)

     1  package server
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"github.com/go-kit/kit/log/logrus"
     7  	"io"
     8  	"net/http"
     9  	"strconv"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/pyroscope-io/pyroscope/pkg/convert/speedscope"
    14  
    15  	"github.com/go-kit/log"
    16  	"github.com/go-kit/log/level"
    17  
    18  	"github.com/pyroscope-io/pyroscope/pkg/agent/types"
    19  	"github.com/pyroscope-io/pyroscope/pkg/convert/jfr"
    20  	"github.com/pyroscope-io/pyroscope/pkg/convert/pprof"
    21  	"github.com/pyroscope-io/pyroscope/pkg/convert/profile"
    22  	"github.com/pyroscope-io/pyroscope/pkg/ingestion"
    23  	"github.com/pyroscope-io/pyroscope/pkg/server/httputils"
    24  	"github.com/pyroscope-io/pyroscope/pkg/storage/metadata"
    25  	"github.com/pyroscope-io/pyroscope/pkg/storage/segment"
    26  	"github.com/pyroscope-io/pyroscope/pkg/util/attime"
    27  )
    28  
    29  type ingestHandler struct {
    30  	log       log.Logger
    31  	ingester  ingestion.Ingester
    32  	onSuccess func(*ingestion.IngestInput)
    33  	httpUtils httputils.ErrorUtils
    34  }
    35  
    36  func (ctrl *Controller) ingestHandler() http.Handler {
    37  	return NewIngestHandler(logrus.NewLogger(ctrl.log), ctrl.ingestser, func(pi *ingestion.IngestInput) {
    38  		ctrl.StatsInc("ingest")
    39  		ctrl.StatsInc("ingest:" + pi.Metadata.SpyName)
    40  		ctrl.appStats.Add(hashString(pi.Metadata.Key.AppName()))
    41  	}, ctrl.httpUtils)
    42  }
    43  
    44  func NewIngestHandler(l log.Logger, p ingestion.Ingester, onSuccess func(*ingestion.IngestInput), httpUtils httputils.ErrorUtils) http.Handler {
    45  	return ingestHandler{
    46  		log:       l,
    47  		ingester:  p,
    48  		onSuccess: onSuccess,
    49  		httpUtils: httpUtils,
    50  	}
    51  }
    52  
    53  func (h ingestHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    54  	input, err := h.ingestInputFromRequest(r)
    55  	if err != nil {
    56  		h.httpUtils.WriteError(r, w, http.StatusBadRequest, err, "invalid parameter")
    57  		return
    58  	}
    59  
    60  	err = h.ingester.Ingest(r.Context(), input)
    61  	switch {
    62  	case err == nil:
    63  		h.onSuccess(input)
    64  	case ingestion.IsIngestionError(err):
    65  		h.httpUtils.WriteError(r, w, http.StatusInternalServerError, err, "error happened while ingesting data")
    66  	default:
    67  		h.httpUtils.WriteError(r, w, http.StatusUnprocessableEntity, err, "error happened while parsing request body")
    68  	}
    69  }
    70  
    71  func (h ingestHandler) ingestInputFromRequest(r *http.Request) (*ingestion.IngestInput, error) {
    72  	var (
    73  		q     = r.URL.Query()
    74  		input ingestion.IngestInput
    75  		err   error
    76  	)
    77  
    78  	input.Metadata.Key, err = segment.ParseKey(q.Get("name"))
    79  	if err != nil {
    80  		return nil, fmt.Errorf("name: %w", err)
    81  	}
    82  
    83  	if qt := q.Get("from"); qt != "" {
    84  		input.Metadata.StartTime = attime.Parse(qt)
    85  	} else {
    86  		input.Metadata.StartTime = time.Now()
    87  	}
    88  
    89  	if qt := q.Get("until"); qt != "" {
    90  		input.Metadata.EndTime = attime.Parse(qt)
    91  	} else {
    92  		input.Metadata.EndTime = time.Now()
    93  	}
    94  
    95  	if sr := q.Get("sampleRate"); sr != "" {
    96  		sampleRate, err := strconv.Atoi(sr)
    97  		if err != nil {
    98  			_ = level.Error(h.log).Log(
    99  				"err", err,
   100  				"msg", fmt.Sprintf("invalid sample rate: %q", sr),
   101  			)
   102  			input.Metadata.SampleRate = types.DefaultSampleRate
   103  		} else {
   104  			input.Metadata.SampleRate = uint32(sampleRate)
   105  		}
   106  	} else {
   107  		input.Metadata.SampleRate = types.DefaultSampleRate
   108  	}
   109  
   110  	if sn := q.Get("spyName"); sn != "" {
   111  		// TODO: error handling
   112  		input.Metadata.SpyName = sn
   113  	} else {
   114  		input.Metadata.SpyName = "unknown"
   115  	}
   116  
   117  	if u := q.Get("units"); u != "" {
   118  		// TODO(petethepig): add validation for these?
   119  		input.Metadata.Units = metadata.Units(u)
   120  	} else {
   121  		input.Metadata.Units = metadata.SamplesUnits
   122  	}
   123  
   124  	if at := q.Get("aggregationType"); at != "" {
   125  		// TODO(petethepig): add validation for these?
   126  		input.Metadata.AggregationType = metadata.AggregationType(at)
   127  	} else {
   128  		input.Metadata.AggregationType = metadata.SumAggregationType
   129  	}
   130  
   131  	b, err := copyBody(r)
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	format := q.Get("format")
   137  	contentType := r.Header.Get("Content-Type")
   138  	switch {
   139  	default:
   140  		input.Format = ingestion.FormatGroups
   141  	case format == "trie", contentType == "binary/octet-stream+trie":
   142  		input.Format = ingestion.FormatTrie
   143  	case format == "tree", contentType == "binary/octet-stream+tree":
   144  		input.Format = ingestion.FormatTree
   145  	case format == "lines":
   146  		input.Format = ingestion.FormatLines
   147  
   148  	case format == "jfr":
   149  		input.Format = ingestion.FormatJFR
   150  		input.Profile = &jfr.RawProfile{
   151  			FormDataContentType: contentType,
   152  			RawData:             b,
   153  		}
   154  
   155  	case format == "pprof":
   156  		input.Format = ingestion.FormatPprof
   157  		input.Profile = &pprof.RawProfile{
   158  			RawData: b,
   159  		}
   160  
   161  	case format == "speedscope":
   162  		input.Format = ingestion.FormatSpeedscope
   163  		input.Profile = &speedscope.RawProfile{
   164  			RawData: b,
   165  		}
   166  
   167  	case strings.Contains(contentType, "multipart/form-data"):
   168  		input.Profile = &pprof.RawProfile{
   169  			FormDataContentType: contentType,
   170  			RawData:             b,
   171  			StreamingParser:     true,
   172  			PoolStreamingParser: true,
   173  		}
   174  	}
   175  
   176  	if input.Profile == nil {
   177  		input.Profile = &profile.RawProfile{
   178  			Format:  input.Format,
   179  			RawData: b,
   180  		}
   181  	}
   182  
   183  	return &input, nil
   184  }
   185  
   186  func copyBody(r *http.Request) ([]byte, error) {
   187  	buf := bytes.NewBuffer(make([]byte, 0, 64<<10))
   188  	if _, err := io.Copy(buf, r.Body); err != nil {
   189  		return nil, err
   190  	}
   191  	return buf.Bytes(), nil
   192  }