blob: 00451f37b5ad7d84b0845cf351d7913512eb6d37 [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
import (
"fmt"
"sort"
)
// convert raw events into Events
func (p *Parsed) createEvents(f func(string)) error {
// multiple passes:
// create some Events
// sort them by time (and adjust their times to be nanonseconds)
// remove events not in the desired time interval
// make the events consistent (adding initializing events at the beginning)
// remove the futile events
// add links (and do final checking)
// shared by firstEvents
p.byproc = make(map[int][]*Event)
p.lastGs = make(map[int]uint64)
// p.batches are always sorted by time. otherwise a batch for one p that is totally
// later than another batch might be done first, confusing us about g's
for i, b := range p.batches {
if b.raws == nil {
continue
}
if err := p.firstEvents(b); err != nil {
return fmt.Errorf("%v", err) // PJW: this is not useful
}
// we done with b.raws now
p.batches[i].raws = nil
}
f("firstEvents finished")
sorted := []*Event{}
for _, v := range p.byproc {
sorted = append(sorted, v...)
}
// PJW: are we done with p.byproc now? Yes. This shrinks a little.
p.byproc = nil
// Why wasn't this done earlier? Or, why do it at all?
for _, ev := range sorted {
switch ev.Type {
case EvGoStartLocal:
ev.Type = EvGoStart
case EvGoUnblockLocal:
ev.Type = EvGoUnblock
case EvGoSysExitLocal:
ev.Type = EvGoSysExit
}
}
// change to nanoseconds
freq := 1e9 / float64(p.TicksPerSec)
for i, ev := range sorted {
// Move timers and syscalls to separate fake Ps.
// This could be done in the loop at line 38
// or maybe after robust fixes things.
if p.timerGoids[ev.G] && ev.Type == EvGoUnblock {
ev.Args[2] = uint64(ev.P) // save for robust() to use
ev.P = TimerP
}
// sometimes the ts is not what it should be
if ev.Type == EvGoSysExit {
ev.P = SyscallP
if ev.Args[2] != 0 {
// PJW: test for this being safe. There might be no preceding
// EvSysBlock, EvGoInSyscall, or its time might be later than this
ev.Ts = int64(ev.Args[2])
}
}
if ev.Type == EvGCStart {
ev.P = GCP
}
t := ev.Ts - p.minticks
if t < 0 {
return fmt.Errorf("event %d %s would be %d mints=%x", i, ev, t, p.minticks)
}
ev.Ts = int64(float64(ev.Ts-p.minticks) * freq)
}
// Stable for the case of equal Ts's.
sort.SliceStable(sorted, func(i, j int) bool { return sorted[i].Ts < sorted[j].Ts })
f("sorted")
// and ignore the ones with times out of bounds
firstwant, lastwant := 0, len(sorted)
for i, ev := range sorted {
if ev.Ts < p.MinWant {
firstwant = i + 1
} else if ev.Ts > p.MaxWant { // closed interval [minwant, maxwant]
lastwant = i
break // sorted by Ts
}
}
f("nanoseconds")
var err error
sorted, _, err = p.robust(sorted[firstwant:lastwant]) // PJW: copy info from aux
f("consistent")
if err != nil {
return err
}
events, cnt := p.removeFutile(sorted) // err is always nil here.
f(fmt.Sprintf("removed %d futiles", cnt))
// and finally, do some checks and put in links
err = p.postProcess(events)
f("post processed")
if err != nil {
return err // PJW: is this enough? NO
}
p.Events = events
return nil
}
// Special P identifiers.
const (
FakeP = 1000000 + iota
TimerP // depicts timer unblocks
NetpollP // depicts network unblocks
SyscallP // depicts returns from syscalls
GCP // depicts GC state
)
// convert the raw events for a batch into Events, and keep track of
// which G is running on the P that is common to the batch.
func (p *Parsed) firstEvents(b batch) error {
for _, raw := range b.raws {
desc := EventDescriptions[raw.typ]
narg := p.rawArgNum(&raw)
if p.Err != nil {
return p.Err
}
if raw.typ == EvBatch {
// first event, record information about P, G, and Ts
p.lastGs[p.lastP] = p.lastG // 0 the first time through
p.lastP = int(raw.Arg(0))
p.lastG = p.lastGs[p.lastP]
p.lastTs = int64(raw.Arg(1))
continue
}
e := &Event{Type: raw.typ, P: int32(p.lastP), G: p.lastG}
var argoffset int
if p.Version < 1007 { // can't happen.
e.Ts = p.lastTs + int64(raw.Arg(1))
argoffset = 2
} else {
e.Ts = p.lastTs + int64(raw.Arg(0))
argoffset = 1
}
p.lastTs = e.Ts
// collect the args for the raw event e
for i := argoffset; i < narg; i++ {
// evade one byte of corruption (from fuzzing typically)
if raw.args == nil {
return fmt.Errorf("raw.args is nil %s", evname(raw.typ))
}
if i > 0 && i-1 >= len(*raw.args) {
return fmt.Errorf("%s wants arg %d of %d", evname(raw.typ), i, len(*raw.args))
}
if i == narg-1 && desc.Stack {
e.StkID = uint32(raw.Arg(i))
} else {
e.Args[i-argoffset] = raw.Arg(i)
}
}
switch raw.typ {
case EvGoSysCall, EvGCSweepDone, EvGCSweepStart:
if e.G == 0 {
// missing some earlier G's from this P
continue // so we don't know which G is doing it
}
case EvGoStart, EvGoStartLocal, EvGoStartLabel:
p.lastG = e.Args[0]
e.G = p.lastG
if raw.typ == EvGoStartLabel {
e.SArgs = []string{p.Strings[e.Args[2]]}
}
case EvGCSTWStart:
e.G = 0
switch e.Args[0] {
case 0:
e.SArgs = []string{"mark termination"}
case 1:
e.SArgs = []string{"sweep termination"}
default:
return fmt.Errorf("unknown STW kind %d!=0,1 %s", e.Args[0], e)
}
case EvGCStart, EvGCDone, EvGCSTWDone:
e.G = 0
case EvGoEnd, EvGoStop, EvGoSched, EvGoPreempt,
EvGoSleep, EvGoBlock, EvGoBlockSend, EvGoBlockRecv,
EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond, EvGoBlockNet,
EvGoSysBlock, EvGoBlockGC:
p.lastG = 0
if e.G == 0 {
// missing some earlier G's from this P
continue // so we don't know which G is doing it
}
case EvGoSysExit, EvGoWaiting, EvGoInSyscall:
e.G = e.Args[0]
case EvUserTaskCreate:
// e.Args 0: taskID, 1:parentID, 2:nameID
e.SArgs = []string{p.Strings[e.Args[2]]}
case EvUserRegion:
if e.G == 0 {
continue // don't know its G
}
// e.Args 0: taskID, 1: mode, 2:nameID
e.SArgs = []string{p.Strings[e.Args[2]]}
case EvUserLog:
// e.Args 0: taskID, 1:keyID, 2: stackID
e.SArgs = []string{p.Strings[e.Args[1]], raw.sarg}
}
p.byproc[p.lastP] = append(p.byproc[p.lastP], e)
}
return nil
}
func (p *Parsed) removeFutile(events []*Event) ([]*Event, int) {
// 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)
cnt := 0
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)
cnt++
}
}
// 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, cnt // PJW: cnt doesn't count the futile[]s
}
// Arg gets the n-th arg from a raw event
func (r *rawEvent) Arg(n int) uint64 {
if n == 0 {
return r.arg0
}
return (*r.args)[n-1]
}
// report the number of arguments. (historical differences)
func (p *Parsed) rawArgNum(ev *rawEvent) int {
desc := EventDescriptions[ev.typ]
switch ev.typ {
case EvStack, EvFrequency, EvTimerGoroutine:
p.Err = fmt.Errorf("%s unexpected in rawArgNum", evname(ev.typ))
return 0
}
narg := len(desc.Args)
if desc.Stack {
narg++
}
if ev.typ == EvBatch {
if p.Version < 1007 {
narg++ // used to be an extra unused arg
}
return narg
}
narg++ // timestamp
if p.Version < 1007 {
narg++ // sequence
}
// various special historical cases
switch ev.typ {
case EvGCSweepDone:
if p.Version < 1009 {
narg -= 2 // 1.9 added 2 args
}
case EvGCStart, EvGoStart, EvGoUnblock:
if p.Version < 1007 {
narg-- // one more since 1.7
}
case EvGCSTWStart:
if p.Version < 1010 {
narg-- // 1.10 added an argument
}
}
return narg
}