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	"internal/trace"
9)
10
11var _ generator = &goroutineGenerator{}
12
13type goroutineGenerator struct {
14	globalRangeGenerator
15	globalMetricGenerator
16	stackSampleGenerator[trace.GoID]
17	logEventGenerator[trace.GoID]
18
19	gStates map[trace.GoID]*gState[trace.GoID]
20	focus   trace.GoID
21	filter  map[trace.GoID]struct{}
22}
23
24func newGoroutineGenerator(ctx *traceContext, focus trace.GoID, filter map[trace.GoID]struct{}) *goroutineGenerator {
25	gg := new(goroutineGenerator)
26	rg := func(ev *trace.Event) trace.GoID {
27		return ev.Goroutine()
28	}
29	gg.stackSampleGenerator.getResource = rg
30	gg.logEventGenerator.getResource = rg
31	gg.gStates = make(map[trace.GoID]*gState[trace.GoID])
32	gg.focus = focus
33	gg.filter = filter
34
35	// Enable a filter on the emitter.
36	if filter != nil {
37		ctx.SetResourceFilter(func(resource uint64) bool {
38			_, ok := filter[trace.GoID(resource)]
39			return ok
40		})
41	}
42	return gg
43}
44
45func (g *goroutineGenerator) Sync() {
46	g.globalRangeGenerator.Sync()
47}
48
49func (g *goroutineGenerator) GoroutineLabel(ctx *traceContext, ev *trace.Event) {
50	l := ev.Label()
51	g.gStates[l.Resource.Goroutine()].setLabel(l.Label)
52}
53
54func (g *goroutineGenerator) GoroutineRange(ctx *traceContext, ev *trace.Event) {
55	r := ev.Range()
56	switch ev.Kind() {
57	case trace.EventRangeBegin:
58		g.gStates[r.Scope.Goroutine()].rangeBegin(ev.Time(), r.Name, ev.Stack())
59	case trace.EventRangeActive:
60		g.gStates[r.Scope.Goroutine()].rangeActive(r.Name)
61	case trace.EventRangeEnd:
62		gs := g.gStates[r.Scope.Goroutine()]
63		gs.rangeEnd(ev.Time(), r.Name, ev.Stack(), ctx)
64	}
65}
66
67func (g *goroutineGenerator) GoroutineTransition(ctx *traceContext, ev *trace.Event) {
68	st := ev.StateTransition()
69	goID := st.Resource.Goroutine()
70
71	// If we haven't seen this goroutine before, create a new
72	// gState for it.
73	gs, ok := g.gStates[goID]
74	if !ok {
75		gs = newGState[trace.GoID](goID)
76		g.gStates[goID] = gs
77	}
78
79	// Try to augment the name of the goroutine.
80	gs.augmentName(st.Stack)
81
82	// Handle the goroutine state transition.
83	from, to := st.Goroutine()
84	if from == to {
85		// Filter out no-op events.
86		return
87	}
88	if from.Executing() && !to.Executing() {
89		if to == trace.GoWaiting {
90			// Goroutine started blocking.
91			gs.block(ev.Time(), ev.Stack(), st.Reason, ctx)
92		} else {
93			gs.stop(ev.Time(), ev.Stack(), ctx)
94		}
95	}
96	if !from.Executing() && to.Executing() {
97		start := ev.Time()
98		if from == trace.GoUndetermined {
99			// Back-date the event to the start of the trace.
100			start = ctx.startTime
101		}
102		gs.start(start, goID, ctx)
103	}
104
105	if from == trace.GoWaiting {
106		// Goroutine unblocked.
107		gs.unblock(ev.Time(), ev.Stack(), ev.Goroutine(), ctx)
108	}
109	if from == trace.GoNotExist && to == trace.GoRunnable {
110		// Goroutine was created.
111		gs.created(ev.Time(), ev.Goroutine(), ev.Stack())
112	}
113	if from == trace.GoSyscall && to != trace.GoRunning {
114		// Exiting blocked syscall.
115		gs.syscallEnd(ev.Time(), true, ctx)
116		gs.blockedSyscallEnd(ev.Time(), ev.Stack(), ctx)
117	} else if from == trace.GoSyscall {
118		// Check if we're exiting a syscall in a non-blocking way.
119		gs.syscallEnd(ev.Time(), false, ctx)
120	}
121
122	// Handle syscalls.
123	if to == trace.GoSyscall {
124		start := ev.Time()
125		if from == trace.GoUndetermined {
126			// Back-date the event to the start of the trace.
127			start = ctx.startTime
128		}
129		// Write down that we've entered a syscall. Note: we might have no G or P here
130		// if we're in a cgo callback or this is a transition from GoUndetermined
131		// (i.e. the G has been blocked in a syscall).
132		gs.syscallBegin(start, goID, ev.Stack())
133	}
134
135	// Note down the goroutine transition.
136	_, inMarkAssist := gs.activeRanges["GC mark assist"]
137	ctx.GoroutineTransition(ctx.elapsed(ev.Time()), viewerGState(from, inMarkAssist), viewerGState(to, inMarkAssist))
138}
139
140func (g *goroutineGenerator) ProcRange(ctx *traceContext, ev *trace.Event) {
141	// TODO(mknyszek): Extend procRangeGenerator to support rendering proc ranges
142	// that overlap with a goroutine's execution.
143}
144
145func (g *goroutineGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
146	// Not needed. All relevant information for goroutines can be derived from goroutine transitions.
147}
148
149func (g *goroutineGenerator) Finish(ctx *traceContext) {
150	ctx.SetResourceType("G")
151
152	// Finish off global ranges.
153	g.globalRangeGenerator.Finish(ctx)
154
155	// Finish off all the goroutine slices.
156	for id, gs := range g.gStates {
157		gs.finish(ctx)
158
159		// Tell the emitter about the goroutines we want to render.
160		ctx.Resource(uint64(id), gs.name())
161	}
162
163	// Set the goroutine to focus on.
164	if g.focus != trace.NoGoroutine {
165		ctx.Focus(uint64(g.focus))
166	}
167}
168