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  }