Skip to content

Commit 0b8a289

Browse files
committed
chore: move symbolizatoin into query frontend & add intermediate representation for lookups in dwarf
1 parent bde1501 commit 0b8a289

File tree

21 files changed

+1267
-642
lines changed

21 files changed

+1267
-642
lines changed

cmd/symbolization/main.go

+86-55
Original file line numberDiff line numberDiff line change
@@ -5,93 +5,124 @@ import (
55
"fmt"
66
"log"
77

8-
pprof "github.com/google/pprof/profile"
9-
8+
googlev1 "github.com/grafana/pyroscope/api/gen/proto/go/google/v1"
9+
queryv1 "github.com/grafana/pyroscope/api/gen/proto/go/query/v1"
1010
"github.com/grafana/pyroscope/pkg/experiment/symbolizer"
1111
)
1212

1313
const (
1414
debuginfodBaseURL = "https://debuginfod.elfutils.org"
15-
buildID = "2fa2055ef20fabc972d5751147e093275514b142"
15+
// buildID = "c047672cae7964324658491e7dee26748ae5d2f8"
16+
buildID = "2fa2055ef20fabc972d5751147e093275514b142"
1617
)
1718

1819
func main() {
19-
client := symbolizer.NewDebuginfodClient(debuginfodBaseURL, nil)
20+
client := symbolizer.NewDebuginfodClient(debuginfodBaseURL, symbolizer.NewMetrics(nil))
2021

2122
// Alternatively, use a local debug info file:
2223
//client := &localDebuginfodClient{debugFilePath: "/path/to/your/debug/file"}
2324

24-
s := symbolizer.NewSymbolizer(client, nil, nil)
25+
s := symbolizer.NewProfileSymbolizer(client, nil, symbolizer.NewMetrics(nil))
2526
ctx := context.Background()
2627

2728
_, err := client.FetchDebuginfo(buildID)
2829
if err != nil {
2930
log.Fatalf("Failed to fetch debug info: %v", err)
3031
}
31-
//defer os.Remove(debugFilePath)
3232

33-
// Create a request to symbolize specific addresses
34-
req := symbolizer.Request{
35-
BuildID: buildID,
36-
Mappings: []symbolizer.RequestMapping{
33+
// {
34+
// Address: 0x1500,
35+
// Mapping: &pprof.Mapping{},
36+
// },
37+
// {
38+
// Address: 0x3c5a,
39+
// Mapping: &pprof.Mapping{},
40+
// },
41+
// {
42+
// Address: 0x2745,
43+
// Mapping: &pprof.Mapping{},
44+
// },
45+
46+
// Create a profile with the address we want to symbolize
47+
// c047672cae7964324658491e7dee26748ae5d2f8
48+
// profile := &googlev1.Profile{
49+
// Mapping: []*googlev1.Mapping{{
50+
// BuildId: 1,
51+
// MemoryStart: 0x0,
52+
// MemoryLimit: 0x1000000,
53+
// FileOffset: 0x0,
54+
// }},
55+
// Location: []*googlev1.Location{{
56+
// MappingId: 1,
57+
// Address: 0x11a230,
58+
// }},
59+
// StringTable: []string{"", buildID},
60+
// }
61+
62+
profile := &googlev1.Profile{
63+
Mapping: []*googlev1.Mapping{{
64+
BuildId: 1,
65+
MemoryStart: 0x0,
66+
MemoryLimit: 0x1000000,
67+
FileOffset: 0x0,
68+
}},
69+
Location: []*googlev1.Location{
70+
{
71+
MappingId: 1,
72+
Address: 0x1500,
73+
},
74+
{
75+
MappingId: 1,
76+
Address: 0x3c5a,
77+
},
3778
{
38-
Locations: []*symbolizer.Location{
39-
{
40-
Address: 0x1500,
41-
Mapping: &pprof.Mapping{},
42-
},
43-
{
44-
Address: 0x3c5a,
45-
Mapping: &pprof.Mapping{},
46-
},
47-
{
48-
Address: 0x2745,
49-
Mapping: &pprof.Mapping{},
50-
},
51-
},
79+
MappingId: 1,
80+
Address: 0x2745,
5281
},
5382
},
83+
StringTable: []string{"", buildID},
84+
}
85+
86+
// Marshal profile into tree report
87+
data, err := profile.MarshalVT()
88+
if err != nil {
89+
log.Fatalf("Failed to marshal profile: %v", err)
5490
}
91+
report := &queryv1.TreeReport{Tree: data}
5592

56-
if err := s.Symbolize(ctx, req); err != nil {
93+
// Run symbolization
94+
if err := s.SymbolizeTree(ctx, report); err != nil {
5795
log.Fatalf("Failed to symbolize: %v", err)
5896
}
5997

98+
// Unmarshal result for printing
99+
result := &googlev1.Profile{}
100+
if err := result.UnmarshalVT(report.Tree); err != nil {
101+
log.Fatalf("Failed to unmarshal result: %v", err)
102+
}
103+
104+
printResults(result)
105+
}
106+
107+
func printResults(p *googlev1.Profile) {
60108
fmt.Println("Symbolization Results:")
61-
fmt.Printf("Build ID: %s\n", buildID)
62-
fmt.Println("----------------------------------------")
63-
64-
for i, mapping := range req.Mappings {
65-
fmt.Printf("Mapping #%d:\n", i+1)
66-
for _, loc := range mapping.Locations {
67-
fmt.Printf("\nAddress: 0x%x\n", loc.Address)
68-
if len(loc.Lines) == 0 {
69-
fmt.Println(" No symbolization information found")
70-
continue
71-
}
109+
for _, loc := range p.Location {
110+
fmt.Printf("\nAddress: 0x%x\n", loc.Address)
111+
if len(loc.Line) == 0 {
112+
fmt.Println(" No symbolization information found")
113+
continue
114+
}
72115

73-
for j, line := range loc.Lines {
74-
fmt.Printf(" Line %d:\n", j+1)
75-
if line.Function != nil {
76-
fmt.Printf(" Function: %s\n", line.Function.Name)
77-
fmt.Printf(" File: %s\n", line.Function.Filename)
78-
fmt.Printf(" Line: %d\n", line.Line)
79-
fmt.Printf(" StartLine: %d\n", line.Function.StartLine)
80-
} else {
81-
fmt.Println(" No function information available")
82-
}
116+
for i, line := range loc.Line {
117+
fmt.Printf(" Line %d:\n", i+1)
118+
if fn := p.Function[line.FunctionId]; fn != nil {
119+
fmt.Printf(" Function: %s\n", p.StringTable[fn.Name])
120+
fmt.Printf(" File: %s\n", p.StringTable[fn.Filename])
121+
fmt.Printf(" Line: %d\n", line.Line)
122+
fmt.Printf(" StartLine: %d\n", fn.StartLine)
83123
}
84-
fmt.Println("----------------------------------------")
85124
}
86125
}
87-
88-
// Alternatively: Symbolize all addresses in the binary
89-
// Note: Comment out the above specific symbolization when using this
90-
// as it's a different approach meant for exploring all available symbols
91-
//if err := symbolizer.SymbolizeAll(ctx, buildID); err != nil {
92-
// log.Fatalf("Failed to symbolize all addresses: %v", err)
93-
//}
94-
95126
fmt.Println("\nSymbolization completed successfully.")
96127
}
97128

pkg/experiment/query_backend/backend.go

+1-23
Original file line numberDiff line numberDiff line change
@@ -4,31 +4,27 @@ import (
44
"context"
55
"flag"
66
"fmt"
7+
78
"github.com/go-kit/log"
89
"github.com/grafana/dskit/grpcclient"
910
"github.com/grafana/dskit/services"
10-
"github.com/grafana/pyroscope/pkg/objstore/client"
1111
"github.com/opentracing/opentracing-go"
1212
"github.com/prometheus/client_golang/prometheus"
1313
"golang.org/x/sync/errgroup"
1414

1515
metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1"
1616
queryv1 "github.com/grafana/pyroscope/api/gen/proto/go/query/v1"
17-
"github.com/grafana/pyroscope/pkg/experiment/symbolizer"
1817
"github.com/grafana/pyroscope/pkg/util"
1918
)
2019

2120
type Config struct {
2221
Address string `yaml:"address"`
2322
GRPCClientConfig grpcclient.Config `yaml:"grpc_client_config" doc:"description=Configures the gRPC client used to communicate between the query-frontends and the query-schedulers."`
24-
Symbolizer symbolizer.Config `yaml:"symbolizer"`
25-
DebugStorage client.Config `yaml:"debug_storage"`
2623
}
2724

2825
func (cfg *Config) RegisterFlags(f *flag.FlagSet) {
2926
f.StringVar(&cfg.Address, "query-backend.address", "localhost:9095", "")
3027
cfg.GRPCClientConfig.RegisterFlagsWithPrefix("query-backend.grpc-client-config", f)
31-
cfg.Symbolizer.RegisterFlagsWithPrefix("query-backend.symbolizer", f)
3228
}
3329

3430
func (cfg *Config) Validate() error {
@@ -52,8 +48,6 @@ type QueryBackend struct {
5248

5349
backendClient QueryHandler
5450
blockReader QueryHandler
55-
56-
symbolizer *symbolizer.Symbolizer
5751
}
5852

5953
func New(
@@ -63,29 +57,13 @@ func New(
6357
backendClient QueryHandler,
6458
blockReader QueryHandler,
6559
) (*QueryBackend, error) {
66-
var sym *symbolizer.Symbolizer
67-
if config.Symbolizer.DebuginfodURL != "" {
68-
var err error
69-
sym, err = symbolizer.NewFromConfig(context.Background(), config.Symbolizer, reg)
70-
if err != nil {
71-
return nil, fmt.Errorf("create symbolizer: %w", err)
72-
}
73-
}
74-
7560
q := QueryBackend{
7661
config: config,
7762
logger: logger,
7863
reg: reg,
7964
backendClient: backendClient,
8065
blockReader: blockReader,
81-
symbolizer: sym,
82-
}
83-
84-
// Pass symbolizer to BlockReader if it's the right type
85-
if br, ok := blockReader.(*BlockReader); ok {
86-
br.symbolizer = sym
8766
}
88-
8967
q.service = services.NewIdleService(q.starting, q.stopping)
9068
return &q, nil
9169
}

pkg/experiment/query_backend/query_tree.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,7 @@ func queryTree(q *queryContext, query *queryv1.Query) (*queryv1.Report, error) {
5656
defer runutil.CloseWithErrCapture(&err, profiles, "failed to close profile stream")
5757

5858
resolver := symdb.NewResolver(q.ctx, q.ds.Symbols(),
59-
symdb.WithResolverMaxNodes(query.Tree.GetMaxNodes()),
60-
symdb.WithSymbolizer(q.symbolizer))
61-
59+
symdb.WithResolverMaxNodes(query.Tree.GetMaxNodes()))
6260
defer resolver.Release()
6361

6462
if len(spanSelector) > 0 {

pkg/experiment/symbolizer/addrmapper.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ func MapRuntimeAddress(runtimeAddr uint64, ei *BinaryLayout, m Mapping) (uint64,
5959
return runtimeAddr, fmt.Errorf("calculate base offset: %w", err)
6060
}
6161

62-
return runtimeAddr - baseOffset, nil
62+
result := runtimeAddr - baseOffset
63+
return result, nil
6364
}
6465

6566
// CalculateBase determines the base address adjustment needed for address translation.

pkg/experiment/symbolizer/cache.go

-2
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,9 @@ func NewNullCache() DebugInfoCache {
105105
}
106106

107107
func (n *NullCache) Get(ctx context.Context, buildID string) (io.ReadCloser, error) {
108-
// Always return cache miss
109108
return nil, fmt.Errorf("cache miss")
110109
}
111110

112111
func (n *NullCache) Put(ctx context.Context, buildID string, reader io.Reader) error {
113-
// Do nothing
114112
return nil
115113
}

pkg/experiment/symbolizer/debuginfod_client.go

+39-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package symbolizer
22

33
import (
4+
"context"
45
"fmt"
56
"io"
67
"net/http"
@@ -15,21 +16,44 @@ type DebuginfodClient interface {
1516
}
1617

1718
type debuginfodClient struct {
18-
baseURL string
19-
metrics *Metrics
19+
baseURL string
20+
metrics *Metrics
21+
httpClient *http.Client
2022
}
2123

2224
func NewDebuginfodClient(baseURL string, metrics *Metrics) DebuginfodClient {
25+
// Create a default transport with reasonable timeouts
26+
transport := &http.Transport{
27+
MaxIdleConnsPerHost: 10,
28+
IdleConnTimeout: 90 * time.Second,
29+
TLSHandshakeTimeout: 10 * time.Second,
30+
}
31+
32+
client := &http.Client{
33+
Transport: transport,
34+
Timeout: 30 * time.Second, // Overall timeout for requests
35+
}
36+
2337
return &debuginfodClient{
24-
baseURL: baseURL,
25-
metrics: metrics,
38+
baseURL: baseURL,
39+
metrics: metrics,
40+
httpClient: client,
2641
}
2742
}
2843

2944
// FetchDebuginfo fetches the debuginfo file for a specific build ID.
3045
func (c *debuginfodClient) FetchDebuginfo(buildID string) (string, error) {
31-
c.metrics.debuginfodRequestsTotal.Inc()
3246
start := time.Now()
47+
var err error
48+
c.metrics.debuginfodRequestsTotal.Inc()
49+
defer func() {
50+
status := "success"
51+
if err != nil {
52+
status = "error"
53+
c.metrics.debuginfodRequestErrorsTotal.WithLabelValues("http").Inc()
54+
}
55+
c.metrics.debuginfodRequestDuration.WithLabelValues(status).Observe(time.Since(start).Seconds())
56+
}()
3357

3458
sanitizedBuildID, err := sanitizeBuildID(buildID)
3559
if err != nil {
@@ -39,7 +63,16 @@ func (c *debuginfodClient) FetchDebuginfo(buildID string) (string, error) {
3963

4064
url := fmt.Sprintf("%s/buildid/%s/debuginfo", c.baseURL, sanitizedBuildID)
4165

42-
resp, err := http.Get(url)
66+
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil)
67+
if err != nil {
68+
c.metrics.debuginfodRequestErrorsTotal.WithLabelValues("request_creation").Inc()
69+
return "", fmt.Errorf("failed to create request: %w", err)
70+
}
71+
72+
req.Header.Set("Accept-Encoding", "gzip, deflate")
73+
74+
resp, err := c.httpClient.Do(req)
75+
//resp, err := http.Get(url)
4376
if err != nil {
4477
c.metrics.debuginfodRequestErrorsTotal.WithLabelValues("http").Inc()
4578
c.metrics.debuginfodRequestDuration.WithLabelValues("error").Observe(time.Since(start).Seconds())
@@ -65,20 +98,16 @@ func (c *debuginfodClient) FetchDebuginfo(buildID string) (string, error) {
6598
outFile, err := os.Create(filePath)
6699
if err != nil {
67100
c.metrics.debuginfodRequestErrorsTotal.WithLabelValues("file_create").Inc()
68-
c.metrics.debuginfodRequestDuration.WithLabelValues("error").Observe(time.Since(start).Seconds())
69101
return "", fmt.Errorf("failed to create temp file: %w", err)
70102
}
71103
defer outFile.Close()
72104

73105
_, err = io.Copy(outFile, resp.Body)
74106
if err != nil {
75107
c.metrics.debuginfodRequestErrorsTotal.WithLabelValues("write").Inc()
76-
c.metrics.debuginfodRequestDuration.WithLabelValues("error").Observe(time.Since(start).Seconds())
77108
return "", fmt.Errorf("failed to write debuginfod to file: %w", err)
78109
}
79110

80-
c.metrics.debuginfodRequestDuration.WithLabelValues("success").Observe(time.Since(start).Seconds())
81-
82111
return filePath, nil
83112
}
84113

0 commit comments

Comments
 (0)