github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/internal/randvar/flag.go (about) 1 // Copyright 2019 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 randvar 6 7 import ( 8 "encoding/binary" 9 "regexp" 10 "strconv" 11 "strings" 12 13 "github.com/cockroachdb/errors" 14 "golang.org/x/exp/rand" 15 ) 16 17 var randVarRE = regexp.MustCompile(`^(?:(latest|uniform|zipf):)?(\d+)(?:-(\d+))?$`) 18 19 // Flag provides a command line flag interface for specifying static random 20 // variables. 21 type Flag struct { 22 Static 23 spec string 24 } 25 26 // NewFlag creates a new Flag initialized with the specified spec. 27 func NewFlag(spec string) *Flag { 28 f := &Flag{} 29 if err := f.Set(spec); err != nil { 30 panic(err) 31 } 32 return f 33 } 34 35 func (f *Flag) String() string { 36 return f.spec 37 } 38 39 // Type implements the Flag.Value interface. 40 func (f *Flag) Type() string { 41 return "randvar" 42 } 43 44 // Set implements the Flag.Value interface. 45 func (f *Flag) Set(spec string) error { 46 m := randVarRE.FindStringSubmatch(spec) 47 if m == nil { 48 return errors.Errorf("invalid random var spec: %s", errors.Safe(spec)) 49 } 50 51 min, err := strconv.Atoi(m[2]) 52 if err != nil { 53 return err 54 } 55 max := min 56 if m[3] != "" { 57 max, err = strconv.Atoi(m[3]) 58 if err != nil { 59 return err 60 } 61 } 62 63 switch strings.ToLower(m[1]) { 64 case "", "uniform": 65 f.Static = NewUniform(uint64(min), uint64(max)) 66 case "latest": 67 f.Static, err = NewSkewedLatest(uint64(min), uint64(max), 0.99) 68 if err != nil { 69 return err 70 } 71 case "zipf": 72 var err error 73 f.Static, err = NewZipf(uint64(min), uint64(max), 0.99) 74 if err != nil { 75 return err 76 } 77 default: 78 return errors.Errorf("unknown random var distribution: %s", errors.Safe(m[1])) 79 } 80 f.spec = spec 81 return nil 82 } 83 84 // BytesFlag provides a command line flag interface for specifying random 85 // bytes. The specification provides for both the length of the random bytes 86 // and a target compression ratio. 87 type BytesFlag struct { 88 sizeFlag Flag 89 targetCompression float64 90 spec string 91 } 92 93 // NewBytesFlag creates a new BytesFlag initialized with the specified spec. 94 func NewBytesFlag(spec string) *BytesFlag { 95 f := &BytesFlag{} 96 if err := f.Set(spec); err != nil { 97 panic(err) 98 } 99 return f 100 } 101 102 func (f *BytesFlag) String() string { 103 return f.spec 104 } 105 106 // Type implements the Flag.Value interface. 107 func (f *BytesFlag) Type() string { 108 return "randbytes" 109 } 110 111 // Set implements the Flag.Value interface. 112 func (f *BytesFlag) Set(spec string) error { 113 parts := strings.Split(spec, "/") 114 if len(parts) == 0 || len(parts) > 2 { 115 return errors.Errorf("invalid randbytes spec: %s", errors.Safe(spec)) 116 } 117 if err := f.sizeFlag.Set(parts[0]); err != nil { 118 return err 119 } 120 f.targetCompression = 1.0 121 if len(parts) == 2 { 122 var err error 123 f.targetCompression, err = strconv.ParseFloat(parts[1], 64) 124 if err != nil { 125 return err 126 } 127 } 128 f.spec = spec 129 return nil 130 } 131 132 // Bytes returns random bytes. The length of the random bytes comes from the 133 // internal sizeFlag. 134 func (f *BytesFlag) Bytes(r *rand.Rand, buf []byte) []byte { 135 size := int(f.sizeFlag.Uint64(r)) 136 uniqueSize := int(float64(size) / f.targetCompression) 137 if uniqueSize < 1 { 138 uniqueSize = 1 139 } 140 if cap(buf) < size { 141 buf = make([]byte, size) 142 } 143 data := buf[:size] 144 offset := 0 145 for offset+8 <= uniqueSize { 146 binary.LittleEndian.PutUint64(data[offset:], r.Uint64()) 147 offset += 8 148 } 149 word := r.Uint64() 150 for offset < uniqueSize { 151 data[offset] = byte(word) 152 word >>= 8 153 offset++ 154 } 155 for offset < size { 156 data[offset] = data[offset-uniqueSize] 157 offset++ 158 } 159 return data 160 }