github.com/grafana/pyroscope@v1.18.0/pkg/operations/v2/handlers.go (about)

     1  package v2
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"slices"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/dustin/go-humanize"
    12  	"github.com/go-kit/log"
    13  	"github.com/gorilla/mux"
    14  	"github.com/pkg/errors"
    15  	"google.golang.org/grpc"
    16  
    17  	metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1"
    18  	"github.com/grafana/pyroscope/pkg/objstore"
    19  	httputil "github.com/grafana/pyroscope/pkg/util/http"
    20  )
    21  
    22  type MetastoreClient interface {
    23  	QueryMetadata(ctx context.Context, req *metastorev1.QueryMetadataRequest, opts ...grpc.CallOption) (*metastorev1.QueryMetadataResponse, error)
    24  	GetTenants(ctx context.Context, req *metastorev1.GetTenantsRequest, opts ...grpc.CallOption) (*metastorev1.GetTenantsResponse, error)
    25  	GetBlockMetadata(ctx context.Context, req *metastorev1.GetBlockMetadataRequest, opts ...grpc.CallOption) (*metastorev1.GetBlockMetadataResponse, error)
    26  }
    27  
    28  type Handlers struct {
    29  	MetastoreClient MetastoreClient
    30  	Bucket          objstore.Bucket
    31  	Logger          log.Logger
    32  }
    33  
    34  func (h *Handlers) CreateIndexHandler() func(http.ResponseWriter, *http.Request) {
    35  	return func(w http.ResponseWriter, r *http.Request) {
    36  		resp, err := h.MetastoreClient.GetTenants(r.Context(), &metastorev1.GetTenantsRequest{})
    37  		if err != nil {
    38  			httputil.Error(w, errors.Wrap(err, "failed to get tenants"))
    39  			return
    40  		}
    41  
    42  		slices.SortFunc(resp.TenantIds, func(a, b string) int {
    43  			return strings.Compare(a, b)
    44  		})
    45  
    46  		err = pageTemplates.indexTemplate.Execute(w, indexPageContent{
    47  			Users: resp.TenantIds,
    48  			Now:   time.Now().UTC().Format(time.RFC3339),
    49  		})
    50  		if err != nil {
    51  			httputil.Error(w, err)
    52  		}
    53  	}
    54  }
    55  
    56  func (h *Handlers) CreateBlocksHandler() func(http.ResponseWriter, *http.Request) {
    57  	return func(w http.ResponseWriter, r *http.Request) {
    58  		vars := mux.Vars(r)
    59  		tenantId := vars["tenant"]
    60  		if tenantId == "" {
    61  			httputil.Error(w, errors.New("No tenant id provided"))
    62  			return
    63  		}
    64  
    65  		query := readQuery(r)
    66  		startTimeMs := query.parsedFrom.UnixMilli()
    67  		endTimeMs := query.parsedTo.UnixMilli()
    68  
    69  		metadataResp, err := h.MetastoreClient.QueryMetadata(r.Context(), &metastorev1.QueryMetadataRequest{
    70  			TenantId:  []string{tenantId},
    71  			Query:     "{}",
    72  			StartTime: startTimeMs,
    73  			EndTime:   endTimeMs,
    74  		})
    75  		if err != nil {
    76  			httputil.Error(w, errors.Wrap(err, "failed to query metadata for blocks"))
    77  			return
    78  		}
    79  
    80  		err = pageTemplates.blocksTemplate.Execute(w, blockListPageContent{
    81  			User:           tenantId,
    82  			Query:          query,
    83  			Now:            time.Now().UTC().Format(time.RFC3339),
    84  			SelectedBlocks: h.groupBlocks(metadataResp.Blocks),
    85  		})
    86  		if err != nil {
    87  			httputil.Error(w, err)
    88  			return
    89  		}
    90  	}
    91  }
    92  
    93  func (h *Handlers) groupBlocks(blocks []*metastorev1.BlockMeta) *blockListResult {
    94  	blockGroupMap := make(map[time.Time]*blockGroup)
    95  	blockGroups := make([]*blockGroup, 0)
    96  
    97  	for _, blk := range blocks {
    98  		minTime := msToTime(blk.MinTime).UTC()
    99  		maxTime := msToTime(blk.MaxTime).UTC()
   100  		truncatedMinTime := minTime.Truncate(time.Hour)
   101  
   102  		blkGroup, ok := blockGroupMap[truncatedMinTime]
   103  		if !ok {
   104  			blkGroup = &blockGroup{
   105  				MinTime:                 truncatedMinTime,
   106  				FormattedMinTime:        truncatedMinTime.Format(time.RFC3339),
   107  				Blocks:                  make([]*blockDetails, 0),
   108  				MinTimeAge:              humanize.RelTime(minTime, time.Now(), "ago", ""),
   109  				MaxBlockDurationMinutes: durationInMinutes(minTime, maxTime),
   110  			}
   111  			blockGroups = append(blockGroups, blkGroup)
   112  			blockGroupMap[truncatedMinTime] = blkGroup
   113  		}
   114  
   115  		duration := durationInMinutes(minTime, maxTime)
   116  
   117  		blockDetails := &blockDetails{
   118  			ID:                blk.Id,
   119  			MinTime:           minTime.Format(time.RFC3339),
   120  			MaxTime:           maxTime.Format(time.RFC3339),
   121  			Duration:          duration,
   122  			FormattedDuration: formatDuration(duration),
   123  			Shard:             blk.Shard,
   124  			CompactionLevel:   blk.CompactionLevel,
   125  			Size:              humanize.Bytes(blk.Size),
   126  			BlockTenant:       blk.StringTable[blk.Tenant],
   127  		}
   128  
   129  		blkGroup.Blocks = append(blkGroup.Blocks, blockDetails)
   130  		if duration > blkGroup.MaxBlockDurationMinutes {
   131  			blkGroup.MaxBlockDurationMinutes = duration
   132  		}
   133  	}
   134  
   135  	sortBlockGroupsByMinTimeDec(blockGroups)
   136  
   137  	maxBlocksPerGroup := 0
   138  	maxBlockGroupDuration := 0
   139  	for _, blockGroup := range blockGroups {
   140  		sortBlockDetailsByMinTimeDec(blockGroup.Blocks)
   141  		if len(blockGroup.Blocks) > maxBlocksPerGroup {
   142  			maxBlocksPerGroup = len(blockGroup.Blocks)
   143  		}
   144  		if blockGroup.MaxBlockDurationMinutes > maxBlockGroupDuration {
   145  			maxBlockGroupDuration = blockGroup.MaxBlockDurationMinutes
   146  		}
   147  	}
   148  
   149  	return &blockListResult{
   150  		BlockGroups:          blockGroups,
   151  		MaxBlocksPerGroup:    maxBlocksPerGroup,
   152  		GroupDurationMinutes: maxBlockGroupDuration,
   153  	}
   154  }
   155  
   156  func (h *Handlers) CreateBlockDetailsHandler() func(http.ResponseWriter, *http.Request) {
   157  	return func(w http.ResponseWriter, r *http.Request) {
   158  		vars := mux.Vars(r)
   159  		tenantId := vars["tenant"]
   160  		if tenantId == "" {
   161  			httputil.Error(w, errors.New("No tenant id provided"))
   162  			return
   163  		}
   164  		blockId := vars["block"]
   165  		if blockId == "" {
   166  			httputil.Error(w, errors.New("No block id provided"))
   167  			return
   168  		}
   169  		shardStr := r.URL.Query().Get("shard")
   170  		if shardStr == "" {
   171  			httputil.Error(w, errors.New("No shard provided"))
   172  			return
   173  		}
   174  		var shard uint32
   175  		if _, err := fmt.Sscanf(shardStr, "%d", &shard); err != nil {
   176  			httputil.Error(w, errors.Wrap(err, "invalid shard parameter"))
   177  			return
   178  		}
   179  
   180  		blockTenant := r.URL.Query().Get("block_tenant")
   181  
   182  		metadataResp, err := h.MetastoreClient.GetBlockMetadata(r.Context(), &metastorev1.GetBlockMetadataRequest{
   183  			Blocks: &metastorev1.BlockList{
   184  				Tenant: blockTenant,
   185  				Shard:  shard,
   186  				Blocks: []string{blockId},
   187  			},
   188  		})
   189  		if err != nil {
   190  			httputil.Error(w, errors.Wrap(err, "failed to get block metadata"))
   191  			return
   192  		}
   193  		if len(metadataResp.Blocks) == 0 {
   194  			httputil.Error(w, errors.New("Block not found"))
   195  			return
   196  		}
   197  
   198  		blockMeta := metadataResp.Blocks[0]
   199  
   200  		blockDetails := h.convertBlockMeta(blockMeta)
   201  		err = pageTemplates.blockDetailsTemplate.Execute(w, blockDetailsPageContent{
   202  			User:        tenantId,
   203  			Block:       blockDetails,
   204  			Shard:       shard,
   205  			BlockTenant: blockTenant,
   206  			Now:         time.Now().UTC().Format(time.RFC3339),
   207  		})
   208  		if err != nil {
   209  			httputil.Error(w, err)
   210  			return
   211  		}
   212  	}
   213  }
   214  
   215  func (h *Handlers) convertBlockMeta(meta *metastorev1.BlockMeta) *blockDetails {
   216  	minTime := msToTime(meta.MinTime).UTC()
   217  	maxTime := msToTime(meta.MaxTime).UTC()
   218  	duration := durationInMinutes(minTime, maxTime)
   219  
   220  	datasets := make([]datasetDetails, 0, len(meta.Datasets))
   221  	for _, ds := range meta.Datasets {
   222  		datasets = append(datasets, h.convertDataset(ds, meta.StringTable))
   223  	}
   224  
   225  	return &blockDetails{
   226  		ID:                meta.Id,
   227  		MinTime:           minTime.Format(time.RFC3339),
   228  		MaxTime:           maxTime.Format(time.RFC3339),
   229  		Duration:          duration,
   230  		FormattedDuration: formatDuration(duration),
   231  		Shard:             meta.Shard,
   232  		CompactionLevel:   meta.CompactionLevel,
   233  		Size:              humanize.Bytes(meta.Size),
   234  		Datasets:          datasets,
   235  	}
   236  }