proc/proc: Extend GoroutinesInfo to allow specifying a range
Instead of unconditionally returning all present goroutines, GoroutinesInfo now allows specifying a range (start and count). In addition to the array of goroutines and the error, it now also returns the next goroutine to be processed, to be used as 'start' argument on the next call, or 0 if all present goroutines have already been processed. This way clients can avoid eating large amounts of RAM while debugging core dumps and processes with a exceptionally high amount of goroutines. Fixes #1403
This commit is contained in:
parent
3cfb00b2cc
commit
11accd4d71
@ -190,7 +190,7 @@ func TestCore(t *testing.T) {
|
||||
}
|
||||
p := withCoreFile(t, "panic", "")
|
||||
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
if err != nil || len(gs) == 0 {
|
||||
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
|
||||
}
|
||||
@ -260,7 +260,7 @@ func TestCoreFpRegisters(t *testing.T) {
|
||||
|
||||
p := withCoreFile(t, "fputest/", "panic")
|
||||
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
if err != nil || len(gs) == 0 {
|
||||
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
|
||||
}
|
||||
@ -337,7 +337,7 @@ func TestCoreWithEmptyString(t *testing.T) {
|
||||
}
|
||||
p := withCoreFile(t, "coreemptystring", "")
|
||||
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo")
|
||||
|
||||
var mainFrame *proc.Stackframe
|
||||
|
@ -488,14 +488,22 @@ func StepOut(dbp Process) error {
|
||||
return Continue(dbp)
|
||||
}
|
||||
|
||||
// GoroutinesInfo returns an array of G structures representing the information
|
||||
// Delve cares about from the internal runtime G structure.
|
||||
func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
// GoroutinesInfo searches for goroutines starting at index 'start', and
|
||||
// returns an array of up to 'count' (or all found elements, if 'count' is 0)
|
||||
// G structures representing the information Delve care about from the internal
|
||||
// runtime G structure.
|
||||
// GoroutinesInfo also returns the next index to be used as 'start' argument
|
||||
// while scanning for all available goroutines, or -1 if there was an error
|
||||
// or if the index already reached the last possible value.
|
||||
func GoroutinesInfo(dbp Process, start, count int) ([]*G, int, error) {
|
||||
if _, err := dbp.Valid(); err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
if dbp.Common().allGCache != nil {
|
||||
return dbp.Common().allGCache, nil
|
||||
// We can't use the cached array to fulfill a subrange request
|
||||
if start == 0 && (count == 0 || count >= len(dbp.Common().allGCache)) {
|
||||
return dbp.Common().allGCache, -1, nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
@ -517,12 +525,12 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
|
||||
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
allglenBytes := make([]byte, 8)
|
||||
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
allglen := binary.LittleEndian.Uint64(allglenBytes)
|
||||
|
||||
@ -532,17 +540,20 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
// try old name (pre Go 1.6)
|
||||
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
}
|
||||
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
|
||||
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
allgptr := binary.LittleEndian.Uint64(faddr)
|
||||
|
||||
for i := uint64(0); i < allglen; i++ {
|
||||
for i := uint64(start); i < allglen; i++ {
|
||||
if count != 0 && len(allg) >= count {
|
||||
return allg, int(i), nil
|
||||
}
|
||||
gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true)
|
||||
if err != nil {
|
||||
allg = append(allg, &G{Unreadable: err})
|
||||
@ -556,7 +567,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
if thg, allocated := threadg[g.ID]; allocated {
|
||||
loc, err := thg.Thread.Location()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, -1, err
|
||||
}
|
||||
g.Thread = thg.Thread
|
||||
// Prefer actual thread location information.
|
||||
@ -569,7 +580,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
|
||||
}
|
||||
dbp.Common().allGCache = allg
|
||||
|
||||
return allg, nil
|
||||
return allg, -1, nil
|
||||
}
|
||||
|
||||
// FindGoroutine returns a G struct representing the goroutine
|
||||
@ -579,7 +590,7 @@ func FindGoroutine(dbp Process, gid int) (*G, error) {
|
||||
return dbp.SelectedGoroutine(), nil
|
||||
}
|
||||
|
||||
gs, err := GoroutinesInfo(dbp)
|
||||
gs, _, err := GoroutinesInfo(dbp, 0, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -920,7 +920,7 @@ func TestStacktraceGoroutine(t *testing.T) {
|
||||
|
||||
assertNoError(proc.Continue(p), t, "Continue()")
|
||||
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo")
|
||||
|
||||
agoroutineCount := 0
|
||||
@ -1239,7 +1239,7 @@ func TestFrameEvaluation(t *testing.T) {
|
||||
t.Logf("stopped on thread %d, goroutine: %#v", p.CurrentThread().ThreadID(), p.SelectedGoroutine())
|
||||
|
||||
// Testing evaluation on goroutines
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo")
|
||||
found := make([]bool, 10)
|
||||
for _, g := range gs {
|
||||
@ -1523,7 +1523,7 @@ func BenchmarkGoroutinesInfo(b *testing.B) {
|
||||
assertNoError(proc.Continue(p), b, "Continue()")
|
||||
for i := 0; i < b.N; i++ {
|
||||
p.Common().ClearAllGCache()
|
||||
_, err := proc.GoroutinesInfo(p)
|
||||
_, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, b, "GoroutinesInfo")
|
||||
}
|
||||
})
|
||||
@ -1953,7 +1953,7 @@ func TestNextParked(t *testing.T) {
|
||||
}
|
||||
assertNoError(err, t, "Continue()")
|
||||
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo()")
|
||||
|
||||
// Search for a parked goroutine that we know for sure will have to be
|
||||
@ -2005,7 +2005,7 @@ func TestStepParked(t *testing.T) {
|
||||
}
|
||||
assertNoError(err, t, "Continue()")
|
||||
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo()")
|
||||
|
||||
for _, g := range gs {
|
||||
@ -2729,7 +2729,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
|
||||
return
|
||||
}
|
||||
assertNoError(err, t, "Continue()")
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo()")
|
||||
for _, th := range p.ThreadList() {
|
||||
if bp := th.Breakpoint(); bp.Breakpoint == nil {
|
||||
@ -2760,7 +2760,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
|
||||
|
||||
assertNoError(proc.StepOut(p), t, "StepOut()")
|
||||
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo()")
|
||||
|
||||
for _, goid := range stackBarrierGoids {
|
||||
|
@ -15,7 +15,7 @@ func TestGoroutineCreationLocation(t *testing.T) {
|
||||
assertNoError(err, t, "BreakByLocation()")
|
||||
assertNoError(proc.Continue(p), t, "Continue()")
|
||||
|
||||
gs, err := proc.GoroutinesInfo(p)
|
||||
gs, _, err := proc.GoroutinesInfo(p, 0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo")
|
||||
|
||||
for _, g := range gs {
|
||||
|
@ -570,6 +570,27 @@ func (a byGoroutineID) Len() int { return len(a) }
|
||||
func (a byGoroutineID) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byGoroutineID) Less(i, j int) bool { return a[i].ID < a[j].ID }
|
||||
|
||||
// The number of goroutines we're going to request on each RPC call
|
||||
const goroutineBatchSize = 10000
|
||||
|
||||
func printGoroutines(t *Term, gs []*api.Goroutine, fgl formatGoroutineLoc, bPrintStack bool, state *api.DebuggerState) error {
|
||||
for _, g := range gs {
|
||||
prefix := " "
|
||||
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
|
||||
prefix = "* "
|
||||
}
|
||||
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
|
||||
if bPrintStack {
|
||||
stack, err := t.client.Stacktrace(g.ID, 10, false, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printStack(stack, "\t", false)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func goroutines(t *Term, ctx callContext, argstr string) error {
|
||||
args := strings.Split(argstr, " ")
|
||||
var fgl = fglUserCurrent
|
||||
@ -604,26 +625,24 @@ func goroutines(t *Term, ctx callContext, argstr string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gs, err := t.client.ListGoroutines()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sort.Sort(byGoroutineID(gs))
|
||||
fmt.Printf("[%d goroutines]\n", len(gs))
|
||||
for _, g := range gs {
|
||||
prefix := " "
|
||||
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID {
|
||||
prefix = "* "
|
||||
var (
|
||||
start = 0
|
||||
gslen = 0
|
||||
gs []*api.Goroutine
|
||||
)
|
||||
for start >= 0 {
|
||||
gs, start, err = t.client.ListGoroutines(start, goroutineBatchSize)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl))
|
||||
if bPrintStack {
|
||||
stack, err := t.client.Stacktrace(g.ID, 10, false, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
printStack(stack, "\t", false)
|
||||
sort.Sort(byGoroutineID(gs))
|
||||
err = printGoroutines(t, gs, fgl, bPrintStack, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
gslen += len(gs)
|
||||
}
|
||||
fmt.Printf("[%d goroutines]\n", gslen)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -95,7 +95,7 @@ type Client interface {
|
||||
ListRegisters(threadID int, includeFp bool) (api.Registers, error)
|
||||
|
||||
// ListGoroutines lists all goroutines.
|
||||
ListGoroutines() ([]*api.Goroutine, error)
|
||||
ListGoroutines(start, count int) ([]*api.Goroutine, int, error)
|
||||
|
||||
// Returns stacktrace
|
||||
Stacktrace(goroutineID int, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error)
|
||||
|
@ -907,19 +907,19 @@ func (d *Debugger) SetVariableInScope(scope api.EvalScope, symbol, value string)
|
||||
}
|
||||
|
||||
// Goroutines will return a list of goroutines in the target process.
|
||||
func (d *Debugger) Goroutines() ([]*api.Goroutine, error) {
|
||||
func (d *Debugger) Goroutines(start, count int) ([]*api.Goroutine, int, error) {
|
||||
d.processMutex.Lock()
|
||||
defer d.processMutex.Unlock()
|
||||
|
||||
goroutines := []*api.Goroutine{}
|
||||
gs, err := proc.GoroutinesInfo(d.target)
|
||||
gs, nextg, err := proc.GoroutinesInfo(d.target, start, count)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, 0, err
|
||||
}
|
||||
for _, g := range gs {
|
||||
goroutines = append(goroutines, api.ConvertGoroutine(g))
|
||||
}
|
||||
return goroutines, err
|
||||
return goroutines, nextg, err
|
||||
}
|
||||
|
||||
// Stacktrace returns a list of Stackframes for the given goroutine. The
|
||||
|
@ -283,7 +283,7 @@ func (s *RPCServer) ListTypes(filter string, types *[]string) error {
|
||||
}
|
||||
|
||||
func (s *RPCServer) ListGoroutines(arg interface{}, goroutines *[]*api.Goroutine) error {
|
||||
gs, err := s.debugger.Goroutines()
|
||||
gs, _, err := s.debugger.Goroutines(0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -298,10 +298,10 @@ func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([
|
||||
return out.Args, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) ListGoroutines() ([]*api.Goroutine, error) {
|
||||
func (c *RPCClient) ListGoroutines(start, count int) ([]*api.Goroutine, int, error) {
|
||||
var out ListGoroutinesOut
|
||||
err := c.call("ListGoroutines", ListGoroutinesIn{}, &out)
|
||||
return out.Goroutines, err
|
||||
err := c.call("ListGoroutines", ListGoroutinesIn{start, count}, &out)
|
||||
return out.Goroutines, out.Nextg, err
|
||||
}
|
||||
|
||||
func (c *RPCClient) Stacktrace(goroutineId, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) {
|
||||
|
@ -508,19 +508,23 @@ func (s *RPCServer) ListTypes(arg ListTypesIn, out *ListTypesOut) error {
|
||||
}
|
||||
|
||||
type ListGoroutinesIn struct {
|
||||
Start int
|
||||
Count int
|
||||
}
|
||||
|
||||
type ListGoroutinesOut struct {
|
||||
Goroutines []*api.Goroutine
|
||||
Nextg int
|
||||
}
|
||||
|
||||
// ListGoroutines lists all goroutines.
|
||||
func (s *RPCServer) ListGoroutines(arg ListGoroutinesIn, out *ListGoroutinesOut) error {
|
||||
gs, err := s.debugger.Goroutines()
|
||||
gs, nextg, err := s.debugger.Goroutines(arg.Start, arg.Count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
out.Goroutines = gs
|
||||
out.Nextg = nextg
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -801,7 +801,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
|
||||
t.Fatalf("Continue(): %v\n", state.Err)
|
||||
}
|
||||
|
||||
gs, err := c.ListGoroutines()
|
||||
gs, _, err := c.ListGoroutines(0, 0)
|
||||
assertNoError(err, t, "GoroutinesInfo()")
|
||||
found := make([]bool, 10)
|
||||
for _, g := range gs {
|
||||
@ -916,7 +916,7 @@ func TestIssue355(t *testing.T) {
|
||||
assertError(err, t, "ListFunctionArgs()")
|
||||
_, err = c.ListRegisters(0, false)
|
||||
assertError(err, t, "ListRegisters()")
|
||||
_, err = c.ListGoroutines()
|
||||
_, _, err = c.ListGoroutines(0, 0)
|
||||
assertError(err, t, "ListGoroutines()")
|
||||
_, err = c.Stacktrace(gid, 10, false, &normalLoadConfig)
|
||||
assertError(err, t, "Stacktrace()")
|
||||
|
Loading…
Reference in New Issue
Block a user