You are an expert in writing code in many different languages. Your goal is to perform code completion for the following code, keeping in mind the rest of the code and the file meta data.
    Metadata details:
    filename: catchup_scan_bench_test.go
    path of file: ./repos/cockroach/pkg/kv/kvserver/rangefeed
    
    the code of the file until where you have to start completion: // Copyright 2021 The Cockroach Authors.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

package rangefeed_test

import (
	"context"
	"fmt"
	"math/rand"
	"os"
	"path/filepath"
	"testing"

	"github.com/cockroachdb/cockroach/pkg/clusterversion"
	"github.com/cockroachdb/cockroach/pkg/kv/kvpb"
	"github.com/cockroachdb/cockroach/pkg/kv/kvserver/rangefeed"
	"github.com/cockroachdb/cockroach/pkg/roachpb"
	"github.com/cockroachdb/cockroach/pkg/settings/cluster"
	"github.com/cockroachdb/cockroach/pkg/storage"
	"github.com/cockroachdb/cockroach/pkg/storage/fs"
	"github.com/cockroachdb/cockroach/pkg/testutils"
	"github.com/cockroachdb/cockroach/pkg/testu
    
func BenchmarkCatchUpScan(b *testing.B) {
	defer log.Scope(b).Close(b)
	skip.UnderShort(b)

	numKeys := 1_000_000
	valueBytes := 64

	dataOpts := map[string]benchDataOptions{
		// linear-keys is one of our best-case scenarios. In
		// this case, each newly written row is at a key
		// following the previously written row and at a later
		// timestamp. Further, once compacted, all of the SSTs
		// should be in L5 and L6. As a result, the time-based
		// optimization can exclude SSTs fairly easily.
		"linear-keys": {
			numKeys:    numKeys,
			valueBytes: valueBytes,
			rwMode:     fs.ReadWrite,
		},
		// random-keys is our worst case. We write keys in
		// random order but with timestamps that keep marching
		// forward. Once compacted, most of the data is in L5
		// and L6. So, we have very few overlapping SSTs and
		// most SSTs in our lower level will have at least 1
		// key that needs to be included in our scan, despite
		// the time based optimization.
		"random-keys": {
			randomKeyOrder: true,
			numKeys:        numKeys,
			valueBytes:     valueBytes,
			rwMode:         fs.ReadWrite,
		},
		// mixed-case is a middling case.
		//
		// This case is trying to simulate a larger store, but
		// with fewer bytes. If we did not reduce
		// LBaseMaxBytes, almost all data would be in Lbase or
		// L6, and TBI would be ineffective. By reducing
		// LBaseMaxBytes, the data should spread out over more
		// levels, like in a real store. The LSM state
		// depicted below shows that this was only partially
		// successful.
		//
		// We return a read only engine to prevent read-based
		// compactions after the initial data generation.
		//
		// As of 2021-08-18 data generated using these
		// settings looked like:
		//
		//__level_____count____size___score______in__ingest(sz_cnt)____move(sz_cnt)___write(sz_cnt)____read___r-amp___w-amp
		//   WAL         1     0 B       -     0 B       -       -       -       -     0 B       -       -       -     0.0
		//     0         0     0 B    0.00     0 B     0 B       0     0 B       0     0 B       0     0 B       0     0.0
		//     1         2   4.4 M 1819285.94     0 B     0 B       0     0 B       0     0 B       0     0 B       0     0.0
		//     2         0     0 B    0.00     0 B     0 B       0     0 B       0     0 B       0     0 B       0     0.0
		//     3         0     0 B    0.00     0 B     0 B       0     0 B       0     0 B       0     0 B       0     0.0
		//     4         1   397 K    0.79     0 B     0 B       0     0 B       0     0 B       0     0 B       0     0.0
		//     5         0     0 B    0.00     0 B     0 B       0     0 B       0     0 B       0     0 B       0     0.0
		//     6         1    83 M       -     0 B     0 B       0     0 B       0     0 B       0     0 B       0     0.0
		// total         4    88 M       -     0 B     0 B       0     0 B       0     0 B       0     0 B       0     0.0
		"mixed-case": {
			randomKeyOrder: true,
			numKeys:        numKeys,
			valueBytes:     valueBytes,
			rwMode:         fs.ReadOnly,
			lBaseMaxBytes:  256,
		},
	}

	for name, do := range dataOpts {
		b.Run(name, func(b *testing.B) {
			for _, withDiff := range []bool{true, false} {
				b.Run(fmt.Sprintf("withDiff=%v", withDiff), func(b *testing.B) {
					for _, tsExcludePercent := range []float64{0.0, 0.50, 0.75, 0.95, 0.99} {
						wallTime := int64((5 * (float64(numKeys)*tsExcludePercent + 1)))
						ts := hlc.Timestamp{WallTime: wallTime}
						b.Run(fmt.Sprintf("perc=%2.2f", tsExcludePercent*100), func(b *testing.B) {
							for _, numRangeKeys := range []int{0, 1, 100} {
								b.Run(fmt.Sprintf("numRangeKeys=%d", numRangeKeys), func(b *testing.B) {
									do := do
									do.numRangeKeys = numRangeKeys
									n := runCatchUpBenchmark(b, setupMVCCPebble, benchOptions{
										dataOpts: do,
										ts:       ts,
										withDiff: withDiff,
									})
									// We shouldn't be seeing the range deletions returned in this
									// benchmark since they are at timestamp 1 and we catch up at
									// a timestamp >= 5 (which corresponds to tsExcludePercent ==
									// 0). Note that the oldest key is always excluded, since the
									// floor for wallTime is 5 and that's the oldest key's
									// timestamp but the start timestamp is exclusive.
									require.EqualValues(b, int64(numKeys)-wallTime/5, n)
								})
							}
						})
					}
				})
			}
		})
	}
}