| // 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)) |
| } |
| } |