LCOV - code coverage report
Current view: top level - pebble/vfs/errorfs - latency.go (source / functions) Hit Total Coverage
Test: 2024-06-02 08:15Z 907d8652 - meta test only.lcov Lines: 42 66 63.6 %
Date: 2024-06-02 08:16:16 Functions: 0 0 -

          Line data    Source code
       1             : package errorfs
       2             : 
       3             : import (
       4             :         "encoding/binary"
       5             :         "fmt"
       6             :         "go/token"
       7             :         "hash/maphash"
       8             :         "math/rand"
       9             :         "strconv"
      10             :         "sync"
      11             :         "time"
      12             : 
      13             :         "github.com/cockroachdb/errors"
      14             :         "github.com/cockroachdb/pebble/internal/dsl"
      15             : )
      16             : 
      17             : // RandomLatency constructs an Injector that does not inject errors but instead
      18             : // injects random latency into operations that match the provided predicate. The
      19             : // amount of latency injected follows an exponential distribution with the
      20             : // provided mean. Latency injected is derived from the provided seed and is
      21             : // deterministic with respect to each file's path.
      22           1 : func RandomLatency(pred Predicate, mean time.Duration, seed int64) Injector {
      23           1 :         rl := &randomLatency{
      24           1 :                 predicate: pred,
      25           1 :                 mean:      mean,
      26           1 :         }
      27           1 :         rl.keyedPrng.init(seed)
      28           1 :         return rl
      29           1 : }
      30             : 
      31           0 : func parseRandomLatency(p *Parser, s *dsl.Scanner) Injector {
      32           0 :         dur, err := time.ParseDuration(s.ConsumeString())
      33           0 :         if err != nil {
      34           0 :                 panic(errors.Newf("parsing RandomLatency: %s", err))
      35             :         }
      36           0 :         lit := s.Consume(token.INT).Lit
      37           0 :         seed, err := strconv.ParseInt(lit, 10, 64)
      38           0 :         if err != nil {
      39           0 :                 panic(err)
      40             :         }
      41           0 :         var pred Predicate
      42           0 :         tok := s.Scan()
      43           0 :         if tok.Kind == token.LPAREN || tok.Kind == token.IDENT {
      44           0 :                 pred = p.predicates.ParseFromPos(s, tok)
      45           0 :                 tok = s.Scan()
      46           0 :         }
      47           0 :         if tok.Kind != token.RPAREN {
      48           0 :                 panic(errors.Errorf("errorfs: unexpected token %s; expected %s", tok.String(), token.RPAREN))
      49             :         }
      50           0 :         return RandomLatency(pred, dur, seed)
      51             : }
      52             : 
      53             : type randomLatency struct {
      54             :         predicate Predicate
      55             :         // p defines the probability of an error being injected.
      56             :         mean time.Duration
      57             :         keyedPrng
      58             : }
      59             : 
      60           0 : func (rl *randomLatency) String() string {
      61           0 :         if rl.predicate == nil {
      62           0 :                 return fmt.Sprintf("(RandomLatency %q %d)", rl.mean, rl.rootSeed)
      63           0 :         }
      64           0 :         return fmt.Sprintf("(RandomLatency %q %d %s)", rl.mean, rl.rootSeed, rl.predicate)
      65             : }
      66             : 
      67           1 : func (rl *randomLatency) MaybeError(op Op) error {
      68           1 :         if rl.predicate != nil && !rl.predicate.Evaluate(op) {
      69           1 :                 return nil
      70           1 :         }
      71           1 :         var dur time.Duration
      72           1 :         rl.keyedPrng.withKey(op.Path, func(prng *rand.Rand) {
      73           1 :                 // We cap the max latency to 100x: Otherwise, it seems possible
      74           1 :                 // (although very unlikely) ExpFloat64 generates a multiplier high
      75           1 :                 // enough that causes a test timeout.
      76           1 :                 dur = time.Duration(min(prng.ExpFloat64(), 20.0) * float64(rl.mean))
      77           1 :         })
      78           1 :         time.Sleep(dur)
      79           1 :         return nil
      80             : }
      81             : 
      82             : // keyedPrng maintains a separate prng per-key that's deterministic with
      83             : // respect to the key: its behavior for a particular key is deterministic
      84             : // regardless of intervening evaluations for operations on other keys. This can
      85             : // be used to ensure determinism despite nondeterministic concurrency if the
      86             : // concurrency is constrained to separate keys.
      87             : type keyedPrng struct {
      88             :         rootSeed int64
      89             :         mu       struct {
      90             :                 sync.Mutex
      91             :                 h           maphash.Hash
      92             :                 perFilePrng map[string]*rand.Rand
      93             :         }
      94             : }
      95             : 
      96           1 : func (p *keyedPrng) init(rootSeed int64) {
      97           1 :         p.rootSeed = rootSeed
      98           1 :         p.mu.perFilePrng = make(map[string]*rand.Rand)
      99           1 : }
     100             : 
     101           1 : func (p *keyedPrng) withKey(key string, fn func(*rand.Rand)) {
     102           1 :         p.mu.Lock()
     103           1 :         defer p.mu.Unlock()
     104           1 :         prng, ok := p.mu.perFilePrng[key]
     105           1 :         if !ok {
     106           1 :                 // This is the first time an operation has been performed on the key.
     107           1 :                 // Initialize the per-key prng by computing a deterministic hash of the
     108           1 :                 // key.
     109           1 :                 p.mu.h.Reset()
     110           1 :                 var b [8]byte
     111           1 :                 binary.LittleEndian.PutUint64(b[:], uint64(p.rootSeed))
     112           1 :                 if _, err := p.mu.h.Write(b[:]); err != nil {
     113           0 :                         panic(err)
     114             :                 }
     115           1 :                 if _, err := p.mu.h.WriteString(key); err != nil {
     116           0 :                         panic(err)
     117             :                 }
     118           1 :                 seed := p.mu.h.Sum64()
     119           1 :                 prng = rand.New(rand.NewSource(int64(seed)))
     120           1 :                 p.mu.perFilePrng[key] = prng
     121             :         }
     122           1 :         fn(prng)
     123             : }

Generated by: LCOV version 1.14