mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
swarm/storage/feed/lookup: Refactor mock storage, add perf counters
This commit is contained in:
parent
34bf9c4993
commit
7301f2b6ee
2 changed files with 198 additions and 115 deletions
|
|
@ -20,11 +20,9 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync/atomic"
|
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/log"
|
|
||||||
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
|
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -40,60 +38,25 @@ var algorithms = []AlgorithmInfo{
|
||||||
|
|
||||||
const enablePrintMetrics = true // set to true to display algorithm benchmarking stats
|
const enablePrintMetrics = true // set to true to display algorithm benchmarking stats
|
||||||
|
|
||||||
func printMetric(metric string, reads int32, elapsed time.Duration) {
|
func printMetric(metric string, store *Store, elapsed time.Duration) {
|
||||||
if enablePrintMetrics {
|
if enablePrintMetrics {
|
||||||
fmt.Printf("metric=%s, readcount=%d, elapsed=%s\n", metric, reads, elapsed)
|
fmt.Printf("metric=%s, readcount=%d (successful=%d, failed=%d), cached=%d, canceled=%d, elapsed=%s\n", metric,
|
||||||
|
store.reads, store.sucessful, store.failed, store.cacheHits, store.canceled, elapsed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Data struct {
|
|
||||||
Payload uint64
|
|
||||||
Time uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Data) String() string {
|
|
||||||
return fmt.Sprintf("%d-%d", d.Payload, d.Time)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Store map[lookup.EpochID]*Data
|
|
||||||
|
|
||||||
func write(store Store, epoch lookup.Epoch, value *Data) {
|
|
||||||
log.Debug("Write: %d-%d, value='%d'\n", epoch.Base(), epoch.Level, value.Payload)
|
|
||||||
store[epoch.ID()] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
func update(store Store, last lookup.Epoch, now uint64, value *Data) lookup.Epoch {
|
|
||||||
epoch := lookup.GetNextEpoch(last, now)
|
|
||||||
|
|
||||||
write(store, epoch, value)
|
|
||||||
|
|
||||||
return epoch
|
|
||||||
}
|
|
||||||
|
|
||||||
const Day = 60 * 60 * 24
|
const Day = 60 * 60 * 24
|
||||||
const Year = Day * 365
|
const Year = Day * 365
|
||||||
const Month = Day * 30
|
const Month = Day * 30
|
||||||
|
|
||||||
func makeReadFunc(store Store, counter *int32) lookup.ReadFunc {
|
// DefaultStoreConfig indicates the time the different read
|
||||||
return func(ctx context.Context, epoch lookup.Epoch, now uint64) (interface{}, error) {
|
// operations will take in the simulation
|
||||||
atomic.AddInt32(counter, 1)
|
// This allows to measure an algorithm performance relative
|
||||||
select {
|
// to other
|
||||||
case <-lookup.TimeAfter(1000 * time.Millisecond):
|
var DefaultStoreConfig = &StoreConfig{
|
||||||
case <-ctx.Done():
|
CacheReadTime: 50 * time.Millisecond,
|
||||||
return nil, ctx.Err()
|
FailedReadTime: 1000 * time.Millisecond,
|
||||||
}
|
SuccessfulReadTime: 500 * time.Millisecond,
|
||||||
data := store[epoch.ID()]
|
|
||||||
var valueStr string
|
|
||||||
if data != nil {
|
|
||||||
valueStr = fmt.Sprintf("%d", data.Payload)
|
|
||||||
}
|
|
||||||
log.Debug("Read: %d-%d, value='%s'\n", epoch.Base(), epoch.Level, valueStr)
|
|
||||||
|
|
||||||
if data != nil && data.Time <= now {
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestLookup verifies if the last update and intermediates are
|
// TestLookup verifies if the last update and intermediates are
|
||||||
|
|
@ -105,9 +68,8 @@ func TestLookup(t *testing.T) {
|
||||||
defer stopwatch.Stop()
|
defer stopwatch.Stop()
|
||||||
|
|
||||||
// ### 2.- Setup mock storage and generate updates
|
// ### 2.- Setup mock storage and generate updates
|
||||||
store := make(Store)
|
store := NewStore(DefaultStoreConfig)
|
||||||
var readCount int32 = 0
|
readFunc := store.MakeReadFunc()
|
||||||
readFunc := makeReadFunc(store, &readCount)
|
|
||||||
|
|
||||||
// write an update every month for 12 months 3 years ago and then silence for two years
|
// write an update every month for 12 months 3 years ago and then silence for two years
|
||||||
now := uint64(1533799046)
|
now := uint64(1533799046)
|
||||||
|
|
@ -120,14 +82,15 @@ func TestLookup(t *testing.T) {
|
||||||
Payload: t, //our "payload" will be the timestamp itself.
|
Payload: t, //our "payload" will be the timestamp itself.
|
||||||
Time: t,
|
Time: t,
|
||||||
}
|
}
|
||||||
epoch = update(store, epoch, t, &data)
|
epoch = store.Update(epoch, t, &data)
|
||||||
lastData = &data
|
lastData = &data
|
||||||
}
|
}
|
||||||
|
|
||||||
// ### 3.- Test all algorithms
|
// ### 3.- Test all algorithms
|
||||||
for _, algo := range algorithms {
|
for _, algo := range algorithms {
|
||||||
t.Run(algo.Name, func(t *testing.T) {
|
t.Run(algo.Name, func(t *testing.T) {
|
||||||
readCount = 0
|
|
||||||
|
store.Reset() // reset the store read counters
|
||||||
|
|
||||||
// ### 3.1.- Test how long it takes to find the last update without a hint:
|
// ### 3.1.- Test how long it takes to find the last update without a hint:
|
||||||
timeElapsedWithoutHint := stopwatch.Measure(func() {
|
timeElapsedWithoutHint := stopwatch.Measure(func() {
|
||||||
|
|
@ -142,10 +105,9 @@ func TestLookup(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
printMetric("SIMPLE READ", readCount, timeElapsedWithoutHint)
|
printMetric("SIMPLE READ", store, timeElapsedWithoutHint)
|
||||||
|
|
||||||
// reset the read count for the next test
|
store.Reset() // reset the read counters for the next test
|
||||||
readCount = 0
|
|
||||||
|
|
||||||
// ### 3.2.- Test how long it takes to find the last update *with* a hint.
|
// ### 3.2.- Test how long it takes to find the last update *with* a hint.
|
||||||
// it should take less time!
|
// it should take less time!
|
||||||
|
|
@ -159,14 +121,13 @@ func TestLookup(t *testing.T) {
|
||||||
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value)
|
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
printMetric("WITH HINT", readCount, stopwatch.Elapsed())
|
printMetric("WITH HINT", store, stopwatch.Elapsed())
|
||||||
|
|
||||||
if timeElapsed > timeElapsedWithoutHint {
|
if timeElapsed > timeElapsedWithoutHint {
|
||||||
t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed)
|
t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// reset the read count for the next test
|
store.Reset() // reset the read counters for the next test
|
||||||
readCount = 0
|
|
||||||
|
|
||||||
// ### 3.3.- try to get an intermediate value
|
// ### 3.3.- try to get an intermediate value
|
||||||
// if we look for a value in, e.g., now - Year*3 + 6*Month, we should get that value
|
// if we look for a value in, e.g., now - Year*3 + 6*Month, we should get that value
|
||||||
|
|
@ -188,7 +149,7 @@ func TestLookup(t *testing.T) {
|
||||||
t.Fatalf("Expected value timestamp to be %d, got %d", data.Time, expectedTime)
|
t.Fatalf("Expected value timestamp to be %d, got %d", data.Time, expectedTime)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
printMetric("INTERMEDIATE READ", readCount, timeElapsed)
|
printMetric("INTERMEDIATE READ", store, timeElapsed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -202,10 +163,9 @@ func TestOneUpdateAt0(t *testing.T) {
|
||||||
defer stopwatch.Stop()
|
defer stopwatch.Stop()
|
||||||
|
|
||||||
// ### 2.- Setup mock storage and generate updates
|
// ### 2.- Setup mock storage and generate updates
|
||||||
store := make(Store)
|
store := NewStore(DefaultStoreConfig)
|
||||||
var readCount int32 = 0
|
readFunc := store.MakeReadFunc()
|
||||||
|
|
||||||
readFunc := makeReadFunc(store, &readCount)
|
|
||||||
now := uint64(1533903729)
|
now := uint64(1533903729)
|
||||||
|
|
||||||
var epoch lookup.Epoch
|
var epoch lookup.Epoch
|
||||||
|
|
@ -213,12 +173,12 @@ func TestOneUpdateAt0(t *testing.T) {
|
||||||
Payload: 79,
|
Payload: 79,
|
||||||
Time: 0,
|
Time: 0,
|
||||||
}
|
}
|
||||||
update(store, epoch, 0, &data) //place 1 update in t=0
|
store.Update(epoch, 0, &data) //place 1 update in t=0
|
||||||
|
|
||||||
// ### 3.- Test all algorithms
|
// ### 3.- Test all algorithms
|
||||||
for _, algo := range algorithms {
|
for _, algo := range algorithms {
|
||||||
t.Run(algo.Name, func(t *testing.T) {
|
t.Run(algo.Name, func(t *testing.T) {
|
||||||
readCount = 0
|
store.Reset() // reset the read counters for the next test
|
||||||
timeElapsed := stopwatch.Measure(func() {
|
timeElapsed := stopwatch.Measure(func() {
|
||||||
value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc)
|
value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -228,7 +188,7 @@ func TestOneUpdateAt0(t *testing.T) {
|
||||||
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value)
|
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
printMetric("SIMPLE", readCount, timeElapsed)
|
printMetric("SIMPLE", store, timeElapsed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -241,10 +201,9 @@ func TestBadHint(t *testing.T) {
|
||||||
defer stopwatch.Stop()
|
defer stopwatch.Stop()
|
||||||
|
|
||||||
// ### 2.- Setup mock storage and generate updates
|
// ### 2.- Setup mock storage and generate updates
|
||||||
store := make(Store)
|
store := NewStore(DefaultStoreConfig)
|
||||||
var readCount int32 = 0
|
readFunc := store.MakeReadFunc()
|
||||||
|
|
||||||
readFunc := makeReadFunc(store, &readCount)
|
|
||||||
now := uint64(1533903729)
|
now := uint64(1533903729)
|
||||||
|
|
||||||
var epoch lookup.Epoch
|
var epoch lookup.Epoch
|
||||||
|
|
@ -254,7 +213,7 @@ func TestBadHint(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// place an update for t=1200
|
// place an update for t=1200
|
||||||
update(store, epoch, 1200, &data)
|
store.Update(epoch, 1200, &data)
|
||||||
|
|
||||||
// come up with some evil hint
|
// come up with some evil hint
|
||||||
badHint := lookup.Epoch{
|
badHint := lookup.Epoch{
|
||||||
|
|
@ -265,7 +224,7 @@ func TestBadHint(t *testing.T) {
|
||||||
// ### 3.- Test all algorithms
|
// ### 3.- Test all algorithms
|
||||||
for _, algo := range algorithms {
|
for _, algo := range algorithms {
|
||||||
t.Run(algo.Name, func(t *testing.T) {
|
t.Run(algo.Name, func(t *testing.T) {
|
||||||
readCount = 0
|
store.Reset()
|
||||||
timeElapsed := stopwatch.Measure(func() {
|
timeElapsed := stopwatch.Measure(func() {
|
||||||
value, err := algo.Lookup(context.Background(), now, badHint, readFunc)
|
value, err := algo.Lookup(context.Background(), now, badHint, readFunc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -275,7 +234,7 @@ func TestBadHint(t *testing.T) {
|
||||||
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value)
|
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
printMetric("SIMPLE", readCount, timeElapsed)
|
printMetric("SIMPLE", store, timeElapsed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -288,10 +247,9 @@ func TestBadHintNextToUpdate(t *testing.T) {
|
||||||
defer stopwatch.Stop()
|
defer stopwatch.Stop()
|
||||||
|
|
||||||
// ### 2.- Setup mock storage and generate updates
|
// ### 2.- Setup mock storage and generate updates
|
||||||
store := make(Store)
|
store := NewStore(DefaultStoreConfig)
|
||||||
var readCount int32 = 0
|
readFunc := store.MakeReadFunc()
|
||||||
|
|
||||||
readFunc := makeReadFunc(store, &readCount)
|
|
||||||
now := uint64(1533903729)
|
now := uint64(1533903729)
|
||||||
var last *Data
|
var last *Data
|
||||||
|
|
||||||
|
|
@ -317,7 +275,7 @@ func TestBadHintNextToUpdate(t *testing.T) {
|
||||||
Time: 0,
|
Time: 0,
|
||||||
}
|
}
|
||||||
last = &data
|
last = &data
|
||||||
epoch = update(store, epoch, 1200000000+i, &data)
|
epoch = store.Update(epoch, 1200000000+i, &data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// come up with some evil hint:
|
// come up with some evil hint:
|
||||||
|
|
@ -330,7 +288,7 @@ func TestBadHintNextToUpdate(t *testing.T) {
|
||||||
// ### 3.- Test all algorithms
|
// ### 3.- Test all algorithms
|
||||||
for _, algo := range algorithms {
|
for _, algo := range algorithms {
|
||||||
t.Run(algo.Name, func(t *testing.T) {
|
t.Run(algo.Name, func(t *testing.T) {
|
||||||
readCount = 0
|
store.Reset() // reset read counters for next test
|
||||||
|
|
||||||
timeElapsed := stopwatch.Measure(func() {
|
timeElapsed := stopwatch.Measure(func() {
|
||||||
value, err := algo.Lookup(context.Background(), now, badHint, readFunc)
|
value, err := algo.Lookup(context.Background(), now, badHint, readFunc)
|
||||||
|
|
@ -341,12 +299,12 @@ func TestBadHintNextToUpdate(t *testing.T) {
|
||||||
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", last, value)
|
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", last, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
printMetric("SIMPLE", readCount, timeElapsed)
|
printMetric("SIMPLE", store, timeElapsed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestContextCancellation checks whether a lookup can be cancelled
|
// TestContextCancellation checks whether a lookup can be canceled
|
||||||
func TestContextCancellation(t *testing.T) {
|
func TestContextCancellation(t *testing.T) {
|
||||||
|
|
||||||
// ### 1.- Test all algorithms
|
// ### 1.- Test all algorithms
|
||||||
|
|
@ -370,7 +328,7 @@ func TestContextCancellation(t *testing.T) {
|
||||||
cancel() //actually cancel the lookup
|
cancel() //actually cancel the lookup
|
||||||
|
|
||||||
if err := <-errc; err != context.Canceled {
|
if err := <-errc; err != context.Canceled {
|
||||||
t.Fatalf("Expected lookup to return a context Cancelled error, got %v", err)
|
t.Fatalf("Expected lookup to return a context canceled error, got %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ### 2.2.- Test context cancellation during hint lookup:
|
// ### 2.2.- Test context cancellation during hint lookup:
|
||||||
|
|
@ -380,7 +338,7 @@ func TestContextCancellation(t *testing.T) {
|
||||||
Level: 25,
|
Level: 25,
|
||||||
Time: 300,
|
Time: 300,
|
||||||
}
|
}
|
||||||
// put up a read function that gets cancelled only on hint lookup
|
// put up a read function that gets canceled only on hint lookup
|
||||||
readFunc = func(ctx context.Context, epoch lookup.Epoch, now uint64) (interface{}, error) {
|
readFunc = func(ctx context.Context, epoch lookup.Epoch, now uint64) (interface{}, error) {
|
||||||
if epoch == someHint {
|
if epoch == someHint {
|
||||||
go cancel()
|
go cancel()
|
||||||
|
|
@ -396,7 +354,7 @@ func TestContextCancellation(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err := <-errc; err != context.Canceled {
|
if err := <-errc; err != context.Canceled {
|
||||||
t.Fatalf("Expected lookup to return a context Cancelled error, got %v", err)
|
t.Fatalf("Expected lookup to return a context canceled error, got %v", err)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -415,17 +373,15 @@ func TestLookupFail(t *testing.T) {
|
||||||
// don't write anything and try to look up.
|
// don't write anything and try to look up.
|
||||||
// we're testing we don't get stuck in a loop and that the lookup
|
// we're testing we don't get stuck in a loop and that the lookup
|
||||||
// function converges in a timely fashion
|
// function converges in a timely fashion
|
||||||
|
store := NewStore(DefaultStoreConfig)
|
||||||
|
readFunc := store.MakeReadFunc()
|
||||||
|
|
||||||
store := make(Store)
|
|
||||||
var readCount int32 = 0
|
|
||||||
|
|
||||||
readFunc := makeReadFunc(store, &readCount)
|
|
||||||
now := uint64(1533903729)
|
now := uint64(1533903729)
|
||||||
|
|
||||||
// ### 3.- Test all algorithms
|
// ### 3.- Test all algorithms
|
||||||
for _, algo := range algorithms {
|
for _, algo := range algorithms {
|
||||||
t.Run(algo.Name, func(t *testing.T) {
|
t.Run(algo.Name, func(t *testing.T) {
|
||||||
readCount = 0
|
store.Reset()
|
||||||
|
|
||||||
stopwatch.Measure(func() {
|
stopwatch.Measure(func() {
|
||||||
value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc)
|
value, err := algo.Lookup(context.Background(), now, lookup.NoClue, readFunc)
|
||||||
|
|
@ -437,7 +393,7 @@ func TestLookupFail(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
printMetric("SIMPLE", readCount, stopwatch.Elapsed())
|
printMetric("SIMPLE", store, stopwatch.Elapsed())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -450,10 +406,9 @@ func TestHighFreqUpdates(t *testing.T) {
|
||||||
|
|
||||||
// ### 2.- Setup mock storage and add one update per second
|
// ### 2.- Setup mock storage and add one update per second
|
||||||
// for the last 1000 seconds:
|
// for the last 1000 seconds:
|
||||||
store := make(Store)
|
store := NewStore(DefaultStoreConfig)
|
||||||
var readCount int32 = 0
|
readFunc := store.MakeReadFunc()
|
||||||
|
|
||||||
readFunc := makeReadFunc(store, &readCount)
|
|
||||||
now := uint64(1533903729)
|
now := uint64(1533903729)
|
||||||
|
|
||||||
var epoch lookup.Epoch
|
var epoch lookup.Epoch
|
||||||
|
|
@ -465,14 +420,14 @@ func TestHighFreqUpdates(t *testing.T) {
|
||||||
Payload: T, //our "payload" will be the timestamp itself.
|
Payload: T, //our "payload" will be the timestamp itself.
|
||||||
Time: T,
|
Time: T,
|
||||||
}
|
}
|
||||||
epoch = update(store, epoch, T, &data)
|
epoch = store.Update(epoch, T, &data)
|
||||||
lastData = &data
|
lastData = &data
|
||||||
}
|
}
|
||||||
|
|
||||||
// ### 3.- Test all algorithms
|
// ### 3.- Test all algorithms
|
||||||
for _, algo := range algorithms {
|
for _, algo := range algorithms {
|
||||||
t.Run(algo.Name, func(t *testing.T) {
|
t.Run(algo.Name, func(t *testing.T) {
|
||||||
readCount = 0
|
store.Reset() // reset read counters for next test
|
||||||
|
|
||||||
// ### 3.1.- Test how long it takes to find the last update without a hint:
|
// ### 3.1.- Test how long it takes to find the last update without a hint:
|
||||||
timeElapsedWithoutHint := stopwatch.Measure(func() {
|
timeElapsedWithoutHint := stopwatch.Measure(func() {
|
||||||
|
|
@ -486,10 +441,10 @@ func TestHighFreqUpdates(t *testing.T) {
|
||||||
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value)
|
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
printMetric("SIMPLE", readCount, timeElapsedWithoutHint)
|
printMetric("SIMPLE", store, timeElapsedWithoutHint)
|
||||||
|
|
||||||
// reset the read count for the next test
|
// reset the read count for the next test
|
||||||
readCount = 0
|
store.Reset()
|
||||||
|
|
||||||
// ### 3.2.- Now test how long it takes to find the last update *with* a hint,
|
// ### 3.2.- Now test how long it takes to find the last update *with* a hint,
|
||||||
// it should take less time!
|
// it should take less time!
|
||||||
|
|
@ -509,9 +464,9 @@ func TestHighFreqUpdates(t *testing.T) {
|
||||||
if timeElapsed > timeElapsedWithoutHint {
|
if timeElapsed > timeElapsedWithoutHint {
|
||||||
t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed)
|
t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed)
|
||||||
}
|
}
|
||||||
printMetric("WITH HINT", readCount, timeElapsed)
|
printMetric("WITH HINT", store, timeElapsed)
|
||||||
|
|
||||||
readCount = 0
|
store.Reset() // reset read counters
|
||||||
|
|
||||||
// ### 3.3.- Test multiple lookups at different intervals
|
// ### 3.3.- Test multiple lookups at different intervals
|
||||||
timeElapsed = stopwatch.Measure(func() {
|
timeElapsed = stopwatch.Measure(func() {
|
||||||
|
|
@ -530,41 +485,44 @@ func TestHighFreqUpdates(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
printMetric("MULTIPLE", readCount, timeElapsed)
|
printMetric("MULTIPLE", store, timeElapsed)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSparseUpdates checks the lookup algorithm when
|
||||||
|
// updates come sparsely and in bursts
|
||||||
func TestSparseUpdates(t *testing.T) {
|
func TestSparseUpdates(t *testing.T) {
|
||||||
// ### 1.- Initialize stopwatch time sim
|
// ### 1.- Initialize stopwatch time sim
|
||||||
stopwatch := NewStopwatch(50 * time.Millisecond)
|
stopwatch := NewStopwatch(50 * time.Millisecond)
|
||||||
lookup.TimeAfter = stopwatch.TimeAfter()
|
lookup.TimeAfter = stopwatch.TimeAfter()
|
||||||
defer stopwatch.Stop()
|
defer stopwatch.Stop()
|
||||||
|
|
||||||
// ### 2.- Setup mock storage and write an update sparsely,
|
// ### 2.- Setup mock storage and write an updates sparsely in bursts,
|
||||||
// every 5 years 3 times starting in Jan 1st 1970 and then silence
|
// every 5 years 3 times starting in Jan 1st 1970 and then silence
|
||||||
store := make(Store)
|
store := NewStore(DefaultStoreConfig)
|
||||||
var readCount int32 = 0
|
readFunc := store.MakeReadFunc()
|
||||||
readFunc := makeReadFunc(store, &readCount)
|
|
||||||
|
|
||||||
now := uint64(1533799046)
|
now := uint64(633799046)
|
||||||
var epoch lookup.Epoch
|
var epoch lookup.Epoch
|
||||||
|
|
||||||
var lastData *Data
|
var lastData *Data
|
||||||
for i := uint64(0); i < 5; i++ {
|
for i := uint64(0); i < 3; i++ {
|
||||||
T := uint64(Year * 5 * i) // write an update every 5 years 3 times starting in Jan 1st 1970 and then silence
|
for j := uint64(0); j < 10; j++ {
|
||||||
|
T := uint64(Year*5*i + j) // write a burst of 10 updates every 5 years 3 times starting in Jan 1st 1970 and then silence
|
||||||
data := Data{
|
data := Data{
|
||||||
Payload: T, //our "payload" will be the timestamp itself.
|
Payload: T, //our "payload" will be the timestamp itself.
|
||||||
Time: T,
|
Time: T,
|
||||||
}
|
}
|
||||||
epoch = update(store, epoch, T, &data)
|
epoch = store.Update(epoch, T, &data)
|
||||||
lastData = &data
|
lastData = &data
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ### 3.- Test all algorithms
|
// ### 3.- Test all algorithms
|
||||||
for _, algo := range algorithms {
|
for _, algo := range algorithms {
|
||||||
t.Run(algo.Name, func(t *testing.T) {
|
t.Run(algo.Name, func(t *testing.T) {
|
||||||
readCount = 0
|
store.Reset() // reset read counters for next test
|
||||||
|
|
||||||
// ### 3.1.- Test how long it takes to find the last update without a hint:
|
// ### 3.1.- Test how long it takes to find the last update without a hint:
|
||||||
timeElapsedWithoutHint := stopwatch.Measure(func() {
|
timeElapsedWithoutHint := stopwatch.Measure(func() {
|
||||||
|
|
@ -578,16 +536,15 @@ func TestSparseUpdates(t *testing.T) {
|
||||||
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value)
|
t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
printMetric("SIMPLE", readCount, timeElapsedWithoutHint)
|
printMetric("SIMPLE", store, timeElapsedWithoutHint)
|
||||||
|
|
||||||
// reset the read count for the next test
|
// reset the read count for the next test
|
||||||
readCount = 0
|
store.Reset()
|
||||||
|
|
||||||
// ### 3.2.- Now test how long it takes to find the last update *with* a hint,
|
// ### 3.2.- Now test how long it takes to find the last update *with* a hint,
|
||||||
// it should take less time!
|
// it should take less time!
|
||||||
timeElapsed := stopwatch.Measure(func() {
|
timeElapsed := stopwatch.Measure(func() {
|
||||||
value, err := algo.Lookup(context.Background(), now, epoch, readFunc)
|
value, err := algo.Lookup(context.Background(), now, epoch, readFunc)
|
||||||
stopwatch.Stop()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -600,7 +557,7 @@ func TestSparseUpdates(t *testing.T) {
|
||||||
t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed)
|
t.Fatalf("Expected lookup to complete faster than %s since we provided a hint. Took %s", timeElapsedWithoutHint, timeElapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
printMetric("WITH HINT", readCount, stopwatch.Elapsed())
|
printMetric("WITH HINT", store, stopwatch.Elapsed())
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
126
swarm/storage/feed/lookup/store_test.go
Normal file
126
swarm/storage/feed/lookup/store_test.go
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
package lookup_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/log"
|
||||||
|
"github.com/ethereum/go-ethereum/swarm/storage/feed/lookup"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
Payload uint64
|
||||||
|
Time uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Data) String() string {
|
||||||
|
return fmt.Sprintf("%d-%d", d.Payload, d.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataMap map[lookup.EpochID]*Data
|
||||||
|
|
||||||
|
type StoreConfig struct {
|
||||||
|
CacheReadTime time.Duration
|
||||||
|
FailedReadTime time.Duration
|
||||||
|
SuccessfulReadTime time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type StoreCounters struct {
|
||||||
|
reads int
|
||||||
|
cacheHits int
|
||||||
|
failed int
|
||||||
|
sucessful int
|
||||||
|
canceled int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Store struct {
|
||||||
|
StoreConfig
|
||||||
|
StoreCounters
|
||||||
|
data DataMap
|
||||||
|
cache DataMap
|
||||||
|
lock sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStore(config *StoreConfig) *Store {
|
||||||
|
store := &Store{
|
||||||
|
StoreConfig: *config,
|
||||||
|
data: make(DataMap),
|
||||||
|
}
|
||||||
|
|
||||||
|
store.Reset()
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Reset() {
|
||||||
|
s.cache = make(DataMap)
|
||||||
|
s.StoreCounters = StoreCounters{}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Update(last lookup.Epoch, now uint64, value *Data) lookup.Epoch {
|
||||||
|
epoch := lookup.GetNextEpoch(last, now)
|
||||||
|
s.Put(epoch, value)
|
||||||
|
return epoch
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) Get(ctx context.Context, epoch lookup.Epoch, now uint64) (value interface{}, err error) {
|
||||||
|
epochID := epoch.ID()
|
||||||
|
var operationTime time.Duration
|
||||||
|
s.reads++
|
||||||
|
|
||||||
|
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()
|
||||||
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
|
// 1.- Simulate a cache read
|
||||||
|
item := s.cache[epochID]
|
||||||
|
operationTime += s.CacheReadTime
|
||||||
|
|
||||||
|
if item != nil {
|
||||||
|
s.cacheHits++
|
||||||
|
if item.Time <= now {
|
||||||
|
s.sucessful++
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2.- simulate a full read
|
||||||
|
|
||||||
|
item = s.data[epochID]
|
||||||
|
if item != nil {
|
||||||
|
operationTime += s.SuccessfulReadTime
|
||||||
|
s.sucessful++
|
||||||
|
s.cache[epochID] = item
|
||||||
|
if item.Time <= now {
|
||||||
|
return item, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
operationTime += s.FailedReadTime
|
||||||
|
s.failed++
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Store) MakeReadFunc() lookup.ReadFunc {
|
||||||
|
return func(ctx context.Context, epoch lookup.Epoch, now uint64) (interface{}, error) {
|
||||||
|
return s.Get(ctx, epoch, now)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue