提交 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) {
}
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()")
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册