Source file src/cmd/trace/trace_test.go

     1  // Copyright 2016 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  
     5  //go:build !js
     6  
     7  package main
     8  
     9  import (
    10  	"cmd/internal/traceviewer"
    11  	"context"
    12  	"internal/trace"
    13  	"io"
    14  	rtrace "runtime/trace"
    15  	"strings"
    16  	"sync"
    17  	"testing"
    18  	"time"
    19  )
    20  
    21  // stacks is a fake stack map populated for test.
    22  type stacks map[uint64][]*trace.Frame
    23  
    24  // add adds a stack with a single frame whose Fn field is
    25  // set to the provided fname and returns a unique stack id.
    26  func (s *stacks) add(fname string) uint64 {
    27  	if *s == nil {
    28  		*s = make(map[uint64][]*trace.Frame)
    29  	}
    30  
    31  	id := uint64(len(*s))
    32  	(*s)[id] = []*trace.Frame{{Fn: fname}}
    33  	return id
    34  }
    35  
    36  // TestGoroutineCount tests runnable/running goroutine counts computed by generateTrace
    37  // remain in the valid range.
    38  //   - the counts must not be negative. generateTrace will return an error.
    39  //   - the counts must not include goroutines blocked waiting on channels or in syscall.
    40  func TestGoroutineCount(t *testing.T) {
    41  	w := trace.NewWriter()
    42  	w.Emit(trace.EvBatch, 0, 0)  // start of per-P batch event [pid, timestamp]
    43  	w.Emit(trace.EvFrequency, 1) // [ticks per second]
    44  
    45  	var s stacks
    46  
    47  	// In this test, we assume a valid trace contains EvGoWaiting or EvGoInSyscall
    48  	// event for every blocked goroutine.
    49  
    50  	// goroutine 10: blocked
    51  	w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
    52  	w.Emit(trace.EvGoWaiting, 1, 10)                                   // [timestamp, goroutine id]
    53  
    54  	// goroutine 20: in syscall
    55  	w.Emit(trace.EvGoCreate, 1, 20, s.add("pkg.f2"), s.add("main.f2"))
    56  	w.Emit(trace.EvGoInSyscall, 1, 20) // [timestamp, goroutine id]
    57  
    58  	// goroutine 30: runnable
    59  	w.Emit(trace.EvGoCreate, 1, 30, s.add("pkg.f3"), s.add("main.f3"))
    60  
    61  	w.Emit(trace.EvProcStart, 2, 0) // [timestamp, thread id]
    62  
    63  	// goroutine 40: runnable->running->runnable
    64  	w.Emit(trace.EvGoCreate, 1, 40, s.add("pkg.f4"), s.add("main.f4"))
    65  	w.Emit(trace.EvGoStartLocal, 1, 40)          // [timestamp, goroutine id]
    66  	w.Emit(trace.EvGoSched, 1, s.add("main.f4")) // [timestamp, stack]
    67  
    68  	res, err := trace.Parse(w, "")
    69  	if err != nil {
    70  		t.Fatalf("failed to parse test trace: %v", err)
    71  	}
    72  	res.Stacks = s // use fake stacks.
    73  
    74  	params := &traceParams{
    75  		parsed:  res,
    76  		endTime: int64(1<<63 - 1),
    77  	}
    78  
    79  	// Use the default viewerDataTraceConsumer but replace
    80  	// consumeViewerEvent to intercept the ViewerEvents for testing.
    81  	c := viewerDataTraceConsumer(io.Discard, 0, 1<<63-1)
    82  	c.consumeViewerEvent = func(ev *traceviewer.Event, _ bool) {
    83  		if ev.Name == "Goroutines" {
    84  			cnt := ev.Arg.(*goroutineCountersArg)
    85  			if cnt.Runnable+cnt.Running > 2 {
    86  				t.Errorf("goroutine count=%+v; want no more than 2 goroutines in runnable/running state", cnt)
    87  			}
    88  			t.Logf("read %+v %+v", ev, cnt)
    89  		}
    90  	}
    91  
    92  	// If the counts drop below 0, generateTrace will return an error.
    93  	if err := generateTrace(params, c); err != nil {
    94  		t.Fatalf("generateTrace failed: %v", err)
    95  	}
    96  }
    97  
    98  func TestGoroutineFilter(t *testing.T) {
    99  	// Test that we handle state changes to selected goroutines
   100  	// caused by events on goroutines that are not selected.
   101  
   102  	var s stacks
   103  
   104  	w := trace.NewWriter()
   105  	w.Emit(trace.EvBatch, 0, 0)  // start of per-P batch event [pid, timestamp]
   106  	w.Emit(trace.EvFrequency, 1) // [ticks per second]
   107  
   108  	// goroutine 10: blocked
   109  	w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
   110  	w.Emit(trace.EvGoWaiting, 1, 10)                                   // [timestamp, goroutine id]
   111  
   112  	// goroutine 20: runnable->running->unblock 10
   113  	w.Emit(trace.EvGoCreate, 1, 20, s.add("pkg.f2"), s.add("main.f2"))
   114  	w.Emit(trace.EvGoStartLocal, 1, 20)                    // [timestamp, goroutine id]
   115  	w.Emit(trace.EvGoUnblockLocal, 1, 10, s.add("pkg.f2")) // [timestamp, goroutine id, stack]
   116  	w.Emit(trace.EvGoEnd, 1)                               // [timestamp]
   117  
   118  	// goroutine 10: runnable->running->block
   119  	w.Emit(trace.EvGoStartLocal, 1, 10)         // [timestamp, goroutine id]
   120  	w.Emit(trace.EvGoBlock, 1, s.add("pkg.f3")) // [timestamp, stack]
   121  
   122  	res, err := trace.Parse(w, "")
   123  	if err != nil {
   124  		t.Fatalf("failed to parse test trace: %v", err)
   125  	}
   126  	res.Stacks = s // use fake stacks
   127  
   128  	params := &traceParams{
   129  		parsed:  res,
   130  		endTime: int64(1<<63 - 1),
   131  		gs:      map[uint64]bool{10: true},
   132  	}
   133  
   134  	c := viewerDataTraceConsumer(io.Discard, 0, 1<<63-1)
   135  	if err := generateTrace(params, c); err != nil {
   136  		t.Fatalf("generateTrace failed: %v", err)
   137  	}
   138  }
   139  
   140  func TestPreemptedMarkAssist(t *testing.T) {
   141  	w := trace.NewWriter()
   142  	w.Emit(trace.EvBatch, 0, 0)  // start of per-P batch event [pid, timestamp]
   143  	w.Emit(trace.EvFrequency, 1) // [ticks per second]
   144  
   145  	var s stacks
   146  	// goroutine 9999: running -> mark assisting -> preempted -> assisting -> running -> block
   147  	w.Emit(trace.EvGoCreate, 1, 9999, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
   148  	w.Emit(trace.EvGoStartLocal, 1, 9999)                                // [timestamp, goroutine id]
   149  	w.Emit(trace.EvGCMarkAssistStart, 1, s.add("main.f1"))               // [timestamp, stack]
   150  	w.Emit(trace.EvGoPreempt, 1, s.add("main.f1"))                       // [timestamp, stack]
   151  	w.Emit(trace.EvGoStartLocal, 1, 9999)                                // [timestamp, goroutine id]
   152  	w.Emit(trace.EvGCMarkAssistDone, 1)                                  // [timestamp]
   153  	w.Emit(trace.EvGoBlock, 1, s.add("main.f2"))                         // [timestamp, stack]
   154  
   155  	res, err := trace.Parse(w, "")
   156  	if err != nil {
   157  		t.Fatalf("failed to parse test trace: %v", err)
   158  	}
   159  	res.Stacks = s // use fake stacks
   160  
   161  	params := &traceParams{
   162  		parsed:  res,
   163  		endTime: int64(1<<63 - 1),
   164  	}
   165  
   166  	c := viewerDataTraceConsumer(io.Discard, 0, 1<<63-1)
   167  
   168  	marks := 0
   169  	c.consumeViewerEvent = func(ev *traceviewer.Event, _ bool) {
   170  		if strings.Contains(ev.Name, "MARK ASSIST") {
   171  			marks++
   172  		}
   173  	}
   174  	if err := generateTrace(params, c); err != nil {
   175  		t.Fatalf("generateTrace failed: %v", err)
   176  	}
   177  
   178  	if marks != 2 {
   179  		t.Errorf("Got %v MARK ASSIST events, want %v", marks, 2)
   180  	}
   181  }
   182  
   183  func TestFoo(t *testing.T) {
   184  	prog0 := func() {
   185  		ctx, task := rtrace.NewTask(context.Background(), "ohHappyDay")
   186  		rtrace.Log(ctx, "", "log before task ends")
   187  		task.End()
   188  		rtrace.Log(ctx, "", "log after task ends") // log after task ends
   189  	}
   190  	if err := traceProgram(t, prog0, "TestFoo"); err != nil {
   191  		t.Fatalf("failed to trace the program: %v", err)
   192  	}
   193  	res, err := parseTrace()
   194  	if err != nil {
   195  		t.Fatalf("failed to parse the trace: %v", err)
   196  	}
   197  	annotRes, _ := analyzeAnnotations()
   198  	var task *taskDesc
   199  	for _, t := range annotRes.tasks {
   200  		if t.name == "ohHappyDay" {
   201  			task = t
   202  			break
   203  		}
   204  	}
   205  	if task == nil {
   206  		t.Fatal("failed to locate expected task event")
   207  	}
   208  
   209  	params := &traceParams{
   210  		parsed:    res,
   211  		mode:      modeTaskOriented,
   212  		startTime: task.firstTimestamp() - 1,
   213  		endTime:   task.lastTimestamp() + 1,
   214  		tasks:     []*taskDesc{task},
   215  	}
   216  
   217  	c := viewerDataTraceConsumer(io.Discard, 0, 1<<63-1)
   218  
   219  	var logBeforeTaskEnd, logAfterTaskEnd bool
   220  	c.consumeViewerEvent = func(ev *traceviewer.Event, _ bool) {
   221  		if ev.Name == "log before task ends" {
   222  			logBeforeTaskEnd = true
   223  		}
   224  		if ev.Name == "log after task ends" {
   225  			logAfterTaskEnd = true
   226  		}
   227  	}
   228  	if err := generateTrace(params, c); err != nil {
   229  		t.Fatalf("generateTrace failed: %v", err)
   230  	}
   231  	if !logBeforeTaskEnd {
   232  		t.Error("failed to find 'log before task ends'")
   233  	}
   234  	if !logAfterTaskEnd {
   235  		t.Error("failed to find 'log after task ends'")
   236  	}
   237  
   238  }
   239  
   240  func TestDirectSemaphoreHandoff(t *testing.T) {
   241  	prog0 := func() {
   242  		var mu sync.Mutex
   243  		var wg sync.WaitGroup
   244  		mu.Lock()
   245  		// This is modeled after src/sync/mutex_test.go to trigger Mutex
   246  		// starvation mode, in which the goroutine that calls Unlock hands off
   247  		// both the semaphore and its remaining time slice. See issue 36186.
   248  		for i := 0; i < 2; i++ {
   249  			wg.Add(1)
   250  			go func() {
   251  				defer wg.Done()
   252  				for i := 0; i < 100; i++ {
   253  					mu.Lock()
   254  					time.Sleep(100 * time.Microsecond)
   255  					mu.Unlock()
   256  				}
   257  			}()
   258  		}
   259  		mu.Unlock()
   260  		wg.Wait()
   261  	}
   262  	if err := traceProgram(t, prog0, "TestDirectSemaphoreHandoff"); err != nil {
   263  		t.Fatalf("failed to trace the program: %v", err)
   264  	}
   265  	_, err := parseTrace()
   266  	if err != nil {
   267  		t.Fatalf("failed to parse the trace: %v", err)
   268  	}
   269  }
   270  

View as plain text