blob: 70fbd04b3faa7fc1a14b239997756d4880502ee4 [file] [log] [blame]
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package traceparser
// postProcess is a final check of consistency, and if all is well,
// adds links to Events
import (
"fmt"
)
type gStatus int
const (
gDead gStatus = iota
gRunnable
gRunning
gWaiting
)
// This code is copied from internal/trace/parser.go. With greater understanding it could be
// simplified. Sets ev.P for GCStart, and set various Link fields
func (p *Parsed) postProcess(events []*Event) error {
type gdesc struct {
state gStatus
ev *Event
evStart *Event
evCreate *Event
evMarkAssist *Event
}
type pdesc struct {
running bool
g uint64
evSTW *Event
evSweep *Event
}
gs := make(map[uint64]gdesc)
ps := make(map[int]pdesc)
tasks := make(map[uint64]*Event) // task id to task creation events
activeRegions := make(map[uint64][]*Event) // goroutine id to stack of spans
gs[0] = gdesc{state: gRunning}
var evGC, evSTW *Event
checkRunning := func(pd pdesc, g gdesc, ev *Event, allowG0 bool) error {
if g.state != gRunning {
return fmt.Errorf("saw %v, but g %d is not running", ev, ev.G)
}
if pd.g != ev.G {
return fmt.Errorf("saw %v, but it's P is running %d, not %d", ev, pd.g, ev.G)
}
if !allowG0 && ev.G == 0 {
return fmt.Errorf("saw %v with unexpected g==0", ev)
}
return nil
}
for i, ev := range events {
g := gs[ev.G]
px := ps[int(ev.P)]
switch ev.Type {
case EvProcStart:
if px.running {
return fmt.Errorf("%d: running before start %s", i, ev)
}
px.running = true
case EvProcStop:
if !px.running {
return fmt.Errorf("%d: p %d not running %s", i, ev.P, ev)
}
if px.g != 0 {
return fmt.Errorf("p %d is running a goroutine %s", ev.P, ev)
}
px.running = false
case EvGCStart:
if evGC != nil {
return fmt.Errorf("GC already running %s, was %s", ev, evGC)
}
evGC = ev
// Attribute this to the global GC state.
ev.P = GCP
case EvGCDone:
if evGC == nil {
return fmt.Errorf("%d:%s bogus GC end", i, ev)
}
evGC.Link = ev
evGC = nil
case EvGCSTWStart:
evp := &evSTW
if p.Version < 1010 {
// Before 1.10, EvGCSTWStart was per-P.
evp = &px.evSTW
}
if *evp != nil {
return fmt.Errorf("STW %s still running at %s", *evp, ev)
}
*evp = ev
case EvGCSTWDone:
evp := &evSTW
if p.Version < 1010 {
// Before 1.10, EvGCSTWDone was per-P.
evp = &px.evSTW
}
if *evp == nil {
return fmt.Errorf("%d: no STW running %s", i, ev)
}
(*evp).Link = ev
*evp = nil
case EvGCMarkAssistStart:
if g.evMarkAssist != nil {
return fmt.Errorf("%d: MarkAssist %s is still running at %s",
i, g.evMarkAssist, ev)
}
g.evMarkAssist = ev
case EvGCMarkAssistDone:
// Unlike most events, mark assists can be in progress when a
// goroutine starts tracing, so we can't report an error here.
if g.evMarkAssist != nil {
g.evMarkAssist.Link = ev
g.evMarkAssist = nil
}
case EvGCSweepStart:
if px.evSweep != nil {
return fmt.Errorf("sweep not done %d: %s", i, ev)
}
px.evSweep = ev
case EvGCSweepDone:
if px.evSweep == nil {
return fmt.Errorf("%d: no sweep happening %s", i, ev)
}
px.evSweep.Link = ev
px.evSweep = nil
case EvGoWaiting:
if g.state != gRunnable {
return fmt.Errorf("not runnable before %d:%s", i, ev)
}
g.state = gWaiting
g.ev = ev
case EvGoInSyscall:
if g.state != gRunnable {
return fmt.Errorf("not runnable before %d:%s", i, ev)
}
g.state = gWaiting
g.ev = ev
case EvGoCreate:
if err := checkRunning(px, g, ev, true); err != nil {
return err
}
if _, ok := gs[ev.Args[0]]; ok {
return fmt.Errorf("%d: already exists %s", i, ev)
}
gs[ev.Args[0]] = gdesc{state: gRunnable, ev: ev, evCreate: ev}
case EvGoStart, EvGoStartLabel:
if g.state != gRunnable {
return fmt.Errorf("not runnable before start %d:%s %+v", i, ev, g)
}
if px.g != 0 {
return fmt.Errorf("%d: %s has p running %d already %v", i, ev, px.g, px)
}
g.state = gRunning
g.evStart = ev
px.g = ev.G
if g.evCreate != nil {
if p.Version < 1007 {
// +1 because symbolizer expects return pc.
//PJW: aren't doing < 1007. ev.stk = []*Frame{{PC: g.evCreate.args[1] + 1}}
} else {
ev.StkID = uint32(g.evCreate.Args[1])
}
g.evCreate = nil
}
if g.ev != nil {
g.ev.Link = ev
g.ev = nil
}
case EvGoEnd, EvGoStop:
if err := checkRunning(px, g, ev, false); err != nil {
return fmt.Errorf("%d: %s", i, err)
}
g.evStart.Link = ev
g.evStart = nil
g.state = gDead
px.g = 0
if ev.Type == EvGoEnd { // flush all active Regions
spans := activeRegions[ev.G]
for _, s := range spans {
s.Link = ev
}
delete(activeRegions, ev.G)
}
case EvGoSched, EvGoPreempt:
if err := checkRunning(px, g, ev, false); err != nil {
return err
}
g.state = gRunnable
g.evStart.Link = ev
g.evStart = nil
px.g = 0
g.ev = ev
case EvGoUnblock:
if g.state != gRunning { // PJW, error message
return fmt.Errorf("Event %d (%s) is not running at unblock %s", i, ev, g.state)
}
if ev.P != TimerP && px.g != ev.G {
// PJW: do better here.
return fmt.Errorf("%d: %s p %d is not running g", i, ev, px.g)
}
g1 := gs[ev.Args[0]]
if g1.state != gWaiting {
return fmt.Errorf("g %v is not waiting before unpark i=%d g1=%v %s",
ev.Args[0], i, g1, ev)
}
if g1.ev != nil && g1.ev.Type == EvGoBlockNet && ev.P != TimerP {
ev.P = NetpollP
}
if g1.ev != nil {
g1.ev.Link = ev
}
g1.state = gRunnable
g1.ev = ev
gs[ev.Args[0]] = g1
case EvGoSysCall:
if err := checkRunning(px, g, ev, false); err != nil {
return err
}
g.ev = ev
case EvGoSysBlock:
if err := checkRunning(px, g, ev, false); err != nil {
return err
}
g.state = gWaiting
g.evStart.Link = ev
g.evStart = nil
px.g = 0
case EvGoSysExit:
if g.state != gWaiting {
return fmt.Errorf("not waiting when %s", ev)
}
if g.ev != nil && g.ev.Type == EvGoSysCall {
g.ev.Link = ev
}
g.state = gRunnable
g.ev = ev
case EvGoSleep, EvGoBlock, EvGoBlockSend, EvGoBlockRecv,
EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond, EvGoBlockNet, EvGoBlockGC:
if err := checkRunning(px, g, ev, false); err != nil {
return err
}
g.state = gWaiting
g.ev = ev
g.evStart.Link = ev
g.evStart = nil
px.g = 0
case EvUserTaskCreate:
taskid := ev.Args[0]
if prevEv, ok := tasks[taskid]; ok {
return fmt.Errorf("task id conflicts (id:%d), %q vs %q", taskid, ev, prevEv)
}
tasks[ev.Args[0]] = ev
case EvUserTaskEnd:
if prevEv, ok := tasks[ev.Args[0]]; ok {
prevEv.Link = ev
ev.Link = prevEv
}
case EvUserRegion:
mode := ev.Args[1]
spans := activeRegions[ev.G]
if mode == 0 { // span start
activeRegions[ev.G] = append(spans, ev) // push
} else if mode == 1 { // span end
n := len(spans)
if n > 0 { // matching span start event is in the trace.
s := spans[n-1]
if s.Args[0] != ev.Args[0] || s.SArgs[0] != ev.SArgs[0] { // task id, span name mismatch
return fmt.Errorf("misuse of span in goroutine %d: span end %q when the inner-most active span start event is %q",
ev.G, ev, s)
}
// Link span start event with span end event
s.Link = ev
ev.Link = s
if n > 1 {
activeRegions[ev.G] = spans[:n-1]
} else {
delete(activeRegions, ev.G)
}
}
} else {
return fmt.Errorf("invalid user region, mode: %q", ev)
}
}
gs[ev.G] = g
ps[int(ev.P)] = px
}
return nil
}
func (g gStatus) String() string {
switch g {
case gDead:
return "gDead"
case gRunnable:
return "gRunnable"
case gRunning:
return "gRunning"
case gWaiting:
return "gWaiting"
}
return fmt.Sprintf("gStatus?%d", g)
}