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  }