github.com/pyroscope-io/pyroscope@v0.37.3-0.20230725203016-5f6947968bd0/pkg/api/adhoc.go (about) 1 package api 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "time" 11 12 "github.com/gorilla/mux" 13 "github.com/pyroscope-io/pyroscope/pkg/model" 14 "github.com/pyroscope-io/pyroscope/pkg/server/httputils" 15 "github.com/pyroscope-io/pyroscope/pkg/storage/metadata" 16 "github.com/pyroscope-io/pyroscope/pkg/structs/flamebearer" 17 "github.com/pyroscope-io/pyroscope/pkg/structs/flamebearer/convert" 18 ) 19 20 //go:generate mockgen -destination mocks/adhock.go -package mocks . AdhocService 21 22 type AdhocService interface { 23 // GetProfileByID retrieves profile with the given ID. 24 GetProfileByID(ctx context.Context, id string) (*flamebearer.FlamebearerProfile, error) 25 // GetAllProfiles lists all the known profiles. 26 GetAllProfiles(context.Context) ([]model.AdhocProfile, error) 27 // GetProfileDiffByID retrieves two profiles identified by their IDs and builds the profile diff. 28 GetProfileDiffByID(context.Context, model.GetAdhocProfileDiffByIDParams) (*flamebearer.FlamebearerProfile, error) 29 // UploadProfile stores the profile provided and returns the entity created. 30 UploadProfile(context.Context, model.UploadAdhocProfileParams) (p *flamebearer.FlamebearerProfile, id string, err error) 31 } 32 33 type AdhocHandler struct { 34 adhocService AdhocService 35 httpUtils httputils.Utils 36 maxBodySize int64 37 maxNodes int 38 } 39 40 func NewAdhocHandler(adhocService AdhocService, httpUtils httputils.Utils, maxBodySize int64) AdhocHandler { 41 return AdhocHandler{ 42 adhocService: adhocService, 43 httpUtils: httpUtils, 44 maxBodySize: maxBodySize, 45 } 46 } 47 48 type adhocProfile struct { 49 ID string `json:"id"` 50 Name string `json:"name"` 51 UpdatedAt time.Time `json:"updatedAt"` 52 } 53 54 type buildProfileDiffRequest struct { 55 Base *flamebearer.FlamebearerProfile `json:"base"` 56 Diff *flamebearer.FlamebearerProfile `json:"diff"` 57 } 58 59 type adhocUploadRequest struct { 60 Filename string `json:"filename"` 61 Profile []byte `json:"profile"` 62 Type string `json:"type"` 63 TypeData adhocProfileTypeData `json:"fileTypeData"` 64 } 65 66 type adhocProfileTypeData struct { 67 SpyName string `json:"spyName"` 68 Units string `json:"units"` 69 } 70 71 type adhocUploadResponse struct { 72 ID string `json:"id"` 73 Flamebearer *flamebearer.FlamebearerProfile `json:"flamebearer"` 74 } 75 76 func flamebearerFileFromAdhocRequest(req adhocUploadRequest) convert.ProfileFile { 77 return convert.ProfileFile{ 78 Name: req.Filename, 79 Data: req.Profile, 80 Type: convert.ProfileFileType(req.Type), 81 TypeData: convert.ProfileFileTypeData{ 82 SpyName: req.TypeData.SpyName, 83 Units: metadata.Units(req.TypeData.Units), 84 }, 85 } 86 } 87 88 func adhocProfileFromModel(m model.AdhocProfile) adhocProfile { 89 return adhocProfile{ 90 ID: m.ID, 91 Name: m.Name, 92 UpdatedAt: m.UpdatedAt, 93 } 94 } 95 96 func (h AdhocHandler) GetProfile(w http.ResponseWriter, r *http.Request) { 97 id := mux.Vars(r)["id"] 98 p, err := h.adhocService.GetProfileByID(r.Context(), id) 99 if err != nil { 100 h.httpUtils.HandleError(r, w, err) 101 return 102 } 103 h.httpUtils.MustJSON(r, w, p) 104 } 105 106 func (h AdhocHandler) GetProfiles(w http.ResponseWriter, r *http.Request) { 107 profiles, err := h.adhocService.GetAllProfiles(r.Context()) 108 if err != nil { 109 h.httpUtils.HandleError(r, w, err) 110 return 111 } 112 resp := make(map[string]adhocProfile, len(profiles)) 113 for _, p := range profiles { 114 resp[p.ID] = adhocProfileFromModel(p) 115 } 116 h.httpUtils.MustJSON(r, w, resp) 117 } 118 119 func (h AdhocHandler) GetProfileDiff(w http.ResponseWriter, r *http.Request) { 120 p, err := h.adhocService.GetProfileDiffByID(r.Context(), model.GetAdhocProfileDiffByIDParams{ 121 BaseID: mux.Vars(r)["left"], 122 DiffID: mux.Vars(r)["right"], 123 }) 124 if err != nil { 125 h.httpUtils.HandleError(r, w, err) 126 return 127 } 128 h.httpUtils.MustJSON(r, w, p) 129 } 130 131 func (h AdhocHandler) Upload(w http.ResponseWriter, r *http.Request) { 132 if h.maxBodySize > 0 { 133 r.Body = &MaxBytesReader{http.MaxBytesReader(w, r.Body, h.maxBodySize)} 134 } 135 var req adhocUploadRequest 136 if err := json.NewDecoder(r.Body).Decode(&req); err != nil { 137 h.httpUtils.HandleError(r, w, httputils.JSONError{Err: err}) 138 return 139 } 140 params := model.UploadAdhocProfileParams{ 141 Profile: flamebearerFileFromAdhocRequest(req), 142 } 143 p, id, err := h.adhocService.UploadProfile(r.Context(), params) 144 if err != nil { 145 h.httpUtils.HandleError(r, w, err) 146 return 147 } 148 h.httpUtils.MustJSON(r, w, adhocUploadResponse{ 149 ID: id, 150 Flamebearer: p, 151 }) 152 } 153 154 type MaxBytesReader struct { 155 r io.ReadCloser 156 } 157 158 func (m MaxBytesReader) Read(p []byte) (n int, err error) { 159 n, err = m.r.Read(p) 160 if err != nil { 161 targetErr := &http.MaxBytesError{} 162 if errors.As(err, &targetErr) { 163 err = fmt.Errorf("profile too large, max size is %d bytes", targetErr.Limit) 164 } 165 } 166 return n, err 167 } 168 169 func (m *MaxBytesReader) Close() error { 170 return m.r.Close() 171 }