Skip to content

Commit e2c55cf

Browse files
committed
perf(symbolizer): Speed up symbol resolution
Keep all resolved symbols in the map and always consult when symbolizing the frame.
1 parent 3b8494e commit e2c55cf

File tree

2 files changed

+60
-37
lines changed

2 files changed

+60
-37
lines changed

pkg/symbolize/symbolizer.go

Lines changed: 59 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ var (
5858
// symCacheHits counts the number of cache hits in the symbols cache
5959
symCacheHits = expvar.NewInt("symbolizer.cache.hits")
6060

61+
// symCachedSymbols counts the number of cached symbol infos
62+
symCachedSymbols = expvar.NewInt("symbolizer.cached.symbols")
63+
6164
// symModulesCount counts the number of loaded module exports
6265
symModulesCount = expvar.NewInt("symbolizer.modules.count")
6366

@@ -101,6 +104,11 @@ type module struct {
101104
hasExports bool
102105
}
103106

107+
type syminfo struct {
108+
module string
109+
symbol string
110+
}
111+
104112
func (m *module) keepalive() {
105113
m.accessed = time.Now()
106114
}
@@ -125,11 +133,10 @@ type Symbolizer struct {
125133
// return address and the symbol information
126134
// identifying the originated call. It is populated
127135
// by the Debug Help API function when the module
128-
// doesn't exist in process state. Subsequent
129-
// calls to the produceFrame method will inspect
130-
// this cache whenever the module is not located
131-
// in process state
132-
symbols map[uint32]map[va.Address]string
136+
// doesn't exist in process state, and in addition
137+
// it is populated by each export directory symbol
138+
// resolution
139+
symbols map[uint32]map[va.Address]syminfo
133140

134141
r Resolver
135142
psnap ps.Snapshotter
@@ -150,7 +157,7 @@ func NewSymbolizer(r Resolver, psnap ps.Snapshotter, config *config.Config, enqu
150157
config: config,
151158
procs: make(map[uint32]*process),
152159
mods: make(map[va.Address]*module),
153-
symbols: make(map[uint32]map[va.Address]string),
160+
symbols: make(map[uint32]map[va.Address]syminfo),
154161
cleaner: time.NewTicker(time.Second * 2),
155162
purger: time.NewTicker(time.Minute * 5),
156163
quit: make(chan struct{}, 1),
@@ -196,6 +203,10 @@ func (s *Symbolizer) ProcessEvent(e *kevent.Kevent) (bool, error) {
196203
pid := e.Kparams.MustGetPid()
197204
s.mu.Lock()
198205
defer s.mu.Unlock()
206+
if _, ok := s.symbols[pid]; !ok {
207+
return true, nil
208+
}
209+
symCachedSymbols.Add(-int64(len(s.symbols[pid])))
199210
delete(s.symbols, pid)
200211
proc, ok := s.procs[pid]
201212
if !ok {
@@ -304,6 +315,9 @@ func (s *Symbolizer) processCallstack(e *kevent.Kevent) error {
304315
return nil
305316
}
306317

318+
s.mu.Lock()
319+
defer s.mu.Unlock()
320+
307321
if e.IsCreateFile() && e.IsOpenDisposition() {
308322
// for high-volume events decorating
309323
// the frames with symbol information
@@ -313,8 +327,6 @@ func (s *Symbolizer) processCallstack(e *kevent.Kevent) error {
313327
s.pushFrames(addrs, e, true, false)
314328
return nil
315329
}
316-
s.mu.Lock()
317-
defer s.mu.Unlock()
318330

319331
if e.PS != nil {
320332
var (
@@ -438,7 +450,7 @@ func (s *Symbolizer) pushFrames(addrs []va.Address, e *kevent.Kevent, fast, look
438450
// state. All symbols are resolved from the
439451
// PE export directory entries. If either the
440452
// symbol or module are not resolved, then we
441-
// fallback to Debug API.
453+
// fall back to Debug API.
442454
func (s *Symbolizer) produceFrame(addr va.Address, e *kevent.Kevent, fast, lookupExport bool) kevent.Frame {
443455
frame := kevent.Frame{PID: e.PID, Addr: addr}
444456
if addr.InSystemRange() {
@@ -448,6 +460,16 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *kevent.Kevent, fast, looku
448460
}
449461
return frame
450462
}
463+
464+
// did we hit this address previously?
465+
if sym, ok := s.symbols[e.PID]; ok {
466+
if symbol, ok := sym[addr]; ok {
467+
symCacheHits.Add(1)
468+
frame.Module, frame.Symbol = symbol.module, symbol.symbol
469+
return frame
470+
}
471+
}
472+
451473
if fast {
452474
if e.PS != nil {
453475
mod := e.PS.FindModuleByVa(addr)
@@ -511,22 +533,12 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *kevent.Kevent, fast, looku
511533
m.keepalive()
512534
}
513535
if frame.Module != "" && frame.Symbol != "" {
536+
// store resolved symbol information in cache
537+
s.cacheSymbol(e.PID, addr, &frame)
514538
return frame
515539
}
516540
}
517541

518-
// did we hit this address previously?
519-
if sym, ok := s.symbols[e.PID]; ok && sym[addr] != "" {
520-
symCacheHits.Add(1)
521-
n := strings.Split(sym[addr], "!")
522-
if len(n) > 1 {
523-
frame.Module, frame.Symbol = n[0], n[1]
524-
}
525-
}
526-
if frame.Module != "" && frame.Symbol != "" {
527-
return frame
528-
}
529-
530542
debugHelpFallbacks.Add(1)
531543

532544
// fallback to Debug Help API
@@ -560,16 +572,21 @@ func (s *Symbolizer) produceFrame(addr va.Address, e *kevent.Kevent, fast, looku
560572
}
561573

562574
// store resolved symbol information in cache
563-
sym := frame.Module + "!" + frame.Symbol
564-
if mod, ok := s.symbols[e.PID]; ok {
565-
if _, ok := mod[addr]; !ok {
566-
s.symbols[e.PID][addr] = sym
575+
s.cacheSymbol(e.PID, addr, &frame)
576+
577+
return frame
578+
}
579+
580+
func (s *Symbolizer) cacheSymbol(pid uint32, addr va.Address, frame *kevent.Frame) {
581+
if sym, ok := s.symbols[pid]; ok {
582+
if _, ok := sym[addr]; !ok {
583+
symCachedSymbols.Add(1)
584+
s.symbols[pid][addr] = syminfo{module: frame.Module, symbol: frame.Symbol}
567585
}
568586
} else {
569-
s.symbols[e.PID] = map[va.Address]string{addr: sym}
587+
symCachedSymbols.Add(1)
588+
s.symbols[pid] = map[va.Address]syminfo{addr: {module: frame.Module, symbol: frame.Symbol}}
570589
}
571-
572-
return frame
573590
}
574591

575592
// resolveSymbolFromExportDirectory parses the module PE
@@ -599,11 +616,11 @@ func (s *Symbolizer) symbolizeAddress(pid uint32, addr va.Address, mod *pstypes.
599616
symbol, ok := s.symbols[pid][addr]
600617
if !ok && mod != nil {
601618
// resolve symbol from the export directory
602-
symbol = s.resolveSymbolFromExportDirectory(addr, mod)
619+
symbol.symbol = s.resolveSymbolFromExportDirectory(addr, mod)
603620
}
604621

605622
// try to get the symbol via Debug Help API
606-
if symbol == "" {
623+
if symbol.symbol == "" {
607624
proc, ok := s.procs[pid]
608625
if !ok {
609626
handle, err := windows.OpenProcess(windows.SYNCHRONIZE|windows.PROCESS_QUERY_INFORMATION, false, pid)
@@ -622,23 +639,29 @@ func (s *Symbolizer) symbolizeAddress(pid uint32, addr va.Address, mod *pstypes.
622639
s.procs[pid] = proc
623640

624641
// resolve address to symbol
625-
symbol, _ = s.r.GetSymbolNameAndOffset(handle, addr)
642+
symbol.symbol, _ = s.r.GetSymbolNameAndOffset(handle, addr)
643+
symbol.module = s.r.GetModuleName(handle, addr)
626644
} else {
627-
symbol, _ = s.r.GetSymbolNameAndOffset(proc.handle, addr)
645+
symbol.symbol, _ = s.r.GetSymbolNameAndOffset(proc.handle, addr)
646+
symbol.module = s.r.GetModuleName(proc.handle, addr)
628647
proc.keepalive()
629648
}
630649
}
631650

651+
if symbol.module == "" && mod != nil {
652+
symbol.module = mod.Name
653+
}
654+
632655
// cache the resolved symbol
633-
if addrs, ok := s.symbols[pid]; ok {
634-
if _, ok := addrs[addr]; !ok {
656+
if sym, ok := s.symbols[pid]; ok {
657+
if _, ok := sym[addr]; !ok {
635658
s.symbols[pid][addr] = symbol
636659
}
637660
} else {
638-
s.symbols[pid] = map[va.Address]string{addr: symbol}
661+
s.symbols[pid] = map[va.Address]syminfo{addr: symbol}
639662
}
640663

641-
return symbol
664+
return symbol.symbol
642665
}
643666

644667
// symbolFromRVA finds the closest export address before RVA.

pkg/symbolize/symbolizer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,7 @@ func TestProcessCallstackPeExports(t *testing.T) {
258258

259259
// should have populated the symbols cache
260260
assert.Len(t, s.symbols, 1)
261-
assert.Equal(t, "unbacked!?", s.symbols[e.PID][0x2638e59e0a5])
261+
assert.Equal(t, syminfo{module: "unbacked", symbol: "?"}, s.symbols[e.PID][0x2638e59e0a5])
262262

263263
// image load event should add module exports
264264
// and when the image is unloaded and there are

0 commit comments

Comments
 (0)