package p2p import ( "math" "math/rand" "testing" "time" gogotypes "github.com/gogo/protobuf/types" "github.com/stretchr/testify/require" tmsync "github.com/tendermint/tendermint/internal/libs/sync" "github.com/tendermint/tendermint/libs/log" ) type testMessage = gogotypes.StringValue func TestWDRRQueue_EqualWeights(t *testing.T) { chDescs := []ChannelDescriptor{ {ID: 0x01, Priority: 1, MaxSendBytes: 4}, {ID: 0x02, Priority: 1, MaxSendBytes: 4}, {ID: 0x03, Priority: 1, MaxSendBytes: 4}, {ID: 0x04, Priority: 1, MaxSendBytes: 4}, {ID: 0x05, Priority: 1, MaxSendBytes: 4}, {ID: 0x06, Priority: 1, MaxSendBytes: 4}, } peerQueue := newWDRRScheduler(log.NewNopLogger(), NopMetrics(), chDescs, 1000, 1000, 120) peerQueue.start() totalMsgs := make(map[ChannelID]int) deliveredMsgs := make(map[ChannelID]int) successRates := make(map[ChannelID]float64) closer := tmsync.NewCloser() go func() { timout := 10 * time.Second ticker := time.NewTicker(timout) defer ticker.Stop() for { select { case e := <-peerQueue.dequeue(): deliveredMsgs[e.channelID]++ ticker.Reset(timout) case <-ticker.C: closer.Close() } } }() rng := rand.New(rand.NewSource(time.Now().UnixNano())) maxMsgs := 5000 minMsgs := 1000 for _, chDesc := range chDescs { total := rng.Intn(maxMsgs-minMsgs) + minMsgs // total = rand[minMsgs, maxMsgs) totalMsgs[ChannelID(chDesc.ID)] = total go func(cID ChannelID, n int) { for i := 0; i < n; i++ { peerQueue.enqueue() <- Envelope{ channelID: cID, Message: &testMessage{Value: "foo"}, // 5 bytes } } }(ChannelID(chDesc.ID), total) } // wait for dequeueing to complete <-closer.Done() // close queue and wait for cleanup peerQueue.close() <-peerQueue.closed() var ( sum float64 stdDev float64 ) for _, chDesc := range peerQueue.chDescs { chID := ChannelID(chDesc.ID) require.Zero(t, peerQueue.deficits[chID], "expected flow deficit to be zero") require.Len(t, peerQueue.buffer[chID], 0, "expected flow queue to be empty") total := totalMsgs[chID] delivered := deliveredMsgs[chID] successRate := float64(delivered) / float64(total) sum += successRate successRates[chID] = successRate // require some messages dropped require.Less(t, delivered, total, "expected some messages to be dropped") require.Less(t, successRate, 1.0, "expected a success rate below 100%") } require.Zero(t, peerQueue.size, "expected scheduler size to be zero") numFlows := float64(len(peerQueue.buffer)) mean := sum / numFlows for _, successRate := range successRates { stdDev += math.Pow(successRate-mean, 2) } stdDev = math.Sqrt(stdDev / numFlows) require.Less(t, stdDev, 0.02, "expected success rate standard deviation to be less than 2%") } func TestWDRRQueue_DecreasingWeights(t *testing.T) { chDescs := []ChannelDescriptor{ {ID: 0x01, Priority: 18, MaxSendBytes: 4}, {ID: 0x02, Priority: 10, MaxSendBytes: 4}, {ID: 0x03, Priority: 2, MaxSendBytes: 4}, {ID: 0x04, Priority: 1, MaxSendBytes: 4}, {ID: 0x05, Priority: 1, MaxSendBytes: 4}, {ID: 0x06, Priority: 1, MaxSendBytes: 4}, } peerQueue := newWDRRScheduler(log.NewNopLogger(), NopMetrics(), chDescs, 0, 0, 500) peerQueue.start() totalMsgs := make(map[ChannelID]int) deliveredMsgs := make(map[ChannelID]int) successRates := make(map[ChannelID]float64) for _, chDesc := range chDescs { total := 1000 totalMsgs[ChannelID(chDesc.ID)] = total go func(cID ChannelID, n int) { for i := 0; i < n; i++ { peerQueue.enqueue() <- Envelope{ channelID: cID, Message: &testMessage{Value: "foo"}, // 5 bytes } } }(ChannelID(chDesc.ID), total) } closer := tmsync.NewCloser() go func() { timout := 20 * time.Second ticker := time.NewTicker(timout) defer ticker.Stop() for { select { case e := <-peerQueue.dequeue(): deliveredMsgs[e.channelID]++ ticker.Reset(timout) case <-ticker.C: closer.Close() } } }() // wait for dequeueing to complete <-closer.Done() // close queue and wait for cleanup peerQueue.close() <-peerQueue.closed() for i, chDesc := range peerQueue.chDescs { chID := ChannelID(chDesc.ID) require.Zero(t, peerQueue.deficits[chID], "expected flow deficit to be zero") require.Len(t, peerQueue.buffer[chID], 0, "expected flow queue to be empty") total := totalMsgs[chID] delivered := deliveredMsgs[chID] successRate := float64(delivered) / float64(total) successRates[chID] = successRate // Require some messages dropped. Note, the top weighted flows may not have // any dropped if lower priority non-empty queues always exist. if i > 2 { require.Less(t, delivered, total, "expected some messages to be dropped") require.Less(t, successRate, 1.0, "expected a success rate below 100%") } } require.Zero(t, peerQueue.size, "expected scheduler size to be zero") // require channel 0x01 to have the highest success rate due to its weight ch01Rate := successRates[ChannelID(chDescs[0].ID)] for i := 1; i < len(chDescs); i++ { require.GreaterOrEqual(t, ch01Rate, successRates[ChannelID(chDescs[i].ID)]) } // require channel 0x02 to have the 2nd highest success rate due to its weight ch02Rate := successRates[ChannelID(chDescs[1].ID)] for i := 2; i < len(chDescs); i++ { require.GreaterOrEqual(t, ch02Rate, successRates[ChannelID(chDescs[i].ID)]) } // require channel 0x03 to have the 3rd highest success rate due to its weight ch03Rate := successRates[ChannelID(chDescs[2].ID)] for i := 3; i < len(chDescs); i++ { require.GreaterOrEqual(t, ch03Rate, successRates[ChannelID(chDescs[i].ID)]) } }