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

     1  package server
     2  
     3  import (
     4  	"fmt"
     5  	"net/http"
     6  	"strconv"
     7  
     8  	"github.com/pyroscope-io/pyroscope/pkg/flameql"
     9  	"github.com/pyroscope-io/pyroscope/pkg/storage"
    10  	"github.com/pyroscope-io/pyroscope/pkg/structs/flamebearer"
    11  )
    12  
    13  type queryExemplarsParams struct {
    14  	input    storage.QueryExemplarsInput
    15  	maxNodes int
    16  	format   string
    17  }
    18  
    19  type queryExemplarsResponse struct {
    20  	flamebearer.FlamebearerProfile
    21  	Metadata queryExemplarsMetadataResponse `json:"metadata"`
    22  }
    23  
    24  type queryExemplarsMetadataResponse struct {
    25  	flamebearer.FlamebearerMetadataV1
    26  	AppName   string `json:"appName"`
    27  	StartTime int64  `json:"startTime"`
    28  	EndTime   int64  `json:"endTime"`
    29  	Query     string `json:"query"`
    30  	MaxNodes  int    `json:"maxNodes"`
    31  }
    32  
    33  func (h ExemplarsHandler) QueryExemplars(w http.ResponseWriter, r *http.Request) {
    34  	var p queryExemplarsParams
    35  	if err := h.queryExemplarsParamsFromRequest(r, &p); err != nil {
    36  		h.HTTPUtils.WriteInvalidParameterError(r, w, err)
    37  		return
    38  	}
    39  
    40  	out, err := h.ExemplarsQuerier.QueryExemplars(r.Context(), p.input)
    41  	if err != nil {
    42  		h.HTTPUtils.WriteInternalServerError(r, w, err, "failed to retrieve data")
    43  		return
    44  	}
    45  
    46  	flame := flamebearer.NewProfile(flamebearer.ProfileConfig{
    47  		MaxNodes:  p.maxNodes,
    48  		Metadata:  out.Metadata,
    49  		Tree:      out.Tree,
    50  		Heatmap:   h.HeatmapBuilder.BuildFromSketch(out.HeatmapSketch),
    51  		Telemetry: out.Telemetry,
    52  	})
    53  
    54  	md := queryExemplarsMetadataResponse{
    55  		FlamebearerMetadataV1: flame.Metadata,
    56  		AppName:               p.input.Query.AppName,
    57  		StartTime:             p.input.StartTime.Unix(),
    58  		EndTime:               p.input.EndTime.Unix(),
    59  		Query:                 p.input.Query.String(),
    60  		MaxNodes:              p.maxNodes,
    61  	}
    62  
    63  	h.StatsReceiver.StatsInc("exemplars-query")
    64  	h.HTTPUtils.WriteResponseJSON(r, w, queryExemplarsResponse{
    65  		FlamebearerProfile: flame,
    66  		Metadata:           md,
    67  	})
    68  }
    69  
    70  func (h ExemplarsHandler) queryExemplarsParamsFromRequest(r *http.Request, p *queryExemplarsParams) (err error) {
    71  	v := r.URL.Query()
    72  	if p.input.Query, err = flameql.ParseQuery(v.Get("query")); err != nil {
    73  		return fmt.Errorf("query: %w", err)
    74  	}
    75  
    76  	p.input.StartTime = parseTimeFallback(v.Get("startTime"), v.Get("from"))
    77  	p.input.EndTime = parseTimeFallback(v.Get("endTime"), v.Get("until"))
    78  
    79  	p.input.HeatmapParams.StartTime = p.input.StartTime
    80  	p.input.HeatmapParams.EndTime = p.input.EndTime
    81  	if p.input.HeatmapParams.MinValue, err = parseNumber(v.Get("minValue"), false); err != nil {
    82  		return fmt.Errorf("can't parse minValue: %w", err)
    83  	}
    84  	if p.input.HeatmapParams.MaxValue, err = parseNumber(v.Get("maxValue"), true); err != nil {
    85  		return fmt.Errorf("can't parse maxValue: %w", err)
    86  	}
    87  	if heatmapTimeBuckets := v.Get("heatmapTimeBuckets"); heatmapTimeBuckets != "" {
    88  		if p.input.HeatmapParams.TimeBuckets, err = strconv.ParseInt(heatmapTimeBuckets, 10, 64); err != nil {
    89  			return fmt.Errorf("can't parse heatmapTimeBuckets: %w", err)
    90  		}
    91  	}
    92  	if heatmapValueBuckets := v.Get("heatmapValueBuckets"); heatmapValueBuckets != "" {
    93  		if p.input.HeatmapParams.ValueBuckets, err = strconv.ParseInt(heatmapValueBuckets, 10, 64); err != nil {
    94  			return fmt.Errorf("can't parse heatmapValueBuckets: %w", err)
    95  		}
    96  	}
    97  
    98  	p.input.ExemplarsSelection.StartTime = parseTime(v.Get("selectionStartTime"))
    99  	p.input.ExemplarsSelection.EndTime = parseTime(v.Get("selectionEndTime"))
   100  	if p.input.ExemplarsSelection.MinValue, err = parseNumber(v.Get("selectionMinValue"), false); err != nil {
   101  		return fmt.Errorf("can't parse selectionMinValue: %w", err)
   102  	}
   103  	if p.input.ExemplarsSelection.MaxValue, err = parseNumber(v.Get("selectionMaxValue"), true); err != nil {
   104  		return fmt.Errorf("can't parse selectionMaxValue: %w", err)
   105  	}
   106  
   107  	if p.input.HeatmapParams.TimeBuckets == 0 && p.input.HeatmapParams.ValueBuckets == 0 {
   108  		p.input.StartTime = p.input.ExemplarsSelection.StartTime
   109  		p.input.EndTime = p.input.ExemplarsSelection.EndTime
   110  	}
   111  
   112  	p.maxNodes = h.MaxNodesDefault
   113  	if newMaxNodes, ok := MaxNodesFromContext(r.Context()); ok {
   114  		p.maxNodes = newMaxNodes
   115  	}
   116  	var x int
   117  	if x, err = strconv.Atoi(v.Get("max-nodes")); err == nil && x != 0 {
   118  		p.maxNodes = x
   119  	}
   120  	if x, err = strconv.Atoi(v.Get("maxNodes")); err == nil && x != 0 {
   121  		p.maxNodes = x
   122  	}
   123  
   124  	return nil
   125  }