1// Copyright 2023 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package main
6
7import (
8	"fmt"
9	"internal/trace"
10	"internal/trace/traceviewer"
11	"internal/trace/traceviewer/format"
12)
13
14var _ generator = &procGenerator{}
15
16type procGenerator struct {
17	globalRangeGenerator
18	globalMetricGenerator
19	procRangeGenerator
20	stackSampleGenerator[trace.ProcID]
21	logEventGenerator[trace.ProcID]
22
23	gStates   map[trace.GoID]*gState[trace.ProcID]
24	inSyscall map[trace.ProcID]*gState[trace.ProcID]
25	maxProc   trace.ProcID
26}
27
28func newProcGenerator() *procGenerator {
29	pg := new(procGenerator)
30	rg := func(ev *trace.Event) trace.ProcID {
31		return ev.Proc()
32	}
33	pg.stackSampleGenerator.getResource = rg
34	pg.logEventGenerator.getResource = rg
35	pg.gStates = make(map[trace.GoID]*gState[trace.ProcID])
36	pg.inSyscall = make(map[trace.ProcID]*gState[trace.ProcID])
37	return pg
38}
39
40func (g *procGenerator) Sync() {
41	g.globalRangeGenerator.Sync()
42	g.procRangeGenerator.Sync()
43}
44
45func (g *procGenerator) GoroutineLabel(ctx *traceContext, ev *trace.Event) {
46	l := ev.Label()
47	g.gStates[l.Resource.Goroutine()].setLabel(l.Label)
48}
49
50func (g *procGenerator) GoroutineRange(ctx *traceContext, ev *trace.Event) {
51	r := ev.Range()
52	switch ev.Kind() {
53	case trace.EventRangeBegin:
54		g.gStates[r.Scope.Goroutine()].rangeBegin(ev.Time(), r.Name, ev.Stack())
55	case trace.EventRangeActive:
56		g.gStates[r.Scope.Goroutine()].rangeActive(r.Name)
57	case trace.EventRangeEnd:
58		gs := g.gStates[r.Scope.Goroutine()]
59		gs.rangeEnd(ev.Time(), r.Name, ev.Stack(), ctx)
60	}
61}
62
63func (g *procGenerator) GoroutineTransition(ctx *traceContext, ev *trace.Event) {
64	st := ev.StateTransition()
65	goID := st.Resource.Goroutine()
66
67	// If we haven't seen this goroutine before, create a new
68	// gState for it.
69	gs, ok := g.gStates[goID]
70	if !ok {
71		gs = newGState[trace.ProcID](goID)
72		g.gStates[goID] = gs
73	}
74	// If we haven't already named this goroutine, try to name it.
75	gs.augmentName(st.Stack)
76
77	// Handle the goroutine state transition.
78	from, to := st.Goroutine()
79	if from == to {
80		// Filter out no-op events.
81		return
82	}
83	if from == trace.GoRunning && !to.Executing() {
84		if to == trace.GoWaiting {
85			// Goroutine started blocking.
86			gs.block(ev.Time(), ev.Stack(), st.Reason, ctx)
87		} else {
88			gs.stop(ev.Time(), ev.Stack(), ctx)
89		}
90	}
91	if !from.Executing() && to == trace.GoRunning {
92		start := ev.Time()
93		if from == trace.GoUndetermined {
94			// Back-date the event to the start of the trace.
95			start = ctx.startTime
96		}
97		gs.start(start, ev.Proc(), ctx)
98	}
99
100	if from == trace.GoWaiting {
101		// Goroutine was unblocked.
102		gs.unblock(ev.Time(), ev.Stack(), ev.Proc(), ctx)
103	}
104	if from == trace.GoNotExist && to == trace.GoRunnable {
105		// Goroutine was created.
106		gs.created(ev.Time(), ev.Proc(), ev.Stack())
107	}
108	if from == trace.GoSyscall && to != trace.GoRunning {
109		// Goroutine exited a blocked syscall.
110		gs.blockedSyscallEnd(ev.Time(), ev.Stack(), ctx)
111	}
112
113	// Handle syscalls.
114	if to == trace.GoSyscall && ev.Proc() != trace.NoProc {
115		start := ev.Time()
116		if from == trace.GoUndetermined {
117			// Back-date the event to the start of the trace.
118			start = ctx.startTime
119		}
120		// Write down that we've entered a syscall. Note: we might have no P here
121		// if we're in a cgo callback or this is a transition from GoUndetermined
122		// (i.e. the G has been blocked in a syscall).
123		gs.syscallBegin(start, ev.Proc(), ev.Stack())
124		g.inSyscall[ev.Proc()] = gs
125	}
126	// Check if we're exiting a non-blocking syscall.
127	_, didNotBlock := g.inSyscall[ev.Proc()]
128	if from == trace.GoSyscall && didNotBlock {
129		gs.syscallEnd(ev.Time(), false, ctx)
130		delete(g.inSyscall, ev.Proc())
131	}
132
133	// Note down the goroutine transition.
134	_, inMarkAssist := gs.activeRanges["GC mark assist"]
135	ctx.GoroutineTransition(ctx.elapsed(ev.Time()), viewerGState(from, inMarkAssist), viewerGState(to, inMarkAssist))
136}
137
138func (g *procGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
139	st := ev.StateTransition()
140	proc := st.Resource.Proc()
141
142	g.maxProc = max(g.maxProc, proc)
143	viewerEv := traceviewer.InstantEvent{
144		Resource: uint64(proc),
145		Stack:    ctx.Stack(viewerFrames(ev.Stack())),
146	}
147
148	from, to := st.Proc()
149	if from == to {
150		// Filter out no-op events.
151		return
152	}
153	if to.Executing() {
154		start := ev.Time()
155		if from == trace.ProcUndetermined {
156			start = ctx.startTime
157		}
158		viewerEv.Name = "proc start"
159		viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
160		viewerEv.Ts = ctx.elapsed(start)
161		ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1)
162	}
163	if from.Executing() {
164		start := ev.Time()
165		viewerEv.Name = "proc stop"
166		viewerEv.Ts = ctx.elapsed(start)
167		ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, -1)
168
169		// Check if this proc was in a syscall before it stopped.
170		// This means the syscall blocked. We need to emit it to the
171		// viewer at this point because we only display the time the
172		// syscall occupied a P when the viewer is in per-P mode.
173		//
174		// TODO(mknyszek): We could do better in a per-M mode because
175		// all events have to happen on *some* thread, and in v2 traces
176		// we know what that thread is.
177		gs, ok := g.inSyscall[proc]
178		if ok {
179			// Emit syscall slice for blocked syscall.
180			gs.syscallEnd(start, true, ctx)
181			gs.stop(start, ev.Stack(), ctx)
182			delete(g.inSyscall, proc)
183		}
184	}
185	// TODO(mknyszek): Consider modeling procs differently and have them be
186	// transition to and from NotExist when GOMAXPROCS changes. We can emit
187	// events for this to clearly delineate GOMAXPROCS changes.
188
189	if viewerEv.Name != "" {
190		ctx.Instant(viewerEv)
191	}
192}
193
194func (g *procGenerator) Finish(ctx *traceContext) {
195	ctx.SetResourceType("PROCS")
196
197	// Finish off ranges first. It doesn't really matter for the global ranges,
198	// but the proc ranges need to either be a subset of a goroutine slice or
199	// their own slice entirely. If the former, it needs to end first.
200	g.procRangeGenerator.Finish(ctx)
201	g.globalRangeGenerator.Finish(ctx)
202
203	// Finish off all the goroutine slices.
204	for _, gs := range g.gStates {
205		gs.finish(ctx)
206	}
207
208	// Name all the procs to the emitter.
209	for i := uint64(0); i <= uint64(g.maxProc); i++ {
210		ctx.Resource(i, fmt.Sprintf("Proc %v", i))
211	}
212}
213