mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
internal/trace: support event constructor for testing
Implement the new APIs described in #74826. Closes #74826 Change-Id: I6a6a6964229548e9d54e7af95185011e183ee50b Reviewed-on: https://go-review.googlesource.com/c/go/+/691815 Reviewed-by: Michael Knyszek <mknyszek@google.com> Reviewed-by: Cherry Mui <cherryyz@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
parent
eb63ef9d66
commit
54b82e944e
6 changed files with 1243 additions and 37 deletions
|
|
@ -766,7 +766,7 @@ var depsRules = `
|
||||||
FMT, internal/trace/version, io, sort, encoding/binary
|
FMT, internal/trace/version, io, sort, encoding/binary
|
||||||
< internal/trace/internal/tracev1;
|
< internal/trace/internal/tracev1;
|
||||||
|
|
||||||
FMT, encoding/binary, internal/trace/version, internal/trace/internal/tracev1, container/heap, math/rand
|
FMT, encoding/binary, internal/trace/version, internal/trace/internal/tracev1, container/heap, math/rand, regexp
|
||||||
< internal/trace;
|
< internal/trace;
|
||||||
|
|
||||||
# cmd/trace dependencies.
|
# cmd/trace dependencies.
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,16 @@ func (d *dataTable[EI, E]) insert(id EI, data E) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// append adds a new element to the data table and returns its ID.
|
||||||
|
func (d *dataTable[EI, E]) append(data E) EI {
|
||||||
|
if d.sparse == nil {
|
||||||
|
d.sparse = make(map[EI]E)
|
||||||
|
}
|
||||||
|
id := EI(len(d.sparse)) + 1
|
||||||
|
d.sparse[id] = data
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
// compactify attempts to compact sparse into dense.
|
// compactify attempts to compact sparse into dense.
|
||||||
//
|
//
|
||||||
// This is intended to be called only once after insertions are done.
|
// This is intended to be called only once after insertions are done.
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,13 @@
|
||||||
package trace
|
package trace
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"iter"
|
"iter"
|
||||||
"math"
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -256,6 +260,19 @@ type Log struct {
|
||||||
Message string
|
Message string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StackSample is used to construct StackSample events via MakeEvent. There are
|
||||||
|
// no details associated with it, use EventConfig.Stack instead.
|
||||||
|
type StackSample struct{}
|
||||||
|
|
||||||
|
// MakeStack create a stack from a list of stack frames.
|
||||||
|
func MakeStack(frames []StackFrame) Stack {
|
||||||
|
// TODO(felixge): support evTable reuse.
|
||||||
|
tbl := &evTable{pcs: make(map[uint64]frame)}
|
||||||
|
tbl.strings.compactify()
|
||||||
|
tbl.stacks.compactify()
|
||||||
|
return Stack{table: tbl, id: addStack(tbl, frames)}
|
||||||
|
}
|
||||||
|
|
||||||
// Stack represents a stack. It's really a handle to a stack and it's trivially comparable.
|
// Stack represents a stack. It's really a handle to a stack and it's trivially comparable.
|
||||||
//
|
//
|
||||||
// If two Stacks are equal then their Frames are guaranteed to be identical. If they are not
|
// If two Stacks are equal then their Frames are guaranteed to be identical. If they are not
|
||||||
|
|
@ -287,6 +304,22 @@ func (s Stack) Frames() iter.Seq[StackFrame] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// String returns the stack as a human-readable string.
|
||||||
|
//
|
||||||
|
// The format of the string is intended for debugging and is subject to change.
|
||||||
|
func (s Stack) String() string {
|
||||||
|
var sb strings.Builder
|
||||||
|
printStack(&sb, "", s.Frames())
|
||||||
|
return sb.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func printStack(w io.Writer, prefix string, frames iter.Seq[StackFrame]) {
|
||||||
|
for f := range frames {
|
||||||
|
fmt.Fprintf(w, "%s%s @ 0x%x\n", prefix, f.Func, f.PC)
|
||||||
|
fmt.Fprintf(w, "%s\t%s:%d\n", prefix, f.File, f.Line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NoStack is a sentinel value that can be compared against any Stack value, indicating
|
// NoStack is a sentinel value that can be compared against any Stack value, indicating
|
||||||
// a lack of a stack trace.
|
// a lack of a stack trace.
|
||||||
var NoStack = Stack{}
|
var NoStack = Stack{}
|
||||||
|
|
@ -332,9 +365,9 @@ func (e ExperimentalEvent) ArgValue(i int) Value {
|
||||||
}
|
}
|
||||||
if strings.HasSuffix(e.Args[i], "string") {
|
if strings.HasSuffix(e.Args[i], "string") {
|
||||||
s := e.table.strings.mustGet(stringID(e.argValues[i]))
|
s := e.table.strings.mustGet(stringID(e.argValues[i]))
|
||||||
return stringValue(s)
|
return StringValue(s)
|
||||||
}
|
}
|
||||||
return uint64Value(e.argValues[i])
|
return Uint64Value(e.argValues[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExperimentalBatch represents a packet of unparsed data along with metadata about that packet.
|
// ExperimentalBatch represents a packet of unparsed data along with metadata about that packet.
|
||||||
|
|
@ -346,6 +379,416 @@ type ExperimentalBatch struct {
|
||||||
Data []byte
|
Data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type EventDetails interface {
|
||||||
|
Metric | Label | Range | StateTransition | Sync | Task | Region | Log | StackSample
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventConfig holds the data for constructing a trace event.
|
||||||
|
type EventConfig[T EventDetails] struct {
|
||||||
|
// Time is the timestamp of the event.
|
||||||
|
Time Time
|
||||||
|
|
||||||
|
// Kind is the kind of the event.
|
||||||
|
Kind EventKind
|
||||||
|
|
||||||
|
// Goroutine is the goroutine ID of the event.
|
||||||
|
Goroutine GoID
|
||||||
|
|
||||||
|
// Proc is the proc ID of the event.
|
||||||
|
Proc ProcID
|
||||||
|
|
||||||
|
// Thread is the thread ID of the event.
|
||||||
|
Thread ThreadID
|
||||||
|
|
||||||
|
// Stack is the stack of the event.
|
||||||
|
Stack Stack
|
||||||
|
|
||||||
|
// Details is the kind specific details of the event.
|
||||||
|
Details T
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeEvent creates a new trace event from the given configuration.
|
||||||
|
func MakeEvent[T EventDetails](c EventConfig[T]) (e Event, err error) {
|
||||||
|
// TODO(felixge): make the evTable reusable.
|
||||||
|
e = Event{
|
||||||
|
table: &evTable{pcs: make(map[uint64]frame), sync: sync{freq: 1}},
|
||||||
|
base: baseEvent{time: c.Time},
|
||||||
|
ctx: schedCtx{G: c.Goroutine, P: c.Proc, M: c.Thread},
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
// N.b. evSync is not in tracev2.Specs()
|
||||||
|
if err != nil || e.base.typ == evSync {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
spec := tracev2.Specs()[e.base.typ]
|
||||||
|
if len(spec.StackIDs) > 0 && c.Stack != NoStack {
|
||||||
|
// The stack for the main execution context is always the
|
||||||
|
// first stack listed in StackIDs. Subtract one from this
|
||||||
|
// because we've peeled away the timestamp argument.
|
||||||
|
e.base.args[spec.StackIDs[0]-1] = uint64(addStack(e.table, slices.Collect(c.Stack.Frames())))
|
||||||
|
}
|
||||||
|
|
||||||
|
e.table.strings.compactify()
|
||||||
|
e.table.stacks.compactify()
|
||||||
|
}()
|
||||||
|
var defaultKind EventKind
|
||||||
|
switch c.Kind {
|
||||||
|
case defaultKind:
|
||||||
|
return Event{}, fmt.Errorf("the Kind field must be provided")
|
||||||
|
case EventMetric:
|
||||||
|
if m, ok := any(c.Details).(Metric); ok {
|
||||||
|
return makeMetricEvent(e, m)
|
||||||
|
}
|
||||||
|
case EventLabel:
|
||||||
|
if l, ok := any(c.Details).(Label); ok {
|
||||||
|
return makeLabelEvent(e, l)
|
||||||
|
}
|
||||||
|
case EventRangeBegin, EventRangeActive, EventRangeEnd:
|
||||||
|
if r, ok := any(c.Details).(Range); ok {
|
||||||
|
return makeRangeEvent(e, c.Kind, r)
|
||||||
|
}
|
||||||
|
case EventStateTransition:
|
||||||
|
if t, ok := any(c.Details).(StateTransition); ok {
|
||||||
|
return makeStateTransitionEvent(e, t)
|
||||||
|
}
|
||||||
|
case EventSync:
|
||||||
|
if s, ok := any(c.Details).(Sync); ok {
|
||||||
|
return makeSyncEvent(e, s)
|
||||||
|
}
|
||||||
|
case EventTaskBegin, EventTaskEnd:
|
||||||
|
if t, ok := any(c.Details).(Task); ok {
|
||||||
|
return makeTaskEvent(e, c.Kind, t)
|
||||||
|
}
|
||||||
|
case EventRegionBegin, EventRegionEnd:
|
||||||
|
if r, ok := any(c.Details).(Region); ok {
|
||||||
|
return makeRegionEvent(e, c.Kind, r)
|
||||||
|
}
|
||||||
|
case EventLog:
|
||||||
|
if l, ok := any(c.Details).(Log); ok {
|
||||||
|
return makeLogEvent(e, l)
|
||||||
|
}
|
||||||
|
case EventStackSample:
|
||||||
|
if _, ok := any(c.Details).(StackSample); ok {
|
||||||
|
return makeStackSampleEvent(e, c.Stack)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Event{}, fmt.Errorf("the Kind field %s is incompatible with Details type %T", c.Kind, c.Details)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeMetricEvent(e Event, m Metric) (Event, error) {
|
||||||
|
if m.Value.Kind() != ValueUint64 {
|
||||||
|
return Event{}, fmt.Errorf("metric value must be a uint64, got: %s", m.Value.String())
|
||||||
|
}
|
||||||
|
switch m.Name {
|
||||||
|
case "/sched/gomaxprocs:threads":
|
||||||
|
e.base.typ = tracev2.EvProcsChange
|
||||||
|
case "/memory/classes/heap/objects:bytes":
|
||||||
|
e.base.typ = tracev2.EvHeapAlloc
|
||||||
|
case "/gc/heap/goal:bytes":
|
||||||
|
e.base.typ = tracev2.EvHeapGoal
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unknown metric name: %s", m.Name)
|
||||||
|
}
|
||||||
|
e.base.args[0] = uint64(m.Value.Uint64())
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLabelEvent(e Event, l Label) (Event, error) {
|
||||||
|
if l.Resource.Kind != ResourceGoroutine {
|
||||||
|
return Event{}, fmt.Errorf("resource must be a goroutine: %s", l.Resource)
|
||||||
|
}
|
||||||
|
e.base.typ = tracev2.EvGoLabel
|
||||||
|
e.base.args[0] = uint64(e.table.strings.append(l.Label))
|
||||||
|
// TODO(felixge): check against sched ctx and return error on mismatch
|
||||||
|
e.ctx.G = l.Resource.Goroutine()
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var stwRangeRegexp = regexp.MustCompile(`^stop-the-world \((.*)\)$`)
|
||||||
|
|
||||||
|
// TODO(felixge): should this ever manipulate the e ctx? Or just report mismatches?
|
||||||
|
func makeRangeEvent(e Event, kind EventKind, r Range) (Event, error) {
|
||||||
|
// TODO(felixge): Should we add dedicated range kinds rather than using
|
||||||
|
// string names?
|
||||||
|
switch r.Name {
|
||||||
|
case "GC concurrent mark phase":
|
||||||
|
if r.Scope.Kind != ResourceNone {
|
||||||
|
return Event{}, fmt.Errorf("unexpected scope: %s", r.Scope)
|
||||||
|
}
|
||||||
|
switch kind {
|
||||||
|
case EventRangeBegin:
|
||||||
|
e.base.typ = tracev2.EvGCBegin
|
||||||
|
case EventRangeActive:
|
||||||
|
e.base.typ = tracev2.EvGCActive
|
||||||
|
case EventRangeEnd:
|
||||||
|
e.base.typ = tracev2.EvGCEnd
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected range kind: %s", kind)
|
||||||
|
}
|
||||||
|
case "GC incremental sweep":
|
||||||
|
if r.Scope.Kind != ResourceProc {
|
||||||
|
return Event{}, fmt.Errorf("unexpected scope: %s", r.Scope)
|
||||||
|
}
|
||||||
|
switch kind {
|
||||||
|
case EventRangeBegin:
|
||||||
|
e.base.typ = tracev2.EvGCSweepBegin
|
||||||
|
e.ctx.P = r.Scope.Proc()
|
||||||
|
case EventRangeActive:
|
||||||
|
e.base.typ = tracev2.EvGCSweepActive
|
||||||
|
e.base.args[0] = uint64(r.Scope.Proc())
|
||||||
|
case EventRangeEnd:
|
||||||
|
e.base.typ = tracev2.EvGCSweepEnd
|
||||||
|
// TODO(felixge): check against sched ctx and return error on mismatch
|
||||||
|
e.ctx.P = r.Scope.Proc()
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected range kind: %s", kind)
|
||||||
|
}
|
||||||
|
case "GC mark assist":
|
||||||
|
if r.Scope.Kind != ResourceGoroutine {
|
||||||
|
return Event{}, fmt.Errorf("unexpected scope: %s", r.Scope)
|
||||||
|
}
|
||||||
|
switch kind {
|
||||||
|
case EventRangeBegin:
|
||||||
|
e.base.typ = tracev2.EvGCMarkAssistBegin
|
||||||
|
e.ctx.G = r.Scope.Goroutine()
|
||||||
|
case EventRangeActive:
|
||||||
|
e.base.typ = tracev2.EvGCMarkAssistActive
|
||||||
|
e.base.args[0] = uint64(r.Scope.Goroutine())
|
||||||
|
case EventRangeEnd:
|
||||||
|
e.base.typ = tracev2.EvGCMarkAssistEnd
|
||||||
|
// TODO(felixge): check against sched ctx and return error on mismatch
|
||||||
|
e.ctx.G = r.Scope.Goroutine()
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected range kind: %s", kind)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
match := stwRangeRegexp.FindStringSubmatch(r.Name)
|
||||||
|
if len(match) != 2 {
|
||||||
|
return Event{}, fmt.Errorf("unexpected range name: %s", r.Name)
|
||||||
|
}
|
||||||
|
if r.Scope.Kind != ResourceGoroutine {
|
||||||
|
return Event{}, fmt.Errorf("unexpected scope: %s", r.Scope)
|
||||||
|
}
|
||||||
|
switch kind {
|
||||||
|
case EventRangeBegin:
|
||||||
|
e.base.typ = tracev2.EvSTWBegin
|
||||||
|
// TODO(felixge): check against sched ctx and return error on mismatch
|
||||||
|
e.ctx.G = r.Scope.Goroutine()
|
||||||
|
case EventRangeEnd:
|
||||||
|
e.base.typ = tracev2.EvSTWEnd
|
||||||
|
// TODO(felixge): check against sched ctx and return error on mismatch
|
||||||
|
e.ctx.G = r.Scope.Goroutine()
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected range kind: %s", kind)
|
||||||
|
}
|
||||||
|
e.base.args[0] = uint64(e.table.strings.append(match[1]))
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeStateTransitionEvent(e Event, t StateTransition) (Event, error) {
|
||||||
|
switch t.Resource.Kind {
|
||||||
|
case ResourceProc:
|
||||||
|
from, to := ProcState(t.oldState), ProcState(t.newState)
|
||||||
|
switch {
|
||||||
|
case from == ProcIdle && to == ProcIdle:
|
||||||
|
// TODO(felixge): Could this also be a ProcStatus event?
|
||||||
|
e.base.typ = tracev2.EvProcSteal
|
||||||
|
e.base.args[0] = uint64(t.Resource.Proc())
|
||||||
|
e.base.extra(version.Go122)[0] = uint64(tracev2.ProcSyscallAbandoned)
|
||||||
|
case from == ProcIdle && to == ProcRunning:
|
||||||
|
e.base.typ = tracev2.EvProcStart
|
||||||
|
e.base.args[0] = uint64(t.Resource.Proc())
|
||||||
|
case from == ProcRunning && to == ProcIdle:
|
||||||
|
e.base.typ = tracev2.EvProcStop
|
||||||
|
if t.Resource.Proc() != e.ctx.P {
|
||||||
|
e.base.typ = tracev2.EvProcSteal
|
||||||
|
e.base.args[0] = uint64(t.Resource.Proc())
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
e.base.typ = tracev2.EvProcStatus
|
||||||
|
e.base.args[0] = uint64(t.Resource.Proc())
|
||||||
|
e.base.args[1] = uint64(procState2Tracev2ProcStatus[to])
|
||||||
|
e.base.extra(version.Go122)[0] = uint64(procState2Tracev2ProcStatus[from])
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
case ResourceGoroutine:
|
||||||
|
from, to := GoState(t.oldState), GoState(t.newState)
|
||||||
|
stack := slices.Collect(t.Stack.Frames())
|
||||||
|
goroutine := t.Resource.Goroutine()
|
||||||
|
|
||||||
|
if (from == GoUndetermined || from == to) && from != GoNotExist {
|
||||||
|
e.base.typ = tracev2.EvGoStatus
|
||||||
|
if len(stack) > 0 {
|
||||||
|
e.base.typ = tracev2.EvGoStatusStack
|
||||||
|
}
|
||||||
|
e.base.args[0] = uint64(goroutine)
|
||||||
|
e.base.args[2] = uint64(from)<<32 | uint64(goState2Tracev2GoStatus[to])
|
||||||
|
} else {
|
||||||
|
switch from {
|
||||||
|
case GoNotExist:
|
||||||
|
switch to {
|
||||||
|
case GoWaiting:
|
||||||
|
e.base.typ = tracev2.EvGoCreateBlocked
|
||||||
|
e.base.args[0] = uint64(goroutine)
|
||||||
|
e.base.args[1] = uint64(addStack(e.table, stack))
|
||||||
|
case GoRunnable:
|
||||||
|
e.base.typ = tracev2.EvGoCreate
|
||||||
|
e.base.args[0] = uint64(goroutine)
|
||||||
|
e.base.args[1] = uint64(addStack(e.table, stack))
|
||||||
|
case GoSyscall:
|
||||||
|
e.base.typ = tracev2.EvGoCreateSyscall
|
||||||
|
e.base.args[0] = uint64(goroutine)
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to)
|
||||||
|
}
|
||||||
|
case GoRunnable:
|
||||||
|
e.base.typ = tracev2.EvGoStart
|
||||||
|
e.base.args[0] = uint64(goroutine)
|
||||||
|
case GoRunning:
|
||||||
|
switch to {
|
||||||
|
case GoNotExist:
|
||||||
|
e.base.typ = tracev2.EvGoDestroy
|
||||||
|
e.ctx.G = goroutine
|
||||||
|
case GoRunnable:
|
||||||
|
e.base.typ = tracev2.EvGoStop
|
||||||
|
e.ctx.G = goroutine
|
||||||
|
e.base.args[0] = uint64(e.table.strings.append(t.Reason))
|
||||||
|
case GoWaiting:
|
||||||
|
e.base.typ = tracev2.EvGoBlock
|
||||||
|
e.ctx.G = goroutine
|
||||||
|
e.base.args[0] = uint64(e.table.strings.append(t.Reason))
|
||||||
|
case GoSyscall:
|
||||||
|
e.base.typ = tracev2.EvGoSyscallBegin
|
||||||
|
e.ctx.G = goroutine
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to)
|
||||||
|
}
|
||||||
|
case GoSyscall:
|
||||||
|
switch to {
|
||||||
|
case GoNotExist:
|
||||||
|
e.base.typ = tracev2.EvGoDestroySyscall
|
||||||
|
e.ctx.G = goroutine
|
||||||
|
case GoRunning:
|
||||||
|
e.base.typ = tracev2.EvGoSyscallEnd
|
||||||
|
e.ctx.G = goroutine
|
||||||
|
case GoRunnable:
|
||||||
|
e.base.typ = tracev2.EvGoSyscallEndBlocked
|
||||||
|
e.ctx.G = goroutine
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to)
|
||||||
|
}
|
||||||
|
case GoWaiting:
|
||||||
|
switch to {
|
||||||
|
case GoRunnable:
|
||||||
|
e.base.typ = tracev2.EvGoUnblock
|
||||||
|
e.base.args[0] = uint64(goroutine)
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unexpected transition: %s -> %s", from, to)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Event{}, fmt.Errorf("unsupported state transition resource: %s", t.Resource)
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeSyncEvent(e Event, s Sync) (Event, error) {
|
||||||
|
e.base.typ = evSync
|
||||||
|
e.base.args[0] = uint64(s.N)
|
||||||
|
if e.table.expBatches == nil {
|
||||||
|
e.table.expBatches = make(map[tracev2.Experiment][]ExperimentalBatch)
|
||||||
|
}
|
||||||
|
for name, batches := range s.ExperimentalBatches {
|
||||||
|
var found bool
|
||||||
|
for id, exp := range tracev2.Experiments() {
|
||||||
|
if exp == name {
|
||||||
|
found = true
|
||||||
|
e.table.expBatches[tracev2.Experiment(id)] = batches
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
return Event{}, fmt.Errorf("unknown experiment: %s", name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if s.ClockSnapshot != nil {
|
||||||
|
e.table.hasClockSnapshot = true
|
||||||
|
e.table.snapWall = s.ClockSnapshot.Wall
|
||||||
|
e.table.snapMono = s.ClockSnapshot.Mono
|
||||||
|
// N.b. MakeEvent sets e.table.freq to 1.
|
||||||
|
e.table.snapTime = timestamp(s.ClockSnapshot.Trace)
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeTaskEvent(e Event, kind EventKind, t Task) (Event, error) {
|
||||||
|
if t.ID == NoTask {
|
||||||
|
return Event{}, errors.New("task ID cannot be NoTask")
|
||||||
|
}
|
||||||
|
e.base.args[0] = uint64(t.ID)
|
||||||
|
switch kind {
|
||||||
|
case EventTaskBegin:
|
||||||
|
e.base.typ = tracev2.EvUserTaskBegin
|
||||||
|
e.base.args[1] = uint64(t.Parent)
|
||||||
|
e.base.args[2] = uint64(e.table.strings.append(t.Type))
|
||||||
|
case EventTaskEnd:
|
||||||
|
e.base.typ = tracev2.EvUserTaskEnd
|
||||||
|
e.base.extra(version.Go122)[0] = uint64(t.Parent)
|
||||||
|
e.base.extra(version.Go122)[1] = uint64(e.table.addExtraString(t.Type))
|
||||||
|
default:
|
||||||
|
// TODO(felixge): also do this for ranges?
|
||||||
|
panic("unexpected task kind")
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeRegionEvent(e Event, kind EventKind, r Region) (Event, error) {
|
||||||
|
e.base.args[0] = uint64(r.Task)
|
||||||
|
e.base.args[1] = uint64(e.table.strings.append(r.Type))
|
||||||
|
switch kind {
|
||||||
|
case EventRegionBegin:
|
||||||
|
e.base.typ = tracev2.EvUserRegionBegin
|
||||||
|
case EventRegionEnd:
|
||||||
|
e.base.typ = tracev2.EvUserRegionEnd
|
||||||
|
default:
|
||||||
|
panic("unexpected region kind")
|
||||||
|
}
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeLogEvent(e Event, l Log) (Event, error) {
|
||||||
|
e.base.typ = tracev2.EvUserLog
|
||||||
|
e.base.args[0] = uint64(l.Task)
|
||||||
|
e.base.args[1] = uint64(e.table.strings.append(l.Category))
|
||||||
|
e.base.args[2] = uint64(e.table.strings.append(l.Message))
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeStackSampleEvent(e Event, s Stack) (Event, error) {
|
||||||
|
e.base.typ = tracev2.EvCPUSample
|
||||||
|
frames := slices.Collect(s.Frames())
|
||||||
|
e.base.args[0] = uint64(addStack(e.table, frames))
|
||||||
|
return e, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addStack(table *evTable, frames []StackFrame) stackID {
|
||||||
|
var pcs []uint64
|
||||||
|
for _, f := range frames {
|
||||||
|
table.pcs[f.PC] = frame{
|
||||||
|
pc: f.PC,
|
||||||
|
funcID: table.strings.append(f.Func),
|
||||||
|
fileID: table.strings.append(f.File),
|
||||||
|
line: f.Line,
|
||||||
|
}
|
||||||
|
pcs = append(pcs, f.PC)
|
||||||
|
}
|
||||||
|
return table.stacks.append(stack{pcs: pcs})
|
||||||
|
}
|
||||||
|
|
||||||
// Event represents a single event in the trace.
|
// Event represents a single event in the trace.
|
||||||
type Event struct {
|
type Event struct {
|
||||||
table *evTable
|
table *evTable
|
||||||
|
|
@ -435,13 +878,13 @@ func (e Event) Metric() Metric {
|
||||||
switch e.base.typ {
|
switch e.base.typ {
|
||||||
case tracev2.EvProcsChange:
|
case tracev2.EvProcsChange:
|
||||||
m.Name = "/sched/gomaxprocs:threads"
|
m.Name = "/sched/gomaxprocs:threads"
|
||||||
m.Value = uint64Value(e.base.args[0])
|
m.Value = Uint64Value(e.base.args[0])
|
||||||
case tracev2.EvHeapAlloc:
|
case tracev2.EvHeapAlloc:
|
||||||
m.Name = "/memory/classes/heap/objects:bytes"
|
m.Name = "/memory/classes/heap/objects:bytes"
|
||||||
m.Value = uint64Value(e.base.args[0])
|
m.Value = Uint64Value(e.base.args[0])
|
||||||
case tracev2.EvHeapGoal:
|
case tracev2.EvHeapGoal:
|
||||||
m.Name = "/gc/heap/goal:bytes"
|
m.Name = "/gc/heap/goal:bytes"
|
||||||
m.Value = uint64Value(e.base.args[0])
|
m.Value = Uint64Value(e.base.args[0])
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("internal error: unexpected wire-format event type for Metric kind: %d", e.base.typ))
|
panic(fmt.Sprintf("internal error: unexpected wire-format event type for Metric kind: %d", e.base.typ))
|
||||||
}
|
}
|
||||||
|
|
@ -516,11 +959,11 @@ func (e Event) RangeAttributes() []RangeAttribute {
|
||||||
return []RangeAttribute{
|
return []RangeAttribute{
|
||||||
{
|
{
|
||||||
Name: "bytes swept",
|
Name: "bytes swept",
|
||||||
Value: uint64Value(e.base.args[0]),
|
Value: Uint64Value(e.base.args[0]),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bytes reclaimed",
|
Name: "bytes reclaimed",
|
||||||
Value: uint64Value(e.base.args[1]),
|
Value: Uint64Value(e.base.args[1]),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -594,9 +1037,9 @@ func (e Event) StateTransition() StateTransition {
|
||||||
var s StateTransition
|
var s StateTransition
|
||||||
switch e.base.typ {
|
switch e.base.typ {
|
||||||
case tracev2.EvProcStart:
|
case tracev2.EvProcStart:
|
||||||
s = procStateTransition(ProcID(e.base.args[0]), ProcIdle, ProcRunning)
|
s = MakeProcStateTransition(ProcID(e.base.args[0]), ProcIdle, ProcRunning)
|
||||||
case tracev2.EvProcStop:
|
case tracev2.EvProcStop:
|
||||||
s = procStateTransition(e.ctx.P, ProcRunning, ProcIdle)
|
s = MakeProcStateTransition(e.ctx.P, ProcRunning, ProcIdle)
|
||||||
case tracev2.EvProcSteal:
|
case tracev2.EvProcSteal:
|
||||||
// N.B. ordering.advance populates e.base.extra.
|
// N.B. ordering.advance populates e.base.extra.
|
||||||
beforeState := ProcRunning
|
beforeState := ProcRunning
|
||||||
|
|
@ -607,49 +1050,50 @@ func (e Event) StateTransition() StateTransition {
|
||||||
// transition.
|
// transition.
|
||||||
beforeState = ProcIdle
|
beforeState = ProcIdle
|
||||||
}
|
}
|
||||||
s = procStateTransition(ProcID(e.base.args[0]), beforeState, ProcIdle)
|
s = MakeProcStateTransition(ProcID(e.base.args[0]), beforeState, ProcIdle)
|
||||||
case tracev2.EvProcStatus:
|
case tracev2.EvProcStatus:
|
||||||
// N.B. ordering.advance populates e.base.extra.
|
// N.B. ordering.advance populates e.base.extra.
|
||||||
s = procStateTransition(ProcID(e.base.args[0]), ProcState(e.base.extra(version.Go122)[0]), tracev2ProcStatus2ProcState[e.base.args[1]])
|
s = MakeProcStateTransition(ProcID(e.base.args[0]), ProcState(e.base.extra(version.Go122)[0]), tracev2ProcStatus2ProcState[e.base.args[1]])
|
||||||
case tracev2.EvGoCreate, tracev2.EvGoCreateBlocked:
|
case tracev2.EvGoCreate, tracev2.EvGoCreateBlocked:
|
||||||
status := GoRunnable
|
status := GoRunnable
|
||||||
if e.base.typ == tracev2.EvGoCreateBlocked {
|
if e.base.typ == tracev2.EvGoCreateBlocked {
|
||||||
status = GoWaiting
|
status = GoWaiting
|
||||||
}
|
}
|
||||||
s = goStateTransition(GoID(e.base.args[0]), GoNotExist, status)
|
s = MakeGoStateTransition(GoID(e.base.args[0]), GoNotExist, status)
|
||||||
s.Stack = Stack{table: e.table, id: stackID(e.base.args[1])}
|
s.Stack = Stack{table: e.table, id: stackID(e.base.args[1])}
|
||||||
case tracev2.EvGoCreateSyscall:
|
case tracev2.EvGoCreateSyscall:
|
||||||
s = goStateTransition(GoID(e.base.args[0]), GoNotExist, GoSyscall)
|
s = MakeGoStateTransition(GoID(e.base.args[0]), GoNotExist, GoSyscall)
|
||||||
case tracev2.EvGoStart:
|
case tracev2.EvGoStart:
|
||||||
s = goStateTransition(GoID(e.base.args[0]), GoRunnable, GoRunning)
|
s = MakeGoStateTransition(GoID(e.base.args[0]), GoRunnable, GoRunning)
|
||||||
case tracev2.EvGoDestroy:
|
case tracev2.EvGoDestroy:
|
||||||
s = goStateTransition(e.ctx.G, GoRunning, GoNotExist)
|
s = MakeGoStateTransition(e.ctx.G, GoRunning, GoNotExist)
|
||||||
case tracev2.EvGoDestroySyscall:
|
case tracev2.EvGoDestroySyscall:
|
||||||
s = goStateTransition(e.ctx.G, GoSyscall, GoNotExist)
|
s = MakeGoStateTransition(e.ctx.G, GoSyscall, GoNotExist)
|
||||||
case tracev2.EvGoStop:
|
case tracev2.EvGoStop:
|
||||||
s = goStateTransition(e.ctx.G, GoRunning, GoRunnable)
|
s = MakeGoStateTransition(e.ctx.G, GoRunning, GoRunnable)
|
||||||
s.Reason = e.table.strings.mustGet(stringID(e.base.args[0]))
|
s.Reason = e.table.strings.mustGet(stringID(e.base.args[0]))
|
||||||
s.Stack = e.Stack() // This event references the resource the event happened on.
|
s.Stack = e.Stack() // This event references the resource the event happened on.
|
||||||
case tracev2.EvGoBlock:
|
case tracev2.EvGoBlock:
|
||||||
s = goStateTransition(e.ctx.G, GoRunning, GoWaiting)
|
s = MakeGoStateTransition(e.ctx.G, GoRunning, GoWaiting)
|
||||||
s.Reason = e.table.strings.mustGet(stringID(e.base.args[0]))
|
s.Reason = e.table.strings.mustGet(stringID(e.base.args[0]))
|
||||||
s.Stack = e.Stack() // This event references the resource the event happened on.
|
s.Stack = e.Stack() // This event references the resource the event happened on.
|
||||||
case tracev2.EvGoUnblock, tracev2.EvGoSwitch, tracev2.EvGoSwitchDestroy:
|
case tracev2.EvGoUnblock, tracev2.EvGoSwitch, tracev2.EvGoSwitchDestroy:
|
||||||
// N.B. GoSwitch and GoSwitchDestroy both emit additional events, but
|
// N.B. GoSwitch and GoSwitchDestroy both emit additional events, but
|
||||||
// the first thing they both do is unblock the goroutine they name,
|
// the first thing they both do is unblock the goroutine they name,
|
||||||
// identically to an unblock event (even their arguments match).
|
// identically to an unblock event (even their arguments match).
|
||||||
s = goStateTransition(GoID(e.base.args[0]), GoWaiting, GoRunnable)
|
s = MakeGoStateTransition(GoID(e.base.args[0]), GoWaiting, GoRunnable)
|
||||||
case tracev2.EvGoSyscallBegin:
|
case tracev2.EvGoSyscallBegin:
|
||||||
s = goStateTransition(e.ctx.G, GoRunning, GoSyscall)
|
s = MakeGoStateTransition(e.ctx.G, GoRunning, GoSyscall)
|
||||||
s.Stack = e.Stack() // This event references the resource the event happened on.
|
s.Stack = e.Stack() // This event references the resource the event happened on.
|
||||||
case tracev2.EvGoSyscallEnd:
|
case tracev2.EvGoSyscallEnd:
|
||||||
s = goStateTransition(e.ctx.G, GoSyscall, GoRunning)
|
s = MakeGoStateTransition(e.ctx.G, GoSyscall, GoRunning)
|
||||||
case tracev2.EvGoSyscallEndBlocked:
|
case tracev2.EvGoSyscallEndBlocked:
|
||||||
s = goStateTransition(e.ctx.G, GoSyscall, GoRunnable)
|
s = MakeGoStateTransition(e.ctx.G, GoSyscall, GoRunnable)
|
||||||
case tracev2.EvGoStatus, tracev2.EvGoStatusStack:
|
case tracev2.EvGoStatus, tracev2.EvGoStatusStack:
|
||||||
packedStatus := e.base.args[2]
|
packedStatus := e.base.args[2]
|
||||||
from, to := packedStatus>>32, packedStatus&((1<<32)-1)
|
from, to := packedStatus>>32, packedStatus&((1<<32)-1)
|
||||||
s = goStateTransition(GoID(e.base.args[0]), GoState(from), tracev2GoStatus2GoState[to])
|
s = MakeGoStateTransition(GoID(e.base.args[0]), GoState(from), tracev2GoStatus2GoState[to])
|
||||||
|
s.Stack = e.Stack() // This event references the resource the event happened on.
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("internal error: unexpected wire-format event type for StateTransition kind: %d", e.base.typ))
|
panic(fmt.Sprintf("internal error: unexpected wire-format event type for StateTransition kind: %d", e.base.typ))
|
||||||
}
|
}
|
||||||
|
|
@ -793,6 +1237,13 @@ var tracev2GoStatus2GoState = [...]GoState{
|
||||||
tracev2.GoSyscall: GoSyscall,
|
tracev2.GoSyscall: GoSyscall,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var goState2Tracev2GoStatus = [...]tracev2.GoStatus{
|
||||||
|
GoRunnable: tracev2.GoRunnable,
|
||||||
|
GoRunning: tracev2.GoRunning,
|
||||||
|
GoWaiting: tracev2.GoWaiting,
|
||||||
|
GoSyscall: tracev2.GoSyscall,
|
||||||
|
}
|
||||||
|
|
||||||
var tracev2ProcStatus2ProcState = [...]ProcState{
|
var tracev2ProcStatus2ProcState = [...]ProcState{
|
||||||
tracev2.ProcRunning: ProcRunning,
|
tracev2.ProcRunning: ProcRunning,
|
||||||
tracev2.ProcIdle: ProcIdle,
|
tracev2.ProcIdle: ProcIdle,
|
||||||
|
|
@ -800,6 +1251,12 @@ var tracev2ProcStatus2ProcState = [...]ProcState{
|
||||||
tracev2.ProcSyscallAbandoned: ProcIdle,
|
tracev2.ProcSyscallAbandoned: ProcIdle,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var procState2Tracev2ProcStatus = [...]tracev2.ProcStatus{
|
||||||
|
ProcRunning: tracev2.ProcRunning,
|
||||||
|
ProcIdle: tracev2.ProcIdle,
|
||||||
|
// TODO(felixge): how to map ProcSyscall and ProcSyscallAbandoned?
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the event as a human-readable string.
|
// String returns the event as a human-readable string.
|
||||||
//
|
//
|
||||||
// The format of the string is intended for debugging and is subject to change.
|
// The format of the string is intended for debugging and is subject to change.
|
||||||
|
|
@ -857,10 +1314,7 @@ func (e Event) String() string {
|
||||||
if s.Stack != NoStack {
|
if s.Stack != NoStack {
|
||||||
fmt.Fprintln(&sb)
|
fmt.Fprintln(&sb)
|
||||||
fmt.Fprintln(&sb, "TransitionStack=")
|
fmt.Fprintln(&sb, "TransitionStack=")
|
||||||
for f := range s.Stack.Frames() {
|
printStack(&sb, "\t", s.Stack.Frames())
|
||||||
fmt.Fprintf(&sb, "\t%s @ 0x%x\n", f.Func, f.PC)
|
|
||||||
fmt.Fprintf(&sb, "\t\t%s:%d\n", f.File, f.Line)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case EventExperimental:
|
case EventExperimental:
|
||||||
r := e.Experimental()
|
r := e.Experimental()
|
||||||
|
|
@ -886,10 +1340,7 @@ func (e Event) String() string {
|
||||||
if stk := e.Stack(); stk != NoStack {
|
if stk := e.Stack(); stk != NoStack {
|
||||||
fmt.Fprintln(&sb)
|
fmt.Fprintln(&sb)
|
||||||
fmt.Fprintln(&sb, "Stack=")
|
fmt.Fprintln(&sb, "Stack=")
|
||||||
for f := range stk.Frames() {
|
printStack(&sb, "\t", stk.Frames())
|
||||||
fmt.Fprintf(&sb, "\t%s @ 0x%x\n", f.Func, f.PC)
|
|
||||||
fmt.Fprintf(&sb, "\t\t%s:%d\n", f.File, f.Line)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,748 @@
|
||||||
|
|
||||||
package trace
|
package trace
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"internal/diff"
|
||||||
|
"reflect"
|
||||||
|
"slices"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMakeEvent(t *testing.T) {
|
||||||
|
checkTime := func(t *testing.T, ev Event, want Time) {
|
||||||
|
t.Helper()
|
||||||
|
if ev.Time() != want {
|
||||||
|
t.Errorf("expected time to be %d, got %d", want, ev.Time())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
checkValid := func(t *testing.T, err error, valid bool) bool {
|
||||||
|
t.Helper()
|
||||||
|
if valid && err == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if valid && err != nil {
|
||||||
|
t.Errorf("expected no error, got %v", err)
|
||||||
|
} else if !valid && err == nil {
|
||||||
|
t.Errorf("expected error, got %v", err)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
type stackType string
|
||||||
|
const (
|
||||||
|
schedStack stackType = "sched stack"
|
||||||
|
stStack stackType = "state transition stack"
|
||||||
|
)
|
||||||
|
checkStack := func(t *testing.T, got Stack, want Stack, which stackType) {
|
||||||
|
t.Helper()
|
||||||
|
diff := diff.Diff("want", []byte(want.String()), "got", []byte(got.String()))
|
||||||
|
if len(diff) > 0 {
|
||||||
|
t.Errorf("unexpected %s: %s", which, diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stk1 := MakeStack([]StackFrame{
|
||||||
|
{PC: 1, Func: "foo", File: "foo.go", Line: 10},
|
||||||
|
{PC: 2, Func: "bar", File: "bar.go", Line: 20},
|
||||||
|
})
|
||||||
|
stk2 := MakeStack([]StackFrame{
|
||||||
|
{PC: 1, Func: "foo", File: "foo.go", Line: 10},
|
||||||
|
{PC: 2, Func: "bar", File: "bar.go", Line: 20},
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Metric", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
metric string
|
||||||
|
val uint64
|
||||||
|
stack Stack
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{name: "gomaxprocs", metric: "/sched/gomaxprocs:threads", valid: true, val: 1, stack: NoStack},
|
||||||
|
{name: "gomaxprocs with stack", metric: "/sched/gomaxprocs:threads", valid: true, val: 1, stack: stk1},
|
||||||
|
{name: "heap objects", metric: "/memory/classes/heap/objects:bytes", valid: true, val: 2, stack: NoStack},
|
||||||
|
{name: "heap goal", metric: "/gc/heap/goal:bytes", valid: true, val: 3, stack: NoStack},
|
||||||
|
{name: "invalid metric", metric: "/test", valid: false, val: 4, stack: NoStack},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ev, err := MakeEvent(EventConfig[Metric]{
|
||||||
|
Kind: EventMetric,
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Details: Metric{Name: test.metric, Value: Uint64Value(test.val)},
|
||||||
|
Stack: test.stack,
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
checkStack(t, ev.Stack(), test.stack, schedStack)
|
||||||
|
got := ev.Metric()
|
||||||
|
if got.Name != test.metric {
|
||||||
|
t.Errorf("expected name to be %q, got %q", test.metric, got.Name)
|
||||||
|
}
|
||||||
|
if got.Value.Uint64() != test.val {
|
||||||
|
t.Errorf("expected value to be %d, got %d", test.val, got.Value.Uint64())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Label", func(t *testing.T) {
|
||||||
|
ev, err := MakeEvent(EventConfig[Label]{
|
||||||
|
Kind: EventLabel,
|
||||||
|
Time: 42,
|
||||||
|
Details: Label{Label: "test", Resource: MakeResourceID(GoID(23))},
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
label := ev.Label()
|
||||||
|
if label.Label != "test" {
|
||||||
|
t.Errorf("expected label to be test, got %q", label.Label)
|
||||||
|
}
|
||||||
|
if label.Resource.Kind != ResourceGoroutine {
|
||||||
|
t.Errorf("expected label resource to be goroutine, got %d", label.Resource.Kind)
|
||||||
|
}
|
||||||
|
if label.Resource.id != 23 {
|
||||||
|
t.Errorf("expected label resource to be 23, got %d", label.Resource.id)
|
||||||
|
}
|
||||||
|
checkTime(t, ev, 42)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Range", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
kind EventKind
|
||||||
|
name string
|
||||||
|
scope ResourceID
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{kind: EventRangeBegin, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true},
|
||||||
|
{kind: EventRangeActive, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true},
|
||||||
|
{kind: EventRangeEnd, name: "GC concurrent mark phase", scope: ResourceID{}, valid: true},
|
||||||
|
{kind: EventMetric, name: "GC concurrent mark phase", scope: ResourceID{}, valid: false},
|
||||||
|
{kind: EventRangeBegin, name: "GC concurrent mark phase - INVALID", scope: ResourceID{}, valid: false},
|
||||||
|
|
||||||
|
{kind: EventRangeBegin, name: "GC incremental sweep", scope: MakeResourceID(ProcID(1)), valid: true},
|
||||||
|
{kind: EventRangeActive, name: "GC incremental sweep", scope: MakeResourceID(ProcID(2)), valid: true},
|
||||||
|
{kind: EventRangeEnd, name: "GC incremental sweep", scope: MakeResourceID(ProcID(3)), valid: true},
|
||||||
|
{kind: EventMetric, name: "GC incremental sweep", scope: MakeResourceID(ProcID(4)), valid: false},
|
||||||
|
{kind: EventRangeBegin, name: "GC incremental sweep - INVALID", scope: MakeResourceID(ProcID(5)), valid: false},
|
||||||
|
|
||||||
|
{kind: EventRangeBegin, name: "GC mark assist", scope: MakeResourceID(GoID(1)), valid: true},
|
||||||
|
{kind: EventRangeActive, name: "GC mark assist", scope: MakeResourceID(GoID(2)), valid: true},
|
||||||
|
{kind: EventRangeEnd, name: "GC mark assist", scope: MakeResourceID(GoID(3)), valid: true},
|
||||||
|
{kind: EventMetric, name: "GC mark assist", scope: MakeResourceID(GoID(4)), valid: false},
|
||||||
|
{kind: EventRangeBegin, name: "GC mark assist - INVALID", scope: MakeResourceID(GoID(5)), valid: false},
|
||||||
|
|
||||||
|
{kind: EventRangeBegin, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(1)), valid: true},
|
||||||
|
{kind: EventRangeActive, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(2)), valid: false},
|
||||||
|
{kind: EventRangeEnd, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(3)), valid: true},
|
||||||
|
{kind: EventMetric, name: "stop-the-world (for a good reason)", scope: MakeResourceID(GoID(4)), valid: false},
|
||||||
|
{kind: EventRangeBegin, name: "stop-the-world (for a good reason) - INVALID", scope: MakeResourceID(GoID(5)), valid: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
name := fmt.Sprintf("%s/%s/%s", test.kind, test.name, test.scope)
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
ev, err := MakeEvent(EventConfig[Range]{
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Kind: test.kind,
|
||||||
|
Details: Range{Name: test.name, Scope: test.scope},
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got := ev.Range()
|
||||||
|
if got.Name != test.name {
|
||||||
|
t.Errorf("expected name to be %q, got %q", test.name, got.Name)
|
||||||
|
}
|
||||||
|
if ev.Kind() != test.kind {
|
||||||
|
t.Errorf("expected kind to be %s, got %s", test.kind, ev.Kind())
|
||||||
|
}
|
||||||
|
if got.Scope.String() != test.scope.String() {
|
||||||
|
t.Errorf("expected scope to be %s, got %s", test.scope.String(), got.Scope.String())
|
||||||
|
}
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("GoroutineTransition", func(t *testing.T) {
|
||||||
|
const anotherG = 999 // indicates hat sched g is different from transition g
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
g GoID
|
||||||
|
stack Stack
|
||||||
|
stG GoID
|
||||||
|
from GoState
|
||||||
|
to GoState
|
||||||
|
reason string
|
||||||
|
stStack Stack
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "EvGoCreate",
|
||||||
|
g: anotherG,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 1,
|
||||||
|
from: GoNotExist,
|
||||||
|
to: GoRunnable,
|
||||||
|
reason: "",
|
||||||
|
stStack: stk2,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoCreateBlocked",
|
||||||
|
g: anotherG,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 2,
|
||||||
|
from: GoNotExist,
|
||||||
|
to: GoWaiting,
|
||||||
|
reason: "",
|
||||||
|
stStack: stk2,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoCreateSyscall",
|
||||||
|
g: anotherG,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 3,
|
||||||
|
from: GoNotExist,
|
||||||
|
to: GoSyscall,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoStart",
|
||||||
|
g: anotherG,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 4,
|
||||||
|
from: GoRunnable,
|
||||||
|
to: GoRunning,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoDestroy",
|
||||||
|
g: 5,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 5,
|
||||||
|
from: GoRunning,
|
||||||
|
to: GoNotExist,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoDestroySyscall",
|
||||||
|
g: 6,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 6,
|
||||||
|
from: GoSyscall,
|
||||||
|
to: GoNotExist,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoStop",
|
||||||
|
g: 7,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 7,
|
||||||
|
from: GoRunning,
|
||||||
|
to: GoRunnable,
|
||||||
|
reason: "preempted",
|
||||||
|
stStack: stk1,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoBlock",
|
||||||
|
g: 8,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 8,
|
||||||
|
from: GoRunning,
|
||||||
|
to: GoWaiting,
|
||||||
|
reason: "blocked",
|
||||||
|
stStack: stk1,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoUnblock",
|
||||||
|
g: 9,
|
||||||
|
stack: stk1,
|
||||||
|
stG: anotherG,
|
||||||
|
from: GoWaiting,
|
||||||
|
to: GoRunnable,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
// N.b. EvGoUnblock, EvGoSwitch and EvGoSwitchDestroy cannot be
|
||||||
|
// distinguished from each other in Event form, so MakeEvent only
|
||||||
|
// produces EvGoUnblock events for Waiting -> Runnable transitions.
|
||||||
|
{
|
||||||
|
name: "EvGoSyscallBegin",
|
||||||
|
g: 10,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 10,
|
||||||
|
from: GoRunning,
|
||||||
|
to: GoSyscall,
|
||||||
|
reason: "",
|
||||||
|
stStack: stk1,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoSyscallEnd",
|
||||||
|
g: 11,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 11,
|
||||||
|
from: GoSyscall,
|
||||||
|
to: GoRunning,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "EvGoSyscallEndBlocked",
|
||||||
|
g: 12,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 12,
|
||||||
|
from: GoSyscall,
|
||||||
|
to: GoRunnable,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
// TODO(felixge): Use coverage testsing to check if we need all these GoStatus/GoStatusStack cases
|
||||||
|
{
|
||||||
|
name: "GoStatus Undetermined->Waiting",
|
||||||
|
g: anotherG,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 13,
|
||||||
|
from: GoUndetermined,
|
||||||
|
to: GoWaiting,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GoStatus Undetermined->Running",
|
||||||
|
g: anotherG,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 14,
|
||||||
|
from: GoUndetermined,
|
||||||
|
to: GoRunning,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GoStatusStack Undetermined->Waiting",
|
||||||
|
g: anotherG,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 15,
|
||||||
|
from: GoUndetermined,
|
||||||
|
to: GoWaiting,
|
||||||
|
reason: "",
|
||||||
|
stStack: stk1,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GoStatusStack Undetermined->Runnable",
|
||||||
|
g: anotherG,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 16,
|
||||||
|
from: GoUndetermined,
|
||||||
|
to: GoRunnable,
|
||||||
|
reason: "",
|
||||||
|
stStack: stk1,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GoStatus Runnable->Runnable",
|
||||||
|
g: anotherG,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 17,
|
||||||
|
from: GoRunnable,
|
||||||
|
to: GoRunnable,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "GoStatus Runnable->Running",
|
||||||
|
g: anotherG,
|
||||||
|
stack: NoStack,
|
||||||
|
stG: 18,
|
||||||
|
from: GoRunnable,
|
||||||
|
to: GoRunning,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid NotExits->NotExists",
|
||||||
|
g: anotherG,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 18,
|
||||||
|
from: GoNotExist,
|
||||||
|
to: GoNotExist,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid Running->Undetermined",
|
||||||
|
g: anotherG,
|
||||||
|
stack: stk1,
|
||||||
|
stG: 19,
|
||||||
|
from: GoRunning,
|
||||||
|
to: GoUndetermined,
|
||||||
|
reason: "",
|
||||||
|
stStack: NoStack,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
st := MakeGoStateTransition(test.stG, test.from, test.to)
|
||||||
|
st.Stack = test.stStack
|
||||||
|
st.Reason = test.reason
|
||||||
|
ev, err := MakeEvent(EventConfig[StateTransition]{
|
||||||
|
Kind: EventStateTransition,
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Goroutine: test.g,
|
||||||
|
Stack: test.stack,
|
||||||
|
Details: st,
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkStack(t, ev.Stack(), test.stack, schedStack)
|
||||||
|
if ev.Goroutine() != test.g {
|
||||||
|
t.Errorf("expected goroutine to be %d, got %d", test.g, ev.Goroutine())
|
||||||
|
}
|
||||||
|
got := ev.StateTransition()
|
||||||
|
if got.Resource.Goroutine() != test.stG {
|
||||||
|
t.Errorf("expected resource to be %d, got %d", test.stG, got.Resource.Goroutine())
|
||||||
|
}
|
||||||
|
from, to := got.Goroutine()
|
||||||
|
if from != test.from {
|
||||||
|
t.Errorf("from got=%s want=%s", from, test.from)
|
||||||
|
}
|
||||||
|
if to != test.to {
|
||||||
|
t.Errorf("to got=%s want=%s", to, test.to)
|
||||||
|
}
|
||||||
|
if got.Reason != test.reason {
|
||||||
|
t.Errorf("expected reason to be %s, got %s", test.reason, got.Reason)
|
||||||
|
}
|
||||||
|
checkStack(t, got.Stack, test.stStack, stStack)
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ProcTransition", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
proc ProcID
|
||||||
|
schedProc ProcID
|
||||||
|
from ProcState
|
||||||
|
to ProcState
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{name: "ProcStart", proc: 1, schedProc: 99, from: ProcIdle, to: ProcRunning, valid: true},
|
||||||
|
{name: "ProcStop", proc: 2, schedProc: 2, from: ProcRunning, to: ProcIdle, valid: true},
|
||||||
|
{name: "ProcSteal", proc: 3, schedProc: 99, from: ProcRunning, to: ProcIdle, valid: true},
|
||||||
|
{name: "ProcSteal lost info", proc: 4, schedProc: 99, from: ProcIdle, to: ProcIdle, valid: true},
|
||||||
|
{name: "ProcStatus", proc: 5, schedProc: 99, from: ProcUndetermined, to: ProcRunning, valid: true},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
st := MakeProcStateTransition(test.proc, test.from, test.to)
|
||||||
|
ev, err := MakeEvent(EventConfig[StateTransition]{
|
||||||
|
Kind: EventStateTransition,
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Proc: test.schedProc,
|
||||||
|
Details: st,
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
gotSt := ev.StateTransition()
|
||||||
|
from, to := gotSt.Proc()
|
||||||
|
if from != test.from {
|
||||||
|
t.Errorf("from got=%s want=%s", from, test.from)
|
||||||
|
}
|
||||||
|
if to != test.to {
|
||||||
|
t.Errorf("to got=%s want=%s", to, test.to)
|
||||||
|
}
|
||||||
|
if ev.Proc() != test.schedProc {
|
||||||
|
t.Errorf("expected proc to be %d, got %d", test.schedProc, ev.Proc())
|
||||||
|
}
|
||||||
|
if gotSt.Resource.Proc() != test.proc {
|
||||||
|
t.Errorf("expected resource to be %d, got %d", test.proc, gotSt.Resource.Proc())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Sync", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
kind EventKind
|
||||||
|
n int
|
||||||
|
clock *ClockSnapshot
|
||||||
|
batches map[string][]ExperimentalBatch
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "invalid kind",
|
||||||
|
n: 1,
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "N",
|
||||||
|
kind: EventSync,
|
||||||
|
n: 1,
|
||||||
|
batches: map[string][]ExperimentalBatch{},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "N+ClockSnapshot",
|
||||||
|
kind: EventSync,
|
||||||
|
n: 1,
|
||||||
|
batches: map[string][]ExperimentalBatch{},
|
||||||
|
clock: &ClockSnapshot{
|
||||||
|
Trace: 1,
|
||||||
|
Wall: time.Unix(59, 123456789),
|
||||||
|
Mono: 2,
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "N+Batches",
|
||||||
|
kind: EventSync,
|
||||||
|
n: 1,
|
||||||
|
batches: map[string][]ExperimentalBatch{
|
||||||
|
"AllocFree": {{Thread: 1, Data: []byte{1, 2, 3}}},
|
||||||
|
},
|
||||||
|
valid: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown experiment",
|
||||||
|
kind: EventSync,
|
||||||
|
n: 1,
|
||||||
|
batches: map[string][]ExperimentalBatch{
|
||||||
|
"does-not-exist": {{Thread: 1, Data: []byte{1, 2, 3}}},
|
||||||
|
},
|
||||||
|
valid: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ev, err := MakeEvent(EventConfig[Sync]{
|
||||||
|
Kind: test.kind,
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Details: Sync{N: test.n, ClockSnapshot: test.clock, ExperimentalBatches: test.batches},
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got := ev.Sync()
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
if got.N != test.n {
|
||||||
|
t.Errorf("expected N to be %d, got %d", test.n, got.N)
|
||||||
|
}
|
||||||
|
if test.clock != nil && got.ClockSnapshot == nil {
|
||||||
|
t.Fatalf("expected ClockSnapshot to be non-nil")
|
||||||
|
} else if test.clock == nil && got.ClockSnapshot != nil {
|
||||||
|
t.Fatalf("expected ClockSnapshot to be nil")
|
||||||
|
} else if test.clock != nil && got.ClockSnapshot != nil {
|
||||||
|
if got.ClockSnapshot.Trace != test.clock.Trace {
|
||||||
|
t.Errorf("expected ClockSnapshot.Trace to be %d, got %d", test.clock.Trace, got.ClockSnapshot.Trace)
|
||||||
|
}
|
||||||
|
if !got.ClockSnapshot.Wall.Equal(test.clock.Wall) {
|
||||||
|
t.Errorf("expected ClockSnapshot.Wall to be %s, got %s", test.clock.Wall, got.ClockSnapshot.Wall)
|
||||||
|
}
|
||||||
|
if got.ClockSnapshot.Mono != test.clock.Mono {
|
||||||
|
t.Errorf("expected ClockSnapshot.Mono to be %d, got %d", test.clock.Mono, got.ClockSnapshot.Mono)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(got.ExperimentalBatches, test.batches) {
|
||||||
|
t.Errorf("expected ExperimentalBatches to be %#v, got %#v", test.batches, got.ExperimentalBatches)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Task", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
kind EventKind
|
||||||
|
id TaskID
|
||||||
|
parent TaskID
|
||||||
|
typ string
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{name: "no task", kind: EventTaskBegin, id: NoTask, parent: 1, typ: "type-0", valid: false},
|
||||||
|
{name: "invalid kind", kind: EventMetric, id: 1, parent: 2, typ: "type-1", valid: false},
|
||||||
|
{name: "EvUserTaskBegin", kind: EventTaskBegin, id: 2, parent: 3, typ: "type-2", valid: true},
|
||||||
|
{name: "EvUserTaskEnd", kind: EventTaskEnd, id: 3, parent: 4, typ: "type-3", valid: true},
|
||||||
|
{name: "no parent", kind: EventTaskBegin, id: 4, parent: NoTask, typ: "type-4", valid: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ev, err := MakeEvent(EventConfig[Task]{
|
||||||
|
Kind: test.kind,
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Details: Task{ID: test.id, Parent: test.parent, Type: test.typ},
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
got := ev.Task()
|
||||||
|
if got.ID != test.id {
|
||||||
|
t.Errorf("expected ID to be %d, got %d", test.id, got.ID)
|
||||||
|
}
|
||||||
|
if got.Parent != test.parent {
|
||||||
|
t.Errorf("expected Parent to be %d, got %d", test.parent, got.Parent)
|
||||||
|
}
|
||||||
|
if got.Type != test.typ {
|
||||||
|
t.Errorf("expected Type to be %s, got %s", test.typ, got.Type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Region", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
kind EventKind
|
||||||
|
task TaskID
|
||||||
|
typ string
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{name: "invalid kind", kind: EventMetric, task: 1, typ: "type-1", valid: false},
|
||||||
|
{name: "EvUserRegionBegin", kind: EventRegionBegin, task: 2, typ: "type-2", valid: true},
|
||||||
|
{name: "EvUserRegionEnd", kind: EventRegionEnd, task: 3, typ: "type-3", valid: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ev, err := MakeEvent(EventConfig[Region]{
|
||||||
|
Kind: test.kind,
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Details: Region{Task: test.task, Type: test.typ},
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
got := ev.Region()
|
||||||
|
if got.Task != test.task {
|
||||||
|
t.Errorf("expected Task to be %d, got %d", test.task, got.Task)
|
||||||
|
}
|
||||||
|
if got.Type != test.typ {
|
||||||
|
t.Errorf("expected Type to be %s, got %s", test.typ, got.Type)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Log", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
kind EventKind
|
||||||
|
task TaskID
|
||||||
|
category string
|
||||||
|
message string
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{name: "invalid kind", kind: EventMetric, task: 1, category: "category-1", message: "message-1", valid: false},
|
||||||
|
{name: "basic", kind: EventLog, task: 2, category: "category-2", message: "message-2", valid: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ev, err := MakeEvent(EventConfig[Log]{
|
||||||
|
Kind: test.kind,
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Details: Log{Task: test.task, Category: test.category, Message: test.message},
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
got := ev.Log()
|
||||||
|
if got.Task != test.task {
|
||||||
|
t.Errorf("expected Task to be %d, got %d", test.task, got.Task)
|
||||||
|
}
|
||||||
|
if got.Category != test.category {
|
||||||
|
t.Errorf("expected Category to be %s, got %s", test.category, got.Category)
|
||||||
|
}
|
||||||
|
if got.Message != test.message {
|
||||||
|
t.Errorf("expected Message to be %s, got %s", test.message, got.Message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("StackSample", func(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
kind EventKind
|
||||||
|
stack Stack
|
||||||
|
valid bool
|
||||||
|
}{
|
||||||
|
{name: "invalid kind", kind: EventMetric, stack: stk1, valid: false},
|
||||||
|
{name: "basic", kind: EventStackSample, stack: stk1, valid: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
ev, err := MakeEvent(EventConfig[StackSample]{
|
||||||
|
Kind: test.kind,
|
||||||
|
Time: Time(42 + i),
|
||||||
|
Stack: test.stack,
|
||||||
|
// N.b. Details defaults to StackSample{}, so we can
|
||||||
|
// omit it here.
|
||||||
|
})
|
||||||
|
if !checkValid(t, err, test.valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
checkTime(t, ev, Time(42+i))
|
||||||
|
got := ev.Stack()
|
||||||
|
checkStack(t, got, test.stack, schedStack)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMakeStack(t *testing.T) {
|
||||||
|
frames := []StackFrame{
|
||||||
|
{PC: 1, Func: "foo", File: "foo.go", Line: 10},
|
||||||
|
{PC: 2, Func: "bar", File: "bar.go", Line: 20},
|
||||||
|
}
|
||||||
|
got := slices.Collect(MakeStack(frames).Frames())
|
||||||
|
if len(got) != len(frames) {
|
||||||
|
t.Errorf("got=%d want=%d", len(got), len(frames))
|
||||||
|
}
|
||||||
|
for i := range got {
|
||||||
|
if got[i] != frames[i] {
|
||||||
|
t.Errorf("got=%v want=%v", got[i], frames[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPanicEvent(t *testing.T) {
|
func TestPanicEvent(t *testing.T) {
|
||||||
// Use a sync event for this because it doesn't have any extra metadata.
|
// Use a sync event for this because it doesn't have any extra metadata.
|
||||||
|
|
|
||||||
|
|
@ -228,7 +228,8 @@ type StateTransition struct {
|
||||||
newState uint8
|
newState uint8
|
||||||
}
|
}
|
||||||
|
|
||||||
func goStateTransition(id GoID, from, to GoState) StateTransition {
|
// MakeGoStateTransition creates a goroutine state transition.
|
||||||
|
func MakeGoStateTransition(id GoID, from, to GoState) StateTransition {
|
||||||
return StateTransition{
|
return StateTransition{
|
||||||
Resource: ResourceID{Kind: ResourceGoroutine, id: int64(id)},
|
Resource: ResourceID{Kind: ResourceGoroutine, id: int64(id)},
|
||||||
oldState: uint8(from),
|
oldState: uint8(from),
|
||||||
|
|
@ -236,7 +237,8 @@ func goStateTransition(id GoID, from, to GoState) StateTransition {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func procStateTransition(id ProcID, from, to ProcState) StateTransition {
|
// MakeProcStateTransition creates a proc state transition.
|
||||||
|
func MakeProcStateTransition(id ProcID, from, to ProcState) StateTransition {
|
||||||
return StateTransition{
|
return StateTransition{
|
||||||
Resource: ResourceID{Kind: ResourceProc, id: int64(id)},
|
Resource: ResourceID{Kind: ResourceProc, id: int64(id)},
|
||||||
oldState: uint8(from),
|
oldState: uint8(from),
|
||||||
|
|
|
||||||
|
|
@ -58,10 +58,12 @@ func (v Value) String() string {
|
||||||
return "Value{Bad}"
|
return "Value{Bad}"
|
||||||
}
|
}
|
||||||
|
|
||||||
func uint64Value(x uint64) Value {
|
// Uint64Value creates a value of kind ValueUint64.
|
||||||
|
func Uint64Value(x uint64) Value {
|
||||||
return Value{kind: ValueUint64, scalar: x}
|
return Value{kind: ValueUint64, scalar: x}
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringValue(s string) Value {
|
// StringValue creates a value of kind ValueString.
|
||||||
|
func StringValue(s string) Value {
|
||||||
return Value{kind: ValueString, scalar: uint64(len(s)), pointer: unsafe.Pointer(unsafe.StringData(s))}
|
return Value{kind: ValueString, scalar: uint64(len(s)), pointer: unsafe.Pointer(unsafe.StringData(s))}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue