Home | History | Annotate | Download | only in trace
      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 package main
      6 
      7 import (
      8 	"internal/trace"
      9 	"strings"
     10 	"testing"
     11 )
     12 
     13 // stacks is a fake stack map populated for test.
     14 type stacks map[uint64][]*trace.Frame
     15 
     16 // add adds a stack with a single frame whose Fn field is
     17 // set to the provided fname and returns a unique stack id.
     18 func (s *stacks) add(fname string) uint64 {
     19 	if *s == nil {
     20 		*s = make(map[uint64][]*trace.Frame)
     21 	}
     22 
     23 	id := uint64(len(*s))
     24 	(*s)[id] = []*trace.Frame{{Fn: fname}}
     25 	return id
     26 }
     27 
     28 // TestGoroutineCount tests runnable/running goroutine counts computed by generateTrace
     29 // remain in the valid range.
     30 //   - the counts must not be negative. generateTrace will return an error.
     31 //   - the counts must not include goroutines blocked waiting on channels or in syscall.
     32 func TestGoroutineCount(t *testing.T) {
     33 	w := trace.NewWriter()
     34 	w.Emit(trace.EvBatch, 0, 0)  // start of per-P batch event [pid, timestamp]
     35 	w.Emit(trace.EvFrequency, 1) // [ticks per second]
     36 
     37 	var s stacks
     38 
     39 	// In this test, we assume a valid trace contains EvGoWaiting or EvGoInSyscall
     40 	// event for every blocked goroutine.
     41 
     42 	// goroutine 10: blocked
     43 	w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
     44 	w.Emit(trace.EvGoWaiting, 1, 10)                                   // [timestamp, goroutine id]
     45 
     46 	// goroutine 20: in syscall
     47 	w.Emit(trace.EvGoCreate, 1, 20, s.add("pkg.f2"), s.add("main.f2"))
     48 	w.Emit(trace.EvGoInSyscall, 1, 20) // [timestamp, goroutine id]
     49 
     50 	// goroutine 30: runnable
     51 	w.Emit(trace.EvGoCreate, 1, 30, s.add("pkg.f3"), s.add("main.f3"))
     52 
     53 	w.Emit(trace.EvProcStart, 2, 0) // [timestamp, thread id]
     54 
     55 	// goroutine 40: runnable->running->runnable
     56 	w.Emit(trace.EvGoCreate, 1, 40, s.add("pkg.f4"), s.add("main.f4"))
     57 	w.Emit(trace.EvGoStartLocal, 1, 40)          // [timestamp, goroutine id]
     58 	w.Emit(trace.EvGoSched, 1, s.add("main.f4")) // [timestamp, stack]
     59 
     60 	res, err := trace.Parse(w, "")
     61 	if err != nil {
     62 		t.Fatalf("failed to parse test trace: %v", err)
     63 	}
     64 	res.Stacks = s // use fake stacks.
     65 
     66 	params := &traceParams{
     67 		parsed:  res,
     68 		endTime: int64(1<<63 - 1),
     69 	}
     70 
     71 	// If the counts drop below 0, generateTrace will return an error.
     72 	viewerData, err := generateTrace(params)
     73 	if err != nil {
     74 		t.Fatalf("generateTrace failed: %v", err)
     75 	}
     76 	for _, ev := range viewerData.Events {
     77 		if ev.Name == "Goroutines" {
     78 			cnt := ev.Arg.(*goroutineCountersArg)
     79 			if cnt.Runnable+cnt.Running > 2 {
     80 				t.Errorf("goroutine count=%+v; want no more than 2 goroutines in runnable/running state", cnt)
     81 			}
     82 			t.Logf("read %+v %+v", ev, cnt)
     83 		}
     84 	}
     85 }
     86 
     87 func TestGoroutineFilter(t *testing.T) {
     88 	// Test that we handle state changes to selected goroutines
     89 	// caused by events on goroutines that are not selected.
     90 
     91 	var s stacks
     92 
     93 	w := trace.NewWriter()
     94 	w.Emit(trace.EvBatch, 0, 0)  // start of per-P batch event [pid, timestamp]
     95 	w.Emit(trace.EvFrequency, 1) // [ticks per second]
     96 
     97 	// goroutine 10: blocked
     98 	w.Emit(trace.EvGoCreate, 1, 10, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
     99 	w.Emit(trace.EvGoWaiting, 1, 10)                                   // [timestamp, goroutine id]
    100 
    101 	// goroutine 20: runnable->running->unblock 10
    102 	w.Emit(trace.EvGoCreate, 1, 20, s.add("pkg.f2"), s.add("main.f2"))
    103 	w.Emit(trace.EvGoStartLocal, 1, 20)                    // [timestamp, goroutine id]
    104 	w.Emit(trace.EvGoUnblockLocal, 1, 10, s.add("pkg.f2")) // [timestamp, goroutine id, stack]
    105 	w.Emit(trace.EvGoEnd, 1)                               // [timestamp]
    106 
    107 	// goroutine 10: runnable->running->block
    108 	w.Emit(trace.EvGoStartLocal, 1, 10)         // [timestamp, goroutine id]
    109 	w.Emit(trace.EvGoBlock, 1, s.add("pkg.f3")) // [timestamp, stack]
    110 
    111 	res, err := trace.Parse(w, "")
    112 	if err != nil {
    113 		t.Fatalf("failed to parse test trace: %v", err)
    114 	}
    115 	res.Stacks = s // use fake stacks
    116 
    117 	params := &traceParams{
    118 		parsed:  res,
    119 		endTime: int64(1<<63 - 1),
    120 		gs:      map[uint64]bool{10: true},
    121 	}
    122 
    123 	_, err = generateTrace(params)
    124 	if err != nil {
    125 		t.Fatalf("generateTrace failed: %v", err)
    126 	}
    127 }
    128 
    129 func TestPreemptedMarkAssist(t *testing.T) {
    130 	w := trace.NewWriter()
    131 	w.Emit(trace.EvBatch, 0, 0)  // start of per-P batch event [pid, timestamp]
    132 	w.Emit(trace.EvFrequency, 1) // [ticks per second]
    133 
    134 	var s stacks
    135 	// goroutine 9999: running -> mark assisting -> preempted -> assisting -> running -> block
    136 	w.Emit(trace.EvGoCreate, 1, 9999, s.add("pkg.f1"), s.add("main.f1")) // [timestamp, new goroutine id, new stack id, stack id]
    137 	w.Emit(trace.EvGoStartLocal, 1, 9999)                                // [timestamp, goroutine id]
    138 	w.Emit(trace.EvGCMarkAssistStart, 1, s.add("main.f1"))               // [timestamp, stack]
    139 	w.Emit(trace.EvGoPreempt, 1, s.add("main.f1"))                       // [timestamp, stack]
    140 	w.Emit(trace.EvGoStartLocal, 1, 9999)                                // [timestamp, goroutine id]
    141 	w.Emit(trace.EvGCMarkAssistDone, 1)                                  // [timestamp]
    142 	w.Emit(trace.EvGoBlock, 1, s.add("main.f2"))                         // [timestamp, stack]
    143 
    144 	res, err := trace.Parse(w, "")
    145 	if err != nil {
    146 		t.Fatalf("failed to parse test trace: %v", err)
    147 	}
    148 	res.Stacks = s // use fake stacks
    149 
    150 	params := &traceParams{
    151 		parsed:  res,
    152 		endTime: int64(1<<63 - 1),
    153 	}
    154 
    155 	viewerData, err := generateTrace(params)
    156 	if err != nil {
    157 		t.Fatalf("generateTrace failed: %v", err)
    158 	}
    159 
    160 	marks := 0
    161 	for _, ev := range viewerData.Events {
    162 		if strings.Contains(ev.Name, "MARK ASSIST") {
    163 			marks++
    164 		}
    165 	}
    166 	if marks != 2 {
    167 		t.Errorf("Got %v MARK ASSIST events, want %v", marks, 2)
    168 	}
    169 }
    170