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:
parent
6aa54c5c9f
commit
278e4d10c8
@ -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.
|
||||||
|
25
_fixtures/condperghitcount.go
Normal file
25
_fixtures/condperghitcount.go
Normal file
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user