@@ -15,6 +15,7 @@ import (
1515 "github.com/grafana/pyroscope/pkg/block/metadata"
1616 phlaremodel "github.com/grafana/pyroscope/pkg/model"
1717 "github.com/grafana/pyroscope/pkg/phlaredb"
18+ schemav1 "github.com/grafana/pyroscope/pkg/phlaredb/schemas/v1"
1819 "github.com/grafana/pyroscope/pkg/phlaredb/tsdb/index"
1920 httputil "github.com/grafana/pyroscope/pkg/util/http"
2021)
@@ -304,3 +305,203 @@ func (h *Handlers) getIndexSeries(idx phlaredb.IndexReader) ([]seriesInfo, error
304305
305306 return seriesList , nil
306307}
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+ }
0 commit comments