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 }