github.com/cockroachdb/pebble@v1.1.2/event_listener_test.go (about)

     1  // Copyright 2018 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package pebble
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"reflect"
    11  	"runtime"
    12  	"strings"
    13  	"sync"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/cockroachdb/datadriven"
    18  	"github.com/cockroachdb/errors"
    19  	"github.com/cockroachdb/pebble/internal/base"
    20  	"github.com/cockroachdb/pebble/objstorage/objstorageprovider"
    21  	"github.com/cockroachdb/pebble/sstable"
    22  	"github.com/cockroachdb/pebble/vfs"
    23  	"github.com/cockroachdb/redact"
    24  	"github.com/stretchr/testify/require"
    25  )
    26  
    27  // Verify event listener actions, as well as expected filesystem operations.
    28  func TestEventListener(t *testing.T) {
    29  	if runtime.GOARCH == "386" {
    30  		t.Skip("skipped on 32-bit due to slightly varied output")
    31  	}
    32  	var d *DB
    33  	var memLog base.InMemLogger
    34  	mem := vfs.NewMem()
    35  	require.NoError(t, mem.MkdirAll("ext", 0755))
    36  
    37  	datadriven.RunTest(t, "testdata/event_listener", func(t *testing.T, td *datadriven.TestData) string {
    38  		switch td.Cmd {
    39  		case "open":
    40  			memLog.Reset()
    41  			lel := MakeLoggingEventListener(&memLog)
    42  			flushBegin, flushEnd := lel.FlushBegin, lel.FlushEnd
    43  			lel.FlushBegin = func(info FlushInfo) {
    44  				// Make deterministic.
    45  				info.InputBytes = 100
    46  				flushBegin(info)
    47  			}
    48  			lel.FlushEnd = func(info FlushInfo) {
    49  				// Make deterministic.
    50  				info.InputBytes = 100
    51  				flushEnd(info)
    52  			}
    53  			opts := &Options{
    54  				FS:                    vfs.WithLogging(mem, memLog.Infof),
    55  				FormatMajorVersion:    internalFormatNewest,
    56  				EventListener:         &lel,
    57  				MaxManifestFileSize:   1,
    58  				L0CompactionThreshold: 10,
    59  				WALDir:                "wal",
    60  			}
    61  			// The table stats collector runs asynchronously and its
    62  			// timing is less predictable. It increments nextJobID, which
    63  			// can make these tests flaky. The TableStatsLoaded event is
    64  			// tested separately in TestTableStats.
    65  			opts.private.disableTableStats = true
    66  			var err error
    67  			d, err = Open("db", opts)
    68  			if err != nil {
    69  				return err.Error()
    70  			}
    71  			t := time.Now()
    72  			d.timeNow = func() time.Time {
    73  				t = t.Add(time.Second)
    74  				return t
    75  			}
    76  			d.opts.private.testingAlwaysWaitForCleanup = true
    77  			return memLog.String()
    78  
    79  		case "close":
    80  			memLog.Reset()
    81  			if err := d.Close(); err != nil {
    82  				return err.Error()
    83  			}
    84  			return memLog.String()
    85  
    86  		case "flush":
    87  			memLog.Reset()
    88  			if err := d.Set([]byte("a"), nil, nil); err != nil {
    89  				return err.Error()
    90  			}
    91  			if err := d.Flush(); err != nil {
    92  				return err.Error()
    93  			}
    94  			return memLog.String()
    95  
    96  		case "compact":
    97  			memLog.Reset()
    98  			if err := d.Set([]byte("a"), nil, nil); err != nil {
    99  				return err.Error()
   100  			}
   101  			if err := d.Compact([]byte("a"), []byte("b"), false); err != nil {
   102  				return err.Error()
   103  			}
   104  			return memLog.String()
   105  
   106  		case "checkpoint":
   107  			memLog.Reset()
   108  			if err := d.Checkpoint("checkpoint"); err != nil {
   109  				return err.Error()
   110  			}
   111  			return memLog.String()
   112  
   113  		case "disable-file-deletions":
   114  			memLog.Reset()
   115  			d.mu.Lock()
   116  			d.disableFileDeletions()
   117  			d.mu.Unlock()
   118  			return memLog.String()
   119  
   120  		case "enable-file-deletions":
   121  			memLog.Reset()
   122  			func() {
   123  				defer func() {
   124  					if r := recover(); r != nil {
   125  						memLog.Infof("%v", r)
   126  					}
   127  				}()
   128  				d.mu.Lock()
   129  				defer d.mu.Unlock()
   130  				d.enableFileDeletions()
   131  			}()
   132  			d.TestOnlyWaitForCleaning()
   133  			return memLog.String()
   134  
   135  		case "ingest":
   136  			memLog.Reset()
   137  			f, err := mem.Create("ext/0")
   138  			if err != nil {
   139  				return err.Error()
   140  			}
   141  			w := sstable.NewWriter(objstorageprovider.NewFileWritable(f), sstable.WriterOptions{
   142  				TableFormat: d.FormatMajorVersion().MaxTableFormat(),
   143  			})
   144  			if err := w.Add(base.MakeInternalKey([]byte("a"), 0, InternalKeyKindSet), nil); err != nil {
   145  				return err.Error()
   146  			}
   147  			if err := w.Close(); err != nil {
   148  				return err.Error()
   149  			}
   150  			if err := d.Ingest([]string{"ext/0"}); err != nil {
   151  				return err.Error()
   152  			}
   153  			return memLog.String()
   154  
   155  		case "ingest-flushable":
   156  			memLog.Reset()
   157  
   158  			// Prevent flushes during this test to ensure determinism.
   159  			d.mu.Lock()
   160  			d.mu.compact.flushing = true
   161  			d.mu.Unlock()
   162  
   163  			b := d.NewBatch()
   164  			if err := b.Set([]byte("a"), nil, nil); err != nil {
   165  				return err.Error()
   166  			}
   167  			if err := d.Apply(b, nil); err != nil {
   168  				return err.Error()
   169  			}
   170  			writeTable := func(name string, key byte) error {
   171  				f, err := mem.Create(name)
   172  				if err != nil {
   173  					return err
   174  				}
   175  				w := sstable.NewWriter(objstorageprovider.NewFileWritable(f), sstable.WriterOptions{
   176  					TableFormat: d.FormatMajorVersion().MaxTableFormat(),
   177  				})
   178  				if err := w.Add(base.MakeInternalKey([]byte{key}, 0, InternalKeyKindSet), nil); err != nil {
   179  					return err
   180  				}
   181  				if err := w.Close(); err != nil {
   182  					return err
   183  				}
   184  				return nil
   185  			}
   186  			tableA, tableB := "ext/a", "ext/b"
   187  			if err := writeTable(tableA, 'a'); err != nil {
   188  				return err.Error()
   189  			}
   190  			if err := writeTable(tableB, 'b'); err != nil {
   191  				return err.Error()
   192  			}
   193  			if err := d.Ingest([]string{tableA, tableB}); err != nil {
   194  				return err.Error()
   195  			}
   196  
   197  			// Re-enable flushes, to allow the subsequent flush to proceed.
   198  			d.mu.Lock()
   199  			d.mu.compact.flushing = false
   200  			d.mu.Unlock()
   201  			if err := d.Flush(); err != nil {
   202  				return err.Error()
   203  			}
   204  			return memLog.String()
   205  
   206  		case "metrics":
   207  			// The asynchronous loading of table stats can change metrics, so
   208  			// wait for all the tables' stats to be loaded.
   209  			d.mu.Lock()
   210  			d.waitTableStats()
   211  			d.mu.Unlock()
   212  
   213  			return d.Metrics().StringForTests()
   214  
   215  		case "sstables":
   216  			var buf bytes.Buffer
   217  			tableInfos, _ := d.SSTables()
   218  			for i, level := range tableInfos {
   219  				if len(level) == 0 {
   220  					continue
   221  				}
   222  				fmt.Fprintf(&buf, "%d:\n", i)
   223  				for _, m := range level {
   224  					fmt.Fprintf(&buf, "  %d:[%s-%s]\n",
   225  						m.FileNum, m.Smallest.UserKey, m.Largest.UserKey)
   226  				}
   227  			}
   228  			return buf.String()
   229  
   230  		default:
   231  			return fmt.Sprintf("unknown command: %s", td.Cmd)
   232  		}
   233  	})
   234  }
   235  
   236  func TestWriteStallEvents(t *testing.T) {
   237  	const flushCount = 10
   238  	const writeStallEnd = "write stall ending"
   239  
   240  	testCases := []struct {
   241  		delayFlush bool
   242  		expected   string
   243  	}{
   244  		{true, "memtable count limit reached"},
   245  		{false, "L0 file count limit exceeded"},
   246  	}
   247  
   248  	for _, c := range testCases {
   249  		t.Run("", func(t *testing.T) {
   250  			stallEnded := make(chan struct{}, 1)
   251  			createReleased := make(chan struct{}, flushCount)
   252  			var log base.InMemLogger
   253  			var delayOnce sync.Once
   254  			listener := &EventListener{
   255  				TableCreated: func(info TableCreateInfo) {
   256  					if c.delayFlush == (info.Reason == "flushing") {
   257  						delayOnce.Do(func() {
   258  							<-createReleased
   259  						})
   260  					}
   261  				},
   262  				WriteStallBegin: func(info WriteStallBeginInfo) {
   263  					log.Infof("%s", info.String())
   264  					createReleased <- struct{}{}
   265  				},
   266  				WriteStallEnd: func() {
   267  					log.Infof("%s", writeStallEnd)
   268  					select {
   269  					case stallEnded <- struct{}{}:
   270  					default:
   271  					}
   272  				},
   273  			}
   274  			d, err := Open("db", &Options{
   275  				EventListener:               listener,
   276  				FS:                          vfs.NewMem(),
   277  				MemTableSize:                initialMemTableSize,
   278  				MemTableStopWritesThreshold: 2,
   279  				L0CompactionThreshold:       2,
   280  				L0StopWritesThreshold:       2,
   281  			})
   282  			require.NoError(t, err)
   283  			defer d.Close()
   284  
   285  			for i := 0; i < flushCount; i++ {
   286  				require.NoError(t, d.Set([]byte("a"), nil, NoSync))
   287  
   288  				ch, err := d.AsyncFlush()
   289  				require.NoError(t, err)
   290  
   291  				// If we're delaying the flush (because we're testing for memtable
   292  				// write stalls), we can't wait for the flush to finish as doing so
   293  				// would deadlock. If we're not delaying the flush (because we're
   294  				// testing for L0 write stals), we wait for the flush to finish so we
   295  				// don't create too many memtables which would trigger a memtable write
   296  				// stall.
   297  				if !c.delayFlush {
   298  					<-ch
   299  				}
   300  				if strings.Contains(log.String(), c.expected) {
   301  					break
   302  				}
   303  			}
   304  			<-stallEnded
   305  
   306  			events := log.String()
   307  			require.Contains(t, events, c.expected)
   308  			require.Contains(t, events, writeStallEnd)
   309  			if testing.Verbose() {
   310  				t.Logf("\n%s", events)
   311  			}
   312  		})
   313  	}
   314  }
   315  
   316  type redactLogger struct {
   317  	logger Logger
   318  }
   319  
   320  // Infof implements the Logger.Infof interface.
   321  func (l redactLogger) Infof(format string, args ...interface{}) {
   322  	l.logger.Infof("%s", redact.Sprintf(format, args...).Redact())
   323  }
   324  
   325  // Fatalf implements the Logger.Fatalf interface.
   326  func (l redactLogger) Fatalf(format string, args ...interface{}) {
   327  	l.logger.Fatalf("%s", redact.Sprintf(format, args...).Redact())
   328  }
   329  
   330  func TestEventListenerRedact(t *testing.T) {
   331  	// The vast majority of event listener fields logged are safe and do not
   332  	// need to be redacted. Verify that the rare, unsafe error does appear in
   333  	// the log redacted.
   334  	var log base.InMemLogger
   335  	l := MakeLoggingEventListener(redactLogger{logger: &log})
   336  	l.WALDeleted(WALDeleteInfo{
   337  		JobID:   5,
   338  		FileNum: FileNum(20),
   339  		Err:     errors.Errorf("unredacted error: %s", "unredacted string"),
   340  	})
   341  	require.Equal(t, "[JOB 5] WAL delete error: unredacted error: ‹×›\n", log.String())
   342  }
   343  
   344  func TestEventListenerEnsureDefaultsBackgroundError(t *testing.T) {
   345  	e := EventListener{}
   346  	e.EnsureDefaults(nil)
   347  	e.BackgroundError(errors.New("an example error"))
   348  }
   349  
   350  func TestEventListenerEnsureDefaultsSetsAllCallbacks(t *testing.T) {
   351  	e := EventListener{}
   352  	e.EnsureDefaults(nil)
   353  	testAllCallbacksSetInEventListener(t, e)
   354  }
   355  
   356  func TestMakeLoggingEventListenerSetsAllCallbacks(t *testing.T) {
   357  	e := MakeLoggingEventListener(nil)
   358  	testAllCallbacksSetInEventListener(t, e)
   359  }
   360  
   361  func TestTeeEventListenerSetsAllCallbacks(t *testing.T) {
   362  	e := TeeEventListener(EventListener{}, EventListener{})
   363  	testAllCallbacksSetInEventListener(t, e)
   364  }
   365  
   366  func testAllCallbacksSetInEventListener(t *testing.T, e EventListener) {
   367  	t.Helper()
   368  	v := reflect.ValueOf(e)
   369  	for i := 0; i < v.NumField(); i++ {
   370  		fType := v.Type().Field(i)
   371  		fVal := v.Field(i)
   372  		require.Equal(t, reflect.Func, fType.Type.Kind(), "unexpected non-func field: %s", fType.Name)
   373  		require.False(t, fVal.IsNil(), "unexpected nil field: %s", fType.Name)
   374  	}
   375  }