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 <breakpoint name or id> <boolean expression>.
condition -hitcount <breakpoint name or id> <operator> <argument>. condition -hitcount <breakpoint name or id> <operator> <argument>.
condition -per-g-hitcount <breakpoint name or id> <operator> <argument>.
condition -clear <breakpoint name or id>. condition -clear <breakpoint name or id>.
Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true. 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
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. 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. 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 { switch breaklet.Kind {
case UserBreakpoint: case UserBreakpoint:
var goroutineID int
lbp := bpstate.Breakpoint.Logical lbp := bpstate.Breakpoint.Logical
if lbp != nil { if lbp != nil {
if g, err := GetG(thread); err == nil { if g, err := GetG(thread); err == nil {
lbp.HitCount[g.ID]++ goroutineID = g.ID
lbp.HitCount[goroutineID]++
} }
lbp.TotalHitCount++ lbp.TotalHitCount++
} }
active = checkHitCond(lbp) active = checkHitCond(lbp, goroutineID)
case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint: case StepBreakpoint, NextBreakpoint, NextDeferBreakpoint:
nextDeferOk := true nextDeferOk := true
@ -318,26 +320,30 @@ func (bpstate *BreakpointState) checkCond(tgt *Target, breaklet *Breaklet, threa
} }
// checkHitCond evaluates bp's hit condition on thread. // 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 { if lbp == nil || lbp.HitCond == nil {
return true return true
} }
hitCount := int(lbp.TotalHitCount)
if lbp.HitCondPerG && goroutineID > 0 {
hitCount = int(lbp.HitCount[goroutineID])
}
// Evaluate the breakpoint condition. // Evaluate the breakpoint condition.
switch lbp.HitCond.Op { switch lbp.HitCond.Op {
case token.EQL: case token.EQL:
return int(lbp.TotalHitCount) == lbp.HitCond.Val return hitCount == lbp.HitCond.Val
case token.NEQ: case token.NEQ:
return int(lbp.TotalHitCount) != lbp.HitCond.Val return hitCount != lbp.HitCond.Val
case token.GTR: case token.GTR:
return int(lbp.TotalHitCount) > lbp.HitCond.Val return hitCount > lbp.HitCond.Val
case token.LSS: case token.LSS:
return int(lbp.TotalHitCount) < lbp.HitCond.Val return hitCount < lbp.HitCond.Val
case token.GEQ: case token.GEQ:
return int(lbp.TotalHitCount) >= lbp.HitCond.Val return hitCount >= lbp.HitCond.Val
case token.LEQ: case token.LEQ:
return int(lbp.TotalHitCount) <= lbp.HitCond.Val return hitCount <= lbp.HitCond.Val
case token.REM: case token.REM:
return int(lbp.TotalHitCount)%lbp.HitCond.Val == 0 return hitCount%lbp.HitCond.Val == 0
} }
return false 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 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 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 // HitCond: if not nil the breakpoint will be triggered only if the evaluated HitCond returns
// true with the TotalHitCount. // 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 <breakpoint name or id> <boolean expression>.
condition -hitcount <breakpoint name or id> <operator> <argument>. condition -hitcount <breakpoint name or id> <operator> <argument>.
condition -per-g-hitcount <breakpoint name or id> <operator> <argument>.
condition -clear <breakpoint name or id>. condition -clear <breakpoint name or id>.
Specifies that the breakpoint, tracepoint or watchpoint should break only if the boolean expression is true. 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
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. 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. 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)) attrs = append(attrs, fmt.Sprintf("%scond %s", prefix, bp.Cond))
} }
if bp.HitCond != "" { 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 { if bp.Stacktrace > 0 {
attrs = append(attrs, fmt.Sprintf("%sstack %d", prefix, bp.Stacktrace)) 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") return fmt.Errorf("not enough arguments")
} }
if args[0] == "-hitcount" { hitCondPerG := args[0] == "-per-g-hitcount"
if args[0] == "-hitcount" || hitCondPerG {
// hitcount breakpoint // hitcount breakpoint
if ctx.Prefix == onPrefix { if ctx.Prefix == onPrefix {
ctx.Breakpoint.HitCond = args[1] ctx.Breakpoint.HitCond = args[1]
ctx.Breakpoint.HitCondPerG = hitCondPerG
return nil return nil
} }
@ -2833,6 +2842,7 @@ func conditionCmd(t *Term, ctx callContext, argstr string) error {
} }
bp.HitCond = args[1] bp.HitCond = args[1]
bp.HitCondPerG = hitCondPerG
return t.client.AmendBreakpoint(bp) return t.client.AmendBreakpoint(bp)
} }

@ -1158,6 +1158,26 @@ func TestHitCondBreakpoint(t *testing.T) {
t.Fatalf("wrong value of i") 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) { func TestClearCondBreakpoint(t *testing.T) {

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

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

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