github.com/zuoyebang/bitalostable@v1.0.1-0.20240229032404-e3b99a834294/compaction_picker_test.go (about)

     1  // Copyright 2018 The LevelDB-Go and Pebble and Bitalostored 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 bitalostable
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"math"
    11  	"sort"
    12  	"strconv"
    13  	"strings"
    14  	"testing"
    15  
    16  	"github.com/cockroachdb/errors"
    17  	"github.com/stretchr/testify/require"
    18  	"github.com/zuoyebang/bitalostable/internal/base"
    19  	"github.com/zuoyebang/bitalostable/internal/datadriven"
    20  	"github.com/zuoyebang/bitalostable/internal/manifest"
    21  )
    22  
    23  func loadVersion(d *datadriven.TestData) (*version, *Options, [numLevels]int64, string) {
    24  	var sizes [numLevels]int64
    25  	opts := &Options{}
    26  	opts.EnsureDefaults()
    27  
    28  	if len(d.CmdArgs) != 1 {
    29  		return nil, nil, sizes, fmt.Sprintf("%s expects 1 argument", d.Cmd)
    30  	}
    31  	var err error
    32  	opts.LBaseMaxBytes, err = strconv.ParseInt(d.CmdArgs[0].Key, 10, 64)
    33  	if err != nil {
    34  		return nil, nil, sizes, err.Error()
    35  	}
    36  
    37  	var files [numLevels][]*fileMetadata
    38  	if len(d.Input) > 0 {
    39  		for _, data := range strings.Split(d.Input, "\n") {
    40  			parts := strings.Split(data, " ")
    41  			parts[0] = strings.TrimSuffix(strings.TrimSpace(parts[0]), ":")
    42  			if len(parts) < 2 {
    43  				return nil, nil, sizes, fmt.Sprintf("malformed test:\n%s", d.Input)
    44  			}
    45  			level, err := strconv.Atoi(parts[0])
    46  			if err != nil {
    47  				return nil, nil, sizes, err.Error()
    48  			}
    49  			if files[level] != nil {
    50  				return nil, nil, sizes, fmt.Sprintf("level %d already filled", level)
    51  			}
    52  			size, err := strconv.ParseUint(strings.TrimSpace(parts[1]), 10, 64)
    53  			if err != nil {
    54  				return nil, nil, sizes, err.Error()
    55  			}
    56  			for i := uint64(1); sizes[level] < int64(size); i++ {
    57  				var key InternalKey
    58  				if level == 0 {
    59  					// For L0, make `size` overlapping files.
    60  					key = base.MakeInternalKey([]byte(fmt.Sprintf("%04d", 1)), i, InternalKeyKindSet)
    61  				} else {
    62  					key = base.MakeInternalKey([]byte(fmt.Sprintf("%04d", i)), i, InternalKeyKindSet)
    63  				}
    64  				m := (&fileMetadata{
    65  					SmallestSeqNum: key.SeqNum(),
    66  					LargestSeqNum:  key.SeqNum(),
    67  					Size:           1,
    68  				}).ExtendPointKeyBounds(opts.Comparer.Compare, key, key)
    69  				if size >= 100 {
    70  					// If the requested size of the level is very large only add a single
    71  					// file in order to avoid massive blow-up in the number of files in
    72  					// the Version.
    73  					//
    74  					// TODO(peter): There is tension between the testing in
    75  					// TestCompactionPickerLevelMaxBytes and
    76  					// TestCompactionPickerTargetLevel. Clean this up somehow.
    77  					m.Size = size
    78  				}
    79  				files[level] = append(files[level], m)
    80  				sizes[level] += int64(m.Size)
    81  			}
    82  		}
    83  	}
    84  
    85  	vers := newVersion(opts, files)
    86  	return vers, opts, sizes, ""
    87  }
    88  
    89  func diskAvailBytesInf() uint64 {
    90  	return math.MaxUint64
    91  }
    92  
    93  func TestCompactionPickerByScoreLevelMaxBytes(t *testing.T) {
    94  	datadriven.RunTest(t, "testdata/compaction_picker_level_max_bytes",
    95  		func(d *datadriven.TestData) string {
    96  			switch d.Cmd {
    97  			case "init":
    98  				vers, opts, sizes, errMsg := loadVersion(d)
    99  				if errMsg != "" {
   100  					return errMsg
   101  				}
   102  
   103  				p, ok := newCompactionPicker(vers, opts, nil, sizes, diskAvailBytesInf).(*compactionPickerByScore)
   104  				require.True(t, ok)
   105  				var buf bytes.Buffer
   106  				for level := p.getBaseLevel(); level < numLevels; level++ {
   107  					fmt.Fprintf(&buf, "%d: %d\n", level, p.levelMaxBytes[level])
   108  				}
   109  				return buf.String()
   110  
   111  			default:
   112  				return fmt.Sprintf("unknown command: %s", d.Cmd)
   113  			}
   114  		})
   115  }
   116  
   117  func TestCompactionPickerTargetLevel(t *testing.T) {
   118  	var vers *version
   119  	var opts *Options
   120  	var sizes [numLevels]int64
   121  	var pickerByScore *compactionPickerByScore
   122  
   123  	parseInProgress := func(vals []string) ([]compactionInfo, error) {
   124  		var levels []int
   125  		for _, s := range vals {
   126  			l, err := strconv.ParseInt(s, 10, 8)
   127  			if err != nil {
   128  				return nil, err
   129  			}
   130  			levels = append(levels, int(l))
   131  		}
   132  		if len(levels)%2 != 0 {
   133  			return nil, errors.New("odd number of levels with ongoing compactions")
   134  		}
   135  		var inProgress []compactionInfo
   136  		for i := 0; i < len(levels); i += 2 {
   137  			inProgress = append(inProgress, compactionInfo{
   138  				inputs: []compactionLevel{
   139  					{level: levels[i]},
   140  					{level: levels[i+1]},
   141  				},
   142  				outputLevel: levels[i+1],
   143  			})
   144  		}
   145  		return inProgress, nil
   146  	}
   147  
   148  	resetCompacting := func() {
   149  		for _, files := range vers.Levels {
   150  			files.Slice().Each(func(f *fileMetadata) {
   151  				f.CompactionState = manifest.CompactionStateNotCompacting
   152  			})
   153  		}
   154  	}
   155  
   156  	datadriven.RunTest(t, "testdata/compaction_picker_target_level",
   157  		func(d *datadriven.TestData) string {
   158  			switch d.Cmd {
   159  			case "init":
   160  				var errMsg string
   161  				vers, opts, sizes, errMsg = loadVersion(d)
   162  				if errMsg != "" {
   163  					return errMsg
   164  				}
   165  				return ""
   166  			case "init_cp":
   167  				resetCompacting()
   168  
   169  				var inProgress []compactionInfo
   170  				if len(d.CmdArgs) == 1 {
   171  					arg := d.CmdArgs[0]
   172  					if arg.Key != "ongoing" {
   173  						return "unknown arg: " + arg.Key
   174  					}
   175  					var err error
   176  					inProgress, err = parseInProgress(arg.Vals)
   177  					if err != nil {
   178  						return err.Error()
   179  					}
   180  				}
   181  
   182  				p := newCompactionPicker(vers, opts, inProgress, sizes, diskAvailBytesInf)
   183  				var ok bool
   184  				pickerByScore, ok = p.(*compactionPickerByScore)
   185  				require.True(t, ok)
   186  				return fmt.Sprintf("base: %d", pickerByScore.baseLevel)
   187  			case "queue":
   188  				var b strings.Builder
   189  				var inProgress []compactionInfo
   190  				for {
   191  					env := compactionEnv{
   192  						earliestUnflushedSeqNum: InternalKeySeqNumMax,
   193  						inProgressCompactions:   inProgress,
   194  					}
   195  					pc := pickerByScore.pickAuto(env)
   196  					if pc == nil {
   197  						break
   198  					}
   199  					fmt.Fprintf(&b, "L%d->L%d: %.1f\n", pc.startLevel.level, pc.outputLevel.level, pc.score)
   200  					inProgress = append(inProgress, compactionInfo{
   201  						inputs:      pc.inputs,
   202  						outputLevel: pc.outputLevel.level,
   203  					})
   204  					if pc.outputLevel.level == 0 {
   205  						// Once we pick one L0->L0 compaction, we'll keep on doing so
   206  						// because the test isn't marking files as Compacting.
   207  						break
   208  					}
   209  					for _, cl := range pc.inputs {
   210  						cl.files.Each(func(f *fileMetadata) {
   211  							f.CompactionState = manifest.CompactionStateCompacting
   212  						})
   213  					}
   214  				}
   215  
   216  				resetCompacting()
   217  				return b.String()
   218  			case "pick":
   219  				resetCompacting()
   220  
   221  				var inProgress []compactionInfo
   222  				if len(d.CmdArgs) == 1 {
   223  					arg := d.CmdArgs[0]
   224  					if arg.Key != "ongoing" {
   225  						return "unknown arg: " + arg.Key
   226  					}
   227  					var err error
   228  					inProgress, err = parseInProgress(arg.Vals)
   229  					if err != nil {
   230  						return err.Error()
   231  					}
   232  				}
   233  
   234  				// Mark files as compacting for each in-progress compaction.
   235  				for i := range inProgress {
   236  					c := &inProgress[i]
   237  					iter := vers.Levels[c.inputs[0].level].Iter()
   238  					for f := iter.First(); f != nil; f = iter.Next() {
   239  						if !f.IsCompacting() {
   240  							f.CompactionState = manifest.CompactionStateCompacting
   241  							c.inputs[0].files = iter.Take().Slice()
   242  							break
   243  						}
   244  					}
   245  
   246  					switch {
   247  					case c.inputs[0].level == 0 && c.outputLevel != 0:
   248  						// L0->Lbase: mark all of Lbase as compacting.
   249  						c.inputs[1].files = vers.Levels[c.outputLevel].Slice()
   250  						for _, in := range c.inputs {
   251  							in.files.Each(func(f *fileMetadata) {
   252  								f.CompactionState = manifest.CompactionStateCompacting
   253  							})
   254  						}
   255  					case c.inputs[0].level != c.outputLevel:
   256  						// Ln->Ln+1: mark 1 file in Ln+1 as compacting.
   257  						iter := vers.Levels[c.outputLevel].Iter()
   258  						for f := iter.First(); f != nil; f = iter.Next() {
   259  							if !f.IsCompacting() {
   260  								f.CompactionState = manifest.CompactionStateCompacting
   261  								c.inputs[1].files = iter.Take().Slice()
   262  								break
   263  							}
   264  						}
   265  					}
   266  				}
   267  
   268  				pc := pickerByScore.pickAuto(compactionEnv{
   269  					earliestUnflushedSeqNum: InternalKeySeqNumMax,
   270  					inProgressCompactions:   inProgress,
   271  				})
   272  				if pc == nil {
   273  					return "no compaction"
   274  				}
   275  				return fmt.Sprintf("L%d->L%d: %0.1f", pc.startLevel.level, pc.outputLevel.level, pc.score)
   276  			case "pick_manual":
   277  				startLevel := 0
   278  				start := ""
   279  				end := ""
   280  
   281  				if len(d.CmdArgs) > 0 {
   282  					for _, arg := range d.CmdArgs {
   283  						switch arg.Key {
   284  						case "level":
   285  							startLevel64, err := strconv.ParseInt(arg.Vals[0], 10, 64)
   286  							if err != nil {
   287  								return err.Error()
   288  							}
   289  							startLevel = int(startLevel64)
   290  						case "start":
   291  							start = arg.Vals[0]
   292  						case "end":
   293  							end = arg.Vals[0]
   294  						default:
   295  							return "unknown arg: " + arg.Key
   296  						}
   297  					}
   298  				}
   299  
   300  				iStart := base.MakeInternalKey([]byte(start), InternalKeySeqNumMax, InternalKeyKindMax)
   301  				iEnd := base.MakeInternalKey([]byte(end), 0, 0)
   302  				manual := &manualCompaction{
   303  					done:  make(chan error, 1),
   304  					level: startLevel,
   305  					start: iStart.UserKey,
   306  					end:   iEnd.UserKey,
   307  				}
   308  
   309  				pc, retryLater := pickerByScore.pickManual(compactionEnv{
   310  					earliestUnflushedSeqNum: InternalKeySeqNumMax,
   311  				}, manual)
   312  				if pc == nil {
   313  					return fmt.Sprintf("nil, retryLater = %v", retryLater)
   314  				}
   315  
   316  				return fmt.Sprintf("L%d->L%d, retryLater = %v", pc.startLevel.level, pc.outputLevel.level, retryLater)
   317  			default:
   318  				return fmt.Sprintf("unknown command: %s", d.Cmd)
   319  			}
   320  		})
   321  }
   322  
   323  func TestCompactionPickerEstimatedCompactionDebt(t *testing.T) {
   324  	datadriven.RunTest(t, "testdata/compaction_picker_estimated_debt",
   325  		func(d *datadriven.TestData) string {
   326  			switch d.Cmd {
   327  			case "init":
   328  				vers, opts, sizes, errMsg := loadVersion(d)
   329  				if errMsg != "" {
   330  					return errMsg
   331  				}
   332  				opts.MemTableSize = 1000
   333  
   334  				p := newCompactionPicker(vers, opts, nil, sizes, diskAvailBytesInf)
   335  				return fmt.Sprintf("%d\n", p.estimatedCompactionDebt(0))
   336  
   337  			default:
   338  				return fmt.Sprintf("unknown command: %s", d.Cmd)
   339  			}
   340  		})
   341  }
   342  
   343  func TestCompactionPickerL0(t *testing.T) {
   344  	opts := (*Options)(nil).EnsureDefaults()
   345  	opts.Experimental.L0CompactionConcurrency = 1
   346  
   347  	parseMeta := func(s string) (*fileMetadata, error) {
   348  		parts := strings.Split(s, ":")
   349  		fileNum, err := strconv.Atoi(parts[0])
   350  		if err != nil {
   351  			return nil, err
   352  		}
   353  		fields := strings.Fields(parts[1])
   354  		parts = strings.Split(fields[0], "-")
   355  		if len(parts) != 2 {
   356  			return nil, errors.Errorf("malformed table spec: %s", s)
   357  		}
   358  		m := (&fileMetadata{
   359  			FileNum: base.FileNum(fileNum),
   360  		}).ExtendPointKeyBounds(
   361  			opts.Comparer.Compare,
   362  			base.ParseInternalKey(strings.TrimSpace(parts[0])),
   363  			base.ParseInternalKey(strings.TrimSpace(parts[1])),
   364  		)
   365  		m.SmallestSeqNum = m.Smallest.SeqNum()
   366  		m.LargestSeqNum = m.Largest.SeqNum()
   367  		return m, nil
   368  	}
   369  
   370  	var picker *compactionPickerByScore
   371  	var inProgressCompactions []compactionInfo
   372  	var pc *pickedCompaction
   373  
   374  	datadriven.RunTest(t, "testdata/compaction_picker_L0", func(td *datadriven.TestData) string {
   375  		switch td.Cmd {
   376  		case "define":
   377  			fileMetas := [manifest.NumLevels][]*fileMetadata{}
   378  			baseLevel := manifest.NumLevels - 1
   379  			level := 0
   380  			var err error
   381  			lines := strings.Split(td.Input, "\n")
   382  			var compactionLines []string
   383  
   384  			for len(lines) > 0 {
   385  				data := strings.TrimSpace(lines[0])
   386  				lines = lines[1:]
   387  				switch data {
   388  				case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
   389  					level, err = strconv.Atoi(data[1:])
   390  					if err != nil {
   391  						return err.Error()
   392  					}
   393  				case "compactions":
   394  					compactionLines, lines = lines, nil
   395  				default:
   396  					meta, err := parseMeta(data)
   397  					if err != nil {
   398  						return err.Error()
   399  					}
   400  					if level != 0 && level < baseLevel {
   401  						baseLevel = level
   402  					}
   403  					fileMetas[level] = append(fileMetas[level], meta)
   404  				}
   405  			}
   406  
   407  			// Parse in-progress compactions in the form of:
   408  			//   L0 000001 -> L2 000005
   409  			inProgressCompactions = nil
   410  			for len(compactionLines) > 0 {
   411  				parts := strings.Fields(compactionLines[0])
   412  				compactionLines = compactionLines[1:]
   413  
   414  				var level int
   415  				var info compactionInfo
   416  				first := true
   417  				compactionFiles := map[int][]*fileMetadata{}
   418  				for _, p := range parts {
   419  					switch p {
   420  					case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
   421  						var err error
   422  						level, err = strconv.Atoi(p[1:])
   423  						if err != nil {
   424  							return err.Error()
   425  						}
   426  						if len(info.inputs) > 0 && info.inputs[len(info.inputs)-1].level == level {
   427  							// eg, L0 -> L0 compaction or L6 -> L6 compaction
   428  							continue
   429  						}
   430  						if info.outputLevel < level {
   431  							info.outputLevel = level
   432  						}
   433  						info.inputs = append(info.inputs, compactionLevel{level: level})
   434  					case "->":
   435  						continue
   436  					default:
   437  						fileNum, err := strconv.Atoi(p)
   438  						if err != nil {
   439  							return err.Error()
   440  						}
   441  						var compactFile *fileMetadata
   442  						for _, m := range fileMetas[level] {
   443  							if m.FileNum == FileNum(fileNum) {
   444  								compactFile = m
   445  							}
   446  						}
   447  						if compactFile == nil {
   448  							return fmt.Sprintf("cannot find compaction file %s", FileNum(fileNum))
   449  						}
   450  						compactFile.CompactionState = manifest.CompactionStateCompacting
   451  						if first || base.InternalCompare(DefaultComparer.Compare, info.largest, compactFile.Largest) < 0 {
   452  							info.largest = compactFile.Largest
   453  						}
   454  						if first || base.InternalCompare(DefaultComparer.Compare, info.smallest, compactFile.Smallest) > 0 {
   455  							info.smallest = compactFile.Smallest
   456  						}
   457  						first = false
   458  						compactionFiles[level] = append(compactionFiles[level], compactFile)
   459  					}
   460  				}
   461  				for i, cl := range info.inputs {
   462  					files := compactionFiles[cl.level]
   463  					info.inputs[i].files = manifest.NewLevelSliceSeqSorted(files)
   464  					// Mark as intra-L0 compacting if the compaction is
   465  					// L0 -> L0.
   466  					if info.outputLevel == 0 {
   467  						for _, f := range files {
   468  							f.IsIntraL0Compacting = true
   469  						}
   470  					}
   471  				}
   472  				inProgressCompactions = append(inProgressCompactions, info)
   473  			}
   474  
   475  			version := newVersion(opts, fileMetas)
   476  			version.L0Sublevels.InitCompactingFileInfo(inProgressL0Compactions(inProgressCompactions))
   477  			vs := &versionSet{
   478  				opts:    opts,
   479  				cmp:     DefaultComparer.Compare,
   480  				cmpName: DefaultComparer.Name,
   481  			}
   482  			vs.versions.Init(nil)
   483  			vs.append(version)
   484  			picker = &compactionPickerByScore{
   485  				opts:           opts,
   486  				vers:           version,
   487  				baseLevel:      baseLevel,
   488  				diskAvailBytes: diskAvailBytesInf,
   489  			}
   490  			vs.picker = picker
   491  			for l := 0; l < len(picker.levelSizes); l++ {
   492  				version.Levels[l].Slice().Each(func(m *fileMetadata) {
   493  					picker.levelSizes[l] += int64(m.Size)
   494  				})
   495  			}
   496  			picker.initLevelMaxBytes(inProgressCompactions)
   497  
   498  			var buf bytes.Buffer
   499  			fmt.Fprint(&buf, version.String())
   500  			if len(inProgressCompactions) > 0 {
   501  				fmt.Fprintln(&buf, "compactions")
   502  				for _, c := range inProgressCompactions {
   503  					fmt.Fprintf(&buf, "  %s\n", c.String())
   504  				}
   505  			}
   506  			return buf.String()
   507  		case "pick-auto":
   508  			for _, arg := range td.CmdArgs {
   509  				var err error
   510  				switch arg.Key {
   511  				case "l0_compaction_threshold":
   512  					opts.L0CompactionThreshold, err = strconv.Atoi(arg.Vals[0])
   513  					if err != nil {
   514  						return err.Error()
   515  					}
   516  				case "l0_compaction_file_threshold":
   517  					opts.L0CompactionFileThreshold, err = strconv.Atoi(arg.Vals[0])
   518  					if err != nil {
   519  						return err.Error()
   520  					}
   521  				}
   522  			}
   523  
   524  			pc = picker.pickAuto(compactionEnv{
   525  				earliestUnflushedSeqNum: math.MaxUint64,
   526  				inProgressCompactions:   inProgressCompactions,
   527  			})
   528  			var result strings.Builder
   529  			if pc != nil {
   530  				c := newCompaction(pc, opts)
   531  				fmt.Fprintf(&result, "L%d -> L%d\n", pc.startLevel.level, pc.outputLevel.level)
   532  				fmt.Fprintf(&result, "L%d: %s\n", pc.startLevel.level, fileNums(pc.startLevel.files))
   533  				if !pc.outputLevel.files.Empty() {
   534  					fmt.Fprintf(&result, "L%d: %s\n", pc.outputLevel.level, fileNums(pc.outputLevel.files))
   535  				}
   536  				if !c.grandparents.Empty() {
   537  					fmt.Fprintf(&result, "grandparents: %s\n", fileNums(c.grandparents))
   538  				}
   539  			} else {
   540  				return "nil"
   541  			}
   542  			return result.String()
   543  		case "mark-for-compaction":
   544  			var fileNum uint64
   545  			td.ScanArgs(t, "file", &fileNum)
   546  			for l, lm := range picker.vers.Levels {
   547  				iter := lm.Iter()
   548  				for f := iter.First(); f != nil; f = iter.Next() {
   549  					if f.FileNum != base.FileNum(fileNum) {
   550  						continue
   551  					}
   552  					f.MarkedForCompaction = true
   553  					picker.vers.Stats.MarkedForCompaction++
   554  					picker.vers.Levels[l].InvalidateAnnotation(markedForCompactionAnnotator{})
   555  					return fmt.Sprintf("marked L%d.%s", l, f.FileNum)
   556  				}
   557  			}
   558  			return "not-found"
   559  		case "max-output-file-size":
   560  			if pc == nil {
   561  				return "no compaction"
   562  			}
   563  			return fmt.Sprintf("%d", pc.maxOutputFileSize)
   564  		case "max-overlap-bytes":
   565  			if pc == nil {
   566  				return "no compaction"
   567  			}
   568  			return fmt.Sprintf("%d", pc.maxOverlapBytes)
   569  		}
   570  		return fmt.Sprintf("unrecognized command: %s", td.Cmd)
   571  	})
   572  }
   573  
   574  func TestCompactionPickerConcurrency(t *testing.T) {
   575  	opts := (*Options)(nil).EnsureDefaults()
   576  	opts.Experimental.L0CompactionConcurrency = 1
   577  
   578  	parseMeta := func(s string) (*fileMetadata, error) {
   579  		parts := strings.Split(s, ":")
   580  		fileNum, err := strconv.Atoi(parts[0])
   581  		if err != nil {
   582  			return nil, err
   583  		}
   584  		fields := strings.Fields(parts[1])
   585  		parts = strings.Split(fields[0], "-")
   586  		if len(parts) != 2 {
   587  			return nil, errors.Errorf("malformed table spec: %s", s)
   588  		}
   589  		m := (&fileMetadata{
   590  			FileNum: base.FileNum(fileNum),
   591  			Size:    1028,
   592  		}).ExtendPointKeyBounds(
   593  			opts.Comparer.Compare,
   594  			base.ParseInternalKey(strings.TrimSpace(parts[0])),
   595  			base.ParseInternalKey(strings.TrimSpace(parts[1])),
   596  		)
   597  		for _, p := range fields[1:] {
   598  			if strings.HasPrefix(p, "size=") {
   599  				v, err := strconv.Atoi(strings.TrimPrefix(p, "size="))
   600  				if err != nil {
   601  					return nil, err
   602  				}
   603  				m.Size = uint64(v)
   604  			}
   605  		}
   606  		m.SmallestSeqNum = m.Smallest.SeqNum()
   607  		m.LargestSeqNum = m.Largest.SeqNum()
   608  		return m, nil
   609  	}
   610  
   611  	var picker *compactionPickerByScore
   612  	var inProgressCompactions []compactionInfo
   613  
   614  	datadriven.RunTest(t, "testdata/compaction_picker_concurrency", func(td *datadriven.TestData) string {
   615  		switch td.Cmd {
   616  		case "define":
   617  			fileMetas := [manifest.NumLevels][]*fileMetadata{}
   618  			level := 0
   619  			var err error
   620  			lines := strings.Split(td.Input, "\n")
   621  			var compactionLines []string
   622  
   623  			for len(lines) > 0 {
   624  				data := strings.TrimSpace(lines[0])
   625  				lines = lines[1:]
   626  				switch data {
   627  				case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
   628  					level, err = strconv.Atoi(data[1:])
   629  					if err != nil {
   630  						return err.Error()
   631  					}
   632  				case "compactions":
   633  					compactionLines, lines = lines, nil
   634  				default:
   635  					meta, err := parseMeta(data)
   636  					if err != nil {
   637  						return err.Error()
   638  					}
   639  					fileMetas[level] = append(fileMetas[level], meta)
   640  				}
   641  			}
   642  
   643  			// Parse in-progress compactions in the form of:
   644  			//   L0 000001 -> L2 000005
   645  			inProgressCompactions = nil
   646  			for len(compactionLines) > 0 {
   647  				parts := strings.Fields(compactionLines[0])
   648  				compactionLines = compactionLines[1:]
   649  
   650  				var level int
   651  				var info compactionInfo
   652  				first := true
   653  				compactionFiles := map[int][]*fileMetadata{}
   654  				for _, p := range parts {
   655  					switch p {
   656  					case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
   657  						var err error
   658  						level, err = strconv.Atoi(p[1:])
   659  						if err != nil {
   660  							return err.Error()
   661  						}
   662  						if len(info.inputs) > 0 && info.inputs[len(info.inputs)-1].level == level {
   663  							// eg, L0 -> L0 compaction or L6 -> L6 compaction
   664  							continue
   665  						}
   666  						if info.outputLevel < level {
   667  							info.outputLevel = level
   668  						}
   669  						info.inputs = append(info.inputs, compactionLevel{level: level})
   670  					case "->":
   671  						continue
   672  					default:
   673  						fileNum, err := strconv.Atoi(p)
   674  						if err != nil {
   675  							return err.Error()
   676  						}
   677  						var compactFile *fileMetadata
   678  						for _, m := range fileMetas[level] {
   679  							if m.FileNum == FileNum(fileNum) {
   680  								compactFile = m
   681  							}
   682  						}
   683  						if compactFile == nil {
   684  							return fmt.Sprintf("cannot find compaction file %s", FileNum(fileNum))
   685  						}
   686  						compactFile.CompactionState = manifest.CompactionStateCompacting
   687  						if first || base.InternalCompare(DefaultComparer.Compare, info.largest, compactFile.Largest) < 0 {
   688  							info.largest = compactFile.Largest
   689  						}
   690  						if first || base.InternalCompare(DefaultComparer.Compare, info.smallest, compactFile.Smallest) > 0 {
   691  							info.smallest = compactFile.Smallest
   692  						}
   693  						first = false
   694  						compactionFiles[level] = append(compactionFiles[level], compactFile)
   695  					}
   696  				}
   697  				for i, cl := range info.inputs {
   698  					files := compactionFiles[cl.level]
   699  					if cl.level == 0 {
   700  						info.inputs[i].files = manifest.NewLevelSliceSeqSorted(files)
   701  					} else {
   702  						info.inputs[i].files = manifest.NewLevelSliceKeySorted(DefaultComparer.Compare, files)
   703  					}
   704  					// Mark as intra-L0 compacting if the compaction is
   705  					// L0 -> L0.
   706  					if info.outputLevel == 0 {
   707  						for _, f := range files {
   708  							f.IsIntraL0Compacting = true
   709  						}
   710  					}
   711  				}
   712  				inProgressCompactions = append(inProgressCompactions, info)
   713  			}
   714  
   715  			version := newVersion(opts, fileMetas)
   716  			version.L0Sublevels.InitCompactingFileInfo(inProgressL0Compactions(inProgressCompactions))
   717  			vs := &versionSet{
   718  				opts:    opts,
   719  				cmp:     DefaultComparer.Compare,
   720  				cmpName: DefaultComparer.Name,
   721  			}
   722  			vs.versions.Init(nil)
   723  			vs.append(version)
   724  			var sizes [numLevels]int64
   725  			for l := 0; l < len(sizes); l++ {
   726  				version.Levels[l].Slice().Each(func(m *fileMetadata) {
   727  					sizes[l] += int64(m.Size)
   728  				})
   729  			}
   730  			picker = newCompactionPicker(version, opts, inProgressCompactions, sizes, diskAvailBytesInf).(*compactionPickerByScore)
   731  			vs.picker = picker
   732  
   733  			var buf bytes.Buffer
   734  			fmt.Fprint(&buf, version.String())
   735  			if len(inProgressCompactions) > 0 {
   736  				fmt.Fprintln(&buf, "compactions")
   737  				for _, c := range inProgressCompactions {
   738  					fmt.Fprintf(&buf, "  %s\n", c.String())
   739  				}
   740  			}
   741  			return buf.String()
   742  
   743  		case "pick-auto":
   744  			for _, arg := range td.CmdArgs {
   745  				var err error
   746  				switch arg.Key {
   747  				case "l0_compaction_threshold":
   748  					opts.L0CompactionThreshold, err = strconv.Atoi(arg.Vals[0])
   749  					if err != nil {
   750  						return err.Error()
   751  					}
   752  				case "l0_compaction_concurrency":
   753  					opts.Experimental.L0CompactionConcurrency, err = strconv.Atoi(arg.Vals[0])
   754  					if err != nil {
   755  						return err.Error()
   756  					}
   757  				case "compaction_debt_concurrency":
   758  					opts.Experimental.CompactionDebtConcurrency, err = strconv.Atoi(arg.Vals[0])
   759  					if err != nil {
   760  						return err.Error()
   761  					}
   762  				}
   763  			}
   764  
   765  			pc := picker.pickAuto(compactionEnv{
   766  				earliestUnflushedSeqNum: math.MaxUint64,
   767  				inProgressCompactions:   inProgressCompactions,
   768  			})
   769  			var result strings.Builder
   770  			if pc != nil {
   771  				c := newCompaction(pc, opts)
   772  				fmt.Fprintf(&result, "L%d -> L%d\n", pc.startLevel.level, pc.outputLevel.level)
   773  				fmt.Fprintf(&result, "L%d: %s\n", pc.startLevel.level, fileNums(pc.startLevel.files))
   774  				if !pc.outputLevel.files.Empty() {
   775  					fmt.Fprintf(&result, "L%d: %s\n", pc.outputLevel.level, fileNums(pc.outputLevel.files))
   776  				}
   777  				if !c.grandparents.Empty() {
   778  					fmt.Fprintf(&result, "grandparents: %s\n", fileNums(c.grandparents))
   779  				}
   780  			} else {
   781  				return "nil"
   782  			}
   783  			return result.String()
   784  		}
   785  		return fmt.Sprintf("unrecognized command: %s", td.Cmd)
   786  	})
   787  }
   788  
   789  func TestCompactionPickerPickReadTriggered(t *testing.T) {
   790  	opts := (*Options)(nil).EnsureDefaults()
   791  	var picker *compactionPickerByScore
   792  	var rcList readCompactionQueue
   793  	var vers *version
   794  
   795  	parseMeta := func(s string) (*fileMetadata, error) {
   796  		parts := strings.Split(s, ":")
   797  		fileNum, err := strconv.Atoi(parts[0])
   798  		if err != nil {
   799  			return nil, err
   800  		}
   801  		fields := strings.Fields(parts[1])
   802  		parts = strings.Split(fields[0], "-")
   803  		if len(parts) != 2 {
   804  			return nil, errors.Errorf("malformed table spec: %s. usage: <file-num>:start.SET.1-end.SET.2", s)
   805  		}
   806  		m := (&fileMetadata{
   807  			FileNum: base.FileNum(fileNum),
   808  			Size:    1028,
   809  		}).ExtendPointKeyBounds(
   810  			opts.Comparer.Compare,
   811  			base.ParseInternalKey(strings.TrimSpace(parts[0])),
   812  			base.ParseInternalKey(strings.TrimSpace(parts[1])),
   813  		)
   814  		for _, p := range fields[1:] {
   815  			if strings.HasPrefix(p, "size=") {
   816  				v, err := strconv.Atoi(strings.TrimPrefix(p, "size="))
   817  				if err != nil {
   818  					return nil, err
   819  				}
   820  				m.Size = uint64(v)
   821  			}
   822  		}
   823  		m.SmallestSeqNum = m.Smallest.SeqNum()
   824  		m.LargestSeqNum = m.Largest.SeqNum()
   825  		return m, nil
   826  	}
   827  
   828  	datadriven.RunTest(t, "testdata/compaction_picker_read_triggered", func(td *datadriven.TestData) string {
   829  		switch td.Cmd {
   830  		case "define":
   831  			rcList = readCompactionQueue{}
   832  			fileMetas := [manifest.NumLevels][]*fileMetadata{}
   833  			level := 0
   834  			var err error
   835  			lines := strings.Split(td.Input, "\n")
   836  
   837  			for len(lines) > 0 {
   838  				data := strings.TrimSpace(lines[0])
   839  				lines = lines[1:]
   840  				switch data {
   841  				case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
   842  					level, err = strconv.Atoi(data[1:])
   843  					if err != nil {
   844  						return err.Error()
   845  					}
   846  				default:
   847  					meta, err := parseMeta(data)
   848  					if err != nil {
   849  						return err.Error()
   850  					}
   851  					fileMetas[level] = append(fileMetas[level], meta)
   852  				}
   853  			}
   854  
   855  			vers = newVersion(opts, fileMetas)
   856  			vs := &versionSet{
   857  				opts:    opts,
   858  				cmp:     DefaultComparer.Compare,
   859  				cmpName: DefaultComparer.Name,
   860  			}
   861  			vs.versions.Init(nil)
   862  			vs.append(vers)
   863  			var sizes [numLevels]int64
   864  			for l := 0; l < len(sizes); l++ {
   865  				vers.Levels[l].Slice().Each(func(m *fileMetadata) {
   866  					sizes[l] += int64(m.Size)
   867  				})
   868  			}
   869  			var inProgressCompactions []compactionInfo
   870  			picker = newCompactionPicker(vers, opts, inProgressCompactions, sizes, diskAvailBytesInf).(*compactionPickerByScore)
   871  			vs.picker = picker
   872  
   873  			var buf bytes.Buffer
   874  			fmt.Fprint(&buf, vers.String())
   875  			return buf.String()
   876  
   877  		case "add-read-compaction":
   878  			for _, line := range strings.Split(td.Input, "\n") {
   879  				if line == "" {
   880  					continue
   881  				}
   882  				parts := strings.Split(line, " ")
   883  				if len(parts) != 3 {
   884  					return "error: malformed data for add-read-compaction. usage: <level>: <start>-<end> <filenum>"
   885  				}
   886  				if l, err := strconv.Atoi(parts[0][:1]); err == nil {
   887  					keys := strings.Split(parts[1], "-")
   888  					fileNum, _ := strconv.Atoi(parts[2])
   889  
   890  					rc := readCompaction{
   891  						level:   l,
   892  						start:   []byte(keys[0]),
   893  						end:     []byte(keys[1]),
   894  						fileNum: base.FileNum(fileNum),
   895  					}
   896  					rcList.add(&rc, DefaultComparer.Compare)
   897  				} else {
   898  					return err.Error()
   899  				}
   900  			}
   901  			return ""
   902  
   903  		case "show-read-compactions":
   904  			var sb strings.Builder
   905  			if rcList.size == 0 {
   906  				sb.WriteString("(none)")
   907  			}
   908  			for i := 0; i < rcList.size; i++ {
   909  				rc := rcList.at(i)
   910  				sb.WriteString(fmt.Sprintf("(level: %d, start: %s, end: %s)\n", rc.level, string(rc.start), string(rc.end)))
   911  			}
   912  			return sb.String()
   913  
   914  		case "pick-auto":
   915  			pc := picker.pickAuto(compactionEnv{
   916  				earliestUnflushedSeqNum: math.MaxUint64,
   917  				readCompactionEnv: readCompactionEnv{
   918  					readCompactions: &rcList,
   919  					flushing:        false,
   920  				},
   921  			})
   922  			var result strings.Builder
   923  			if pc != nil {
   924  				fmt.Fprintf(&result, "L%d -> L%d\n", pc.startLevel.level, pc.outputLevel.level)
   925  				fmt.Fprintf(&result, "L%d: %s\n", pc.startLevel.level, fileNums(pc.startLevel.files))
   926  				if !pc.outputLevel.files.Empty() {
   927  					fmt.Fprintf(&result, "L%d: %s\n", pc.outputLevel.level, fileNums(pc.outputLevel.files))
   928  				}
   929  			} else {
   930  				return "nil"
   931  			}
   932  			return result.String()
   933  		}
   934  		return fmt.Sprintf("unrecognized command: %s", td.Cmd)
   935  	})
   936  }
   937  
   938  func TestPickedCompactionSetupInputs(t *testing.T) {
   939  	opts := &Options{}
   940  	opts.EnsureDefaults()
   941  	parseMeta := func(s string) *fileMetadata {
   942  		parts := strings.Split(strings.TrimSpace(s), " ")
   943  		var fileSize uint64
   944  		var compacting bool
   945  		for _, part := range parts {
   946  			switch {
   947  			case part == "compacting":
   948  				compacting = true
   949  			case strings.HasPrefix(part, "size="):
   950  				v, err := strconv.ParseUint(strings.TrimPrefix(part, "size="), 10, 64)
   951  				require.NoError(t, err)
   952  				fileSize = v
   953  			}
   954  		}
   955  		tableParts := strings.Split(parts[0], "-")
   956  		if len(tableParts) != 2 {
   957  			t.Fatalf("malformed table spec: %s", s)
   958  		}
   959  		state := manifest.CompactionStateNotCompacting
   960  		if compacting {
   961  			state = manifest.CompactionStateCompacting
   962  		}
   963  		m := (&fileMetadata{
   964  			CompactionState: state,
   965  			Size:            fileSize,
   966  		}).ExtendPointKeyBounds(
   967  			opts.Comparer.Compare,
   968  			base.ParseInternalKey(strings.TrimSpace(tableParts[0])),
   969  			base.ParseInternalKey(strings.TrimSpace(tableParts[1])),
   970  		)
   971  		m.SmallestSeqNum = m.Smallest.SeqNum()
   972  		m.LargestSeqNum = m.Largest.SeqNum()
   973  		return m
   974  	}
   975  
   976  	datadriven.RunTest(t, "testdata/compaction_setup_inputs",
   977  		func(d *datadriven.TestData) string {
   978  			switch d.Cmd {
   979  			case "setup-inputs":
   980  				var availBytes uint64 = math.MaxUint64
   981  				args := d.CmdArgs
   982  
   983  				if len(args) > 0 && args[0].Key == "avail-bytes" {
   984  					require.Equal(t, 1, len(args[0].Vals))
   985  					var err error
   986  					availBytes, err = strconv.ParseUint(args[0].Vals[0], 10, 64)
   987  					require.NoError(t, err)
   988  					args = args[1:]
   989  				}
   990  
   991  				if len(args) != 2 {
   992  					return "setup-inputs [avail-bytes=XXX] <start> <end>"
   993  				}
   994  
   995  				pc := &pickedCompaction{
   996  					cmp:    DefaultComparer.Compare,
   997  					inputs: []compactionLevel{{level: -1}, {level: -1}},
   998  				}
   999  				pc.startLevel, pc.outputLevel = &pc.inputs[0], &pc.inputs[1]
  1000  				var currentLevel int
  1001  				var files [numLevels][]*fileMetadata
  1002  				fileNum := FileNum(1)
  1003  
  1004  				for _, data := range strings.Split(d.Input, "\n") {
  1005  					switch data {
  1006  					case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
  1007  						level, err := strconv.Atoi(data[1:])
  1008  						if err != nil {
  1009  							return err.Error()
  1010  						}
  1011  						if pc.startLevel.level == -1 {
  1012  							pc.startLevel.level = level
  1013  							currentLevel = level
  1014  						} else if pc.outputLevel.level == -1 {
  1015  							if pc.startLevel.level >= level {
  1016  								return fmt.Sprintf("startLevel=%d >= outputLevel=%d\n", pc.startLevel.level, level)
  1017  							}
  1018  							pc.outputLevel.level = level
  1019  							currentLevel = level
  1020  						} else {
  1021  							return "outputLevel already set\n"
  1022  						}
  1023  
  1024  					default:
  1025  						meta := parseMeta(data)
  1026  						meta.FileNum = fileNum
  1027  						fileNum++
  1028  						files[currentLevel] = append(files[currentLevel], meta)
  1029  					}
  1030  				}
  1031  
  1032  				if pc.outputLevel.level == -1 {
  1033  					pc.outputLevel.level = pc.startLevel.level + 1
  1034  				}
  1035  				pc.version = newVersion(opts, files)
  1036  				pc.startLevel.files = pc.version.Overlaps(pc.startLevel.level, pc.cmp,
  1037  					[]byte(args[0].String()), []byte(args[1].String()), false /* exclusiveEnd */)
  1038  
  1039  				var isCompacting bool
  1040  				if !pc.setupInputs(opts, availBytes, pc.startLevel) {
  1041  					isCompacting = true
  1042  				}
  1043  
  1044  				var buf bytes.Buffer
  1045  				for _, cl := range pc.inputs {
  1046  					if cl.files.Empty() {
  1047  						continue
  1048  					}
  1049  
  1050  					fmt.Fprintf(&buf, "L%d\n", cl.level)
  1051  					cl.files.Each(func(f *fileMetadata) {
  1052  						fmt.Fprintf(&buf, "  %s\n", f)
  1053  					})
  1054  				}
  1055  				if isCompacting {
  1056  					fmt.Fprintf(&buf, "is-compacting")
  1057  				}
  1058  				return buf.String()
  1059  
  1060  			default:
  1061  				return fmt.Sprintf("unknown command: %s", d.Cmd)
  1062  			}
  1063  		})
  1064  }
  1065  
  1066  func TestPickedCompactionExpandInputs(t *testing.T) {
  1067  	opts := &Options{}
  1068  	opts.EnsureDefaults()
  1069  	cmp := DefaultComparer.Compare
  1070  	var files []*fileMetadata
  1071  
  1072  	parseMeta := func(s string) *fileMetadata {
  1073  		parts := strings.Split(s, "-")
  1074  		if len(parts) != 2 {
  1075  			t.Fatalf("malformed table spec: %s", s)
  1076  		}
  1077  		m := (&fileMetadata{}).ExtendPointKeyBounds(
  1078  			opts.Comparer.Compare,
  1079  			base.ParseInternalKey(parts[0]),
  1080  			base.ParseInternalKey(parts[1]),
  1081  		)
  1082  		return m
  1083  	}
  1084  
  1085  	datadriven.RunTest(t, "testdata/compaction_expand_inputs",
  1086  		func(d *datadriven.TestData) string {
  1087  			switch d.Cmd {
  1088  			case "define":
  1089  				files = nil
  1090  				if len(d.Input) == 0 {
  1091  					return ""
  1092  				}
  1093  				for _, data := range strings.Split(d.Input, "\n") {
  1094  					meta := parseMeta(data)
  1095  					meta.FileNum = FileNum(len(files))
  1096  					files = append(files, meta)
  1097  				}
  1098  				manifest.SortBySmallest(files, cmp)
  1099  				return ""
  1100  
  1101  			case "expand-inputs":
  1102  				pc := &pickedCompaction{
  1103  					cmp:    cmp,
  1104  					inputs: []compactionLevel{{level: 1}},
  1105  				}
  1106  				pc.startLevel = &pc.inputs[0]
  1107  
  1108  				var filesLevelled [numLevels][]*fileMetadata
  1109  				filesLevelled[pc.startLevel.level] = files
  1110  				pc.version = newVersion(opts, filesLevelled)
  1111  
  1112  				if len(d.CmdArgs) != 1 {
  1113  					return fmt.Sprintf("%s expects 1 argument", d.Cmd)
  1114  				}
  1115  				index, err := strconv.ParseInt(d.CmdArgs[0].String(), 10, 64)
  1116  				if err != nil {
  1117  					return err.Error()
  1118  				}
  1119  
  1120  				// Advance the iterator to position `index`.
  1121  				iter := pc.version.Levels[pc.startLevel.level].Iter()
  1122  				_ = iter.First()
  1123  				for i := int64(0); i < index; i++ {
  1124  					_ = iter.Next()
  1125  				}
  1126  
  1127  				inputs, _ := expandToAtomicUnit(cmp, iter.Take().Slice(), true /* disableIsCompacting */)
  1128  
  1129  				var buf bytes.Buffer
  1130  				inputs.Each(func(f *fileMetadata) {
  1131  					fmt.Fprintf(&buf, "%d: %s-%s\n", f.FileNum, f.Smallest, f.Largest)
  1132  				})
  1133  				return buf.String()
  1134  
  1135  			default:
  1136  				return fmt.Sprintf("unknown command: %s", d.Cmd)
  1137  			}
  1138  		})
  1139  }
  1140  
  1141  func TestCompactionOutputFileSize(t *testing.T) {
  1142  	opts := (*Options)(nil).EnsureDefaults()
  1143  	var picker *compactionPickerByScore
  1144  	var vers *version
  1145  
  1146  	parseMeta := func(s string) (*fileMetadata, error) {
  1147  		parts := strings.Split(s, ":")
  1148  		fileNum, err := strconv.Atoi(parts[0])
  1149  		if err != nil {
  1150  			return nil, err
  1151  		}
  1152  		fields := strings.Fields(parts[1])
  1153  		parts = strings.Split(fields[0], "-")
  1154  		if len(parts) != 2 {
  1155  			return nil, errors.Errorf("malformed table spec: %s. usage: <file-num>:start.SET.1-end.SET.2", s)
  1156  		}
  1157  		m := (&fileMetadata{
  1158  			FileNum: base.FileNum(fileNum),
  1159  			Size:    1028,
  1160  		}).ExtendPointKeyBounds(
  1161  			opts.Comparer.Compare,
  1162  			base.ParseInternalKey(strings.TrimSpace(parts[0])),
  1163  			base.ParseInternalKey(strings.TrimSpace(parts[1])),
  1164  		)
  1165  		for _, p := range fields[1:] {
  1166  			if strings.HasPrefix(p, "size=") {
  1167  				v, err := strconv.Atoi(strings.TrimPrefix(p, "size="))
  1168  				if err != nil {
  1169  					return nil, err
  1170  				}
  1171  				m.Size = uint64(v)
  1172  			}
  1173  			if strings.HasPrefix(p, "range-deletions-bytes-estimate=") {
  1174  				v, err := strconv.Atoi(strings.TrimPrefix(p, "range-deletions-bytes-estimate="))
  1175  				if err != nil {
  1176  					return nil, err
  1177  				}
  1178  				m.Stats.RangeDeletionsBytesEstimate = uint64(v)
  1179  				m.Stats.NumDeletions = 1 // At least one range del responsible for the deletion bytes.
  1180  				m.StatsMarkValid()
  1181  			}
  1182  		}
  1183  		m.SmallestSeqNum = m.Smallest.SeqNum()
  1184  		m.LargestSeqNum = m.Largest.SeqNum()
  1185  		return m, nil
  1186  	}
  1187  
  1188  	datadriven.RunTest(t, "testdata/compaction_output_file_size", func(td *datadriven.TestData) string {
  1189  		switch td.Cmd {
  1190  		case "define":
  1191  			fileMetas := [manifest.NumLevels][]*fileMetadata{}
  1192  			level := 0
  1193  			var err error
  1194  			lines := strings.Split(td.Input, "\n")
  1195  
  1196  			for len(lines) > 0 {
  1197  				data := strings.TrimSpace(lines[0])
  1198  				lines = lines[1:]
  1199  				switch data {
  1200  				case "L0", "L1", "L2", "L3", "L4", "L5", "L6":
  1201  					level, err = strconv.Atoi(data[1:])
  1202  					if err != nil {
  1203  						return err.Error()
  1204  					}
  1205  				default:
  1206  					meta, err := parseMeta(data)
  1207  					if err != nil {
  1208  						return err.Error()
  1209  					}
  1210  					fileMetas[level] = append(fileMetas[level], meta)
  1211  				}
  1212  			}
  1213  
  1214  			vers = newVersion(opts, fileMetas)
  1215  			vs := &versionSet{
  1216  				opts:    opts,
  1217  				cmp:     DefaultComparer.Compare,
  1218  				cmpName: DefaultComparer.Name,
  1219  			}
  1220  			vs.versions.Init(nil)
  1221  			vs.append(vers)
  1222  			var sizes [numLevels]int64
  1223  			for l := 0; l < len(sizes); l++ {
  1224  				slice := vers.Levels[l].Slice()
  1225  				sizes[l] = int64(slice.SizeSum())
  1226  			}
  1227  			var inProgressCompactions []compactionInfo
  1228  			picker = newCompactionPicker(vers, opts, inProgressCompactions, sizes, diskAvailBytesInf).(*compactionPickerByScore)
  1229  			vs.picker = picker
  1230  
  1231  			var buf bytes.Buffer
  1232  			fmt.Fprint(&buf, vers.String())
  1233  			return buf.String()
  1234  
  1235  		case "pick-auto":
  1236  			pc := picker.pickAuto(compactionEnv{
  1237  				earliestUnflushedSeqNum: math.MaxUint64,
  1238  				earliestSnapshotSeqNum:  math.MaxUint64,
  1239  			})
  1240  			var buf bytes.Buffer
  1241  			if pc != nil {
  1242  				fmt.Fprintf(&buf, "L%d -> L%d\n", pc.startLevel.level, pc.outputLevel.level)
  1243  				fmt.Fprintf(&buf, "L%d: %s\n", pc.startLevel.level, fileNums(pc.startLevel.files))
  1244  				fmt.Fprintf(&buf, "maxOutputFileSize: %d\n", pc.maxOutputFileSize)
  1245  			} else {
  1246  				return "nil"
  1247  			}
  1248  			return buf.String()
  1249  
  1250  		default:
  1251  			return fmt.Sprintf("unrecognized command: %s", td.Cmd)
  1252  		}
  1253  	})
  1254  }
  1255  
  1256  func fileNums(files manifest.LevelSlice) string {
  1257  	var ss []string
  1258  	files.Each(func(f *fileMetadata) {
  1259  		ss = append(ss, f.FileNum.String())
  1260  	})
  1261  	sort.Strings(ss)
  1262  	return strings.Join(ss, ",")
  1263  }