mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
runtime: remove futile wakeups from trace
Channels and sync.Mutex'es allow another goroutine to acquire resource ahead of an unblocked goroutine. This is good for performance, but leads to futile wakeups (the unblocked goroutine needs to block again). Futile wakeups caused user confusion during the very first evaluation of tracing functionality on a real server (a goroutine as if acquires a mutex in a loop, while there is no loop in user code). This change detects futile wakeups on channels and emits a special event to denote the fact. Later parser finds entire wakeup sequences (unblock->start->block) and removes them. sync.Mutex will be supported in a separate change. Change-Id: Iaaaee9d5c0921afc62b449a97447445030ac19d3 Reviewed-on: https://go-review.googlesource.com/7380 Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
parent
1b49a86ece
commit
4396ea96c4
5 changed files with 171 additions and 9 deletions
|
|
@ -56,7 +56,7 @@ const (
|
|||
SyscallP // depicts returns from syscalls
|
||||
)
|
||||
|
||||
// parseTrace parses, post-processes and verifies the trace.
|
||||
// Parse parses, post-processes and verifies the trace.
|
||||
func Parse(r io.Reader) ([]*Event, error) {
|
||||
rawEvents, err := readTrace(r)
|
||||
if err != nil {
|
||||
|
|
@ -66,6 +66,10 @@ func Parse(r io.Reader) ([]*Event, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events, err = removeFutile(events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = postProcessTrace(events)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -265,6 +269,61 @@ func parseEvents(rawEvents []rawEvent) (events []*Event, err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// removeFutile removes all constituents of futile wakeups (block, unblock, start).
|
||||
// For example, a goroutine was unblocked on a mutex, but another goroutine got
|
||||
// ahead and acquired the mutex before the first goroutine is scheduled,
|
||||
// so the first goroutine has to block again. Such wakeups happen on buffered
|
||||
// channels and sync.Mutex, but are generally not interesting for end user.
|
||||
func removeFutile(events []*Event) ([]*Event, error) {
|
||||
// Two non-trivial aspects:
|
||||
// 1. A goroutine can be preempted during a futile wakeup and migrate to another P.
|
||||
// We want to remove all of that.
|
||||
// 2. Tracing can start in the middle of a futile wakeup.
|
||||
// That is, we can see a futile wakeup event w/o the actual wakeup before it.
|
||||
// postProcessTrace runs after us and ensures that we leave the trace in a consistent state.
|
||||
|
||||
// Phase 1: determine futile wakeup sequences.
|
||||
type G struct {
|
||||
futile bool
|
||||
wakeup []*Event // wakeup sequence (subject for removal)
|
||||
}
|
||||
gs := make(map[uint64]G)
|
||||
futile := make(map[*Event]bool)
|
||||
for _, ev := range events {
|
||||
switch ev.Type {
|
||||
case EvGoUnblock:
|
||||
g := gs[ev.Args[0]]
|
||||
g.wakeup = []*Event{ev}
|
||||
gs[ev.Args[0]] = g
|
||||
case EvGoStart, EvGoPreempt, EvFutileWakeup:
|
||||
g := gs[ev.G]
|
||||
g.wakeup = append(g.wakeup, ev)
|
||||
if ev.Type == EvFutileWakeup {
|
||||
g.futile = true
|
||||
}
|
||||
gs[ev.G] = g
|
||||
case EvGoBlock, EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond:
|
||||
g := gs[ev.G]
|
||||
if g.futile {
|
||||
futile[ev] = true
|
||||
for _, ev1 := range g.wakeup {
|
||||
futile[ev1] = true
|
||||
}
|
||||
}
|
||||
delete(gs, ev.G)
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: remove futile wakeup sequences.
|
||||
newEvents := events[:0] // overwrite the original slice
|
||||
for _, ev := range events {
|
||||
if !futile[ev] {
|
||||
newEvents = append(newEvents, ev)
|
||||
}
|
||||
}
|
||||
return newEvents, nil
|
||||
}
|
||||
|
||||
// postProcessTrace does inter-event verification and information restoration.
|
||||
// The resulting trace is guaranteed to be consistent
|
||||
// (for example, a P does not run two Gs at the same time, or a G is indeed
|
||||
|
|
@ -618,7 +677,8 @@ const (
|
|||
EvHeapAlloc = 33 // memstats.heap_alloc change [timestamp, heap_alloc]
|
||||
EvNextGC = 34 // memstats.next_gc change [timestamp, next_gc]
|
||||
EvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id]
|
||||
EvCount = 36
|
||||
EvFutileWakeup = 36 // denotes that the revious wakeup of this goroutine was futile [timestamp]
|
||||
EvCount = 37
|
||||
)
|
||||
|
||||
var EventDescriptions = [EvCount]struct {
|
||||
|
|
@ -662,4 +722,5 @@ var EventDescriptions = [EvCount]struct {
|
|||
EvHeapAlloc: {"HeapAlloc", false, []string{"mem"}},
|
||||
EvNextGC: {"NextGC", false, []string{"mem"}},
|
||||
EvTimerGoroutine: {"TimerGoroutine", false, []string{"g"}},
|
||||
EvFutileWakeup: {"FutileWakeup", false, []string{}},
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue