@@ -4,7 +4,13 @@ import (
4
4
"context"
5
5
"debug/dwarf"
6
6
"debug/elf"
7
+ "flag"
7
8
"fmt"
9
+ "io"
10
+ "os"
11
+ "time"
12
+
13
+ objstoreclient "github.com/grafana/pyroscope/pkg/objstore/client"
8
14
)
9
15
10
16
// DwarfResolver implements the liner interface
@@ -37,44 +43,106 @@ func (d *DwarfResolver) Close() error {
37
43
return d .file .Close ()
38
44
}
39
45
46
+ type Config struct {
47
+ DebuginfodURL string `yaml:"debuginfod_url"`
48
+ Cache CacheConfig `yaml:"cache"`
49
+ Storage objstoreclient.Config `yaml:"storage"`
50
+ }
51
+
40
52
type Symbolizer struct {
41
53
client DebuginfodClient
54
+ cache DebugInfoCache
42
55
}
43
56
44
- func NewSymbolizer (client DebuginfodClient ) * Symbolizer {
57
+ func NewSymbolizer (client DebuginfodClient , cache DebugInfoCache ) * Symbolizer {
58
+ if cache == nil {
59
+ cache = NewNullCache ()
60
+ }
45
61
return & Symbolizer {
46
62
client : client ,
63
+ cache : cache ,
64
+ }
65
+ }
66
+
67
+ func NewFromConfig (ctx context.Context , cfg Config ) (* Symbolizer , error ) {
68
+ client := NewDebuginfodClient (cfg .DebuginfodURL )
69
+
70
+ // Default to no caching
71
+ var cache = NewNullCache ()
72
+
73
+ if cfg .Cache .Enabled {
74
+ if cfg .Storage .Backend == "" {
75
+ return nil , fmt .Errorf ("storage configuration required when cache is enabled" )
76
+ }
77
+ bucket , err := objstoreclient .NewBucket (ctx , cfg .Storage , "debuginfo" )
78
+ if err != nil {
79
+ return nil , fmt .Errorf ("create debug info storage: %w" , err )
80
+ }
81
+ cache = NewObjstoreCache (bucket , cfg .Cache .MaxAge )
47
82
}
83
+
84
+ return & Symbolizer {
85
+ client : client ,
86
+ cache : cache ,
87
+ }, nil
48
88
}
49
89
50
90
func (s * Symbolizer ) Symbolize (ctx context.Context , req Request ) error {
51
- // Fetch debug info file
52
- debugFilePath , err := s .client .FetchDebuginfo (req .BuildID )
91
+ debugReader , err := s .cache .Get (ctx , req .BuildID )
92
+ if err == nil {
93
+ defer debugReader .Close ()
94
+ return s .symbolizeFromReader (ctx , debugReader , req )
95
+ }
96
+
97
+ // Cache miss - fetch from debuginfod
98
+ filepath , err := s .client .FetchDebuginfo (req .BuildID )
53
99
if err != nil {
54
100
return fmt .Errorf ("fetch debuginfo: %w" , err )
55
101
}
56
102
57
- // Open ELF file
58
- f , err := elf .Open (debugFilePath )
103
+ // Open for symbolization
104
+ f , err := os .Open (filepath )
59
105
if err != nil {
60
- return fmt .Errorf ("open ELF file: %w" , err )
106
+ return fmt .Errorf ("open debug file: %w" , err )
61
107
}
62
108
defer f .Close ()
63
109
110
+ // Cache it for future use
111
+ if _ , err := f .Seek (0 , 0 ); err != nil {
112
+ return fmt .Errorf ("seek file: %w" , err )
113
+ }
114
+ if err := s .cache .Put (ctx , req .BuildID , f ); err != nil {
115
+ // TODO: Log it but don't fail?
116
+ }
117
+
118
+ // Seek back to start for symbolization
119
+ if _ , err := f .Seek (0 , 0 ); err != nil {
120
+ return fmt .Errorf ("seek file: %w" , err )
121
+ }
122
+
123
+ return s .symbolizeFromReader (ctx , f , req )
124
+ }
125
+
126
+ func (s * Symbolizer ) symbolizeFromReader (ctx context.Context , r io.ReadCloser , req Request ) error {
127
+ elfFile , err := elf .NewFile (io .NewSectionReader (r .(io.ReaderAt ), 0 , 1 << 63 - 1 ))
128
+ if err != nil {
129
+ return fmt .Errorf ("create ELF file from reader: %w" , err )
130
+ }
131
+ defer elfFile .Close ()
132
+
64
133
// Get executable info for address normalization
65
- ei , err := ExecutableInfoFromELF (f )
134
+ ei , err := ExecutableInfoFromELF (elfFile )
66
135
if err != nil {
67
136
return fmt .Errorf ("executable info from ELF: %w" , err )
68
137
}
69
138
70
139
// Create liner
71
- liner , err := NewDwarfResolver (f )
140
+ liner , err := NewDwarfResolver (elfFile )
72
141
if err != nil {
73
142
return fmt .Errorf ("create liner: %w" , err )
74
143
}
75
144
//defer liner.Close()
76
145
77
- // Process each mapping's locations
78
146
for _ , mapping := range req .Mappings {
79
147
for _ , loc := range mapping .Locations {
80
148
addr , err := MapRuntimeAddress (loc .Address , ei , Mapping {
@@ -92,48 +160,17 @@ func (s *Symbolizer) Symbolize(ctx context.Context, req Request) error {
92
160
continue // Skip errors for individual addresses
93
161
}
94
162
95
- // Update the location directly
96
163
loc .Lines = lines
97
164
}
98
165
}
99
166
100
167
return nil
101
168
}
102
169
103
- func (s * Symbolizer ) SymbolizeAll (ctx context.Context , buildID string ) error {
104
- // Reuse the existing debuginfo file
105
- debugFilePath , err := s .client .FetchDebuginfo (buildID )
106
- if err != nil {
107
- return fmt .Errorf ("fetch debuginfo: %w" , err )
108
- }
170
+ func (cfg * Config ) RegisterFlagsWithPrefix (prefix string , f * flag.FlagSet ) {
171
+ f .StringVar (& cfg .DebuginfodURL , prefix + ".debuginfod-url" , "https://debuginfod.elfutils.org" , "URL of the debuginfod server" )
109
172
110
- f , err := elf .Open (debugFilePath )
111
- if err != nil {
112
- return fmt .Errorf ("open ELF file: %w" , err )
113
- }
114
- defer f .Close ()
115
-
116
- debugData , err := f .DWARF ()
117
- if err != nil {
118
- return fmt .Errorf ("get DWARF data: %w" , err )
119
- }
120
-
121
- debugInfo := NewDWARFInfo (debugData )
122
- allSymbols := debugInfo .SymbolizeAllAddresses ()
123
-
124
- fmt .Println ("\n Symbolizing all addresses in DWARF file:" )
125
- fmt .Println ("----------------------------------------" )
126
-
127
- for addr , lines := range allSymbols {
128
- fmt .Printf ("\n Address: 0x%x\n " , addr )
129
- for _ , line := range lines {
130
- fmt .Printf (" Function: %s\n " , line .Function .Name )
131
- fmt .Printf (" File: %s\n " , line .Function .Filename )
132
- fmt .Printf (" Line: %d\n " , line .Line )
133
- fmt .Printf (" StartLine: %d\n " , line .Function .StartLine )
134
- fmt .Println ("----------------------------------------" )
135
- }
136
- }
137
-
138
- return nil
173
+ cachePrefix := prefix + ".cache"
174
+ f .BoolVar (& cfg .Cache .Enabled , cachePrefix + ".enabled" , false , "Enable debug info caching" )
175
+ f .DurationVar (& cfg .Cache .MaxAge , cachePrefix + ".max-age" , 7 * 24 * time .Hour , "Maximum age of cached debug info" )
139
176
}
0 commit comments