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 }