mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
In the proc view, the thread ID is useful. In the thread view, the proc ID is useful. Add both in both cases forever more. Change-Id: I9cb7bd67a21ee17d865c25d73b2049b3da7aefbc Reviewed-on: https://go-review.googlesource.com/c/go/+/720402 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Pratt <mpratt@google.com> Auto-Submit: Michael Knyszek <mknyszek@google.com>
206 lines
5.7 KiB
Go
206 lines
5.7 KiB
Go
// 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 = &threadGenerator{}
|
|
|
|
type threadGenerator struct {
|
|
globalRangeGenerator
|
|
globalMetricGenerator
|
|
stackSampleGenerator[trace.ThreadID]
|
|
logEventGenerator[trace.ThreadID]
|
|
|
|
gStates map[trace.GoID]*gState[trace.ThreadID]
|
|
threads map[trace.ThreadID]struct{}
|
|
}
|
|
|
|
func newThreadGenerator() *threadGenerator {
|
|
tg := new(threadGenerator)
|
|
rg := func(ev *trace.Event) trace.ThreadID {
|
|
return ev.Thread()
|
|
}
|
|
tg.stackSampleGenerator.getResource = rg
|
|
tg.logEventGenerator.getResource = rg
|
|
tg.gStates = make(map[trace.GoID]*gState[trace.ThreadID])
|
|
tg.threads = make(map[trace.ThreadID]struct{})
|
|
return tg
|
|
}
|
|
|
|
func (g *threadGenerator) Sync() {
|
|
g.globalRangeGenerator.Sync()
|
|
}
|
|
|
|
func (g *threadGenerator) GoroutineLabel(ctx *traceContext, ev *trace.Event) {
|
|
l := ev.Label()
|
|
g.gStates[l.Resource.Goroutine()].setLabel(l.Label)
|
|
}
|
|
|
|
func (g *threadGenerator) 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 *threadGenerator) GoroutineTransition(ctx *traceContext, ev *trace.Event) {
|
|
if ev.Thread() != trace.NoThread {
|
|
if _, ok := g.threads[ev.Thread()]; !ok {
|
|
g.threads[ev.Thread()] = struct{}{}
|
|
}
|
|
}
|
|
|
|
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.ThreadID](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.Executing() && !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.Executing() {
|
|
start := ev.Time()
|
|
if from == trace.GoUndetermined {
|
|
// Back-date the event to the start of the trace.
|
|
start = ctx.startTime
|
|
}
|
|
gs.start(start, ev.Thread(), ctx)
|
|
}
|
|
|
|
if from == trace.GoWaiting {
|
|
// Goroutine was unblocked.
|
|
gs.unblock(ev.Time(), ev.Stack(), ev.Thread(), ctx)
|
|
}
|
|
if from == trace.GoNotExist && to == trace.GoRunnable {
|
|
// Goroutine was created.
|
|
gs.created(ev.Time(), ev.Thread(), ev.Stack())
|
|
}
|
|
if from == trace.GoSyscall {
|
|
// Exiting syscall.
|
|
gs.syscallEnd(ev.Time(), to != trace.GoRunning, ctx)
|
|
}
|
|
|
|
// Handle syscalls.
|
|
if to == trace.GoSyscall {
|
|
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.Thread(), ev.Stack())
|
|
}
|
|
|
|
// 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 *threadGenerator) ProcTransition(ctx *traceContext, ev *trace.Event) {
|
|
if ev.Thread() != trace.NoThread {
|
|
if _, ok := g.threads[ev.Thread()]; !ok {
|
|
g.threads[ev.Thread()] = struct{}{}
|
|
}
|
|
}
|
|
|
|
st := ev.StateTransition()
|
|
viewerEv := traceviewer.InstantEvent{
|
|
Resource: uint64(ev.Thread()),
|
|
Stack: ctx.Stack(viewerFrames(ev.Stack())),
|
|
|
|
// Annotate with the thread and proc. The thread is redundant, but this is to
|
|
// stay consistent with the proc view.
|
|
Arg: format.SchedCtxArg{
|
|
ProcID: uint64(st.Resource.Proc()),
|
|
ThreadID: uint64(ev.Thread()),
|
|
},
|
|
}
|
|
|
|
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.Ts = ctx.elapsed(start)
|
|
// TODO(mknyszek): We don't have a state machine for threads, so approximate
|
|
// running threads with running Ps.
|
|
ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, 1)
|
|
}
|
|
if from.Executing() {
|
|
start := ev.Time()
|
|
viewerEv.Name = "proc stop"
|
|
viewerEv.Ts = ctx.elapsed(start)
|
|
// TODO(mknyszek): We don't have a state machine for threads, so approximate
|
|
// running threads with running Ps.
|
|
ctx.IncThreadStateCount(ctx.elapsed(start), traceviewer.ThreadStateRunning, -1)
|
|
}
|
|
// 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 *threadGenerator) ProcRange(ctx *traceContext, ev *trace.Event) {
|
|
// TODO(mknyszek): Extend procRangeGenerator to support rendering proc ranges on threads.
|
|
}
|
|
|
|
func (g *threadGenerator) Finish(ctx *traceContext) {
|
|
ctx.SetResourceType("OS THREADS")
|
|
|
|
// Finish off global ranges.
|
|
g.globalRangeGenerator.Finish(ctx)
|
|
|
|
// Finish off all the goroutine slices.
|
|
for _, gs := range g.gStates {
|
|
gs.finish(ctx)
|
|
}
|
|
|
|
// Name all the threads to the emitter.
|
|
for id := range g.threads {
|
|
ctx.Resource(uint64(id), fmt.Sprintf("Thread %d", id))
|
|
}
|
|
}
|