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  }