aboutsummaryrefslogtreecommitdiff
path: root/src/internal
diff options
context:
space:
mode:
Diffstat (limited to 'src/internal')
-rw-r--r--src/internal/trace/goroutinesv2.go98
-rw-r--r--src/internal/trace/goroutinesv2_test.go18
-rw-r--r--src/internal/trace/traceviewer/http.go2
3 files changed, 57 insertions, 61 deletions
diff --git a/src/internal/trace/goroutinesv2.go b/src/internal/trace/goroutinesv2.go
index 7ed5771cd3..c5e5fadc0b 100644
--- a/src/internal/trace/goroutinesv2.go
+++ b/src/internal/trace/goroutinesv2.go
@@ -209,26 +209,8 @@ type goroutineSummary struct {
activeRegions []*UserRegionSummary // stack of active regions
}
-// SummarizeGoroutines generates statistics for all goroutines in the trace.
-func SummarizeGoroutines(events []tracev2.Event) map[tracev2.GoID]*GoroutineSummary {
- // Create the analysis state.
- b := goroutineStatsBuilder{
- gs: make(map[tracev2.GoID]*GoroutineSummary),
- syscallingP: make(map[tracev2.ProcID]tracev2.GoID),
- syscallingG: make(map[tracev2.GoID]tracev2.ProcID),
- rangesP: make(map[rangeP]tracev2.GoID),
- }
-
- // Process the trace.
- for i := range events {
- ev := &events[i]
- b.event(ev)
- }
- return b.finalize()
-}
-
-// goroutineStatsBuilder constructs per-goroutine time statistics for v2 traces.
-type goroutineStatsBuilder struct {
+// GoroutineSummarizer constructs per-goroutine time statistics for v2 traces.
+type GoroutineSummarizer struct {
// gs contains the map of goroutine summaries we're building up to return to the caller.
gs map[tracev2.GoID]*GoroutineSummary
@@ -247,22 +229,32 @@ type goroutineStatsBuilder struct {
syncTs tracev2.Time // timestamp of the last sync event processed (or the first timestamp in the trace).
}
+// NewGoroutineSummarizer creates a new struct to build goroutine stats from a trace.
+func NewGoroutineSummarizer() *GoroutineSummarizer {
+ return &GoroutineSummarizer{
+ gs: make(map[tracev2.GoID]*GoroutineSummary),
+ syscallingP: make(map[tracev2.ProcID]tracev2.GoID),
+ syscallingG: make(map[tracev2.GoID]tracev2.ProcID),
+ rangesP: make(map[rangeP]tracev2.GoID),
+ }
+}
+
type rangeP struct {
id tracev2.ProcID
name string
}
-// event feeds a single event into the stats builder.
-func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
- if b.syncTs == 0 {
- b.syncTs = ev.Time()
+// Event feeds a single event into the stats summarizer.
+func (s *GoroutineSummarizer) Event(ev *tracev2.Event) {
+ if s.syncTs == 0 {
+ s.syncTs = ev.Time()
}
- b.lastTs = ev.Time()
+ s.lastTs = ev.Time()
switch ev.Kind() {
// Record sync time for the RangeActive events.
case tracev2.EventSync:
- b.syncTs = ev.Time()
+ s.syncTs = ev.Time()
// Handle state transitions.
case tracev2.EventStateTransition:
@@ -278,14 +270,14 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
}
// Handle transition out.
- g := b.gs[id]
+ g := s.gs[id]
switch old {
case tracev2.GoUndetermined, tracev2.GoNotExist:
g = &GoroutineSummary{ID: id, goroutineSummary: &goroutineSummary{}}
// If we're coming out of GoUndetermined, then the creation time is the
// time of the last sync.
if old == tracev2.GoUndetermined {
- g.CreationTime = b.syncTs
+ g.CreationTime = s.syncTs
} else {
g.CreationTime = ev.Time()
}
@@ -304,14 +296,14 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
//
// N.B. ev.Goroutine() will always be NoGoroutine for the
// Undetermined case, so this is will simply not fire.
- if creatorG := b.gs[ev.Goroutine()]; creatorG != nil && len(creatorG.activeRegions) > 0 {
+ if creatorG := s.gs[ev.Goroutine()]; creatorG != nil && len(creatorG.activeRegions) > 0 {
regions := creatorG.activeRegions
s := regions[len(regions)-1]
if s.TaskID != tracev2.NoTask {
g.activeRegions = []*UserRegionSummary{{TaskID: s.TaskID, Start: ev}}
}
}
- b.gs[g.ID] = g
+ s.gs[g.ID] = g
case tracev2.GoRunning:
// Record execution time as we transition out of running
g.ExecTime += ev.Time().Sub(g.lastStartTime)
@@ -341,8 +333,8 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
g.lastSyscallBlockTime = 0
// Clear the syscall map.
- delete(b.syscallingP, b.syscallingG[id])
- delete(b.syscallingG, id)
+ delete(s.syscallingP, s.syscallingG[id])
+ delete(s.syscallingG, id)
}
}
@@ -388,8 +380,8 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
case tracev2.GoNotExist:
g.finalize(ev.Time(), ev)
case tracev2.GoSyscall:
- b.syscallingP[ev.Proc()] = id
- b.syscallingG[id] = ev.Proc()
+ s.syscallingP[ev.Proc()] = id
+ s.syscallingG[id] = ev.Proc()
g.lastSyscallTime = ev.Time()
}
@@ -399,10 +391,10 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
id := st.Resource.Proc()
old, new := st.Proc()
if old != new && new == tracev2.ProcIdle {
- if goid, ok := b.syscallingP[id]; ok {
- g := b.gs[goid]
+ if goid, ok := s.syscallingP[id]; ok {
+ g := s.gs[goid]
g.lastSyscallBlockTime = ev.Time()
- delete(b.syscallingP, id)
+ delete(s.syscallingP, id)
}
}
}
@@ -418,14 +410,14 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
// goroutine blocked often in mark assist will have both high mark assist
// and high block times. Those interested in a deeper view can look at the
// trace viewer.
- g = b.gs[r.Scope.Goroutine()]
+ g = s.gs[r.Scope.Goroutine()]
case tracev2.ResourceProc:
// N.B. These ranges are not actually bound to the goroutine, they're
// bound to the P. But if we happen to be on the P the whole time, let's
// try to attribute it to the goroutine. (e.g. GC sweeps are here.)
- g = b.gs[ev.Goroutine()]
+ g = s.gs[ev.Goroutine()]
if g != nil {
- b.rangesP[rangeP{id: r.Scope.Proc(), name: r.Name}] = ev.Goroutine()
+ s.rangesP[rangeP{id: r.Scope.Proc(), name: r.Name}] = ev.Goroutine()
}
}
if g == nil {
@@ -433,9 +425,9 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
}
if ev.Kind() == tracev2.EventRangeActive {
if ts := g.lastRangeTime[r.Name]; ts != 0 {
- g.RangeTime[r.Name] += b.syncTs.Sub(ts)
+ g.RangeTime[r.Name] += s.syncTs.Sub(ts)
}
- g.lastRangeTime[r.Name] = b.syncTs
+ g.lastRangeTime[r.Name] = s.syncTs
} else {
g.lastRangeTime[r.Name] = ev.Time()
}
@@ -444,16 +436,16 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
var g *GoroutineSummary
switch r.Scope.Kind {
case tracev2.ResourceGoroutine:
- g = b.gs[r.Scope.Goroutine()]
+ g = s.gs[r.Scope.Goroutine()]
case tracev2.ResourceProc:
rp := rangeP{id: r.Scope.Proc(), name: r.Name}
- if goid, ok := b.rangesP[rp]; ok {
+ if goid, ok := s.rangesP[rp]; ok {
if goid == ev.Goroutine() {
// As the comment in the RangeBegin case states, this is only OK
// if we finish on the same goroutine we started on.
- g = b.gs[goid]
+ g = s.gs[goid]
}
- delete(b.rangesP, rp)
+ delete(s.rangesP, rp)
}
}
if g == nil {
@@ -468,7 +460,7 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
// Handle user-defined regions.
case tracev2.EventRegionBegin:
- g := b.gs[ev.Goroutine()]
+ g := s.gs[ev.Goroutine()]
r := ev.Region()
g.activeRegions = append(g.activeRegions, &UserRegionSummary{
Name: r.Type,
@@ -477,7 +469,7 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
GoroutineExecStats: g.snapshotStat(ev.Time()),
})
case tracev2.EventRegionEnd:
- g := b.gs[ev.Goroutine()]
+ g := s.gs[ev.Goroutine()]
r := ev.Region()
var sd *UserRegionSummary
if regionStk := g.activeRegions; len(regionStk) > 0 {
@@ -496,11 +488,11 @@ func (b *goroutineStatsBuilder) event(ev *tracev2.Event) {
}
}
-// finalize indicates to the builder that we're done processing the trace.
+// Finalize indicates to the summarizer that we're done processing the trace.
// It cleans up any remaining state and returns the full summary.
-func (b *goroutineStatsBuilder) finalize() map[tracev2.GoID]*GoroutineSummary {
- for _, g := range b.gs {
- g.finalize(b.lastTs, nil)
+func (s *GoroutineSummarizer) Finalize() map[tracev2.GoID]*GoroutineSummary {
+ for _, g := range s.gs {
+ g.finalize(s.lastTs, nil)
// Sort based on region start time.
sort.Slice(g.Regions, func(i, j int) bool {
@@ -516,7 +508,7 @@ func (b *goroutineStatsBuilder) finalize() map[tracev2.GoID]*GoroutineSummary {
})
g.goroutineSummary = nil
}
- return b.gs
+ return s.gs
}
// RelatedGoroutinesV2 finds a set of goroutines related to goroutine goid for v2 traces.
diff --git a/src/internal/trace/goroutinesv2_test.go b/src/internal/trace/goroutinesv2_test.go
index ecd7f2631d..ea689aca65 100644
--- a/src/internal/trace/goroutinesv2_test.go
+++ b/src/internal/trace/goroutinesv2_test.go
@@ -92,26 +92,30 @@ func basicSummaryChecks(t *testing.T, summary *GoroutineSummary) {
}
func summarizeTraceTest(t *testing.T, testPath string) map[tracev2.GoID]*GoroutineSummary {
- r, _, err := testtrace.ParseFile(testPath)
+ trace, _, err := testtrace.ParseFile(testPath)
if err != nil {
t.Fatalf("malformed test %s: bad trace file: %v", testPath, err)
}
- var events []tracev2.Event
- tr, err := tracev2.NewReader(r)
+ // Create the analysis state.
+ s := NewGoroutineSummarizer()
+
+ // Create a reader.
+ r, err := tracev2.NewReader(trace)
if err != nil {
- t.Fatalf("failed to create trace reader %s: %v", testPath, err)
+ t.Fatalf("failed to create trace reader for %s: %v", testPath, err)
}
+ // Process the trace.
for {
- ev, err := tr.ReadEvent()
+ ev, err := r.ReadEvent()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("failed to process trace %s: %v", testPath, err)
}
- events = append(events, ev)
+ s.Event(&ev)
}
- return SummarizeGoroutines(events)
+ return s.Finalize()
}
func checkRegionEvents(t *testing.T, wantStart, wantEnd tracev2.EventKind, goid tracev2.GoID, region *UserRegionSummary) {
diff --git a/src/internal/trace/traceviewer/http.go b/src/internal/trace/traceviewer/http.go
index ac2803be2d..b279b62a23 100644
--- a/src/internal/trace/traceviewer/http.go
+++ b/src/internal/trace/traceviewer/http.go
@@ -185,7 +185,7 @@ var templMain = template.Must(template.New("").Parse(`
<ul>
<li><a href="/io">Network blocking profile</a> (<a href="/io?raw=1" download="io.profile">⬇</a>)</li>
<li><a href="/block">Synchronization blocking profile</a> (<a href="/block?raw=1" download="block.profile">⬇</a>)</li>
-<li><a href="/syscall">Syscall blocking profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)</li>
+<li><a href="/syscall">Syscall profile</a> (<a href="/syscall?raw=1" download="syscall.profile">⬇</a>)</li>
<li><a href="/sched">Scheduler latency profile</a> (<a href="/sched?raw=1" download="sched.profile">⬇</a>)</li>
</ul>