github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/replay/workload_capture_test.go (about) 1 package replay 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "fmt" 7 "io" 8 "strconv" 9 "strings" 10 "testing" 11 "time" 12 "unicode" 13 14 "github.com/cockroachdb/datadriven" 15 "github.com/cockroachdb/pebble" 16 "github.com/cockroachdb/pebble/internal/base" 17 "github.com/cockroachdb/pebble/internal/humanize" 18 "github.com/cockroachdb/pebble/vfs" 19 "github.com/stretchr/testify/require" 20 ) 21 22 func TestWorkloadCollector(t *testing.T) { 23 const srcDir = `src` 24 const destDir = `dst` 25 datadriven.Walk(t, "testdata/collect", func(t *testing.T, path string) { 26 fs := vfs.NewMem() 27 require.NoError(t, fs.MkdirAll(srcDir, 0755)) 28 require.NoError(t, fs.MkdirAll(destDir, 0755)) 29 c := NewWorkloadCollector(srcDir) 30 o := &pebble.Options{FS: fs} 31 c.Attach(o) 32 var currentManifest vfs.File 33 var buf bytes.Buffer 34 defer func() { 35 if currentManifest != nil { 36 currentManifest.Close() 37 } 38 }() 39 datadriven.RunTest(t, path, func(t *testing.T, td *datadriven.TestData) string { 40 buf.Reset() 41 switch td.Cmd { 42 case "cmp-files": 43 if len(td.CmdArgs) != 2 { 44 return fmt.Sprintf("expected exactly 2 args, received %d", len(td.CmdArgs)) 45 } 46 b1 := readFile(t, fs, td.CmdArgs[0].String()) 47 b2 := readFile(t, fs, td.CmdArgs[1].String()) 48 if !bytes.Equal(b1, b2) { 49 return fmt.Sprintf("files are unequal: %s (%s) and %s (%s)", 50 td.CmdArgs[0].String(), humanize.Bytes.Uint64(uint64(len(b1))), 51 td.CmdArgs[1].String(), humanize.Bytes.Uint64(uint64(len(b2)))) 52 } 53 return "equal" 54 case "clean": 55 for _, path := range strings.Fields(td.Input) { 56 typ, _, ok := base.ParseFilename(fs, path) 57 require.True(t, ok) 58 require.NoError(t, o.Cleaner.Clean(fs, typ, path)) 59 } 60 return "" 61 case "create-manifest": 62 if currentManifest != nil { 63 require.NoError(t, currentManifest.Close()) 64 } 65 66 var fileNum uint64 67 var err error 68 td.ScanArgs(t, "filenum", &fileNum) 69 path := base.MakeFilepath(fs, srcDir, base.FileTypeManifest, base.FileNum(fileNum).DiskFileNum()) 70 currentManifest, err = fs.Create(path) 71 require.NoError(t, err) 72 _, err = currentManifest.Write(randData(100)) 73 require.NoError(t, err) 74 75 c.onManifestCreated(pebble.ManifestCreateInfo{ 76 Path: path, 77 FileNum: base.FileNum(fileNum), 78 }) 79 return "" 80 case "flush": 81 flushInfo := pebble.FlushInfo{ 82 Done: true, 83 Input: 1, 84 Duration: 100 * time.Millisecond, 85 TotalDuration: 100 * time.Millisecond, 86 } 87 for _, line := range strings.Split(td.Input, "\n") { 88 if line == "" { 89 continue 90 } 91 92 parts := strings.FieldsFunc(line, func(r rune) bool { return unicode.IsSpace(r) || r == ':' }) 93 tableInfo := pebble.TableInfo{Size: 10 << 10} 94 fileNum, err := strconv.ParseUint(parts[0], 10, 64) 95 require.NoError(t, err) 96 tableInfo.FileNum = base.FileNum(fileNum) 97 98 p := writeFile(t, fs, srcDir, base.FileTypeTable, tableInfo.FileNum.DiskFileNum(), randData(int(tableInfo.Size))) 99 fmt.Fprintf(&buf, "created %s\n", p) 100 flushInfo.Output = append(flushInfo.Output, tableInfo) 101 102 // Simulate a version edit applied to the current manifest. 103 _, err = currentManifest.Write(randData(25)) 104 require.NoError(t, err) 105 } 106 flushInfo.InputBytes = 100 // Determinism 107 fmt.Fprint(&buf, flushInfo.String()) 108 c.onFlushEnd(flushInfo) 109 return buf.String() 110 case "ingest": 111 ingestInfo := pebble.TableIngestInfo{} 112 for _, line := range strings.Split(td.Input, "\n") { 113 if line == "" { 114 continue 115 } 116 117 parts := strings.FieldsFunc(line, func(r rune) bool { return unicode.IsSpace(r) || r == ':' }) 118 tableInfo := pebble.TableInfo{Size: 10 << 10} 119 fileNum, err := strconv.ParseUint(parts[0], 10, 64) 120 require.NoError(t, err) 121 tableInfo.FileNum = base.FileNum(fileNum) 122 123 p := writeFile(t, fs, srcDir, base.FileTypeTable, tableInfo.FileNum.DiskFileNum(), randData(int(tableInfo.Size))) 124 fmt.Fprintf(&buf, "created %s\n", p) 125 ingestInfo.Tables = append(ingestInfo.Tables, struct { 126 pebble.TableInfo 127 Level int 128 }{Level: 0, TableInfo: tableInfo}) 129 130 // Simulate a version edit applied to the current manifest. 131 _, err = currentManifest.Write(randData(25)) 132 require.NoError(t, err) 133 } 134 fmt.Fprint(&buf, ingestInfo.String()) 135 c.onTableIngest(ingestInfo) 136 return buf.String() 137 138 case "ls": 139 return runListFiles(t, fs, td) 140 case "start": 141 c.Start(fs, destDir) 142 return "" 143 case "stat": 144 var buf bytes.Buffer 145 for _, arg := range td.CmdArgs { 146 fi, err := fs.Stat(arg.String()) 147 if err != nil { 148 fmt.Fprintf(&buf, "%s: %s\n", arg.String(), err) 149 continue 150 } 151 fmt.Fprintf(&buf, "%s:\n", arg.String()) 152 fmt.Fprintf(&buf, " size: %d\n", fi.Size()) 153 } 154 return buf.String() 155 case "stop": 156 c.Stop() 157 return "" 158 case "wait": 159 // Wait until all pending sstables have been copied, then list 160 // the files in the destination directory. 161 c.mu.Lock() 162 for c.mu.tablesEnqueued != c.mu.tablesCopied { 163 c.mu.copyCond.Wait() 164 } 165 c.mu.Unlock() 166 listFiles(t, fs, &buf, destDir) 167 return buf.String() 168 default: 169 return fmt.Sprintf("unrecognized command %q", td.Cmd) 170 } 171 }) 172 }) 173 } 174 175 func randData(byteCount int) []byte { 176 b := make([]byte, byteCount) 177 rand.Read(b) 178 return b 179 } 180 181 func writeFile( 182 t *testing.T, fs vfs.FS, dir string, typ base.FileType, fileNum base.DiskFileNum, data []byte, 183 ) string { 184 path := base.MakeFilepath(fs, dir, typ, fileNum) 185 f, err := fs.Create(path) 186 require.NoError(t, err) 187 _, err = f.Write(data) 188 require.NoError(t, err) 189 require.NoError(t, f.Close()) 190 return path 191 } 192 193 func readFile(t *testing.T, fs vfs.FS, path string) []byte { 194 r, err := fs.Open(path) 195 require.NoError(t, err) 196 b, err := io.ReadAll(r) 197 require.NoError(t, err) 198 require.NoError(t, r.Close()) 199 return b 200 }