155 lines
3.6 KiB
Go
155 lines
3.6 KiB
Go
package lookup_test
|
|
|
|
/*
|
|
This file contains components to mock a storage for testing
|
|
lookup algorithms and measure the number of reads.
|
|
*/
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/swarm/log"
|
|
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
|
|
)
|
|
|
|
// Data is a struct to keep a value to store/retrieve during testing
|
|
type Data struct {
|
|
Payload uint64
|
|
Time uint64
|
|
}
|
|
|
|
// String implements fmt.Stringer
|
|
func (d *Data) String() string {
|
|
return fmt.Sprintf("%d-%d", d.Payload, d.Time)
|
|
}
|
|
|
|
// Datamap is an internal map to hold the mocked storage
|
|
type DataMap map[lookup.EpochID]*Data
|
|
|
|
// StoreConfig allows to specify the simulated delays for each type of
|
|
// read operation
|
|
type StoreConfig struct {
|
|
CacheReadTime time.Duration // time it takes to read from the cache
|
|
FailedReadTime time.Duration // time it takes to acknowledge a read as failed
|
|
SuccessfulReadTime time.Duration // time it takes to fetch data
|
|
}
|
|
|
|
// StoreCounters will track read count metrics
|
|
type StoreCounters struct {
|
|
reads int
|
|
cacheHits int
|
|
failed int
|
|
successful int
|
|
canceled int
|
|
maxSimultaneous int
|
|
}
|
|
|
|
// Store simulates a store and keeps track of performance counters
|
|
type Store struct {
|
|
StoreConfig
|
|
StoreCounters
|
|
data DataMap
|
|
cache DataMap
|
|
lock sync.RWMutex
|
|
activeReads int
|
|
}
|
|
|
|
// NewStore returns a new mock store ready for use
|
|
func NewStore(config *StoreConfig) *Store {
|
|
store := &Store{
|
|
StoreConfig: *config,
|
|
data: make(DataMap),
|
|
}
|
|
|
|
store.Reset()
|
|
return store
|
|
}
|
|
|
|
// Reset reset performance counters and clears the cache
|
|
func (s *Store) Reset() {
|
|
s.cache = make(DataMap)
|
|
s.StoreCounters = StoreCounters{}
|
|
}
|
|
|
|
// Put stores a value in the mock store at the given epoch
|
|
func (s *Store) Put(epoch lookup.Epoch, value *Data) {
|
|
log.Debug("Write: %d-%d, value='%d'\n", epoch.Base(), epoch.Level, value.Payload)
|
|
s.data[epoch.ID()] = value
|
|
}
|
|
|
|
// Update runs the seed algorithm to place the update in the appropriate epoch
|
|
func (s *Store) Update(last lookup.Epoch, now uint64, value *Data) lookup.Epoch {
|
|
epoch := lookup.GetNextEpoch(last, now)
|
|
s.Put(epoch, value)
|
|
return epoch
|
|
}
|
|
|
|
// Get retrieves data at the specified epoch, simulating a delay
|
|
func (s *Store) Get(ctx context.Context, epoch lookup.Epoch, now uint64) (value interface{}, err error) {
|
|
epochID := epoch.ID()
|
|
var operationTime time.Duration
|
|
|
|
defer func() { // simulate a delay according to what has actually happened
|
|
select {
|
|
case <-lookup.TimeAfter(operationTime):
|
|
case <-ctx.Done():
|
|
s.lock.Lock()
|
|
s.canceled++
|
|
s.lock.Unlock()
|
|
value = nil
|
|
err = ctx.Err()
|
|
}
|
|
s.lock.Lock()
|
|
s.activeReads--
|
|
s.lock.Unlock()
|
|
}()
|
|
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
s.reads++
|
|
s.activeReads++
|
|
if s.activeReads > s.maxSimultaneous {
|
|
s.maxSimultaneous = s.activeReads
|
|
}
|
|
|
|
// 1.- Simulate a cache read
|
|
item := s.cache[epochID]
|
|
operationTime += s.CacheReadTime
|
|
|
|
if item != nil {
|
|
s.cacheHits++
|
|
if item.Time <= now {
|
|
s.successful++
|
|
return item, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// 2.- simulate a full read
|
|
|
|
item = s.data[epochID]
|
|
if item != nil {
|
|
operationTime += s.SuccessfulReadTime
|
|
s.successful++
|
|
s.cache[epochID] = item
|
|
if item.Time <= now {
|
|
return item, nil
|
|
}
|
|
} else {
|
|
operationTime += s.FailedReadTime
|
|
s.failed++
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// MakeReadFunc returns a read function suitable for the lookup algorithm, mapped
|
|
// to this mock storage
|
|
func (s *Store) MakeReadFunc() lookup.ReadFunc {
|
|
return func(ctx context.Context, epoch lookup.Epoch, now uint64) (interface{}, error) {
|
|
return s.Get(ctx, epoch, now)
|
|
}
|
|
}
|