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.
8 tracev2 "internal/trace/v2"
9 "internal/trace/v2/testtrace"
13 func TestSummarizeGoroutinesTrace(t *testing.T) {
14 summaries := summarizeTraceTest(t, "v2/testdata/tests/go122-gc-stress.test")
18 hasGCMarkAssistTime bool
20 for _, summary := range summaries {
21 basicSummaryChecks(t, summary)
22 hasSchedWaitTime = hasSchedWaitTime || summary.SchedWaitTime > 0
23 if dt, ok := summary.BlockTimeByReason["sync"]; ok && dt > 0 {
24 hasSyncBlockTime = true
26 if dt, ok := summary.RangeTime["GC mark assist"]; ok && dt > 0 {
27 hasGCMarkAssistTime = true
30 if !hasSchedWaitTime {
31 t.Error("missing sched wait time")
33 if !hasSyncBlockTime {
34 t.Error("missing sync block time")
36 if !hasGCMarkAssistTime {
37 t.Error("missing GC mark assist time")
41 func TestSummarizeGoroutinesRegionsTrace(t *testing.T) {
42 summaries := summarizeTraceTest(t, "v2/testdata/tests/go122-annotations.test")
44 startKind tracev2.EventKind
45 endKind tracev2.EventKind
47 wantRegions := map[string]region{
48 // N.B. "pre-existing region" never even makes it into the trace.
50 // TODO(mknyszek): Add test case for end-without-a-start, which can happen at
51 // a generation split only.
52 "": {tracev2.EventStateTransition, tracev2.EventStateTransition}, // Task inheritance marker.
53 "task0 region": {tracev2.EventRegionBegin, tracev2.EventBad},
54 "region0": {tracev2.EventRegionBegin, tracev2.EventRegionEnd},
55 "region1": {tracev2.EventRegionBegin, tracev2.EventRegionEnd},
56 "unended region": {tracev2.EventRegionBegin, tracev2.EventStateTransition},
57 "post-existing region": {tracev2.EventRegionBegin, tracev2.EventBad},
59 for _, summary := range summaries {
60 basicSummaryChecks(t, summary)
61 for _, region := range summary.Regions {
62 want, ok := wantRegions[region.Name]
66 checkRegionEvents(t, want.startKind, want.endKind, summary.ID, region)
67 delete(wantRegions, region.Name)
70 if len(wantRegions) != 0 {
71 t.Errorf("failed to find regions: %#v", wantRegions)
75 func basicSummaryChecks(t *testing.T, summary *GoroutineSummary) {
76 if summary.ID == tracev2.NoGoroutine {
77 t.Error("summary found for no goroutine")
80 if (summary.StartTime != 0 && summary.CreationTime > summary.StartTime) ||
81 (summary.StartTime != 0 && summary.EndTime != 0 && summary.StartTime > summary.EndTime) {
82 t.Errorf("bad summary creation/start/end times for G %d: creation=%d start=%d end=%d", summary.ID, summary.CreationTime, summary.StartTime, summary.EndTime)
84 if (summary.PC != 0 && summary.Name == "") || (summary.PC == 0 && summary.Name != "") {
85 t.Errorf("bad name and/or PC for G %d: pc=0x%x name=%q", summary.ID, summary.PC, summary.Name)
87 basicGoroutineExecStatsChecks(t, &summary.GoroutineExecStats)
88 for _, region := range summary.Regions {
89 basicGoroutineExecStatsChecks(t, ®ion.GoroutineExecStats)
93 func summarizeTraceTest(t *testing.T, testPath string) map[tracev2.GoID]*GoroutineSummary {
94 r, _, err := testtrace.ParseFile(testPath)
96 t.Fatalf("malformed test %s: bad trace file: %v", testPath, err)
98 summaries, err := SummarizeGoroutines(r)
100 t.Fatalf("failed to process trace %s: %v", testPath, err)
105 func checkRegionEvents(t *testing.T, wantStart, wantEnd tracev2.EventKind, goid tracev2.GoID, region *UserRegionSummary) {
107 case tracev2.EventBad:
108 if region.Start != nil {
109 t.Errorf("expected nil region start event, got\n%s", region.Start.String())
111 case tracev2.EventStateTransition, tracev2.EventRegionBegin:
112 if region.Start == nil {
113 t.Error("expected non-nil region start event, got nil")
115 kind := region.Start.Kind()
116 if kind != wantStart {
117 t.Errorf("wanted region start event %s, got %s", wantStart, kind)
119 if kind == tracev2.EventRegionBegin {
120 if region.Start.Region().Type != region.Name {
121 t.Errorf("region name mismatch: event has %s, summary has %s", region.Start.Region().Type, region.Name)
124 st := region.Start.StateTransition()
125 if st.Resource.Kind != tracev2.ResourceGoroutine {
126 t.Errorf("found region start event for the wrong resource: %s", st.Resource)
128 if st.Resource.Goroutine() != goid {
129 t.Errorf("found region start event for the wrong resource: wanted goroutine %d, got %s", goid, st.Resource)
131 if old, _ := st.Goroutine(); old != tracev2.GoNotExist && old != tracev2.GoUndetermined {
132 t.Errorf("expected transition from GoNotExist or GoUndetermined, got transition from %s instead", old)
136 t.Errorf("unexpected want start event type: %s", wantStart)
140 case tracev2.EventBad:
141 if region.End != nil {
142 t.Errorf("expected nil region end event, got\n%s", region.End.String())
144 case tracev2.EventStateTransition, tracev2.EventRegionEnd:
145 if region.End == nil {
146 t.Error("expected non-nil region end event, got nil")
148 kind := region.End.Kind()
150 t.Errorf("wanted region end event %s, got %s", wantEnd, kind)
152 if kind == tracev2.EventRegionEnd {
153 if region.End.Region().Type != region.Name {
154 t.Errorf("region name mismatch: event has %s, summary has %s", region.End.Region().Type, region.Name)
157 st := region.End.StateTransition()
158 if st.Resource.Kind != tracev2.ResourceGoroutine {
159 t.Errorf("found region end event for the wrong resource: %s", st.Resource)
161 if st.Resource.Goroutine() != goid {
162 t.Errorf("found region end event for the wrong resource: wanted goroutine %d, got %s", goid, st.Resource)
164 if _, new := st.Goroutine(); new != tracev2.GoNotExist {
165 t.Errorf("expected transition to GoNotExist, got transition to %s instead", new)
169 t.Errorf("unexpected want end event type: %s", wantEnd)
173 func basicGoroutineExecStatsChecks(t *testing.T, stats *GoroutineExecStats) {
174 if stats.ExecTime < 0 {
175 t.Error("found negative ExecTime")
177 if stats.SchedWaitTime < 0 {
178 t.Error("found negative SchedWaitTime")
180 if stats.SyscallTime < 0 {
181 t.Error("found negative SyscallTime")
183 if stats.SyscallBlockTime < 0 {
184 t.Error("found negative SyscallBlockTime")
186 if stats.TotalTime < 0 {
187 t.Error("found negative TotalTime")
189 for reason, dt := range stats.BlockTimeByReason {
191 t.Errorf("found negative BlockTimeByReason for %s", reason)
194 for name, dt := range stats.RangeTime {
196 t.Errorf("found negative RangeTime for range %s", name)
201 func TestRelatedGoroutinesV2Trace(t *testing.T) {
202 testPath := "v2/testdata/tests/go122-gc-stress.test"
203 r, _, err := testtrace.ParseFile(testPath)
205 t.Fatalf("malformed test %s: bad trace file: %v", testPath, err)
207 targetg := tracev2.GoID(86)
208 got, err := RelatedGoroutinesV2(r, targetg)
210 t.Fatalf("failed to find related goroutines for %s: %v", testPath, err)
212 want := map[tracev2.GoID]struct{}{
213 tracev2.GoID(86): struct{}{}, // N.B. Result includes target.
214 tracev2.GoID(71): struct{}{},
215 tracev2.GoID(25): struct{}{},
216 tracev2.GoID(122): struct{}{},
218 for goid := range got {
219 if _, ok := want[goid]; ok {
222 t.Errorf("unexpected goroutine %d found in related goroutines for %d in test %s", goid, targetg, testPath)
226 for goid := range want {
227 t.Errorf("failed to find related goroutine %d for goroutine %d in test %s", goid, targetg, testPath)