提交 11accd4d 编写于 作者: S Sergio Lopez 提交者: Derek Parker

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
上级 3cfb00b2
...@@ -190,7 +190,7 @@ func TestCore(t *testing.T) { ...@@ -190,7 +190,7 @@ func TestCore(t *testing.T) {
} }
p := withCoreFile(t, "panic", "") p := withCoreFile(t, "panic", "")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
if err != nil || len(gs) == 0 { if err != nil || len(gs) == 0 {
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
} }
...@@ -260,7 +260,7 @@ func TestCoreFpRegisters(t *testing.T) { ...@@ -260,7 +260,7 @@ func TestCoreFpRegisters(t *testing.T) {
p := withCoreFile(t, "fputest/", "panic") p := withCoreFile(t, "fputest/", "panic")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
if err != nil || len(gs) == 0 { if err != nil || len(gs) == 0 {
t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err) t.Fatalf("GoroutinesInfo() = %v, %v; wanted at least one goroutine", gs, err)
} }
...@@ -337,7 +337,7 @@ func TestCoreWithEmptyString(t *testing.T) { ...@@ -337,7 +337,7 @@ func TestCoreWithEmptyString(t *testing.T) {
} }
p := withCoreFile(t, "coreemptystring", "") p := withCoreFile(t, "coreemptystring", "")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, t, "GoroutinesInfo") assertNoError(err, t, "GoroutinesInfo")
var mainFrame *proc.Stackframe var mainFrame *proc.Stackframe
......
...@@ -488,14 +488,22 @@ func StepOut(dbp Process) error { ...@@ -488,14 +488,22 @@ func StepOut(dbp Process) error {
return Continue(dbp) return Continue(dbp)
} }
// GoroutinesInfo returns an array of G structures representing the information // GoroutinesInfo searches for goroutines starting at index 'start', and
// Delve cares about from the internal runtime G structure. // returns an array of up to 'count' (or all found elements, if 'count' is 0)
func GoroutinesInfo(dbp Process) ([]*G, error) { // 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 { if _, err := dbp.Valid(); err != nil {
return nil, err return nil, -1, err
} }
if dbp.Common().allGCache != nil { 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 ( var (
...@@ -517,12 +525,12 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { ...@@ -517,12 +525,12 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase) addr, err := rdr.AddrFor("runtime.allglen", dbp.BinInfo().staticBase)
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
allglenBytes := make([]byte, 8) allglenBytes := make([]byte, 8)
_, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr)) _, err = dbp.CurrentThread().ReadMemory(allglenBytes, uintptr(addr))
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
allglen := binary.LittleEndian.Uint64(allglenBytes) allglen := binary.LittleEndian.Uint64(allglenBytes)
...@@ -532,17 +540,20 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { ...@@ -532,17 +540,20 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
// try old name (pre Go 1.6) // try old name (pre Go 1.6)
allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase) allgentryaddr, err = rdr.AddrFor("runtime.allg", dbp.BinInfo().staticBase)
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
} }
faddr := make([]byte, dbp.BinInfo().Arch.PtrSize()) faddr := make([]byte, dbp.BinInfo().Arch.PtrSize())
_, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr)) _, err = dbp.CurrentThread().ReadMemory(faddr, uintptr(allgentryaddr))
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
allgptr := binary.LittleEndian.Uint64(faddr) 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) gvar, err := newGVariable(dbp.CurrentThread(), uintptr(allgptr+(i*uint64(dbp.BinInfo().Arch.PtrSize()))), true)
if err != nil { if err != nil {
allg = append(allg, &G{Unreadable: err}) allg = append(allg, &G{Unreadable: err})
...@@ -556,7 +567,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { ...@@ -556,7 +567,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
if thg, allocated := threadg[g.ID]; allocated { if thg, allocated := threadg[g.ID]; allocated {
loc, err := thg.Thread.Location() loc, err := thg.Thread.Location()
if err != nil { if err != nil {
return nil, err return nil, -1, err
} }
g.Thread = thg.Thread g.Thread = thg.Thread
// Prefer actual thread location information. // Prefer actual thread location information.
...@@ -569,7 +580,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) { ...@@ -569,7 +580,7 @@ func GoroutinesInfo(dbp Process) ([]*G, error) {
} }
dbp.Common().allGCache = allg dbp.Common().allGCache = allg
return allg, nil return allg, -1, nil
} }
// FindGoroutine returns a G struct representing the goroutine // FindGoroutine returns a G struct representing the goroutine
...@@ -579,7 +590,7 @@ func FindGoroutine(dbp Process, gid int) (*G, error) { ...@@ -579,7 +590,7 @@ func FindGoroutine(dbp Process, gid int) (*G, error) {
return dbp.SelectedGoroutine(), nil return dbp.SelectedGoroutine(), nil
} }
gs, err := GoroutinesInfo(dbp) gs, _, err := GoroutinesInfo(dbp, 0, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
...@@ -920,7 +920,7 @@ func TestStacktraceGoroutine(t *testing.T) { ...@@ -920,7 +920,7 @@ func TestStacktraceGoroutine(t *testing.T) {
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, t, "GoroutinesInfo") assertNoError(err, t, "GoroutinesInfo")
agoroutineCount := 0 agoroutineCount := 0
...@@ -1239,7 +1239,7 @@ func TestFrameEvaluation(t *testing.T) { ...@@ -1239,7 +1239,7 @@ func TestFrameEvaluation(t *testing.T) {
t.Logf("stopped on thread %d, goroutine: %#v", p.CurrentThread().ThreadID(), p.SelectedGoroutine()) t.Logf("stopped on thread %d, goroutine: %#v", p.CurrentThread().ThreadID(), p.SelectedGoroutine())
// Testing evaluation on goroutines // Testing evaluation on goroutines
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, t, "GoroutinesInfo") assertNoError(err, t, "GoroutinesInfo")
found := make([]bool, 10) found := make([]bool, 10)
for _, g := range gs { for _, g := range gs {
...@@ -1523,7 +1523,7 @@ func BenchmarkGoroutinesInfo(b *testing.B) { ...@@ -1523,7 +1523,7 @@ func BenchmarkGoroutinesInfo(b *testing.B) {
assertNoError(proc.Continue(p), b, "Continue()") assertNoError(proc.Continue(p), b, "Continue()")
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
p.Common().ClearAllGCache() p.Common().ClearAllGCache()
_, err := proc.GoroutinesInfo(p) _, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, b, "GoroutinesInfo") assertNoError(err, b, "GoroutinesInfo")
} }
}) })
...@@ -1953,7 +1953,7 @@ func TestNextParked(t *testing.T) { ...@@ -1953,7 +1953,7 @@ func TestNextParked(t *testing.T) {
} }
assertNoError(err, t, "Continue()") assertNoError(err, t, "Continue()")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, t, "GoroutinesInfo()") assertNoError(err, t, "GoroutinesInfo()")
// Search for a parked goroutine that we know for sure will have to be // Search for a parked goroutine that we know for sure will have to be
...@@ -2005,7 +2005,7 @@ func TestStepParked(t *testing.T) { ...@@ -2005,7 +2005,7 @@ func TestStepParked(t *testing.T) {
} }
assertNoError(err, t, "Continue()") assertNoError(err, t, "Continue()")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, t, "GoroutinesInfo()") assertNoError(err, t, "GoroutinesInfo()")
for _, g := range gs { for _, g := range gs {
...@@ -2729,7 +2729,7 @@ func TestStacktraceWithBarriers(t *testing.T) { ...@@ -2729,7 +2729,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
return return
} }
assertNoError(err, t, "Continue()") assertNoError(err, t, "Continue()")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, t, "GoroutinesInfo()") assertNoError(err, t, "GoroutinesInfo()")
for _, th := range p.ThreadList() { for _, th := range p.ThreadList() {
if bp := th.Breakpoint(); bp.Breakpoint == nil { if bp := th.Breakpoint(); bp.Breakpoint == nil {
...@@ -2760,7 +2760,7 @@ func TestStacktraceWithBarriers(t *testing.T) { ...@@ -2760,7 +2760,7 @@ func TestStacktraceWithBarriers(t *testing.T) {
assertNoError(proc.StepOut(p), t, "StepOut()") assertNoError(proc.StepOut(p), t, "StepOut()")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, t, "GoroutinesInfo()") assertNoError(err, t, "GoroutinesInfo()")
for _, goid := range stackBarrierGoids { for _, goid := range stackBarrierGoids {
......
...@@ -15,7 +15,7 @@ func TestGoroutineCreationLocation(t *testing.T) { ...@@ -15,7 +15,7 @@ func TestGoroutineCreationLocation(t *testing.T) {
assertNoError(err, t, "BreakByLocation()") assertNoError(err, t, "BreakByLocation()")
assertNoError(proc.Continue(p), t, "Continue()") assertNoError(proc.Continue(p), t, "Continue()")
gs, err := proc.GoroutinesInfo(p) gs, _, err := proc.GoroutinesInfo(p, 0, 0)
assertNoError(err, t, "GoroutinesInfo") assertNoError(err, t, "GoroutinesInfo")
for _, g := range gs { for _, g := range gs {
......
...@@ -570,6 +570,27 @@ func (a byGoroutineID) Len() int { return len(a) } ...@@ -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) 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 } 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 { func goroutines(t *Term, ctx callContext, argstr string) error {
args := strings.Split(argstr, " ") args := strings.Split(argstr, " ")
var fgl = fglUserCurrent var fgl = fglUserCurrent
...@@ -604,26 +625,24 @@ func goroutines(t *Term, ctx callContext, argstr string) error { ...@@ -604,26 +625,24 @@ func goroutines(t *Term, ctx callContext, argstr string) error {
if err != nil { if err != nil {
return err return err
} }
gs, err := t.client.ListGoroutines() var (
if err != nil { start = 0
return err gslen = 0
} gs []*api.Goroutine
sort.Sort(byGoroutineID(gs)) )
fmt.Printf("[%d goroutines]\n", len(gs)) for start >= 0 {
for _, g := range gs { gs, start, err = t.client.ListGoroutines(start, goroutineBatchSize)
prefix := " " if err != nil {
if state.SelectedGoroutine != nil && g.ID == state.SelectedGoroutine.ID { return err
prefix = "* "
} }
fmt.Printf("%sGoroutine %s\n", prefix, formatGoroutine(g, fgl)) sort.Sort(byGoroutineID(gs))
if bPrintStack { err = printGoroutines(t, gs, fgl, bPrintStack, state)
stack, err := t.client.Stacktrace(g.ID, 10, false, nil) if err != nil {
if err != nil { return err
return err
}
printStack(stack, "\t", false)
} }
gslen += len(gs)
} }
fmt.Printf("[%d goroutines]\n", gslen)
return nil return nil
} }
......
...@@ -95,7 +95,7 @@ type Client interface { ...@@ -95,7 +95,7 @@ type Client interface {
ListRegisters(threadID int, includeFp bool) (api.Registers, error) ListRegisters(threadID int, includeFp bool) (api.Registers, error)
// ListGoroutines lists all goroutines. // ListGoroutines lists all goroutines.
ListGoroutines() ([]*api.Goroutine, error) ListGoroutines(start, count int) ([]*api.Goroutine, int, error)
// Returns stacktrace // Returns stacktrace
Stacktrace(goroutineID int, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) 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) ...@@ -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. // 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() d.processMutex.Lock()
defer d.processMutex.Unlock() defer d.processMutex.Unlock()
goroutines := []*api.Goroutine{} goroutines := []*api.Goroutine{}
gs, err := proc.GoroutinesInfo(d.target) gs, nextg, err := proc.GoroutinesInfo(d.target, start, count)
if err != nil { if err != nil {
return nil, err return nil, 0, err
} }
for _, g := range gs { for _, g := range gs {
goroutines = append(goroutines, api.ConvertGoroutine(g)) goroutines = append(goroutines, api.ConvertGoroutine(g))
} }
return goroutines, err return goroutines, nextg, err
} }
// Stacktrace returns a list of Stackframes for the given goroutine. The // Stacktrace returns a list of Stackframes for the given goroutine. The
......
...@@ -283,7 +283,7 @@ func (s *RPCServer) ListTypes(filter string, types *[]string) error { ...@@ -283,7 +283,7 @@ func (s *RPCServer) ListTypes(filter string, types *[]string) error {
} }
func (s *RPCServer) ListGoroutines(arg interface{}, goroutines *[]*api.Goroutine) 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 { if err != nil {
return err return err
} }
......
...@@ -298,10 +298,10 @@ func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([ ...@@ -298,10 +298,10 @@ func (c *RPCClient) ListFunctionArgs(scope api.EvalScope, cfg api.LoadConfig) ([
return out.Args, err 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 var out ListGoroutinesOut
err := c.call("ListGoroutines", ListGoroutinesIn{}, &out) err := c.call("ListGoroutines", ListGoroutinesIn{start, count}, &out)
return out.Goroutines, err return out.Goroutines, out.Nextg, err
} }
func (c *RPCClient) Stacktrace(goroutineId, depth int, readDefers bool, cfg *api.LoadConfig) ([]api.Stackframe, error) { 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 { ...@@ -508,19 +508,23 @@ func (s *RPCServer) ListTypes(arg ListTypesIn, out *ListTypesOut) error {
} }
type ListGoroutinesIn struct { type ListGoroutinesIn struct {
Start int
Count int
} }
type ListGoroutinesOut struct { type ListGoroutinesOut struct {
Goroutines []*api.Goroutine Goroutines []*api.Goroutine
Nextg int
} }
// ListGoroutines lists all goroutines. // ListGoroutines lists all goroutines.
func (s *RPCServer) ListGoroutines(arg ListGoroutinesIn, out *ListGoroutinesOut) error { 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 { if err != nil {
return err return err
} }
out.Goroutines = gs out.Goroutines = gs
out.Nextg = nextg
return nil return nil
} }
......
...@@ -801,7 +801,7 @@ func TestClientServer_FullStacktrace(t *testing.T) { ...@@ -801,7 +801,7 @@ func TestClientServer_FullStacktrace(t *testing.T) {
t.Fatalf("Continue(): %v\n", state.Err) t.Fatalf("Continue(): %v\n", state.Err)
} }
gs, err := c.ListGoroutines() gs, _, err := c.ListGoroutines(0, 0)
assertNoError(err, t, "GoroutinesInfo()") assertNoError(err, t, "GoroutinesInfo()")
found := make([]bool, 10) found := make([]bool, 10)
for _, g := range gs { for _, g := range gs {
...@@ -916,7 +916,7 @@ func TestIssue355(t *testing.T) { ...@@ -916,7 +916,7 @@ func TestIssue355(t *testing.T) {
assertError(err, t, "ListFunctionArgs()") assertError(err, t, "ListFunctionArgs()")
_, err = c.ListRegisters(0, false) _, err = c.ListRegisters(0, false)
assertError(err, t, "ListRegisters()") assertError(err, t, "ListRegisters()")
_, err = c.ListGoroutines() _, _, err = c.ListGoroutines(0, 0)
assertError(err, t, "ListGoroutines()") assertError(err, t, "ListGoroutines()")
_, err = c.Stacktrace(gid, 10, false, &normalLoadConfig) _, err = c.Stacktrace(gid, 10, false, &normalLoadConfig)
assertError(err, t, "Stacktrace()") assertError(err, t, "Stacktrace()")
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册