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 }