github.com/grafana/pyroscope@v1.18.0/pkg/operations/v2/dataset_handlers.go (about) 1 package v2 2 3 import ( 4 "context" 5 "fmt" 6 "net/http" 7 "time" 8 9 "github.com/dustin/go-humanize" 10 "github.com/gorilla/mux" 11 "github.com/pkg/errors" 12 13 metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" 14 "github.com/grafana/pyroscope/pkg/block" 15 "github.com/grafana/pyroscope/pkg/block/metadata" 16 phlaremodel "github.com/grafana/pyroscope/pkg/model" 17 "github.com/grafana/pyroscope/pkg/phlaredb" 18 schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1" 19 "github.com/grafana/pyroscope/pkg/phlaredb/tsdb/index" 20 httputil "github.com/grafana/pyroscope/pkg/util/http" 21 ) 22 23 func (h *Handlers) CreateDatasetDetailsHandler() func(http.ResponseWriter, *http.Request) { 24 return func(w http.ResponseWriter, r *http.Request) { 25 vars := mux.Vars(r) 26 tenantId := vars["tenant"] 27 if tenantId == "" { 28 httputil.Error(w, errors.New("No tenant id provided")) 29 return 30 } 31 blockId := vars["block"] 32 if blockId == "" { 33 httputil.Error(w, errors.New("No block id provided")) 34 return 35 } 36 datasetName := r.URL.Query().Get("dataset") 37 if datasetName == "" { 38 httputil.Error(w, errors.New("No dataset name provided")) 39 return 40 } 41 // Handle special case for empty dataset name 42 if datasetName == "_empty" { 43 datasetName = "" 44 } 45 shardStr := r.URL.Query().Get("shard") 46 if shardStr == "" { 47 httputil.Error(w, errors.New("No shard provided")) 48 return 49 } 50 var shard uint32 51 if _, err := fmt.Sscanf(shardStr, "%d", &shard); err != nil { 52 httputil.Error(w, errors.Wrap(err, "invalid shard parameter")) 53 return 54 } 55 56 blockTenant := r.URL.Query().Get("block_tenant") 57 58 metadataResp, err := h.MetastoreClient.GetBlockMetadata(r.Context(), &metastorev1.GetBlockMetadataRequest{ 59 Blocks: &metastorev1.BlockList{ 60 Tenant: blockTenant, 61 Shard: shard, 62 Blocks: []string{blockId}, 63 }, 64 }) 65 if err != nil { 66 httputil.Error(w, errors.Wrap(err, "failed to get block metadata")) 67 return 68 } 69 70 if len(metadataResp.Blocks) == 0 { 71 httputil.Error(w, errors.New("Block not found")) 72 return 73 } 74 75 blockMeta := metadataResp.Blocks[0] 76 77 var foundDataset *metastorev1.Dataset 78 for _, ds := range blockMeta.Datasets { 79 dsName := blockMeta.StringTable[ds.Name] 80 if dsName == datasetName { 81 foundDataset = ds 82 break 83 } 84 } 85 86 if foundDataset == nil { 87 httputil.Error(w, errors.New("Dataset not found")) 88 return 89 } 90 91 dataset := h.convertDataset(foundDataset, blockMeta.StringTable) 92 93 err = pageTemplates.datasetDetailsTemplate.Execute(w, datasetDetailsPageContent{ 94 User: tenantId, 95 BlockID: blockId, 96 Shard: shard, 97 BlockTenant: blockTenant, 98 Dataset: &dataset, 99 Now: time.Now().UTC().Format(time.RFC3339), 100 }) 101 if err != nil { 102 httputil.Error(w, err) 103 return 104 } 105 } 106 } 107 108 func (h *Handlers) convertDataset(ds *metastorev1.Dataset, stringTable []string) datasetDetails { 109 tenant := stringTable[ds.Tenant] 110 datasetName := stringTable[ds.Name] 111 112 var labelSets []labelSet 113 pairs := metadata.LabelPairs(ds.Labels) 114 for pairs.Next() { 115 p := pairs.At() 116 var currentSet labelSet 117 for len(p) > 0 { 118 if len(p) >= 2 { 119 key := stringTable[p[0]] 120 val := stringTable[p[1]] 121 currentSet.Pairs = append(currentSet.Pairs, labelPair{Key: key, Value: val}) 122 p = p[2:] 123 } else { 124 break 125 } 126 } 127 if len(currentSet.Pairs) > 0 { 128 labelSets = append(labelSets, currentSet) 129 } 130 } 131 132 var profilesSize, indexSize, symbolsSize uint64 133 if len(ds.TableOfContents) >= 3 { 134 profilesSize = ds.TableOfContents[1] - ds.TableOfContents[0] 135 indexSize = ds.TableOfContents[2] - ds.TableOfContents[1] 136 symbolsSize = (ds.TableOfContents[0] + ds.Size) - ds.TableOfContents[2] 137 } 138 139 var profilesPercentage, indexPercentage, symbolsPercentage float64 140 if ds.Size > 0 { 141 profilesPercentage = (float64(profilesSize) / float64(ds.Size)) * 100 142 indexPercentage = (float64(indexSize) / float64(ds.Size)) * 100 143 symbolsPercentage = (float64(symbolsSize) / float64(ds.Size)) * 100 144 } 145 146 return datasetDetails{ 147 Tenant: tenant, 148 Name: datasetName, 149 MinTime: msToTime(ds.MinTime).UTC().Format(time.RFC3339), 150 MaxTime: msToTime(ds.MaxTime).UTC().Format(time.RFC3339), 151 Size: humanize.Bytes(ds.Size), 152 ProfilesSize: humanize.Bytes(profilesSize), 153 IndexSize: humanize.Bytes(indexSize), 154 SymbolsSize: humanize.Bytes(symbolsSize), 155 ProfilesPercentage: profilesPercentage, 156 IndexPercentage: indexPercentage, 157 SymbolsPercentage: symbolsPercentage, 158 LabelSets: labelSets, 159 } 160 } 161 162 func (h *Handlers) CreateDatasetTSDBIndexHandler() func(http.ResponseWriter, *http.Request) { 163 return func(w http.ResponseWriter, r *http.Request) { 164 req, err := parseDatasetRequest(r) 165 if err != nil { 166 httputil.Error(w, err) 167 return 168 } 169 170 blockMeta, foundDataset, err := h.getDatasetMetadata(r.Context(), req) 171 if err != nil { 172 httputil.Error(w, err) 173 return 174 } 175 176 dataset := h.convertDataset(foundDataset, blockMeta.StringTable) 177 178 TSDBIndex, err := h.readTSDBIndex(r.Context(), blockMeta, foundDataset) 179 if err != nil { 180 httputil.Error(w, errors.Wrap(err, "failed to read TSDB index")) 181 return 182 } 183 184 err = pageTemplates.datasetIndexTemplate.Execute(w, datasetIndexPageContent{ 185 User: req.TenantID, 186 BlockID: req.BlockID, 187 Shard: req.Shard, 188 BlockTenant: req.BlockTenant, 189 Dataset: &dataset, 190 TSDBIndex: TSDBIndex, 191 Now: time.Now().UTC().Format(time.RFC3339), 192 }) 193 if err != nil { 194 httputil.Error(w, err) 195 return 196 } 197 } 198 } 199 200 func (h *Handlers) readTSDBIndex(ctx context.Context, blockMeta *metastorev1.BlockMeta, dataset *metastorev1.Dataset) (*tsdbIndexInfo, error) { 201 obj := block.NewObject(h.Bucket, blockMeta) 202 if err := obj.Open(ctx); err != nil { 203 return nil, fmt.Errorf("failed to open block object: %w", err) 204 } 205 defer obj.Close() 206 207 ds := block.NewDataset(dataset, obj) 208 if err := ds.Open(ctx, block.SectionTSDB); err != nil { 209 return nil, fmt.Errorf("failed to open dataset: %w", err) 210 } 211 defer ds.Close() 212 213 idx := ds.Index() 214 215 from, through := idx.Bounds() 216 fromTime := time.Unix(0, from).UTC().Format(time.RFC3339) 217 throughTime := time.Unix(0, through).UTC().Format(time.RFC3339) 218 219 labels, err := h.getIndexLabels(idx) 220 if err != nil { 221 return nil, fmt.Errorf("failed to get labels: %w", err) 222 } 223 224 symbolIter := idx.Symbols() 225 var symbols []string 226 for symbolIter.Next() { 227 symbols = append(symbols, symbolIter.At()) 228 } 229 if err := symbolIter.Err(); err != nil { 230 return nil, fmt.Errorf("failed to iterate symbols: %w", err) 231 } 232 233 series, err := h.getIndexSeries(idx) 234 if err != nil { 235 return nil, fmt.Errorf("failed to get series: %w", err) 236 } 237 238 return &tsdbIndexInfo{ 239 From: fromTime, 240 Through: throughTime, 241 Checksum: idx.Checksum(), 242 Series: series, 243 Symbols: symbols, 244 LabelValueSets: labels, 245 }, nil 246 } 247 248 func (h *Handlers) getIndexLabels(idx phlaredb.IndexReader) ([]labelValueSet, error) { 249 labelNames, err := idx.LabelNames() 250 if err != nil { 251 return nil, fmt.Errorf("failed to get label names: %w", err) 252 } 253 var labelValueSets []labelValueSet 254 for _, labelName := range labelNames { 255 values, err := idx.LabelValues(labelName) 256 if err != nil { 257 return nil, fmt.Errorf("failed to get label values for %s: %w", labelName, err) 258 } 259 260 labelValueSets = append(labelValueSets, labelValueSet{ 261 LabelName: labelName, 262 LabelValues: values, 263 }) 264 } 265 return labelValueSets, nil 266 } 267 268 func (h *Handlers) getIndexSeries(idx phlaredb.IndexReader) ([]seriesInfo, error) { 269 k2, v2 := index.AllPostingsKey() 270 seriesPostings, err := idx.Postings(k2, nil, v2) 271 if err != nil { 272 return nil, fmt.Errorf("failed to get series postings: %w", err) 273 } 274 275 var seriesList []seriesInfo 276 var lbls phlaremodel.Labels 277 chunks := make([]index.ChunkMeta, 1) 278 279 seriesIdx := uint32(0) 280 for seriesPostings.Next() { 281 seriesRef := seriesPostings.At() 282 _, err := idx.Series(seriesRef, &lbls, &chunks) 283 if err != nil { 284 return nil, fmt.Errorf("failed to get series %d: %w", seriesRef, err) 285 } 286 287 var labelPairs []labelPair 288 for _, lbl := range lbls { 289 labelPairs = append(labelPairs, labelPair{ 290 Key: lbl.Name, 291 Value: lbl.Value, 292 }) 293 } 294 295 seriesList = append(seriesList, seriesInfo{ 296 SeriesIndex: seriesIdx, 297 SeriesRef: uint64(seriesRef), 298 Labels: labelPairs, 299 }) 300 seriesIdx++ 301 } 302 if err := seriesPostings.Err(); err != nil { 303 return nil, fmt.Errorf("failed to iterate series postings: %w", err) 304 } 305 306 return seriesList, nil 307 } 308 309 func (h *Handlers) CreateDatasetSymbolsHandler() func(http.ResponseWriter, *http.Request) { 310 return func(w http.ResponseWriter, r *http.Request) { 311 req, err := parseDatasetRequest(r) 312 if err != nil { 313 httputil.Error(w, err) 314 return 315 } 316 317 page := 1 318 if pageStr := r.URL.Query().Get("page"); pageStr != "" { 319 if _, err := fmt.Sscanf(pageStr, "%d", &page); err != nil || page < 1 { 320 page = 1 321 } 322 } 323 324 pageSize := 100 325 if pageSizeStr := r.URL.Query().Get("page_size"); pageSizeStr != "" { 326 if _, err := fmt.Sscanf(pageSizeStr, "%d", &pageSize); err != nil || pageSize < 1 || pageSize > 500 { 327 pageSize = 100 328 } 329 } 330 331 tab := r.URL.Query().Get("tab") 332 if tab == "" { 333 tab = "strings" 334 } 335 336 blockMeta, foundDataset, err := h.getDatasetMetadata(r.Context(), req) 337 if err != nil { 338 httputil.Error(w, err) 339 return 340 } 341 342 dataset := h.convertDataset(foundDataset, blockMeta.StringTable) 343 344 symbols, err := h.readSymbols(r.Context(), blockMeta, foundDataset, page, pageSize) 345 if err != nil { 346 httputil.Error(w, errors.Wrap(err, "failed to read symbols")) 347 return 348 } 349 350 var totalCount int 351 switch tab { 352 case "strings": 353 totalCount = symbols.TotalStrings 354 case "functions": 355 totalCount = symbols.TotalFunctions 356 case "locations": 357 totalCount = symbols.TotalLocations 358 case "mappings": 359 totalCount = symbols.TotalMappings 360 default: 361 totalCount = symbols.TotalStrings 362 } 363 364 totalPages := (totalCount + pageSize - 1) / pageSize 365 if totalPages == 0 { 366 totalPages = 1 367 } 368 369 err = pageTemplates.datasetSymbolsTemplate.Execute(w, datasetSymbolsPageContent{ 370 User: req.TenantID, 371 BlockID: req.BlockID, 372 Shard: req.Shard, 373 BlockTenant: req.BlockTenant, 374 Dataset: &dataset, 375 Symbols: symbols, 376 Page: page, 377 PageSize: pageSize, 378 TotalPages: totalPages, 379 HasPrevPage: page > 1, 380 HasNextPage: page < totalPages, 381 Tab: tab, 382 Now: time.Now().UTC().Format(time.RFC3339), 383 }) 384 if err != nil { 385 httputil.Error(w, err) 386 return 387 } 388 } 389 } 390 391 func (h *Handlers) readSymbols(ctx context.Context, blockMeta *metastorev1.BlockMeta, dataset *metastorev1.Dataset, page, pageSize int) (*symbolsInfo, error) { 392 obj := block.NewObject(h.Bucket, blockMeta) 393 if err := obj.Open(ctx); err != nil { 394 return nil, fmt.Errorf("failed to open block object: %w", err) 395 } 396 defer obj.Close() 397 398 ds := block.NewDataset(dataset, obj) 399 if err := ds.Open(ctx, block.SectionSymbols); err != nil { 400 return nil, fmt.Errorf("failed to open dataset: %w", err) 401 } 402 defer ds.Close() 403 404 symbolsReader := ds.Symbols() 405 406 // NOTE aleks-p: In v2, the partition is always 0. 407 // This might change later on, in which case we'll need to retrieve partition IDs from parquet. 408 partitionID := uint64(0) 409 partition, err := symbolsReader.Partition(ctx, partitionID) 410 if err != nil { 411 return nil, fmt.Errorf("failed to get symbols partition: %w", err) 412 } 413 defer partition.Release() 414 415 symbols := partition.Symbols() 416 417 startIdx := (page - 1) * pageSize 418 endIdx := startIdx + pageSize 419 420 stringEntries := h.getSymbolStrings(symbols.Strings, startIdx, endIdx) 421 functionEntries := h.getSymbolFunctions(symbols.Functions, symbols.Strings, startIdx, endIdx) 422 locationEntries := h.getSymbolLocations(symbols.Locations, symbols.Functions, symbols.Strings, startIdx, endIdx) 423 mappingEntries := h.getSymbolMappings(symbols.Mappings, symbols.Strings, startIdx, endIdx) 424 425 return &symbolsInfo{ 426 Strings: stringEntries, 427 TotalStrings: len(symbols.Strings), 428 Functions: functionEntries, 429 TotalFunctions: len(symbols.Functions), 430 Locations: locationEntries, 431 TotalLocations: len(symbols.Locations), 432 Mappings: mappingEntries, 433 TotalMappings: len(symbols.Mappings), 434 }, nil 435 } 436 437 func (h *Handlers) getSymbolStrings(stringTable []string, startIdx int, endIdx int) []symbolEntry { 438 stringEntries := make([]symbolEntry, 0, endIdx-startIdx) 439 for idx := startIdx; idx < endIdx && idx < len(stringTable); idx++ { 440 stringEntries = append(stringEntries, symbolEntry{ 441 Index: idx, 442 Symbol: stringTable[idx], 443 }) 444 } 445 return stringEntries 446 } 447 448 func (h *Handlers) getSymbolFunctions(functions []schemav1.InMemoryFunction, stringTable []string, startIdx int, endIdx int) []functionEntry { 449 functionEntries := make([]functionEntry, 0, endIdx-startIdx) 450 for idx := startIdx; idx < endIdx && idx < len(functions); idx++ { 451 fn := functions[idx] 452 functionEntries = append(functionEntries, functionEntry{ 453 Index: idx, 454 ID: fn.Id, 455 Name: stringTable[fn.Name], 456 SystemName: stringTable[fn.SystemName], 457 Filename: stringTable[fn.Filename], 458 StartLine: fn.StartLine, 459 }) 460 } 461 return functionEntries 462 } 463 464 func (h *Handlers) getSymbolLocations( 465 locations []schemav1.InMemoryLocation, 466 functions []schemav1.InMemoryFunction, 467 stringTable []string, 468 startIdx int, endIdx int, 469 ) []locationEntry { 470 locationEntries := make([]locationEntry, 0, endIdx-startIdx) 471 for idx := startIdx; idx < endIdx && idx < len(locations); idx++ { 472 loc := locations[idx] 473 var lines []locationLine 474 for _, line := range loc.Line { 475 fn := functions[line.FunctionId] 476 lines = append(lines, locationLine{ 477 FunctionName: stringTable[fn.Name], 478 Line: int64(line.Line), 479 }) 480 } 481 locationEntries = append(locationEntries, locationEntry{ 482 Index: idx, 483 ID: loc.Id, 484 Address: loc.Address, 485 MappingID: loc.MappingId, 486 Lines: lines, 487 }) 488 } 489 return locationEntries 490 } 491 492 func (h *Handlers) getSymbolMappings(mappings []schemav1.InMemoryMapping, stringTable []string, startIdx int, endIdx int) []mappingEntry { 493 mappingEntries := make([]mappingEntry, 0, endIdx-startIdx) 494 for idx := startIdx; idx < endIdx && idx < len(mappings); idx++ { 495 mapping := mappings[idx] 496 mappingEntries = append(mappingEntries, mappingEntry{ 497 Index: idx, 498 ID: mapping.Id, 499 MemoryStart: mapping.MemoryStart, 500 MemoryLimit: mapping.MemoryLimit, 501 FileOffset: mapping.FileOffset, 502 Filename: stringTable[mapping.Filename], 503 BuildID: stringTable[mapping.BuildId], 504 }) 505 } 506 return mappingEntries 507 }