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

     1  package operations
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"net/http"
     7  	"os"
     8  	"path"
     9  	"time"
    10  
    11  	"github.com/dustin/go-humanize"
    12  	"github.com/go-kit/log"
    13  	"github.com/gorilla/mux"
    14  	"github.com/oklog/ulid/v2"
    15  	"github.com/pkg/errors"
    16  	"github.com/prometheus/common/model"
    17  
    18  	"github.com/grafana/pyroscope/pkg/objstore"
    19  	"github.com/grafana/pyroscope/pkg/phlaredb/block"
    20  	"github.com/grafana/pyroscope/pkg/phlaredb/bucket"
    21  	"github.com/grafana/pyroscope/pkg/phlaredb/bucketindex"
    22  	httputil "github.com/grafana/pyroscope/pkg/util/http"
    23  )
    24  
    25  type Handlers struct {
    26  	Bucket           objstore.Bucket
    27  	Logger           log.Logger
    28  	MaxBlockDuration time.Duration
    29  }
    30  
    31  func (h *Handlers) CreateIndexHandler() func(http.ResponseWriter, *http.Request) {
    32  	return func(w http.ResponseWriter, r *http.Request) {
    33  		users, _ := bucket.ListUsers(r.Context(), h.Bucket)
    34  		err := pageTemplates.indexTemplate.Execute(w, indexPageContent{
    35  			Users: users,
    36  			Now:   time.Now().UTC().Format(time.RFC3339),
    37  		})
    38  		if err != nil {
    39  			httputil.Error(w, err)
    40  		}
    41  	}
    42  }
    43  
    44  func (h *Handlers) CreateBlocksHandler() func(http.ResponseWriter, *http.Request) {
    45  	return func(w http.ResponseWriter, r *http.Request) {
    46  		vars := mux.Vars(r)
    47  		tenantId := vars["tenant"]
    48  		if tenantId == "" {
    49  			httputil.Error(w, errors.New("No tenant id provided"))
    50  			return
    51  		}
    52  		index, err := bucketindex.ReadIndex(r.Context(), h.Bucket, tenantId, nil, h.Logger)
    53  		if err != nil {
    54  			httputil.Error(w, err)
    55  			return
    56  		}
    57  		query := readQuery(r)
    58  		err = pageTemplates.blocksTemplate.Execute(w, blockListPageContent{
    59  			Index:          index,
    60  			User:           tenantId,
    61  			Query:          query,
    62  			Now:            time.Now().UTC().Format(time.RFC3339),
    63  			SelectedBlocks: h.filterAndGroupBlocks(index, query, time.Now()),
    64  		})
    65  		if err != nil {
    66  			httputil.Error(w, err)
    67  			return
    68  		}
    69  	}
    70  }
    71  
    72  func (h *Handlers) filterAndGroupBlocks(index *bucketindex.Index, query *blockQuery, now time.Time) *blockListResult {
    73  	queryFrom := model.TimeFromUnix(query.parsedFrom.UnixMilli() / 1000)
    74  	queryTo := model.TimeFromUnix(query.parsedTo.UnixMilli() / 1000)
    75  	blockGroupMap := make(map[time.Time]*blockGroup)
    76  	blockGroups := make([]*blockGroup, 0)
    77  
    78  	deletedBlocks := make(map[ulid.ULID]int64)
    79  	if !query.IncludeDeleted {
    80  		for _, deletionMark := range index.BlockDeletionMarks {
    81  			deletedBlocks[deletionMark.ID] = deletionMark.DeletionTime
    82  		}
    83  	}
    84  
    85  	for _, blk := range index.Blocks {
    86  		if _, deleted := deletedBlocks[blk.ID]; !deleted && blk.Within(queryFrom, queryTo) {
    87  			minTime := blk.MinTime.Time().UTC()
    88  			truncatedMinTime := minTime.Truncate(h.MaxBlockDuration)
    89  			blkGroup, ok := blockGroupMap[truncatedMinTime]
    90  			if !ok {
    91  				blkGroup = &blockGroup{
    92  					MinTime:                 truncatedMinTime,
    93  					FormattedMinTime:        truncatedMinTime.Format(time.RFC3339),
    94  					Blocks:                  make([]*blockDetails, 0),
    95  					MinTimeAge:              humanize.RelTime(blk.MinTime.Time(), now, "ago", ""),
    96  					MaxBlockDurationMinutes: int(math.Round(blk.MaxTime.Sub(blk.MinTime).Minutes())),
    97  				}
    98  				blockGroups = append(blockGroups, blkGroup)
    99  			}
   100  			blockDetails := &blockDetails{
   101  				ID:                blk.ID.String(),
   102  				MinTime:           minTime.Format(time.RFC3339),
   103  				MaxTime:           blk.MaxTime.Time().UTC().Format(time.RFC3339),
   104  				Duration:          int(math.Round(blk.MaxTime.Sub(blk.MinTime).Minutes())),
   105  				FormattedDuration: blk.MaxTime.Sub(blk.MinTime).Round(time.Minute).String(),
   106  				UploadedAt:        blk.GetUploadedAt().UTC().Format(time.RFC3339),
   107  				CompactionLevel:   blk.CompactionLevel,
   108  				CompactorShardID:  blk.CompactorShardID,
   109  			}
   110  			blkGroup.Blocks = append(blkGroup.Blocks, blockDetails)
   111  			if blockDetails.Duration > blkGroup.MaxBlockDurationMinutes {
   112  				blkGroup.MaxBlockDurationMinutes = blockDetails.Duration
   113  			}
   114  			blockGroupMap[truncatedMinTime] = blkGroup
   115  		}
   116  	}
   117  
   118  	sortBlockGroupsByMinTimeDec(blockGroups)
   119  
   120  	maxBlocksPerGroup := 0
   121  	maxBlockGroupDuration := 0
   122  	for _, blockGroup := range blockGroups {
   123  		sortBlockDetailsByMinTimeDec(blockGroup.Blocks)
   124  		if len(blockGroup.Blocks) > maxBlocksPerGroup {
   125  			maxBlocksPerGroup = len(blockGroup.Blocks)
   126  		}
   127  		if blockGroup.MaxBlockDurationMinutes > maxBlockGroupDuration {
   128  			maxBlockGroupDuration = blockGroup.MaxBlockDurationMinutes
   129  		}
   130  	}
   131  
   132  	return &blockListResult{
   133  		BlockGroups:          blockGroups,
   134  		MaxBlocksPerGroup:    maxBlocksPerGroup,
   135  		GroupDurationMinutes: maxBlockGroupDuration,
   136  	}
   137  }
   138  
   139  func (h *Handlers) CreateBlockDetailsHandler() func(http.ResponseWriter, *http.Request) {
   140  	return func(w http.ResponseWriter, r *http.Request) {
   141  		vars := mux.Vars(r)
   142  		tenantId := vars["tenant"]
   143  		if tenantId == "" {
   144  			httputil.Error(w, errors.New("No tenant id provided"))
   145  			return
   146  		}
   147  		blockId := vars["block"]
   148  		if blockId == "" {
   149  			httputil.Error(w, errors.New("No block id provided"))
   150  			return
   151  		}
   152  		bId, err := ulid.Parse(blockId)
   153  		if err != nil {
   154  			httputil.Error(w, err)
   155  			return
   156  		}
   157  
   158  		prefixedBucket := objstore.NewPrefixedBucket(h.Bucket, path.Join(tenantId, "phlaredb/"))
   159  		defer prefixedBucket.Close()
   160  
   161  		fetcher, err := block.NewMetaFetcher(h.Logger, 1, prefixedBucket, os.TempDir(), nil, nil)
   162  		if err != nil {
   163  			httputil.Error(w, err)
   164  			return
   165  		}
   166  
   167  		blockDetails := getBlockDetails(r.Context(), bId, fetcher)
   168  		if blockDetails != nil {
   169  			err = pageTemplates.blockDetailsTemplate.Execute(w, blockDetailsPageContent{
   170  				User:  tenantId,
   171  				Block: blockDetails,
   172  				Now:   time.Now().UTC().Format(time.RFC3339),
   173  			})
   174  			if err != nil {
   175  				httputil.Error(w, err)
   176  				return
   177  			}
   178  		} else {
   179  			httputil.Error(w, errors.New("Could not find block"))
   180  			return
   181  		}
   182  	}
   183  }
   184  
   185  func getBlockDetails(ctx context.Context, id ulid.ULID, fetcher *block.MetaFetcher) *blockDetails {
   186  	meta, err := fetcher.LoadMeta(ctx, id)
   187  	if err != nil {
   188  		return nil
   189  	}
   190  	var blockSize uint64
   191  	for _, f := range meta.Files {
   192  		blockSize += f.SizeBytes
   193  	}
   194  
   195  	return &blockDetails{
   196  		ID:              meta.ULID.String(),
   197  		MinTime:         meta.MinTime.Time().UTC().Format(time.RFC3339),
   198  		MaxTime:         meta.MaxTime.Time().UTC().Format(time.RFC3339),
   199  		Duration:        int(math.Round(meta.MaxTime.Sub(meta.MinTime).Minutes())),
   200  		CompactionLevel: meta.Compaction.Level,
   201  		Size:            humanize.Bytes(blockSize),
   202  		Stats:           meta.Stats,
   203  		Labels:          meta.Labels,
   204  	}
   205  }