blob: 060e62fe04ba3f620399ae2fbeb76ff27892bff4 [file] [log] [blame] [edit]
// Copyright 2023 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 main
import (
"fmt"
"internal/trace"
"internal/trace/traceviewer"
"internal/trace/traceviewer/format"
)
var _ generator = &procGenerator{}
type procGenerator struct {
globalRangeGenerator
globalMetricGenerator
procRangeGenerator
stackSampleGenerator[trace.ProcID]
logEventGenerator[trace.ProcID]
gStates map[trace.GoID]*gState[trace.ProcID]
inSyscall map[trace.ProcID]*gState[trace.ProcID]
maxProc trace.ProcID
}
func newProcGenerator() *procGenerator {
pg := new(procGenerator)
rg := func(ev *trace.Event) trace.ProcID {
return ev.Proc()
}
pg.stackSampleGenerator.getResource = rg
pg.logEventGenerator.getResource = rg
pg.gStates = make(map[trace.GoID]*gState[trace.ProcID])
pg.inSyscall = make(map[trace.ProcID]*gState[trace.ProcID])
return pg
}
func (g *procGenerator) Sync() {
g.globalRangeGenerator.Sync()
g.procRangeGenerator.Sync()
}
func (g *procGenerator) GoroutineLabel(ctx *traceContext, ev *trace.Event) {
l := ev.Label()
g.gStates[l.Resource.Goroutine()].setLabel(l.Label)
}
func (g *procGenerator) GoroutineRange(ctx *traceContext, ev *trace.Event) {
r := ev.Range()
switch ev.Kind() {
case trace.EventRangeBegin:
g.gStates[r.Scope.Goroutine()].rangeBegin(ev.Time(), r.Name, ev.Stack())
case trace.EventRangeActive:
g.gStates[r.Scope.Goroutine()].rangeActive(r.Name)
case trace.EventRangeEnd:
gs := g.gStates[r.Scope.Goroutine()]
gs.rangeEnd(ev.Time(), r.Name, ev.Stack(), ctx)
}
}
func (g *procGenerator) GoroutineTransition(ctx *traceContext, ev *trace.Event) {
st := ev.StateTransition()
goID := st.Resource.Goroutine()
// If we haven't seen this goroutine before, create a new
// gState for it.
gs, ok := g.gStates[goID]
if !ok {
gs = newGState[trace.ProcID](goID)
g.gStates[goID] = gs
}
// If we haven't already named this goroutine, try to name it.
gs.augmentName(st.Stack)
// Handle the goroutine state transition.
from, to := st.Goroutine()
if from == to {
// Filter out no-op events.
return
}
if from == trace.GoRunning && !to.Executing() {
if to == trace.GoWaiting {
// Goroutine started blocking.
gs.block(ev.Time(), ev.Stack(), st.Reason, ctx)
} else {
gs.stop(ev.Time(), ev.Stack(), ctx)
}
}
if !from.Executing() && to == trace.GoRunning {
start := ev.Time()
if from == trace.GoUndetermined {
// Back-date the event to the start of the trace.
start = ctx.startTime
}
gs.start(start, ev.Proc(), ctx)
}
if from == trace.GoWaiting {
// Goroutine was unblocked.
gs.unblock(ev.Time(), ev.Stack(), ev.Proc(), ctx)
}
if from == trace.GoNotExist && to == trace.GoRunnable {
// Goroutine was created.
gs.created(ev.Time(), ev.Proc(), ev.Stack())
}
if from == trace.GoSyscall && to != trace.GoRunning {
// Goroutine exited a blocked syscall.
gs.blockedSyscallEnd(ev.Time(), ev.Stack(), ctx)
}
// Handle syscalls.
if to == trace.GoSyscall && ev.Proc() != trace.NoProc {
start := ev.Time()
if from == trace.GoUndetermined {
// Back-date the event to the start of the trace.
start = ctx.startTime
}
// Write down that we've entered a syscall. Note: we might have no P here
// if we're in a cgo callback or this is a transition from GoUndetermined
// (i.e. the G has been blocked in a syscall).
gs.syscallBegin(start, ev.Proc(), ev.Stack())
g.inSyscall[ev.Proc()] = gs
}
// Check if we're exiting a non-blocking syscall.
_, didNotBlock := g.inSyscall[ev.Proc()]
if from == trace.GoSyscall && didNotBlock {
gs.syscallEnd(ev.Time(), false, ctx)
delete(g.inSyscall, ev.Proc())
}
// Note down the goroutine transition.
_, inMarkAssist := gs.activeRanges["GC mark assist"]
ctx.GoroutineTransition(ctx.elapsed(ev.Time()), viewerGState(from, inMarkAssist), viewerGState(to, inMarkAssist))
}
func (g *procGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
st := ev.StateTransition()
proc := st.Resource.Proc()
g.maxProc = max(g.maxProc, proc)
viewerEv := traceviewer.InstantEvent{
Resource: uint64(proc),
Stack: ctx.Stack(viewerFrames(ev.Stack())),
}
from, to := st.Proc()
if from == to {
// Filter out no-op events.
return
}
if to.Executing() {
start := ev.Time()
if from == trace.ProcUndetermined {
start = ctx.startTime
}
viewerEv.Name = "proc start"
viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
viewerEv.Ts = ctx.elapsed(start)
ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1)
}
if from.Executing() {
start := ev.Time()
viewerEv.Name = "proc stop"
viewerEv.Ts = ctx.elapsed(start)
ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, -1)
// Check if this proc was in a syscall before it stopped.
// This means the syscall blocked. We need to emit it to the
// viewer at this point because we only display the time the
// syscall occupied a P when the viewer is in per-P mode.
//
// TODO(mknyszek): We could do better in a per-M mode because
// all events have to happen on *some* thread, and in v2 traces
// we know what that thread is.
gs, ok := g.inSyscall[proc]
if ok {
// Emit syscall slice for blocked syscall.
gs.syscallEnd(start, true, ctx)
gs.stop(start, ev.Stack(), ctx)
delete(g.inSyscall, proc)
}
}
// TODO(mknyszek): Consider modeling procs differently and have them be
// transition to and from NotExist when GOMAXPROCS changes. We can emit
// events for this to clearly delineate GOMAXPROCS changes.
if viewerEv.Name != "" {
ctx.Instant(viewerEv)
}
}
func (g *procGenerator) Finish(ctx *traceContext) {
ctx.SetResourceType("PROCS")
// Finish off ranges first. It doesn't really matter for the global ranges,
// but the proc ranges need to either be a subset of a goroutine slice or
// their own slice entirely. If the former, it needs to end first.
g.procRangeGenerator.Finish(ctx)
g.globalRangeGenerator.Finish(ctx)
// Finish off all the goroutine slices.
for _, gs := range g.gStates {
gs.finish(ctx)
}
// Name all the procs to the emitter.
for i := uint64(0); i <= uint64(g.maxProc); i++ {
ctx.Resource(i, fmt.Sprintf("Proc %v", i))
}
}