1
0
mirror of https://github.com/golang/go synced 2024-11-19 15:44:44 -07:00

runtime: use per-goroutine sequence numbers in tracer

Currently tracer uses global sequencer and it introduces
significant slowdown on parallel machines (up to 10x).
Replace the global sequencer with per-goroutine sequencer.

If we assign per-goroutine sequence numbers to only 3 types
of events (start, unblock and syscall exit), it is enough to
restore consistent partial ordering of all events. Even these
events don't need sequence numbers all the time (if goroutine
starts on the same P where it was unblocked, then start does
not need sequence number).
The burden of restoring the order is put on trace parser.
Details of the algorithm are described in the comments.

On http benchmark with GOMAXPROCS=48:
no tracing: 5026 ns/op
tracing: 27803 ns/op (+453%)
with this change: 6369 ns/op (+26%, mostly for traceback)

Also trace size is reduced by ~22%. Average event size before: 4.63
bytes/event, after: 3.62 bytes/event.

Besides running trace tests, I've also tested with manually broken
cputicks (random skew for each event, per-P skew and episodic random skew).
In all cases broken timestamps were detected and no test failures.

Change-Id: I078bde421ccc386a66f6c2051ab207bcd5613efa
Reviewed-on: https://go-review.googlesource.com/21512
Run-TryBot: Dmitry Vyukov <dvyukov@google.com>
Reviewed-by: Austin Clements <austin@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Dmitry Vyukov 2016-04-05 15:29:14 +02:00
parent ba966f5d89
commit a3703618ea
6 changed files with 479 additions and 184 deletions

278
src/internal/trace/order.go Normal file
View File

@ -0,0 +1,278 @@
// Copyright 2016 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 trace
import (
"fmt"
"sort"
)
type eventBatch struct {
events []*Event
selected bool
}
type orderEvent struct {
ev *Event
batch int
g uint64
init gState
next gState
}
type gStatus int
type gState struct {
seq uint64
status gStatus
}
const (
gDead gStatus = iota
gRunnable
gRunning
gWaiting
unordered = ^uint64(0)
garbage = ^uint64(0) - 1
noseq = ^uint64(0)
seqinc = ^uint64(0) - 1
)
// order1007 merges a set of per-P event batches into a single, consistent stream.
// The high level idea is as follows. Events within an individual batch are in
// correct order, because they are emitted by a single P. So we need to produce
// a correct interleaving of the batches. To do this we take first unmerged event
// from each batch (frontier). Then choose subset that is "ready" to be merged,
// that is, events for which all dependencies are already merged. Then we choose
// event with the lowest timestamp from the subset, merge it and repeat.
// This approach ensures that we form a consistent stream even if timestamps are
// incorrect (condition observed on some machines).
func order1007(m map[int][]*Event) (events []*Event, err error) {
pending := 0
var batches []*eventBatch
for _, v := range m {
pending += len(v)
batches = append(batches, &eventBatch{v, false})
}
gs := make(map[uint64]gState)
var frontier []orderEvent
for ; pending != 0; pending-- {
for i, b := range batches {
if b.selected || len(b.events) == 0 {
continue
}
ev := b.events[0]
g, init, next := stateTransition(ev)
if !transitionReady(g, gs[g], init) {
continue
}
frontier = append(frontier, orderEvent{ev, i, g, init, next})
b.events = b.events[1:]
b.selected = true
// Get rid of "Local" events, they are intended merely for ordering.
switch ev.Type {
case EvGoStartLocal:
ev.Type = EvGoStart
case EvGoUnblockLocal:
ev.Type = EvGoUnblock
case EvGoSysExitLocal:
ev.Type = EvGoSysExit
}
}
if len(frontier) == 0 {
return nil, fmt.Errorf("no consistent ordering of events possible")
}
sort.Sort(orderEventList(frontier))
f := frontier[0]
frontier[0] = frontier[len(frontier)-1]
frontier = frontier[:len(frontier)-1]
events = append(events, f.ev)
transition(gs, f.g, f.init, f.next)
if !batches[f.batch].selected {
panic("frontier batch is not selected")
}
batches[f.batch].selected = false
}
// At this point we have a consistent stream of events.
// Make sure time stamps respect the ordering.
// The tests will skip (not fail) the test case if they see this error.
if !sort.IsSorted(eventList(events)) {
return nil, ErrTimeOrder
}
// The last part is giving correct timestamps to EvGoSysExit events.
// The problem with EvGoSysExit is that actual syscall exit timestamp (ev.Args[2])
// is potentially acquired long before event emission. So far we've used
// timestamp of event emission (ev.Ts).
// We could not set ev.Ts = ev.Args[2] earlier, because it would produce
// seemingly broken timestamps (misplaced event).
// We also can't simply update the timestamp and resort events, because
// if timestamps are broken we will misplace the event and later report
// logically broken trace (instead of reporting broken timestamps).
lastSysBlock := make(map[uint64]int64)
for _, ev := range events {
switch ev.Type {
case EvGoSysBlock, EvGoInSyscall:
lastSysBlock[ev.G] = ev.Ts
case EvGoSysExit:
ts := int64(ev.Args[2])
if ts == 0 {
continue
}
block := lastSysBlock[ev.G]
if block == 0 {
return nil, fmt.Errorf("stray syscall exit")
}
if ts < block {
return nil, ErrTimeOrder
}
ev.Ts = ts
}
}
sort.Sort(eventList(events))
return
}
// stateTransition returns goroutine state (sequence and status) when the event
// becomes ready for merging (init) and the goroutine state after the event (next).
func stateTransition(ev *Event) (g uint64, init, next gState) {
switch ev.Type {
case EvGoCreate:
g = ev.Args[0]
init = gState{0, gDead}
next = gState{1, gRunnable}
case EvGoWaiting, EvGoInSyscall:
g = ev.G
init = gState{1, gRunnable}
next = gState{2, gWaiting}
case EvGoStart:
g = ev.G
init = gState{ev.Args[1], gRunnable}
next = gState{ev.Args[1] + 1, gRunning}
case EvGoStartLocal:
// noseq means that this event is ready for merging as soon as
// frontier reaches it (EvGoStartLocal is emitted on the same P
// as the corresponding EvGoCreate/EvGoUnblock, and thus the latter
// is already merged).
// seqinc is a stub for cases when event increments g sequence,
// but since we don't know current seq we also don't know next seq.
g = ev.G
init = gState{noseq, gRunnable}
next = gState{seqinc, gRunning}
case EvGoBlock, EvGoBlockSend, EvGoBlockRecv, EvGoBlockSelect,
EvGoBlockSync, EvGoBlockCond, EvGoBlockNet, EvGoSleep, EvGoSysBlock:
g = ev.G
init = gState{noseq, gRunning}
next = gState{noseq, gWaiting}
case EvGoSched, EvGoPreempt:
g = ev.G
init = gState{noseq, gRunning}
next = gState{noseq, gRunnable}
case EvGoUnblock, EvGoSysExit:
g = ev.Args[0]
init = gState{ev.Args[1], gWaiting}
next = gState{ev.Args[1] + 1, gRunnable}
case EvGoUnblockLocal, EvGoSysExitLocal:
g = ev.Args[0]
init = gState{noseq, gWaiting}
next = gState{seqinc, gRunnable}
case EvGCStart:
g = garbage
init = gState{ev.Args[0], gDead}
next = gState{ev.Args[0] + 1, gDead}
default:
// no ordering requirements
g = unordered
}
return
}
func transitionReady(g uint64, curr, init gState) bool {
return g == unordered || (init.seq == noseq || init.seq == curr.seq) && init.status == curr.status
}
func transition(gs map[uint64]gState, g uint64, init, next gState) {
if g == unordered {
return
}
curr := gs[g]
if !transitionReady(g, curr, init) {
panic("event sequences are broken")
}
switch next.seq {
case noseq:
next.seq = curr.seq
case seqinc:
next.seq = curr.seq + 1
}
gs[g] = next
}
// order1005 merges a set of per-P event batches into a single, consistent stream.
func order1005(m map[int][]*Event) (events []*Event, err error) {
for _, batch := range m {
events = append(events, batch...)
}
for _, ev := range events {
if ev.Type == EvGoSysExit {
// EvGoSysExit emission is delayed until the thread has a P.
// Give it the real sequence number and time stamp.
ev.seq = int64(ev.Args[1])
if ev.Args[2] != 0 {
ev.Ts = int64(ev.Args[2])
}
}
}
sort.Sort(eventSeqList(events))
if !sort.IsSorted(eventList(events)) {
return nil, ErrTimeOrder
}
return
}
type orderEventList []orderEvent
func (l orderEventList) Len() int {
return len(l)
}
func (l orderEventList) Less(i, j int) bool {
return l[i].ev.Ts < l[j].ev.Ts
}
func (l orderEventList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
type eventList []*Event
func (l eventList) Len() int {
return len(l)
}
func (l eventList) Less(i, j int) bool {
return l[i].Ts < l[j].Ts
}
func (l eventList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
type eventSeqList []*Event
func (l eventSeqList) Len() int {
return len(l)
}
func (l eventSeqList) Less(i, j int) bool {
return l[i].seq < l[j].seq
}
func (l eventSeqList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}

View File

@ -11,7 +11,6 @@ import (
"io" "io"
"os" "os"
"os/exec" "os/exec"
"sort"
"strconv" "strconv"
"strings" "strings"
) )
@ -135,7 +134,12 @@ func readTrace(r io.Reader) (ver int, events []rawEvent, strings map[uint64]stri
} }
off += n off += n
typ := buf[0] << 2 >> 2 typ := buf[0] << 2 >> 2
narg := buf[0] >> 6 narg := buf[0]>>6 + 1
inlineArgs := byte(4)
if ver < 1007 {
narg++
inlineArgs++
}
if typ == EvNone || typ >= EvCount || EventDescriptions[typ].minVersion > ver { if typ == EvNone || typ >= EvCount || EventDescriptions[typ].minVersion > ver {
err = fmt.Errorf("unknown event type %v at offset 0x%x", typ, off0) err = fmt.Errorf("unknown event type %v at offset 0x%x", typ, off0)
return return
@ -180,8 +184,8 @@ func readTrace(r io.Reader) (ver int, events []rawEvent, strings map[uint64]stri
continue continue
} }
ev := rawEvent{typ: typ, off: off0} ev := rawEvent{typ: typ, off: off0}
if narg < 3 { if narg < inlineArgs {
for i := 0; i < int(narg)+2; i++ { // sequence number and time stamp are present but not counted in narg for i := 0; i < int(narg); i++ {
var v uint64 var v uint64
v, off, err = readVal(r, off) v, off, err = readVal(r, off)
if err != nil { if err != nil {
@ -191,7 +195,7 @@ func readTrace(r io.Reader) (ver int, events []rawEvent, strings map[uint64]stri
ev.args = append(ev.args, v) ev.args = append(ev.args, v)
} }
} else { } else {
// If narg == 3, the first value is length of the event in bytes. // More than inlineArgs args, the first value is length of the event in bytes.
var v uint64 var v uint64
v, off, err = readVal(r, off) v, off, err = readVal(r, off)
if err != nil { if err != nil {
@ -250,34 +254,30 @@ func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (even
var lastP int var lastP int
lastGs := make(map[int]uint64) // last goroutine running on P lastGs := make(map[int]uint64) // last goroutine running on P
stacks = make(map[uint64][]*Frame) stacks = make(map[uint64][]*Frame)
batches := make(map[int][]*Event) // events by P
for _, raw := range rawEvents { for _, raw := range rawEvents {
desc := EventDescriptions[raw.typ] desc := EventDescriptions[raw.typ]
if desc.Name == "" { if desc.Name == "" {
err = fmt.Errorf("missing description for event type %v", raw.typ) err = fmt.Errorf("missing description for event type %v", raw.typ)
return return
} }
if raw.typ != EvStack { narg := argNum(raw, ver)
narg := len(desc.Args) if len(raw.args) != narg {
if desc.Stack { err = fmt.Errorf("%v has wrong number of arguments at offset 0x%x: want %v, got %v",
narg++ desc.Name, raw.off, narg, len(raw.args))
} return
if raw.typ != EvBatch && raw.typ != EvFrequency && raw.typ != EvTimerGoroutine {
narg++ // sequence number
narg++ // timestamp
}
if len(raw.args) != narg {
err = fmt.Errorf("%v has wrong number of arguments at offset 0x%x: want %v, got %v",
desc.Name, raw.off, narg, len(raw.args))
return
}
} }
switch raw.typ { switch raw.typ {
case EvBatch: case EvBatch:
lastGs[lastP] = lastG lastGs[lastP] = lastG
lastP = int(raw.args[0]) lastP = int(raw.args[0])
lastG = lastGs[lastP] lastG = lastGs[lastP]
lastSeq = int64(raw.args[1]) if ver < 1007 {
lastTs = int64(raw.args[2]) lastSeq = int64(raw.args[1])
lastTs = int64(raw.args[2])
} else {
lastTs = int64(raw.args[1])
}
case EvFrequency: case EvFrequency:
ticksPerSec = int64(raw.args[0]) ticksPerSec = int64(raw.args[0])
if ticksPerSec <= 0 { if ticksPerSec <= 0 {
@ -328,18 +328,26 @@ func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (even
} }
default: default:
e := &Event{Off: raw.off, Type: raw.typ, P: lastP, G: lastG} e := &Event{Off: raw.off, Type: raw.typ, P: lastP, G: lastG}
e.seq = lastSeq + int64(raw.args[0]) var argOffset int
e.Ts = lastTs + int64(raw.args[1]) if ver < 1007 {
lastSeq = e.seq e.seq = lastSeq + int64(raw.args[0])
lastTs = e.Ts e.Ts = lastTs + int64(raw.args[1])
for i := range desc.Args { lastSeq = e.seq
e.Args[i] = raw.args[i+2] argOffset = 2
} else {
e.Ts = lastTs + int64(raw.args[0])
argOffset = 1
} }
if desc.Stack { lastTs = e.Ts
e.StkID = raw.args[len(desc.Args)+2] for i := argOffset; i < narg; i++ {
if i == narg-1 && desc.Stack {
e.StkID = raw.args[i]
} else {
e.Args[i-argOffset] = raw.args[i]
}
} }
switch raw.typ { switch raw.typ {
case EvGoStart: case EvGoStart, EvGoStartLocal:
lastG = e.Args[0] lastG = e.Args[0]
e.G = lastG e.G = lastG
case EvGCStart, EvGCDone, EvGCScanStart, EvGCScanDone: case EvGCStart, EvGCDone, EvGCScanStart, EvGCScanDone:
@ -349,28 +357,30 @@ func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (even
EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond, EvGoBlockNet, EvGoBlockSelect, EvGoBlockSync, EvGoBlockCond, EvGoBlockNet,
EvGoSysBlock: EvGoSysBlock:
lastG = 0 lastG = 0
case EvGoSysExit: case EvGoSysExit, EvGoWaiting, EvGoInSyscall:
// EvGoSysExit emission is delayed until the thread has a P. e.G = e.Args[0]
// Give it the real sequence number and time stamp.
e.seq = int64(e.Args[1])
if e.Args[2] != 0 {
e.Ts = int64(e.Args[2])
}
} }
events = append(events, e) batches[lastP] = append(batches[lastP], e)
} }
} }
if len(events) == 0 { if len(batches) == 0 {
err = fmt.Errorf("trace is empty") err = fmt.Errorf("trace is empty")
return return
} }
// Sort by sequence number and translate cpu ticks to real time.
sort.Sort(eventList(events))
if ticksPerSec == 0 { if ticksPerSec == 0 {
err = fmt.Errorf("no EvFrequency event") err = fmt.Errorf("no EvFrequency event")
return return
} }
if ver < 1007 {
events, err = order1005(batches)
} else {
events, err = order1007(batches)
}
if err != nil {
return
}
// Translate cpu ticks to real time.
minTs := events[0].Ts minTs := events[0].Ts
// Use floating point to avoid integer overflows. // Use floating point to avoid integer overflows.
freq := 1e9 / float64(ticksPerSec) freq := 1e9 / float64(ticksPerSec)
@ -382,7 +392,6 @@ func parseEvents(ver int, rawEvents []rawEvent, strings map[uint64]string) (even
} }
if ev.Type == EvGoSysExit { if ev.Type == EvGoSysExit {
ev.P = SyscallP ev.P = SyscallP
ev.G = ev.Args[0]
} }
} }
@ -543,19 +552,15 @@ func postProcessTrace(ver int, events []*Event) error {
p.evSweep.Link = ev p.evSweep.Link = ev
p.evSweep = nil p.evSweep = nil
case EvGoWaiting: case EvGoWaiting:
g1 := gs[ev.Args[0]] if g.state != gRunnable {
if g1.state != gRunnable { return fmt.Errorf("g %v is not runnable before EvGoWaiting (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
return fmt.Errorf("g %v is not runnable before EvGoWaiting (offset %v, time %v)", ev.Args[0], ev.Off, ev.Ts)
} }
g1.state = gWaiting g.state = gWaiting
gs[ev.Args[0]] = g1
case EvGoInSyscall: case EvGoInSyscall:
g1 := gs[ev.Args[0]] if g.state != gRunnable {
if g1.state != gRunnable { return fmt.Errorf("g %v is not runnable before EvGoInSyscall (offset %v, time %v)", ev.G, ev.Off, ev.Ts)
return fmt.Errorf("g %v is not runnable before EvGoInSyscall (offset %v, time %v)", ev.Args[0], ev.Off, ev.Ts)
} }
g1.state = gWaiting g.state = gWaiting
gs[ev.Args[0]] = g1
case EvGoCreate: case EvGoCreate:
if err := checkRunning(p, g, ev, true); err != nil { if err := checkRunning(p, g, ev, true); err != nil {
return err return err
@ -666,18 +671,6 @@ func postProcessTrace(ver int, events []*Event) error {
// TODO(dvyukov): restore stacks for EvGoStart events. // TODO(dvyukov): restore stacks for EvGoStart events.
// TODO(dvyukov): test that all EvGoStart events has non-nil Link. // TODO(dvyukov): test that all EvGoStart events has non-nil Link.
// Last, after all the other consistency checks,
// make sure time stamps respect sequence numbers.
// The tests will skip (not fail) the test case if they see this error,
// so check everything else that could possibly be wrong first.
lastTs := int64(0)
for _, ev := range events {
if ev.Ts < lastTs {
return ErrTimeOrder
}
lastTs = ev.Ts
}
return nil return nil
} }
@ -773,32 +766,53 @@ func readVal(r io.Reader, off0 int) (v uint64, off int, err error) {
return 0, 0, fmt.Errorf("bad value at offset 0x%x", off0) return 0, 0, fmt.Errorf("bad value at offset 0x%x", off0)
} }
type eventList []*Event
func (l eventList) Len() int {
return len(l)
}
func (l eventList) Less(i, j int) bool {
return l[i].seq < l[j].seq
}
func (l eventList) Swap(i, j int) {
l[i], l[j] = l[j], l[i]
}
// Print dumps events to stdout. For debugging. // Print dumps events to stdout. For debugging.
func Print(events []*Event) { func Print(events []*Event) {
for _, ev := range events { for _, ev := range events {
desc := EventDescriptions[ev.Type] PrintEvent(ev)
fmt.Printf("%v %v p=%v g=%v off=%v", ev.Ts, desc.Name, ev.P, ev.G, ev.Off)
for i, a := range desc.Args {
fmt.Printf(" %v=%v", a, ev.Args[i])
}
fmt.Printf("\n")
} }
} }
// PrintEvent dumps the event to stdout. For debugging.
func PrintEvent(ev *Event) {
desc := EventDescriptions[ev.Type]
fmt.Printf("%v %v p=%v g=%v off=%v", ev.Ts, desc.Name, ev.P, ev.G, ev.Off)
for i, a := range desc.Args {
fmt.Printf(" %v=%v", a, ev.Args[i])
}
fmt.Printf("\n")
}
// argNum returns total number of args for the event accounting for timestamps,
// sequence numbers and differences between trace format versions.
func argNum(raw rawEvent, ver int) int {
desc := EventDescriptions[raw.typ]
if raw.typ == EvStack {
return len(raw.args)
}
narg := len(desc.Args)
if desc.Stack {
narg++
}
switch raw.typ {
case EvBatch, EvFrequency, EvTimerGoroutine:
if ver < 1007 {
narg++ // there was an unused arg before 1.7
}
case EvGCStart, EvGoStart, EvGoUnblock:
if ver < 1007 {
narg-- // 1.7 added an additional seq arg
}
fallthrough
default:
narg++ // timestamp
if ver < 1007 {
narg++ // sequence
}
}
return narg
}
// Event types in the trace. // Event types in the trace.
// Verbatim copy from src/runtime/trace.go. // Verbatim copy from src/runtime/trace.go.
const ( const (
@ -809,21 +823,21 @@ const (
EvGomaxprocs = 4 // current value of GOMAXPROCS [timestamp, GOMAXPROCS, stack id] EvGomaxprocs = 4 // current value of GOMAXPROCS [timestamp, GOMAXPROCS, stack id]
EvProcStart = 5 // start of P [timestamp, thread id] EvProcStart = 5 // start of P [timestamp, thread id]
EvProcStop = 6 // stop of P [timestamp] EvProcStop = 6 // stop of P [timestamp]
EvGCStart = 7 // GC start [timestamp, stack id] EvGCStart = 7 // GC start [timestamp, seq, stack id]
EvGCDone = 8 // GC done [timestamp] EvGCDone = 8 // GC done [timestamp]
EvGCScanStart = 9 // GC scan start [timestamp] EvGCScanStart = 9 // GC scan start [timestamp]
EvGCScanDone = 10 // GC scan done [timestamp] EvGCScanDone = 10 // GC scan done [timestamp]
EvGCSweepStart = 11 // GC sweep start [timestamp, stack id] EvGCSweepStart = 11 // GC sweep start [timestamp, stack id]
EvGCSweepDone = 12 // GC sweep done [timestamp] EvGCSweepDone = 12 // GC sweep done [timestamp]
EvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, new stack id, stack id] EvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, new stack id, stack id]
EvGoStart = 14 // goroutine starts running [timestamp, goroutine id] EvGoStart = 14 // goroutine starts running [timestamp, goroutine id, seq]
EvGoEnd = 15 // goroutine ends [timestamp] EvGoEnd = 15 // goroutine ends [timestamp]
EvGoStop = 16 // goroutine stops (like in select{}) [timestamp, stack] EvGoStop = 16 // goroutine stops (like in select{}) [timestamp, stack]
EvGoSched = 17 // goroutine calls Gosched [timestamp, stack] EvGoSched = 17 // goroutine calls Gosched [timestamp, stack]
EvGoPreempt = 18 // goroutine is preempted [timestamp, stack] EvGoPreempt = 18 // goroutine is preempted [timestamp, stack]
EvGoSleep = 19 // goroutine calls Sleep [timestamp, stack] EvGoSleep = 19 // goroutine calls Sleep [timestamp, stack]
EvGoBlock = 20 // goroutine blocks [timestamp, stack] EvGoBlock = 20 // goroutine blocks [timestamp, stack]
EvGoUnblock = 21 // goroutine is unblocked [timestamp, goroutine id, stack] EvGoUnblock = 21 // goroutine is unblocked [timestamp, goroutine id, seq, stack]
EvGoBlockSend = 22 // goroutine blocks on chan send [timestamp, stack] EvGoBlockSend = 22 // goroutine blocks on chan send [timestamp, stack]
EvGoBlockRecv = 23 // goroutine blocks on chan recv [timestamp, stack] EvGoBlockRecv = 23 // goroutine blocks on chan recv [timestamp, stack]
EvGoBlockSelect = 24 // goroutine blocks on select [timestamp, stack] EvGoBlockSelect = 24 // goroutine blocks on select [timestamp, stack]
@ -831,7 +845,7 @@ const (
EvGoBlockCond = 26 // goroutine blocks on Cond [timestamp, stack] EvGoBlockCond = 26 // goroutine blocks on Cond [timestamp, stack]
EvGoBlockNet = 27 // goroutine blocks on network [timestamp, stack] EvGoBlockNet = 27 // goroutine blocks on network [timestamp, stack]
EvGoSysCall = 28 // syscall enter [timestamp, stack] EvGoSysCall = 28 // syscall enter [timestamp, stack]
EvGoSysExit = 29 // syscall exit [timestamp, goroutine id, real timestamp] EvGoSysExit = 29 // syscall exit [timestamp, goroutine id, seq, real timestamp]
EvGoSysBlock = 30 // syscall blocks [timestamp] EvGoSysBlock = 30 // syscall blocks [timestamp]
EvGoWaiting = 31 // denotes that goroutine is blocked when tracing starts [timestamp, goroutine id] EvGoWaiting = 31 // denotes that goroutine is blocked when tracing starts [timestamp, goroutine id]
EvGoInSyscall = 32 // denotes that goroutine is in syscall when tracing starts [timestamp, goroutine id] EvGoInSyscall = 32 // denotes that goroutine is in syscall when tracing starts [timestamp, goroutine id]
@ -840,7 +854,10 @@ const (
EvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id] EvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id]
EvFutileWakeup = 36 // denotes that the previous wakeup of this goroutine was futile [timestamp] EvFutileWakeup = 36 // denotes that the previous wakeup of this goroutine was futile [timestamp]
EvString = 37 // string dictionary entry [ID, length, string] EvString = 37 // string dictionary entry [ID, length, string]
EvCount = 38 EvGoStartLocal = 38 // goroutine starts running on the same P as the last event [timestamp, goroutine id]
EvGoUnblockLocal = 39 // goroutine is unblocked on the same P as the last event [timestamp, goroutine id, stack]
EvGoSysExitLocal = 40 // syscall exit on the same P as the last event [timestamp, goroutine id, real timestamp]
EvCount = 41
) )
var EventDescriptions = [EvCount]struct { var EventDescriptions = [EvCount]struct {
@ -850,27 +867,27 @@ var EventDescriptions = [EvCount]struct {
Args []string Args []string
}{ }{
EvNone: {"None", 1005, false, []string{}}, EvNone: {"None", 1005, false, []string{}},
EvBatch: {"Batch", 1005, false, []string{"p", "seq", "ticks"}}, EvBatch: {"Batch", 1005, false, []string{"p", "ticks"}}, // in 1.5 format it was {"p", "seq", "ticks"}
EvFrequency: {"Frequency", 1005, false, []string{"freq", "unused"}}, EvFrequency: {"Frequency", 1005, false, []string{"freq"}}, // in 1.5 format it was {"freq", "unused"}
EvStack: {"Stack", 1005, false, []string{"id", "siz"}}, EvStack: {"Stack", 1005, false, []string{"id", "siz"}},
EvGomaxprocs: {"Gomaxprocs", 1005, true, []string{"procs"}}, EvGomaxprocs: {"Gomaxprocs", 1005, true, []string{"procs"}},
EvProcStart: {"ProcStart", 1005, false, []string{"thread"}}, EvProcStart: {"ProcStart", 1005, false, []string{"thread"}},
EvProcStop: {"ProcStop", 1005, false, []string{}}, EvProcStop: {"ProcStop", 1005, false, []string{}},
EvGCStart: {"GCStart", 1005, true, []string{}}, EvGCStart: {"GCStart", 1005, true, []string{"seq"}}, // in 1.5 format it was {}
EvGCDone: {"GCDone", 1005, false, []string{}}, EvGCDone: {"GCDone", 1005, false, []string{}},
EvGCScanStart: {"GCScanStart", 1005, false, []string{}}, EvGCScanStart: {"GCScanStart", 1005, false, []string{}},
EvGCScanDone: {"GCScanDone", 1005, false, []string{}}, EvGCScanDone: {"GCScanDone", 1005, false, []string{}},
EvGCSweepStart: {"GCSweepStart", 1005, true, []string{}}, EvGCSweepStart: {"GCSweepStart", 1005, true, []string{}},
EvGCSweepDone: {"GCSweepDone", 1005, false, []string{}}, EvGCSweepDone: {"GCSweepDone", 1005, false, []string{}},
EvGoCreate: {"GoCreate", 1005, true, []string{"g", "stack"}}, EvGoCreate: {"GoCreate", 1005, true, []string{"g", "stack"}},
EvGoStart: {"GoStart", 1005, false, []string{"g"}}, EvGoStart: {"GoStart", 1005, false, []string{"g", "seq"}}, // in 1.5 format it was {"g"}
EvGoEnd: {"GoEnd", 1005, false, []string{}}, EvGoEnd: {"GoEnd", 1005, false, []string{}},
EvGoStop: {"GoStop", 1005, true, []string{}}, EvGoStop: {"GoStop", 1005, true, []string{}},
EvGoSched: {"GoSched", 1005, true, []string{}}, EvGoSched: {"GoSched", 1005, true, []string{}},
EvGoPreempt: {"GoPreempt", 1005, true, []string{}}, EvGoPreempt: {"GoPreempt", 1005, true, []string{}},
EvGoSleep: {"GoSleep", 1005, true, []string{}}, EvGoSleep: {"GoSleep", 1005, true, []string{}},
EvGoBlock: {"GoBlock", 1005, true, []string{}}, EvGoBlock: {"GoBlock", 1005, true, []string{}},
EvGoUnblock: {"GoUnblock", 1005, true, []string{"g"}}, EvGoUnblock: {"GoUnblock", 1005, true, []string{"g", "seq"}}, // in 1.5 format it was {"g"}
EvGoBlockSend: {"GoBlockSend", 1005, true, []string{}}, EvGoBlockSend: {"GoBlockSend", 1005, true, []string{}},
EvGoBlockRecv: {"GoBlockRecv", 1005, true, []string{}}, EvGoBlockRecv: {"GoBlockRecv", 1005, true, []string{}},
EvGoBlockSelect: {"GoBlockSelect", 1005, true, []string{}}, EvGoBlockSelect: {"GoBlockSelect", 1005, true, []string{}},
@ -884,7 +901,10 @@ var EventDescriptions = [EvCount]struct {
EvGoInSyscall: {"GoInSyscall", 1005, false, []string{"g"}}, EvGoInSyscall: {"GoInSyscall", 1005, false, []string{"g"}},
EvHeapAlloc: {"HeapAlloc", 1005, false, []string{"mem"}}, EvHeapAlloc: {"HeapAlloc", 1005, false, []string{"mem"}},
EvNextGC: {"NextGC", 1005, false, []string{"mem"}}, EvNextGC: {"NextGC", 1005, false, []string{"mem"}},
EvTimerGoroutine: {"TimerGoroutine", 1005, false, []string{"g", "unused"}}, EvTimerGoroutine: {"TimerGoroutine", 1005, false, []string{"g"}}, // in 1.5 format it was {"g", "unused"}
EvFutileWakeup: {"FutileWakeup", 1005, false, []string{}}, EvFutileWakeup: {"FutileWakeup", 1005, false, []string{}},
EvString: {"String", 1007, false, []string{}}, EvString: {"String", 1007, false, []string{}},
EvGoStartLocal: {"GoStartLocal", 1007, false, []string{"g"}},
EvGoUnblockLocal: {"GoUnblockLocal", 1007, true, []string{"g"}},
EvGoSysExitLocal: {"GoSysExitLocal", 1007, false, []string{"g", "ts"}},
} }

View File

@ -89,10 +89,10 @@ func TestParseVersion(t *testing.T) {
func TestTimestampOverflow(t *testing.T) { func TestTimestampOverflow(t *testing.T) {
// Test that parser correctly handles large timestamps (long tracing). // Test that parser correctly handles large timestamps (long tracing).
w := newWriter() w := newWriter()
w.emit(EvBatch, 0, 0, 0) w.emit(EvBatch, 0, 0)
w.emit(EvFrequency, 1e9, 0) w.emit(EvFrequency, 1e9)
for ts := uint64(1); ts < 1e16; ts *= 2 { for ts := uint64(1); ts < 1e16; ts *= 2 {
w.emit(EvGoCreate, 1, ts, ts, 1, 0) w.emit(EvGoCreate, ts, ts, 0, 0)
} }
if _, err := Parse(w, ""); err != nil { if _, err := Parse(w, ""); err != nil {
t.Fatalf("failed to parse: %v", err) t.Fatalf("failed to parse: %v", err)
@ -110,7 +110,7 @@ func newWriter() *writer {
} }
func (w *writer) emit(typ byte, args ...uint64) { func (w *writer) emit(typ byte, args ...uint64) {
nargs := byte(len(args)) - 2 nargs := byte(len(args)) - 1
if nargs > 3 { if nargs > 3 {
nargs = 3 nargs = 3
} }

View File

@ -1796,23 +1796,7 @@ func execute(gp *g, inheritTime bool) {
// GoSysExit has to happen when we have a P, but before GoStart. // GoSysExit has to happen when we have a P, but before GoStart.
// So we emit it here. // So we emit it here.
if gp.syscallsp != 0 && gp.sysblocktraced { if gp.syscallsp != 0 && gp.sysblocktraced {
// Since gp.sysblocktraced is true, we must emit an event. traceGoSysExit(gp.sysexitticks)
// There is a race between the code that initializes sysexitseq
// and sysexitticks (in exitsyscall, which runs without a P,
// and therefore is not stopped with the rest of the world)
// and the code that initializes a new trace.
// The recorded sysexitseq and sysexitticks must therefore
// be treated as "best effort". If they are valid for this trace,
// then great, use them for greater accuracy.
// But if they're not valid for this trace, assume that the
// trace was started after the actual syscall exit (but before
// we actually managed to start the goroutine, aka right now),
// and assign a fresh time stamp to keep the log consistent.
seq, ts := gp.sysexitseq, gp.sysexitticks
if seq == 0 || int64(seq)-int64(trace.seqStart) < 0 {
seq, ts = tracestamp()
}
traceGoSysExit(seq, ts)
} }
traceGoStart() traceGoStart()
} }
@ -2481,7 +2465,6 @@ func exitsyscall(dummy int32) {
} }
_g_.sysexitticks = 0 _g_.sysexitticks = 0
_g_.sysexitseq = 0
if trace.enabled { if trace.enabled {
// Wait till traceGoSysBlock event is emitted. // Wait till traceGoSysBlock event is emitted.
// This ensures consistency of the trace (the goroutine is started after it is blocked). // This ensures consistency of the trace (the goroutine is started after it is blocked).
@ -2492,7 +2475,7 @@ func exitsyscall(dummy int32) {
// Tracing code can invoke write barriers that cannot run without a P. // Tracing code can invoke write barriers that cannot run without a P.
// So instead we remember the syscall exit time and emit the event // So instead we remember the syscall exit time and emit the event
// in execute when we have a P. // in execute when we have a P.
_g_.sysexitseq, _g_.sysexitticks = tracestamp() _g_.sysexitticks = cputicks()
} }
_g_.m.locks-- _g_.m.locks--
@ -2540,7 +2523,7 @@ func exitsyscallfast() bool {
// Denote blocking of the new syscall. // Denote blocking of the new syscall.
traceGoSysBlock(_g_.m.p.ptr()) traceGoSysBlock(_g_.m.p.ptr())
// Denote completion of the current syscall. // Denote completion of the current syscall.
traceGoSysExit(tracestamp()) traceGoSysExit(0)
}) })
} }
_g_.m.p.ptr().syscalltick++ _g_.m.p.ptr().syscalltick++
@ -2564,7 +2547,7 @@ func exitsyscallfast() bool {
osyield() osyield()
} }
} }
traceGoSysExit(tracestamp()) traceGoSysExit(0)
} }
}) })
if ok { if ok {

View File

@ -332,16 +332,17 @@ type g struct {
waitsince int64 // approx time when the g become blocked waitsince int64 // approx time when the g become blocked
waitreason string // if status==Gwaiting waitreason string // if status==Gwaiting
schedlink guintptr schedlink guintptr
preempt bool // preemption signal, duplicates stackguard0 = stackpreempt preempt bool // preemption signal, duplicates stackguard0 = stackpreempt
paniconfault bool // panic (instead of crash) on unexpected fault address paniconfault bool // panic (instead of crash) on unexpected fault address
preemptscan bool // preempted g does scan for gc preemptscan bool // preempted g does scan for gc
gcscandone bool // g has scanned stack; protected by _Gscan bit in status gcscandone bool // g has scanned stack; protected by _Gscan bit in status
gcscanvalid bool // false at start of gc cycle, true if G has not run since last scan gcscanvalid bool // false at start of gc cycle, true if G has not run since last scan
throwsplit bool // must not split stack throwsplit bool // must not split stack
raceignore int8 // ignore race detection events raceignore int8 // ignore race detection events
sysblocktraced bool // StartTrace has emitted EvGoInSyscall about this goroutine sysblocktraced bool // StartTrace has emitted EvGoInSyscall about this goroutine
sysexitticks int64 // cputicks when syscall has returned (for tracing) sysexitticks int64 // cputicks when syscall has returned (for tracing)
sysexitseq uint64 // trace seq when syscall has returned (for tracing) traceseq uint64 // trace event sequencer
tracelastp puintptr // last P emitted an event for this goroutine
lockedm *m lockedm *m
sig uint32 sig uint32
writebuf []byte writebuf []byte

View File

@ -13,7 +13,6 @@
package runtime package runtime
import ( import (
"runtime/internal/atomic"
"runtime/internal/sys" "runtime/internal/sys"
"unsafe" "unsafe"
) )
@ -27,21 +26,21 @@ const (
traceEvGomaxprocs = 4 // current value of GOMAXPROCS [timestamp, GOMAXPROCS, stack id] traceEvGomaxprocs = 4 // current value of GOMAXPROCS [timestamp, GOMAXPROCS, stack id]
traceEvProcStart = 5 // start of P [timestamp, thread id] traceEvProcStart = 5 // start of P [timestamp, thread id]
traceEvProcStop = 6 // stop of P [timestamp] traceEvProcStop = 6 // stop of P [timestamp]
traceEvGCStart = 7 // GC start [timestamp, stack id] traceEvGCStart = 7 // GC start [timestamp, seq, stack id]
traceEvGCDone = 8 // GC done [timestamp] traceEvGCDone = 8 // GC done [timestamp]
traceEvGCScanStart = 9 // GC scan start [timestamp] traceEvGCScanStart = 9 // GC scan start [timestamp]
traceEvGCScanDone = 10 // GC scan done [timestamp] traceEvGCScanDone = 10 // GC scan done [timestamp]
traceEvGCSweepStart = 11 // GC sweep start [timestamp, stack id] traceEvGCSweepStart = 11 // GC sweep start [timestamp, stack id]
traceEvGCSweepDone = 12 // GC sweep done [timestamp] traceEvGCSweepDone = 12 // GC sweep done [timestamp]
traceEvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, new stack id, stack id] traceEvGoCreate = 13 // goroutine creation [timestamp, new goroutine id, new stack id, stack id]
traceEvGoStart = 14 // goroutine starts running [timestamp, goroutine id] traceEvGoStart = 14 // goroutine starts running [timestamp, goroutine id, seq]
traceEvGoEnd = 15 // goroutine ends [timestamp] traceEvGoEnd = 15 // goroutine ends [timestamp]
traceEvGoStop = 16 // goroutine stops (like in select{}) [timestamp, stack] traceEvGoStop = 16 // goroutine stops (like in select{}) [timestamp, stack]
traceEvGoSched = 17 // goroutine calls Gosched [timestamp, stack] traceEvGoSched = 17 // goroutine calls Gosched [timestamp, stack]
traceEvGoPreempt = 18 // goroutine is preempted [timestamp, stack] traceEvGoPreempt = 18 // goroutine is preempted [timestamp, stack]
traceEvGoSleep = 19 // goroutine calls Sleep [timestamp, stack] traceEvGoSleep = 19 // goroutine calls Sleep [timestamp, stack]
traceEvGoBlock = 20 // goroutine blocks [timestamp, stack] traceEvGoBlock = 20 // goroutine blocks [timestamp, stack]
traceEvGoUnblock = 21 // goroutine is unblocked [timestamp, goroutine id, stack] traceEvGoUnblock = 21 // goroutine is unblocked [timestamp, goroutine id, seq, stack]
traceEvGoBlockSend = 22 // goroutine blocks on chan send [timestamp, stack] traceEvGoBlockSend = 22 // goroutine blocks on chan send [timestamp, stack]
traceEvGoBlockRecv = 23 // goroutine blocks on chan recv [timestamp, stack] traceEvGoBlockRecv = 23 // goroutine blocks on chan recv [timestamp, stack]
traceEvGoBlockSelect = 24 // goroutine blocks on select [timestamp, stack] traceEvGoBlockSelect = 24 // goroutine blocks on select [timestamp, stack]
@ -49,7 +48,7 @@ const (
traceEvGoBlockCond = 26 // goroutine blocks on Cond [timestamp, stack] traceEvGoBlockCond = 26 // goroutine blocks on Cond [timestamp, stack]
traceEvGoBlockNet = 27 // goroutine blocks on network [timestamp, stack] traceEvGoBlockNet = 27 // goroutine blocks on network [timestamp, stack]
traceEvGoSysCall = 28 // syscall enter [timestamp, stack] traceEvGoSysCall = 28 // syscall enter [timestamp, stack]
traceEvGoSysExit = 29 // syscall exit [timestamp, goroutine id, real timestamp] traceEvGoSysExit = 29 // syscall exit [timestamp, goroutine id, seq, real timestamp]
traceEvGoSysBlock = 30 // syscall blocks [timestamp] traceEvGoSysBlock = 30 // syscall blocks [timestamp]
traceEvGoWaiting = 31 // denotes that goroutine is blocked when tracing starts [timestamp, goroutine id] traceEvGoWaiting = 31 // denotes that goroutine is blocked when tracing starts [timestamp, goroutine id]
traceEvGoInSyscall = 32 // denotes that goroutine is in syscall when tracing starts [timestamp, goroutine id] traceEvGoInSyscall = 32 // denotes that goroutine is in syscall when tracing starts [timestamp, goroutine id]
@ -58,7 +57,10 @@ const (
traceEvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id] traceEvTimerGoroutine = 35 // denotes timer goroutine [timer goroutine id]
traceEvFutileWakeup = 36 // denotes that the previous wakeup of this goroutine was futile [timestamp] traceEvFutileWakeup = 36 // denotes that the previous wakeup of this goroutine was futile [timestamp]
traceEvString = 37 // string dictionary entry [ID, length, string] traceEvString = 37 // string dictionary entry [ID, length, string]
traceEvCount = 38 traceEvGoStartLocal = 38 // goroutine starts running on the same P as the last event [timestamp, goroutine id]
traceEvGoUnblockLocal = 39 // goroutine is unblocked on the same P as the last event [timestamp, goroutine id, stack]
traceEvGoSysExitLocal = 40 // syscall exit on the same P as the last event [timestamp, goroutine id, real timestamp]
traceEvCount = 41
) )
const ( const (
@ -105,6 +107,7 @@ var trace struct {
ticksEnd int64 // cputicks when tracing was stopped ticksEnd int64 // cputicks when tracing was stopped
timeStart int64 // nanotime when tracing was started timeStart int64 // nanotime when tracing was started
timeEnd int64 // nanotime when tracing was stopped timeEnd int64 // nanotime when tracing was stopped
seqGC uint64 // GC start/done sequencer
reading traceBufPtr // buffer currently handed off to user reading traceBufPtr // buffer currently handed off to user
empty traceBufPtr // stack of empty buffers empty traceBufPtr // stack of empty buffers
fullHead traceBufPtr // queue of full buffers fullHead traceBufPtr // queue of full buffers
@ -122,31 +125,9 @@ var trace struct {
buf traceBufPtr // global trace buffer, used when running without a p buf traceBufPtr // global trace buffer, used when running without a p
} }
var traceseq uint64 // global trace sequence number
// tracestamp returns a consistent sequence number, time stamp pair
// for use in a trace. We need to make sure that time stamp ordering
// (assuming synchronized CPUs) and sequence ordering match.
// To do that, we increment traceseq, grab ticks, and increment traceseq again.
// We treat odd traceseq as a sign that another thread is in the middle
// of the sequence and spin until it is done.
// Not splitting stack to avoid preemption, just in case the call sites
// that used to call xadd64 and cputicks are sensitive to that.
//go:nosplit
func tracestamp() (seq uint64, ts int64) {
seq = atomic.Load64(&traceseq)
for seq&1 != 0 || !atomic.Cas64(&traceseq, seq, seq+1) {
seq = atomic.Load64(&traceseq)
}
ts = cputicks()
atomic.Store64(&traceseq, seq+2)
return seq >> 1, ts
}
// traceBufHeader is per-P tracing buffer. // traceBufHeader is per-P tracing buffer.
type traceBufHeader struct { type traceBufHeader struct {
link traceBufPtr // in trace.empty/full link traceBufPtr // in trace.empty/full
lastSeq uint64 // sequence number of last event
lastTicks uint64 // when we wrote the last event lastTicks uint64 // when we wrote the last event
pos int // next write offset in arr pos int // next write offset in arr
stk [traceStackSize]uintptr // scratch buffer for traceback stk [traceStackSize]uintptr // scratch buffer for traceback
@ -194,13 +175,6 @@ func StartTrace() error {
return errorString("tracing is already enabled") return errorString("tracing is already enabled")
} }
trace.seqStart, trace.ticksStart = tracestamp()
trace.timeStart = nanotime()
trace.headerWritten = false
trace.footerWritten = false
trace.strings = make(map[string]uint64)
trace.stringSeq = 0
// Can't set trace.enabled yet. While the world is stopped, exitsyscall could // Can't set trace.enabled yet. While the world is stopped, exitsyscall could
// already emit a delayed event (see exitTicks in exitsyscall) if we set trace.enabled here. // already emit a delayed event (see exitTicks in exitsyscall) if we set trace.enabled here.
// That would lead to an inconsistent trace: // That would lead to an inconsistent trace:
@ -213,12 +187,15 @@ func StartTrace() error {
for _, gp := range allgs { for _, gp := range allgs {
status := readgstatus(gp) status := readgstatus(gp)
if status != _Gdead { if status != _Gdead {
traceGoCreate(gp, gp.startpc) traceGoCreate(gp, gp.startpc) // also resets gp.traceseq/tracelastp
} }
if status == _Gwaiting { if status == _Gwaiting {
// traceEvGoWaiting is implied to have seq=1.
gp.traceseq++
traceEvent(traceEvGoWaiting, -1, uint64(gp.goid)) traceEvent(traceEvGoWaiting, -1, uint64(gp.goid))
} }
if status == _Gsyscall { if status == _Gsyscall {
gp.traceseq++
traceEvent(traceEvGoInSyscall, -1, uint64(gp.goid)) traceEvent(traceEvGoInSyscall, -1, uint64(gp.goid))
} else { } else {
gp.sysblocktraced = false gp.sysblocktraced = false
@ -226,6 +203,17 @@ func StartTrace() error {
} }
traceProcStart() traceProcStart()
traceGoStart() traceGoStart()
// Note: ticksStart needs to be set after we emit traceEvGoInSyscall events.
// If we do it the other way around, it is possible that exitsyscall will
// query sysexitticks after ticksStart but before traceEvGoInSyscall timestamp.
// It will lead to a false conclusion that cputicks is broken.
trace.ticksStart = cputicks()
trace.timeStart = nanotime()
trace.headerWritten = false
trace.footerWritten = false
trace.strings = make(map[string]uint64)
trace.stringSeq = 0
trace.seqGC = 0
_g_.m.startingtrace = false _g_.m.startingtrace = false
trace.enabled = true trace.enabled = true
@ -382,11 +370,9 @@ func ReadTrace() []byte {
var data []byte var data []byte
data = append(data, traceEvFrequency|0<<traceArgCountShift) data = append(data, traceEvFrequency|0<<traceArgCountShift)
data = traceAppend(data, uint64(freq)) data = traceAppend(data, uint64(freq))
data = traceAppend(data, 0)
if timers.gp != nil { if timers.gp != nil {
data = append(data, traceEvTimerGoroutine|0<<traceArgCountShift) data = append(data, traceEvTimerGoroutine|0<<traceArgCountShift)
data = traceAppend(data, uint64(timers.gp.goid)) data = traceAppend(data, uint64(timers.gp.goid))
data = traceAppend(data, 0)
} }
// This will emit a bunch of full buffers, we will pick them up // This will emit a bunch of full buffers, we will pick them up
// on the next iteration. // on the next iteration.
@ -494,19 +480,14 @@ func traceEvent(ev byte, skip int, args ...uint64) {
(*bufp).set(buf) (*bufp).set(buf)
} }
seq, ticksraw := tracestamp() ticks := uint64(cputicks()) / traceTickDiv
seqDiff := seq - buf.lastSeq
ticks := uint64(ticksraw) / traceTickDiv
tickDiff := ticks - buf.lastTicks tickDiff := ticks - buf.lastTicks
if buf.pos == 0 { if buf.pos == 0 {
buf.byte(traceEvBatch | 1<<traceArgCountShift) buf.byte(traceEvBatch | 1<<traceArgCountShift)
buf.varint(uint64(pid)) buf.varint(uint64(pid))
buf.varint(seq)
buf.varint(ticks) buf.varint(ticks)
seqDiff = 0
tickDiff = 0 tickDiff = 0
} }
buf.lastSeq = seq
buf.lastTicks = ticks buf.lastTicks = ticks
narg := byte(len(args)) narg := byte(len(args))
if skip >= 0 { if skip >= 0 {
@ -525,7 +506,6 @@ func traceEvent(ev byte, skip int, args ...uint64) {
buf.varint(0) buf.varint(0)
lenp = &buf.arr[buf.pos-1] lenp = &buf.arr[buf.pos-1]
} }
buf.varint(seqDiff)
buf.varint(tickDiff) buf.varint(tickDiff)
for _, a := range args { for _, a := range args {
buf.varint(a) buf.varint(a)
@ -892,7 +872,8 @@ func traceProcStop(pp *p) {
} }
func traceGCStart() { func traceGCStart() {
traceEvent(traceEvGCStart, 3) traceEvent(traceEvGCStart, 3, trace.seqGC)
trace.seqGC++
} }
func traceGCDone() { func traceGCDone() {
@ -916,13 +897,23 @@ func traceGCSweepDone() {
} }
func traceGoCreate(newg *g, pc uintptr) { func traceGoCreate(newg *g, pc uintptr) {
newg.traceseq = 0
newg.tracelastp = getg().m.p
// +PCQuantum because traceFrameForPC expects return PCs and subtracts PCQuantum. // +PCQuantum because traceFrameForPC expects return PCs and subtracts PCQuantum.
id := trace.stackTab.put([]uintptr{pc + sys.PCQuantum}) id := trace.stackTab.put([]uintptr{pc + sys.PCQuantum})
traceEvent(traceEvGoCreate, 2, uint64(newg.goid), uint64(id)) traceEvent(traceEvGoCreate, 2, uint64(newg.goid), uint64(id))
} }
func traceGoStart() { func traceGoStart() {
traceEvent(traceEvGoStart, -1, uint64(getg().m.curg.goid)) _g_ := getg().m.curg
_p_ := _g_.m.p
_g_.traceseq++
if _g_.tracelastp == _p_ {
traceEvent(traceEvGoStartLocal, -1, uint64(_g_.goid))
} else {
_g_.tracelastp = _p_
traceEvent(traceEvGoStart, -1, uint64(_g_.goid), _g_.traceseq)
}
} }
func traceGoEnd() { func traceGoEnd() {
@ -930,10 +921,14 @@ func traceGoEnd() {
} }
func traceGoSched() { func traceGoSched() {
_g_ := getg()
_g_.tracelastp = _g_.m.p
traceEvent(traceEvGoSched, 1) traceEvent(traceEvGoSched, 1)
} }
func traceGoPreempt() { func traceGoPreempt() {
_g_ := getg()
_g_.tracelastp = _g_.m.p
traceEvent(traceEvGoPreempt, 1) traceEvent(traceEvGoPreempt, 1)
} }
@ -945,19 +940,37 @@ func traceGoPark(traceEv byte, skip int, gp *g) {
} }
func traceGoUnpark(gp *g, skip int) { func traceGoUnpark(gp *g, skip int) {
traceEvent(traceEvGoUnblock, skip, uint64(gp.goid)) _p_ := getg().m.p
gp.traceseq++
if gp.tracelastp == _p_ {
traceEvent(traceEvGoUnblockLocal, skip, uint64(gp.goid))
} else {
gp.tracelastp = _p_
traceEvent(traceEvGoUnblock, skip, uint64(gp.goid), gp.traceseq)
}
} }
func traceGoSysCall() { func traceGoSysCall() {
traceEvent(traceEvGoSysCall, 1) traceEvent(traceEvGoSysCall, 1)
} }
func traceGoSysExit(seq uint64, ts int64) { func traceGoSysExit(ts int64) {
if int64(seq)-int64(trace.seqStart) < 0 { if ts != 0 && ts < trace.ticksStart {
// The timestamp was obtained during a previous tracing session, ignore. // There is a race between the code that initializes sysexitticks
return // (in exitsyscall, which runs without a P, and therefore is not
// stopped with the rest of the world) and the code that initializes
// a new trace. The recorded sysexitticks must therefore be treated
// as "best effort". If they are valid for this trace, then great,
// use them for greater accuracy. But if they're not valid for this
// trace, assume that the trace was started after the actual syscall
// exit (but before we actually managed to start the goroutine,
// aka right now), and assign a fresh time stamp to keep the log consistent.
ts = 0
} }
traceEvent(traceEvGoSysExit, -1, uint64(getg().m.curg.goid), seq, uint64(ts)/traceTickDiv) _g_ := getg().m.curg
_g_.traceseq++
_g_.tracelastp = _g_.m.p
traceEvent(traceEvGoSysExit, -1, uint64(_g_.goid), _g_.traceseq, uint64(ts)/traceTickDiv)
} }
func traceGoSysBlock(pp *p) { func traceGoSysBlock(pp *p) {