aboutsummaryrefslogblamecommitdiffstats
path: root/integration_test/stats.go
blob: 5d66e293a404a973e52ef70c29612a21cc22a203 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
















                                                                               





                   

                                                                








                                                                                 
                                  
                      








                                            































                                                                               
                                          

                                                                    

                              
                                       















                                                                               

                                                                             




                                                                   
                                                                   
                   
                                                







                                                                
                                                                 


                                  

                                                         








                                                             
                                                                       

                      

                                                  












                                                                 
                                                                               


                                                       


                                                                               






                                                           

                                         
 
                             

                               
                                     






                                                               
                                        

                                                             
                                                                          









                                                                                   
// Copyright 2018 The dexon-consensus Authors
// This file is part of the dexon-consensus library.
//
// The dexon-consensus library is free software: you can redistribute it
// and/or modify it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, either version 3 of the License,
// or (at your option) any later version.
//
// The dexon-consensus library is distributed in the hope that it will be
// useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
// General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the dexon-consensus library. If not, see
// <http://www.gnu.org/licenses/>.

package integration

import (
    "fmt"
    "time"

    "github.com/dexon-foundation/dexon-consensus/core/test"
    "github.com/dexon-foundation/dexon-consensus/core/types"
)

// Errors when calculating statistics for events.
var (
    ErrUnknownEvent              = fmt.Errorf("unknown event")
    ErrUnknownConsensusEventType = fmt.Errorf("unknown consensus event type")
)

// StatsSet represents accumulatee result of a group of related events
// (ex. All events from one node).
type StatsSet struct {
    ProposedBlockCount     int
    ReceivedBlockCount     int
    ConfirmedBlockCount    int
    TotalOrderedBlockCount int
    DeliveredBlockCount    int
    ProposingLatency       time.Duration
    ReceivingLatency       time.Duration
    PrepareExecLatency     time.Duration
    ProcessExecLatency     time.Duration
}

// newBlockProposeEvent accumulates a block proposing event.
func (s *StatsSet) newBlockProposeEvent(
    e *test.Event, payload *consensusEventPayload, history []*test.Event) {

    // Find previous block proposing event.
    if e.ParentHistoryIndex != -1 {
        parentEvent := history[e.ParentHistoryIndex]
        s.ProposingLatency +=
            e.Time.Sub(parentEvent.Time) - parentEvent.ExecInterval
    }
    s.PrepareExecLatency += e.ExecInterval
    s.ProposedBlockCount++
}

// newBlockReceiveEvent accumulates a block received event.
func (s *StatsSet) newBlockReceiveEvent(
    e *test.Event,
    payload *consensusEventPayload,
    history []*test.Event,
    app *test.App) {

    // Find previous block proposing event.
    parentEvent := history[e.ParentHistoryIndex]
    s.ReceivingLatency +=
        e.Time.Sub(parentEvent.Time) - parentEvent.ExecInterval
    s.ProcessExecLatency += e.ExecInterval
    s.ReceivedBlockCount++

    // Find statistics from test.App
    block := payload.PiggyBack.(*types.Block)
    app.WithLock(func(app *test.App) {
        // Is this block confirmed?
        if _, exists := app.Confirmed[block.Hash]; !exists {
            return
        }
        s.ConfirmedBlockCount++
        // Is this block total ordered?
        if _, exists := app.TotalOrderedByHash[block.Hash]; !exists {
            return
        }
        s.TotalOrderedBlockCount++

        // Is this block delivered?
        if _, exists := app.Delivered[block.Hash]; !exists {
            return
        }
        s.DeliveredBlockCount++
    })
}

// done would divide the latencies we cached with related event count. This way
// to calculate average latency is more accurate.
func (s *StatsSet) done(nodeCount int) {
    s.ProposingLatency /= time.Duration(s.ProposedBlockCount - nodeCount)
    s.ReceivingLatency /= time.Duration(s.ReceivedBlockCount)
    s.PrepareExecLatency /= time.Duration(s.ProposedBlockCount)
    s.ProcessExecLatency /= time.Duration(s.ReceivedBlockCount)
}

// Stats is statistics of a slice of test.Event generated by nodes.
type Stats struct {
    ByNode        map[types.NodeID]*StatsSet
    All           *StatsSet
    BPS           float64
    ExecutionTime time.Duration
}

// NewStats constructs an Stats instance by providing a slice of
// test.Event.
func NewStats(
    history []*test.Event, apps map[types.NodeID]*test.App) (
    stats *Stats, err error) {

    stats = &Stats{
        ByNode: make(map[types.NodeID]*StatsSet),
        All:    &StatsSet{},
    }
    if err = stats.calculate(history, apps); err != nil {
        stats = nil
    }
    stats.summary(history)
    return
}

func (stats *Stats) calculate(
    history []*test.Event, apps map[types.NodeID]*test.App) error {

    defer func() {
        stats.All.done(len(stats.ByNode))
        for _, set := range stats.ByNode {
            set.done(1)
        }
    }()

    for _, e := range history {
        payload, ok := e.Payload.(*consensusEventPayload)
        if !ok {
            return ErrUnknownEvent
        }
        switch payload.Type {
        case evtProposeBlock:
            stats.All.newBlockProposeEvent(
                e, payload, history)
            stats.getStatsSetByNode(e.NodeID).newBlockProposeEvent(
                e, payload, history)
        case evtReceiveBlock:
            stats.All.newBlockReceiveEvent(
                e, payload, history, apps[e.NodeID])
            stats.getStatsSetByNode(e.NodeID).newBlockReceiveEvent(
                e, payload, history, apps[e.NodeID])
        default:
            return ErrUnknownConsensusEventType
        }
    }
    return nil
}

func (stats *Stats) getStatsSetByNode(
    vID types.NodeID) (s *StatsSet) {

    s = stats.ByNode[vID]
    if s == nil {
        s = &StatsSet{}
        stats.ByNode[vID] = s
    }
    return
}

func (stats *Stats) summary(history []*test.Event) {
    // Find average delivered block count among all blocks.
    totalConfirmedBlocks := 0
    for _, s := range stats.ByNode {
        totalConfirmedBlocks += s.DeliveredBlockCount
    }
    averageConfirmedBlocks := totalConfirmedBlocks / len(stats.ByNode)

    // Find execution time.
    // Note: it's a simplified way to calculate the execution time:
    //       the latest event might not be at the end of history when
    //       the number of worker routine is larger than 1.
    stats.ExecutionTime = history[len(history)-1].Time.Sub(history[0].Time)
    // Calculate BPS.
    latencyAsSecond := stats.ExecutionTime.Nanoseconds() / (1000 * 1000 * 1000)
    stats.BPS = float64(averageConfirmedBlocks) / float64(latencyAsSecond)
}