golang.org/x/exp@v0.0.0-20240506185415-9bf2ced13842/trace/flightrecorder_test.go (about)

     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.
     4  
     5  //go:build go1.22
     6  
     7  package trace_test
     8  
     9  import (
    10  	"bytes"
    11  	"context"
    12  	"io"
    13  	"runtime/trace"
    14  	"sync"
    15  	"sync/atomic"
    16  	"testing"
    17  	"time"
    18  
    19  	"golang.org/x/exp/trace/internal/testtrace"
    20  
    21  	. "golang.org/x/exp/trace"
    22  )
    23  
    24  func TestFlightRecorderDoubleStart(t *testing.T) {
    25  	fr := NewFlightRecorder()
    26  
    27  	if err := fr.Start(); err != nil {
    28  		t.Fatalf("unexpected error on Start: %v", err)
    29  	}
    30  	if err := fr.Start(); err == nil {
    31  		t.Fatalf("expected error from double Start: %v", err)
    32  	}
    33  	if err := fr.Stop(); err != nil {
    34  		t.Fatalf("unexpected error on Stop: %v", err)
    35  	}
    36  }
    37  
    38  func TestFlightRecorderDoubleStop(t *testing.T) {
    39  	fr := NewFlightRecorder()
    40  
    41  	if err := fr.Start(); err != nil {
    42  		t.Fatalf("unexpected error on Start: %v", err)
    43  	}
    44  	if err := fr.Stop(); err != nil {
    45  		t.Fatalf("unexpected error on Stop: %v", err)
    46  	}
    47  	if err := fr.Stop(); err == nil {
    48  		t.Fatalf("expected error from double Stop: %v", err)
    49  	}
    50  }
    51  
    52  func TestFlightRecorderEnabled(t *testing.T) {
    53  	fr := NewFlightRecorder()
    54  
    55  	if fr.Enabled() {
    56  		t.Fatal("flight recorder is enabled, but never started")
    57  	}
    58  	if err := fr.Start(); err != nil {
    59  		t.Fatalf("unexpected error on Start: %v", err)
    60  	}
    61  	if !fr.Enabled() {
    62  		t.Fatal("flight recorder is not enabled, but started")
    63  	}
    64  	if err := fr.Stop(); err != nil {
    65  		t.Fatalf("unexpected error on Stop: %v", err)
    66  	}
    67  	if fr.Enabled() {
    68  		t.Fatal("flight recorder is enabled, but stopped")
    69  	}
    70  }
    71  
    72  func TestFlightRecorderWriteToDisabled(t *testing.T) {
    73  	var buf bytes.Buffer
    74  
    75  	fr := NewFlightRecorder()
    76  	if n, err := fr.WriteTo(&buf); err == nil {
    77  		t.Fatalf("successfully wrote %d bytes from disabled flight recorder", n)
    78  	}
    79  	if err := fr.Start(); err != nil {
    80  		t.Fatalf("unexpected error on Start: %v", err)
    81  	}
    82  	if err := fr.Stop(); err != nil {
    83  		t.Fatalf("unexpected error on Stop: %v", err)
    84  	}
    85  	if n, err := fr.WriteTo(&buf); err == nil {
    86  		t.Fatalf("successfully wrote %d bytes from disabled flight recorder", n)
    87  	}
    88  }
    89  
    90  func TestFlightRecorderConcurrentWriteTo(t *testing.T) {
    91  	fr := NewFlightRecorder()
    92  	if err := fr.Start(); err != nil {
    93  		t.Fatalf("unexpected error on Start: %v", err)
    94  	}
    95  
    96  	// Start two goroutines to write snapshots.
    97  	//
    98  	// Most of the time one will fail and one will succeed, but we don't require this.
    99  	// Due to a variety of factors, it's definitely possible for them both to succeed.
   100  	// However, at least one should succeed.
   101  	var bufs [2]bytes.Buffer
   102  	var wg sync.WaitGroup
   103  	var successes atomic.Uint32
   104  	for i := range bufs {
   105  		wg.Add(1)
   106  		go func() {
   107  			defer wg.Done()
   108  
   109  			n, err := fr.WriteTo(&bufs[i])
   110  			if err == ErrSnapshotActive {
   111  				if n != 0 {
   112  					t.Errorf("(goroutine %d) WriteTo bytes written is non-zero for early bail out: %d", i, n)
   113  				}
   114  				return
   115  			}
   116  			if err != nil {
   117  				t.Errorf("(goroutine %d) failed to write snapshot for unexpected reason: %v", i, err)
   118  			}
   119  			successes.Add(1)
   120  
   121  			if n == 0 {
   122  				t.Errorf("(goroutine %d) wrote invalid trace of zero bytes in size", i)
   123  			}
   124  			if n != bufs[i].Len() {
   125  				t.Errorf("(goroutine %d) trace length doesn't match WriteTo result: got %d, want %d", i, n, bufs[i].Len())
   126  			}
   127  		}()
   128  	}
   129  	wg.Wait()
   130  
   131  	// Stop tracing.
   132  	if err := fr.Stop(); err != nil {
   133  		t.Fatalf("unexpected error on Stop: %v", err)
   134  	}
   135  
   136  	// Make sure at least one succeeded to write.
   137  	if successes.Load() == 0 {
   138  		t.Fatal("expected at least one success to write a snapshot, got zero")
   139  	}
   140  
   141  	// Validate the traces that came out.
   142  	for i := range bufs {
   143  		buf := &bufs[i]
   144  		if buf.Len() == 0 {
   145  			continue
   146  		}
   147  		testReader(t, buf, testtrace.ExpectSuccess())
   148  	}
   149  }
   150  
   151  func TestFlightRecorder(t *testing.T) {
   152  	testFlightRecorder(t, NewFlightRecorder(), func(snapshot func()) {
   153  		snapshot()
   154  	})
   155  }
   156  
   157  func TestFlightRecorderStartStop(t *testing.T) {
   158  	fr := NewFlightRecorder()
   159  	for i := 0; i < 5; i++ {
   160  		testFlightRecorder(t, fr, func(snapshot func()) {
   161  			snapshot()
   162  		})
   163  	}
   164  }
   165  
   166  func TestFlightRecorderLog(t *testing.T) {
   167  	tr := testFlightRecorder(t, NewFlightRecorder(), func(snapshot func()) {
   168  		trace.Log(context.Background(), "message", "hello")
   169  		snapshot()
   170  	})
   171  
   172  	// Prepare to read the trace snapshot.
   173  	r, err := NewReader(bytes.NewReader(tr))
   174  	if err != nil {
   175  		t.Fatalf("unexpected error creating trace reader: %v", err)
   176  	}
   177  
   178  	// Find the log message in the trace.
   179  	found := false
   180  	for {
   181  		ev, err := r.ReadEvent()
   182  		if err == io.EOF {
   183  			break
   184  		}
   185  		if err != nil {
   186  			t.Fatalf("unexpected error reading trace: %v", err)
   187  		}
   188  		if !found && ev.Kind() == EventLog {
   189  			log := ev.Log()
   190  			found = log.Category == "message" && log.Message == "hello"
   191  		}
   192  	}
   193  	if !found {
   194  		t.Errorf("failed to find expected log message (%q, %q) in snapshot", "message", "hello")
   195  	}
   196  }
   197  
   198  func TestFlightRecorderOneGeneration(t *testing.T) {
   199  	test := func(t *testing.T, fr *FlightRecorder) {
   200  		tr := testFlightRecorder(t, fr, func(snapshot func()) {
   201  			// Sleep to let a few generations pass.
   202  			time.Sleep(3 * time.Second)
   203  			snapshot()
   204  		})
   205  
   206  		// Prepare to read the trace snapshot.
   207  		r, err := NewReader(bytes.NewReader(tr))
   208  		if err != nil {
   209  			t.Fatalf("unexpected error creating trace reader: %v", err)
   210  		}
   211  
   212  		// Make sure there's only exactly one Sync event.
   213  		sync := 0
   214  		for {
   215  			ev, err := r.ReadEvent()
   216  			if err == io.EOF {
   217  				break
   218  			}
   219  			if err != nil {
   220  				t.Fatalf("unexpected error reading trace: %v", err)
   221  			}
   222  			if ev.Kind() == EventSync {
   223  				sync++
   224  			}
   225  		}
   226  		if sync != 1 {
   227  			t.Errorf("expected one sync event, found %d", sync)
   228  		}
   229  	}
   230  	t.Run("SetPeriod", func(t *testing.T) {
   231  		// Set the period to 0 so that we're always throwing away old generations.
   232  		// This should always result in exactly one generation.
   233  		// Note: this is always going to result in taking the second generation
   234  		// flushed, which is the much less useful one. That's OK, because in practice
   235  		// SetPeriod shouldn't ever be called with a value this low.
   236  		fr := NewFlightRecorder()
   237  		fr.SetPeriod(0)
   238  		test(t, fr)
   239  	})
   240  	t.Run("SetSize", func(t *testing.T) {
   241  		// Set the size to 0 so that we're always throwing away old generations.
   242  		// This should always result in exactly one generation.
   243  		// Note: this is always going to result in taking the second generation
   244  		// flushed, which is the much less useful one. That's OK, because in practice
   245  		// SetPeriod shouldn't ever be called with a value this low.
   246  		fr := NewFlightRecorder()
   247  		fr.SetSize(0)
   248  		test(t, fr)
   249  	})
   250  }
   251  
   252  type flightRecorderTestFunc func(snapshot func())
   253  
   254  func testFlightRecorder(t *testing.T, fr *FlightRecorder, f flightRecorderTestFunc) []byte {
   255  	if trace.IsEnabled() {
   256  		t.Skip("cannot run flight recorder tests when tracing is enabled")
   257  	}
   258  
   259  	// Start the flight recorder.
   260  	if err := fr.Start(); err != nil {
   261  		t.Fatalf("unexpected error on Start: %v", err)
   262  	}
   263  
   264  	// Set up snapshot callback.
   265  	var buf bytes.Buffer
   266  	callback := func() {
   267  		n, err := fr.WriteTo(&buf)
   268  		if err != nil {
   269  			t.Errorf("unexpected failure during flight recording: %v", err)
   270  			return
   271  		}
   272  		if n < 16 {
   273  			t.Errorf("expected a trace size of at least 16 bytes, got %d", n)
   274  		}
   275  		if n != buf.Len() {
   276  			t.Errorf("WriteTo result doesn't match trace size: got %d, want %d", n, buf.Len())
   277  		}
   278  	}
   279  
   280  	// Call the test function.
   281  	f(callback)
   282  
   283  	// Stop the flight recorder.
   284  	if err := fr.Stop(); err != nil {
   285  		t.Fatalf("unexpected error on Stop: %v", err)
   286  	}
   287  
   288  	// Get the trace bytes; we don't want to use the Buffer as a Reader directly
   289  	// since we may want to consume this data more than once.
   290  	traceBytes := buf.Bytes()
   291  
   292  	// Parse the trace to make sure it's not broken.
   293  	testReader(t, bytes.NewReader(traceBytes), testtrace.ExpectSuccess())
   294  	return traceBytes
   295  }