proc: cache goroutine objects
Adds a cache mapping goroutine IDs to goroutine objects, this allows speeding up FindGoroutine and makes commands like 'goroutines -t' not be accidentally quadratic in the number of goroutines.
This commit is contained in:
parent
666a618f47
commit
9af1eac341
@ -114,12 +114,20 @@ type BreakpointManipulation interface {
|
||||
// CommonProcess contains fields used by this package, common to all
|
||||
// implementations of the Process interface.
|
||||
type CommonProcess struct {
|
||||
allGCache []*G
|
||||
goroutineCache
|
||||
|
||||
fncallEnabled bool
|
||||
|
||||
fncallForG map[int]*callInjection
|
||||
}
|
||||
|
||||
type goroutineCache struct {
|
||||
partialGCache map[int]*G
|
||||
allGCache []*G
|
||||
|
||||
allgentryAddr, allglenAddr uint64
|
||||
}
|
||||
|
||||
type callInjection struct {
|
||||
// if continueCompleted is not nil it means we are in the process of
|
||||
// executing an injected function call, see comments throughout
|
||||
@ -133,8 +141,3 @@ type callInjection struct {
|
||||
func NewCommonProcess(fncallEnabled bool) CommonProcess {
|
||||
return CommonProcess{fncallEnabled: fncallEnabled, fncallForG: make(map[int]*callInjection)}
|
||||
}
|
||||
|
||||
// ClearAllGCache clears the cached contents of the cache for runtime.allgs.
|
||||
func (p *CommonProcess) ClearAllGCache() {
|
||||
p.allGCache = nil
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ var ErrNotExecutable = errors.New("not an executable file")
|
||||
// only possible on recorded (traced) programs.
|
||||
var ErrNotRecorded = errors.New("not a recording")
|
||||
|
||||
var ErrNoRuntimeAllG = errors.New("could not find goroutine array")
|
||||
|
||||
const (
|
||||
// UnrecoveredPanic is the name given to the unrecovered panic breakpoint.
|
||||
UnrecoveredPanic = "unrecovered-panic"
|
||||
@ -72,6 +74,8 @@ func PostInitializationSetup(p Process, path string, debugInfoDirs []string, wri
|
||||
createUnrecoveredPanicBreakpoint(p, writeBreakpoint)
|
||||
createFatalThrowBreakpoint(p, writeBreakpoint)
|
||||
|
||||
p.Common().goroutineCache.init(p.BinInfo())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -507,12 +511,9 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
exeimage := dbp.BinInfo().Images[0] // Image corresponding to the executable file
|
||||
|
||||
var (
|
||||
threadg = map[int]*G{}
|
||||
allg []*G
|
||||
rdr = exeimage.DwarfReader()
|
||||
)
|
||||
|
||||
threads := dbp.ThreadList()
|
||||
@ -526,32 +527,10 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := rdr.AddrFor("runtime.allglen", exeimage.StaticBase)
|
||||
allgptr, allglen, err := dbp.Common().getRuntimeAllg(dbp.BinInfo(), dbp.CurrentThread())
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
allglenBytes := make([]byte, 8)
|
||||
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
allglen := binary.LittleEndian.Uint64(allglenBytes)
|
||||
|
||||
rdr.Seek(0)
|
||||
allgentryaddr, err := rdr.AddrFor("runtime.allgs", exeimage.StaticBase)
|
||||
if err != nil {
|
||||
// try old name (pre Go 1.6)
|
||||
allgentryaddr, err = rdr.AddrFor("runtime.allg", exeimage.StaticBase)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
|
||||
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
allgptr := binary.LittleEndian.Uint64(faddr)
|
||||
|
||||
for i := uint64(start); i < allglen; i++ {
|
||||
if count != 0 && len(allg) >= count {
|
||||
@ -580,6 +559,7 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
|
||||
if g.Status != Gdead {
|
||||
allg = append(allg, g)
|
||||
}
|
||||
dbp.Common().addGoroutine(g)
|
||||
}
|
||||
if start == 0 {
|
||||
dbp.Common().allGCache = allg
|
||||
@ -588,6 +568,56 @@ func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
|
||||
return allg, -1, nil
|
||||
}
|
||||
|
||||
func (gcache *goroutineCache) init(bi *BinaryInfo) {
|
||||
var err error
|
||||
|
||||
exeimage := bi.Images[0]
|
||||
rdr := exeimage.DwarfReader()
|
||||
|
||||
gcache.allglenAddr, _ = rdr.AddrFor("runtime.allglen", exeimage.StaticBase)
|
||||
|
||||
rdr.Seek(0)
|
||||
gcache.allgentryAddr, err = rdr.AddrFor("runtime.allgs", exeimage.StaticBase)
|
||||
if err != nil {
|
||||
// try old name (pre Go 1.6)
|
||||
gcache.allgentryAddr, _ = rdr.AddrFor("runtime.allg", exeimage.StaticBase)
|
||||
}
|
||||
}
|
||||
|
||||
func (gcache *goroutineCache) getRuntimeAllg(bi *BinaryInfo, mem MemoryReadWriter) (uint64, uint64, error) {
|
||||
if gcache.allglenAddr == 0 || gcache.allgentryAddr == 0 {
|
||||
return 0, 0, ErrNoRuntimeAllG
|
||||
}
|
||||
allglenBytes := make([]byte, 8)
|
||||
_, err := mem.ReadMemory(allglenBytes, uintptr(gcache.allglenAddr))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
allglen := binary.LittleEndian.Uint64(allglenBytes)
|
||||
|
||||
faddr := make([]byte, bi.Arch.PtrSize())
|
||||
_, err = mem.ReadMemory(faddr, uintptr(gcache.allgentryAddr))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
allgptr := binary.LittleEndian.Uint64(faddr)
|
||||
|
||||
return allgptr, allglen, nil
|
||||
}
|
||||
|
||||
func (gcache *goroutineCache) addGoroutine(g *G) {
|
||||
if gcache.partialGCache == nil {
|
||||
gcache.partialGCache = make(map[int]*G)
|
||||
}
|
||||
gcache.partialGCache[g.ID] = g
|
||||
}
|
||||
|
||||
// ClearAllGCache clears the cached contents of the cache for runtime.allgs.
|
||||
func (gcache *goroutineCache) ClearAllGCache() {
|
||||
gcache.partialGCache = nil
|
||||
gcache.allGCache = nil
|
||||
}
|
||||
|
||||
// FindGoroutine returns a G struct representing the goroutine
|
||||
// specified by `gid`.
|
||||
func FindGoroutine(dbp Process, gid int) (*G, error) {
|
||||
@ -627,6 +657,10 @@ func FindGoroutine(dbp Process, gid int) (*G, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if g := dbp.Common().partialGCache[gid]; g != nil {
|
||||
return g, nil
|
||||
}
|
||||
|
||||
const goroutinesInfoLimit = 10
|
||||
nextg := 0
|
||||
for nextg >= 0 {
|
||||
|
Loading…
Reference in New Issue
Block a user