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 }