github.com/hashicorp/vault/sdk@v0.13.0/physical/latency.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package physical 5 6 import ( 7 "context" 8 "math/rand" 9 "sync" 10 "time" 11 12 log "github.com/hashicorp/go-hclog" 13 uberAtomic "go.uber.org/atomic" 14 ) 15 16 const ( 17 // DefaultJitterPercent is used if no cache size is specified for NewCache 18 DefaultJitterPercent = 20 19 ) 20 21 // LatencyInjector is used to add latency into underlying physical requests 22 type LatencyInjector struct { 23 logger log.Logger 24 backend Backend 25 latency *uberAtomic.Duration 26 jitterPercent int 27 randomLock *sync.Mutex 28 random *rand.Rand 29 } 30 31 // TransactionalLatencyInjector is the transactional version of the latency 32 // injector 33 type TransactionalLatencyInjector struct { 34 *LatencyInjector 35 Transactional 36 } 37 38 // Verify LatencyInjector satisfies the correct interfaces 39 var ( 40 _ Backend = (*LatencyInjector)(nil) 41 _ Transactional = (*TransactionalLatencyInjector)(nil) 42 ) 43 44 // NewLatencyInjector returns a wrapped physical backend to simulate latency 45 func NewLatencyInjector(b Backend, latency time.Duration, jitter int, logger log.Logger) *LatencyInjector { 46 if jitter < 0 || jitter > 100 { 47 jitter = DefaultJitterPercent 48 } 49 logger.Info("creating latency injector") 50 51 return &LatencyInjector{ 52 logger: logger, 53 backend: b, 54 latency: uberAtomic.NewDuration(latency), 55 jitterPercent: jitter, 56 randomLock: new(sync.Mutex), 57 random: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))), 58 } 59 } 60 61 // NewTransactionalLatencyInjector creates a new transactional LatencyInjector 62 // jitter is the random percent that latency will vary between. 63 // For example, if you specify latency = 50ms and jitter = 20, then for any 64 // given operation, the latency will be 50ms +- 10ms (20% of 50), or between 40 and 60ms. 65 func NewTransactionalLatencyInjector(b Backend, latency time.Duration, jitter int, logger log.Logger) *TransactionalLatencyInjector { 66 return &TransactionalLatencyInjector{ 67 LatencyInjector: NewLatencyInjector(b, latency, jitter, logger), 68 Transactional: b.(Transactional), 69 } 70 } 71 72 func (l *LatencyInjector) SetLatency(latency time.Duration) { 73 l.logger.Info("Changing backend latency", "latency", latency) 74 l.latency.Store(latency) 75 } 76 77 func (l *LatencyInjector) addLatency() { 78 // Calculate a value between 1 +- jitter% 79 percent := 100 80 if l.jitterPercent > 0 { 81 min := 100 - l.jitterPercent 82 max := 100 + l.jitterPercent 83 l.randomLock.Lock() 84 percent = l.random.Intn(max-min) + min 85 l.randomLock.Unlock() 86 } 87 latencyDuration := time.Duration(int(l.latency.Load()) * percent / 100) 88 time.Sleep(latencyDuration) 89 } 90 91 // Put is a latent put request 92 func (l *LatencyInjector) Put(ctx context.Context, entry *Entry) error { 93 l.addLatency() 94 return l.backend.Put(ctx, entry) 95 } 96 97 // Get is a latent get request 98 func (l *LatencyInjector) Get(ctx context.Context, key string) (*Entry, error) { 99 l.addLatency() 100 return l.backend.Get(ctx, key) 101 } 102 103 // Delete is a latent delete request 104 func (l *LatencyInjector) Delete(ctx context.Context, key string) error { 105 l.addLatency() 106 return l.backend.Delete(ctx, key) 107 } 108 109 // List is a latent list request 110 func (l *LatencyInjector) List(ctx context.Context, prefix string) ([]string, error) { 111 l.addLatency() 112 return l.backend.List(ctx, prefix) 113 } 114 115 // Transaction is a latent transaction request 116 func (l *TransactionalLatencyInjector) Transaction(ctx context.Context, txns []*TxnEntry) error { 117 l.addLatency() 118 return l.Transactional.Transaction(ctx, txns) 119 } 120 121 // TransactionLimits implements physical.TransactionalLimits 122 func (l *TransactionalLatencyInjector) TransactionLimits() (int, int) { 123 if tl, ok := l.Transactional.(TransactionalLimits); ok { 124 return tl.TransactionLimits() 125 } 126 // We don't have any specific limits of our own so return zeros to signal that 127 // the caller should use whatever reasonable defaults it would if it used a 128 // non-TransactionalLimits backend. 129 return 0, 0 130 }