Skip to content

Commit bf872b8

Browse files
authored
feat(v2): add admin page for inspecting the symbols db (#4560)
1 parent df72d57 commit bf872b8

File tree

8 files changed

+596
-0
lines changed

8 files changed

+596
-0
lines changed

pkg/api/api.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ type AdminService interface {
308308
ProfileDownloadHandler(w http.ResponseWriter, r *http.Request)
309309
ProfileCallTreeHandler(w http.ResponseWriter, r *http.Request)
310310
DatasetTSDBIndexHandler(w http.ResponseWriter, r *http.Request)
311+
DatasetSymbolsHandler(w http.ResponseWriter, r *http.Request)
311312
}
312313

313314
func (a *API) RegisterAdmin(ad AdminService) {
@@ -319,6 +320,7 @@ func (a *API) RegisterAdmin(ad AdminService) {
319320
a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/profiles/download", http.HandlerFunc(ad.ProfileDownloadHandler), a.registerOptionsPublicAccess()...)
320321
a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/profiles/call-tree", http.HandlerFunc(ad.ProfileCallTreeHandler), a.registerOptionsPublicAccess()...)
321322
a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/index", http.HandlerFunc(ad.DatasetTSDBIndexHandler), a.registerOptionsPublicAccess()...)
323+
a.RegisterRoute("/ops/object-store/tenants/{tenant}/blocks/{block}/datasets/symbols", http.HandlerFunc(ad.DatasetSymbolsHandler), a.registerOptionsPublicAccess()...)
322324

323325
a.indexPage.AddLinks(defaultWeight, "Admin", []IndexPageLink{
324326
{Desc: "Object Storage Tenants & Blocks", Path: "/ops/object-store/tenants"},

pkg/operations/admin.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,7 @@ func (a *Admin) ProfileCallTreeHandler(w http.ResponseWriter, r *http.Request) {
6565
func (a *Admin) DatasetTSDBIndexHandler(w http.ResponseWriter, r *http.Request) {
6666
http.Error(w, "Dataset TSDB index not available in v1 storage", http.StatusNotFound)
6767
}
68+
69+
func (a *Admin) DatasetSymbolsHandler(w http.ResponseWriter, r *http.Request) {
70+
http.Error(w, "Dataset symbols not available in v1 storage", http.StatusNotFound)
71+
}

pkg/operations/v2/admin.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,3 +65,7 @@ func (a *Admin) ProfileCallTreeHandler(w http.ResponseWriter, r *http.Request) {
6565
func (a *Admin) DatasetTSDBIndexHandler(w http.ResponseWriter, r *http.Request) {
6666
a.handlers.CreateDatasetTSDBIndexHandler()(w, r)
6767
}
68+
69+
func (a *Admin) DatasetSymbolsHandler(w http.ResponseWriter, r *http.Request) {
70+
a.handlers.CreateDatasetSymbolsHandler()(w, r)
71+
}

pkg/operations/v2/dataset_handlers.go

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

pkg/operations/v2/model.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,70 @@ type datasetIndexPageContent struct {
179179
Now string
180180
}
181181

182+
type symbolsInfo struct {
183+
Strings []symbolEntry
184+
TotalStrings int
185+
Functions []functionEntry
186+
TotalFunctions int
187+
Locations []locationEntry
188+
TotalLocations int
189+
Mappings []mappingEntry
190+
TotalMappings int
191+
}
192+
193+
type symbolEntry struct {
194+
Index int
195+
Symbol string
196+
}
197+
198+
type functionEntry struct {
199+
Index int
200+
ID uint64
201+
Name string
202+
SystemName string
203+
Filename string
204+
StartLine uint32
205+
}
206+
207+
type locationLine struct {
208+
FunctionName string
209+
Line int64
210+
}
211+
212+
type locationEntry struct {
213+
Index int
214+
ID uint64
215+
Address uint64
216+
MappingID uint32
217+
Lines []locationLine
218+
}
219+
220+
type mappingEntry struct {
221+
Index int
222+
ID uint64
223+
MemoryStart uint64
224+
MemoryLimit uint64
225+
FileOffset uint64
226+
Filename string
227+
BuildID string
228+
}
229+
230+
type datasetSymbolsPageContent struct {
231+
User string
232+
BlockID string
233+
Shard uint32
234+
BlockTenant string
235+
Dataset *datasetDetails
236+
Symbols *symbolsInfo
237+
Page int
238+
PageSize int
239+
TotalPages int
240+
HasPrevPage bool
241+
HasNextPage bool
242+
Tab string
243+
Now string
244+
}
245+
182246
const emptyDatasetPlaceholder = "_empty"
183247

184248
type datasetRequest struct {

pkg/operations/v2/pages.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ var paginationHtml string
3030
//go:embed tool.blocks.dataset.index.gohtml
3131
var datasetIndexPageHtml string
3232

33+
//go:embed tool.blocks.dataset.symbols.gohtml
34+
var datasetSymbolsPageHtml string
35+
3336
type indexPageContent struct {
3437
Users []string
3538
Now string
@@ -83,6 +86,7 @@ type templates struct {
8386
datasetProfilesTemplate *template.Template
8487
profileCallTreeTemplate *template.Template
8588
datasetIndexTemplate *template.Template
89+
datasetSymbolsTemplate *template.Template
8690
}
8791

8892
var pageTemplates = initTemplates()
@@ -120,6 +124,14 @@ func initTemplates() *templates {
120124
template.Must(profileCallTreeTemplate.Parse(profileCallTreePageHtml))
121125
datasetIndexTemplate := template.New("dataset-index")
122126
template.Must(datasetIndexTemplate.Parse(datasetIndexPageHtml))
127+
datasetSymbolsTemplate := template.New("dataset-symbols").Funcs(template.FuncMap{
128+
"add": add,
129+
"mul": mul,
130+
"seq": seq,
131+
"dict": dict,
132+
})
133+
template.Must(datasetSymbolsTemplate.Parse(paginationHtml))
134+
template.Must(datasetSymbolsTemplate.Parse(datasetSymbolsPageHtml))
123135
t := &templates{
124136
indexTemplate: indexTemplate,
125137
blocksTemplate: blocksTemplate,
@@ -128,6 +140,7 @@ func initTemplates() *templates {
128140
datasetProfilesTemplate: datasetProfilesTemplate,
129141
profileCallTreeTemplate: profileCallTreeTemplate,
130142
datasetIndexTemplate: datasetIndexTemplate,
143+
datasetSymbolsTemplate: datasetSymbolsTemplate,
131144
}
132145
return t
133146
}

pkg/operations/v2/tool.blocks.dataset.gohtml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,20 @@
153153
</div>
154154
</div>
155155
</div>
156+
<div class="col-md-4 mb-3">
157+
<div class="card bg-dark border-secondary h-100">
158+
<div class="card-body d-flex flex-column">
159+
<h5 class="card-title">
160+
<i class="bi bi-code-slash"></i> Symbols
161+
</h5>
162+
<p class="card-text">Explore the symbols table used for profile stack traces.</p>
163+
<a href="/ops/object-store/tenants/{{ .User }}/blocks/{{ .BlockID }}/datasets/symbols?dataset={{ if .Dataset.Name }}{{ .Dataset.Name }}{{ else }}_empty{{ end }}&shard={{ .Shard }}&block_tenant={{ .BlockTenant }}"
164+
class="btn btn-primary mt-auto">
165+
View Symbols
166+
</a>
167+
</div>
168+
</div>
169+
</div>
156170
</div>
157171
</div>
158172
</div>

0 commit comments

Comments
 (0)