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 = &threadGenerator{}
15
16type threadGenerator struct {
17	globalRangeGenerator
18	globalMetricGenerator
19	stackSampleGenerator[trace.ThreadID]
20	logEventGenerator[trace.ThreadID]
21
22	gStates map[trace.GoID]*gState[trace.ThreadID]
23	threads map[trace.ThreadID]struct{}
24}
25
26func newThreadGenerator() *threadGenerator {
27	tg := new(threadGenerator)
28	rg := func(ev *trace.Event) trace.ThreadID {
29		return ev.Thread()
30	}
31	tg.stackSampleGenerator.getResource = rg
32	tg.logEventGenerator.getResource = rg
33	tg.gStates = make(map[trace.GoID]*gState[trace.ThreadID])
34	tg.threads = make(map[trace.ThreadID]struct{})
35	return tg
36}
37
38func (g *threadGenerator) Sync() {
39	g.globalRangeGenerator.Sync()
40}
41
42func (g *threadGenerator) GoroutineLabel(ctx *traceContext, ev *trace.Event) {
43	l := ev.Label()
44	g.gStates[l.Resource.Goroutine()].setLabel(l.Label)
45}
46
47func (g *threadGenerator) GoroutineRange(ctx *traceContext, ev *trace.Event) {
48	r := ev.Range()
49	switch ev.Kind() {
50	case trace.EventRangeBegin:
51		g.gStates[r.Scope.Goroutine()].rangeBegin(ev.Time(), r.Name, ev.Stack())
52	case trace.EventRangeActive:
53		g.gStates[r.Scope.Goroutine()].rangeActive(r.Name)
54	case trace.EventRangeEnd:
55		gs := g.gStates[r.Scope.Goroutine()]
56		gs.rangeEnd(ev.Time(), r.Name, ev.Stack(), ctx)
57	}
58}
59
60func (g *threadGenerator) GoroutineTransition(ctx *traceContext, ev *trace.Event) {
61	if ev.Thread() != trace.NoThread {
62		if _, ok := g.threads[ev.Thread()]; !ok {
63			g.threads[ev.Thread()] = struct{}{}
64		}
65	}
66
67	st := ev.StateTransition()
68	goID := st.Resource.Goroutine()
69
70	// If we haven't seen this goroutine before, create a new
71	// gState for it.
72	gs, ok := g.gStates[goID]
73	if !ok {
74		gs = newGState[trace.ThreadID](goID)
75		g.gStates[goID] = gs
76	}
77	// If we haven't already named this goroutine, try to name it.
78	gs.augmentName(st.Stack)
79
80	// Handle the goroutine state transition.
81	from, to := st.Goroutine()
82	if from == to {
83		// Filter out no-op events.
84		return
85	}
86	if from.Executing() && !to.Executing() {
87		if to == trace.GoWaiting {
88			// Goroutine started blocking.
89			gs.block(ev.Time(), ev.Stack(), st.Reason, ctx)
90		} else {
91			gs.stop(ev.Time(), ev.Stack(), ctx)
92		}
93	}
94	if !from.Executing() && to.Executing() {
95		start := ev.Time()
96		if from == trace.GoUndetermined {
97			// Back-date the event to the start of the trace.
98			start = ctx.startTime
99		}
100		gs.start(start, ev.Thread(), ctx)
101	}
102
103	if from == trace.GoWaiting {
104		// Goroutine was unblocked.
105		gs.unblock(ev.Time(), ev.Stack(), ev.Thread(), ctx)
106	}
107	if from == trace.GoNotExist && to == trace.GoRunnable {
108		// Goroutine was created.
109		gs.created(ev.Time(), ev.Thread(), ev.Stack())
110	}
111	if from == trace.GoSyscall {
112		// Exiting syscall.
113		gs.syscallEnd(ev.Time(), to != trace.GoRunning, ctx)
114	}
115
116	// Handle syscalls.
117	if to == trace.GoSyscall {
118		start := ev.Time()
119		if from == trace.GoUndetermined {
120			// Back-date the event to the start of the trace.
121			start = ctx.startTime
122		}
123		// Write down that we've entered a syscall. Note: we might have no P here
124		// if we're in a cgo callback or this is a transition from GoUndetermined
125		// (i.e. the G has been blocked in a syscall).
126		gs.syscallBegin(start, ev.Thread(), ev.Stack())
127	}
128
129	// Note down the goroutine transition.
130	_, inMarkAssist := gs.activeRanges["GC mark assist"]
131	ctx.GoroutineTransition(ctx.elapsed(ev.Time()), viewerGState(from, inMarkAssist), viewerGState(to, inMarkAssist))
132}
133
134func (g *threadGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
135	if ev.Thread() != trace.NoThread {
136		if _, ok := g.threads[ev.Thread()]; !ok {
137			g.threads[ev.Thread()] = struct{}{}
138		}
139	}
140
141	type procArg struct {
142		Proc uint64 `json:"proc,omitempty"`
143	}
144	st := ev.StateTransition()
145	viewerEv := traceviewer.InstantEvent{
146		Resource: uint64(ev.Thread()),
147		Stack:    ctx.Stack(viewerFrames(ev.Stack())),
148		Arg:      procArg{Proc: uint64(st.Resource.Proc())},
149	}
150
151	from, to := st.Proc()
152	if from == to {
153		// Filter out no-op events.
154		return
155	}
156	if to.Executing() {
157		start := ev.Time()
158		if from == trace.ProcUndetermined {
159			start = ctx.startTime
160		}
161		viewerEv.Name = "proc start"
162		viewerEv.Arg = format.ThreadIDArg{ThreadID: uint64(ev.Thread())}
163		viewerEv.Ts = ctx.elapsed(start)
164		// TODO(mknyszek): We don't have a state machine for threads, so approximate
165		// running threads with running Ps.
166		ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1)
167	}
168	if from.Executing() {
169		start := ev.Time()
170		viewerEv.Name = "proc stop"
171		viewerEv.Ts = ctx.elapsed(start)
172		// TODO(mknyszek): We don't have a state machine for threads, so approximate
173		// running threads with running Ps.
174		ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, -1)
175	}
176	// TODO(mknyszek): Consider modeling procs differently and have them be
177	// transition to and from NotExist when GOMAXPROCS changes. We can emit
178	// events for this to clearly delineate GOMAXPROCS changes.
179
180	if viewerEv.Name != "" {
181		ctx.Instant(viewerEv)
182	}
183}
184
185func (g *threadGenerator) ProcRange(ctx *traceContext, ev *trace.Event) {
186	// TODO(mknyszek): Extend procRangeGenerator to support rendering proc ranges on threads.
187}
188
189func (g *threadGenerator) Finish(ctx *traceContext) {
190	ctx.SetResourceType("OS THREADS")
191
192	// Finish off global ranges.
193	g.globalRangeGenerator.Finish(ctx)
194
195	// Finish off all the goroutine slices.
196	for _, gs := range g.gStates {
197		gs.finish(ctx)
198	}
199
200	// Name all the threads to the emitter.
201	for id := range g.threads {
202		ctx.Resource(uint64(id), fmt.Sprintf("Thread %d", id))
203	}
204}
205