terminal/command: add -per-g-hitcount option to condition command (#3055)

Support use per goroutine hitcount as hintcond operand.

Fixes #3050

Co-authored-by: roketyyang <roketyyang@tencent.com>
This commit is contained in:
roketyyang 2022-07-12 16:31:34 +08:00 committed by GitHub
parent 6aa54c5c9f
commit 278e4d10c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 81 additions and 12 deletions

@ -180,6 +180,7 @@ Set breakpoint condition.
condition <breakpoint name or id> <boolean expression>.
condition -hitcount <breakpoint name or id> <operator> <argument>.
condition -per-g-hitcount <breakpoint name or id> <operator> <argument>.
condition -clear <breakpoint name or id>.
Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true.
@ -196,6 +197,8 @@ With the -hitcount option a condition on the breakpoint hit count can be set, th
condition -hitcount bp != n
condition -hitcount bp % n
The -per-g-hitcount option works like -hitcount, but use per goroutine hitcount to compare with n.
With the -clear option a condtion on the breakpoint can removed.
The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n.

@ -0,0 +1,25 @@
package main
import (
"sync"
"time"
)
func main() {
var wg sync.WaitGroup
wg.Add(2)
for i := 0; i < 2; i++ {
go func() {
j := 0
for {
j++
if j > 10 {
break
}
}
wg.Done()
}()
time.Sleep(time.Second)
}
wg.Wait()
}

@ -264,14 +264,16 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
switch breaklet.Kind {
case UserBreakpoint:
var goroutineID int
lbp := bpstate.Breakpoint.Logical
if lbp != nil {
if g, err := GetG(thread); err == nil {
lbp.HitCount[g.ID]++
goroutineID = g.ID
lbp.HitCount[goroutineID]++
}
lbp.TotalHitCount++
}
active = checkHitCond(lbp)
active = checkHitCond(lbp, goroutineID)
case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint:
nextDeferOk := true
@ -318,26 +320,30 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
}
// checkHitCond evaluates bp's hit condition on thread.
func checkHitCond(lbp *LogicalBreakpoint) bool {
func checkHitCond(lbp *LogicalBreakpoint, goroutineID int) bool {
if lbp == nil || lbp.HitCond == nil {
return true
}
hitCount := int(lbp.TotalHitCount)
if lbp.HitCondPerG && goroutineID > 0 {
hitCount = int(lbp.HitCount[goroutineID])
}
// Evaluate the breakpoint condition.
switch lbp.HitCond.Op {
case token.EQL:
return int(lbp.TotalHitCount) == lbp.HitCond.Val
return hitCount == lbp.HitCond.Val
case token.NEQ:
return int(lbp.TotalHitCount) != lbp.HitCond.Val
return hitCount != lbp.HitCond.Val
case token.GTR:
return int(lbp.TotalHitCount) > lbp.HitCond.Val
return hitCount > lbp.HitCond.Val
case token.LSS:
return int(lbp.TotalHitCount) < lbp.HitCond.Val
return hitCount < lbp.HitCond.Val
case token.GEQ:
return int(lbp.TotalHitCount) >= lbp.HitCond.Val
return hitCount >= lbp.HitCond.Val
case token.LEQ:
return int(lbp.TotalHitCount) <= lbp.HitCond.Val
return hitCount <= lbp.HitCond.Val
case token.REM:
return int(lbp.TotalHitCount)%lbp.HitCond.Val == 0
return hitCount%lbp.HitCond.Val == 0
}
return false
}
@ -971,6 +977,7 @@ type LogicalBreakpoint struct {
HitCount map[int]uint64 // Number of times a breakpoint has been reached in a certain goroutine
TotalHitCount uint64 // Number of times a breakpoint has been reached
HitCondPerG bool // Use per goroutine hitcount as HitCond operand, instead of total hitcount
// HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns
// true with the TotalHitCount.

@ -451,6 +451,7 @@ The command 'on x -edit' can be used to edit the list of commands executed when
condition <breakpoint name or id> <boolean expression>.
condition -hitcount <breakpoint name or id> <operator> <argument>.
condition -per-g-hitcount <breakpoint name or id> <operator> <argument>.
condition -clear <breakpoint name or id>.
Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true.
@ -467,6 +468,8 @@ With the -hitcount option a condition on the breakpoint hit count can be set, th
condition -hitcount bp != n
condition -hitcount bp % n
The -per-g-hitcount option works like -hitcount, but use per goroutine hitcount to compare with n.
With the -clear option a condtion on the breakpoint can removed.
The '% n' form means we should stop at the breakpoint when the hitcount is a multiple of n.
@ -1654,7 +1657,11 @@ func formatBreakpointAttrs(prefix string, bp *api.Breakpoint, includeTrace bool)
attrs = append(attrs, fmt.Sprintf("%scond %s", prefix, bp.Cond))
}
if bp.HitCond != "" {
attrs = append(attrs, fmt.Sprintf("%scond -hitcount %s", prefix, bp.HitCond))
if bp.HitCondPerG {
attrs = append(attrs, fmt.Sprintf("%scond -per-g-hitcount %s", prefix, bp.HitCond))
} else {
attrs = append(attrs, fmt.Sprintf("%scond -hitcount %s", prefix, bp.HitCond))
}
}
if bp.Stacktrace > 0 {
attrs = append(attrs, fmt.Sprintf("%sstack %d", prefix, bp.Stacktrace))
@ -2814,11 +2821,13 @@ func conditionCmd(t *Term, ctx callContext, argstr string) error {
return fmt.Errorf("not enough arguments")
}
if args[0] == "-hitcount" {
hitCondPerG := args[0] == "-per-g-hitcount"
if args[0] == "-hitcount" || hitCondPerG {
// hitcount breakpoint
if ctx.Prefix == onPrefix {
ctx.Breakpoint.HitCond = args[1]
ctx.Breakpoint.HitCondPerG = hitCondPerG
return nil
}
@ -2833,6 +2842,7 @@ func conditionCmd(t *Term, ctx callContext, argstr string) error {
}
bp.HitCond = args[1]
bp.HitCondPerG = hitCondPerG
return t.client.AmendBreakpoint(bp)
}

@ -1158,6 +1158,26 @@ func TestHitCondBreakpoint(t *testing.T) {
t.Fatalf("wrong value of i")
}
})
withTestTerminal("condperghitcount", t, func(term *FakeTerminal) {
term.MustExec("break bp1 main.main:8")
term.MustExec("condition -per-g-hitcount bp1 == 2")
listIsAt(t, term, "continue", 16, -1, -1)
// first g hit
out := term.MustExec("print j")
t.Logf("%q", out)
if !strings.Contains(out, "2\n") {
t.Fatalf("wrong value of j")
}
term.MustExec("toggle bp1")
listIsAt(t, term, "continue", 16, -1, -1)
// second g hit
out = term.MustExec("print j")
t.Logf("%q", out)
if !strings.Contains(out, "2\n") {
t.Fatalf("wrong value of j")
}
})
}
func TestClearCondBreakpoint(t *testing.T) {

@ -43,6 +43,7 @@ func ConvertLogicalBreakpoint(lbp *proc.LogicalBreakpoint) *Breakpoint {
if lbp.HitCond != nil {
b.HitCond = fmt.Sprintf("%s %d", lbp.HitCond.Op.String(), lbp.HitCond.Val)
b.HitCondPerG = lbp.HitCondPerG
}
var buf bytes.Buffer

@ -92,6 +92,8 @@ type Breakpoint struct {
// Breakpoint hit count condition.
// Supported hit count conditions are "NUMBER" and "OP NUMBER".
HitCond string
// HitCondPerG use per goroutine hitcount as HitCond operand, instead of total hitcount
HitCondPerG bool
// Tracepoint flag, signifying this is a tracepoint.
Tracepoint bool `json:"continue"`

@ -900,6 +900,7 @@ func copyLogicalBreakpointInfo(lbp *proc.LogicalBreakpoint, requested *api.Break
Op token.Token
Val int
}{opTok, val}
lbp.HitCondPerG = requested.HitCondPerG
}
return nil