aboutsummaryrefslogtreecommitdiffstats
path: root/integration_test/stats.go
diff options
context:
space:
mode:
Diffstat (limited to 'integration_test/stats.go')
-rw-r--r--integration_test/stats.go176
1 files changed, 176 insertions, 0 deletions
diff --git a/integration_test/stats.go b/integration_test/stats.go
new file mode 100644
index 0000000..ae8ded4
--- /dev/null
+++ b/integration_test/stats.go
@@ -0,0 +1,176 @@
+package integration
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/dexon-foundation/dexon-consensus-core/core/test"
+ "github.com/dexon-foundation/dexon-consensus-core/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 validator).
+type StatsSet struct {
+ ProposedBlockCount int
+ ReceivedBlockCount int
+ StronglyAckedBlockCount 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.Check(func(app *test.App) {
+ // Is this block strongly acked?
+ if _, exists := app.Acked[block.Hash]; !exists {
+ return
+ }
+ s.StronglyAckedBlockCount++
+
+ // 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(validatorCount int) {
+ s.ProposingLatency /= time.Duration(s.ProposedBlockCount - validatorCount)
+ 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 validators.
+type Stats struct {
+ ByValidator map[types.ValidatorID]*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.ValidatorID]*test.App) (
+ stats *Stats, err error) {
+
+ stats = &Stats{
+ ByValidator: make(map[types.ValidatorID]*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.ValidatorID]*test.App) error {
+
+ defer func() {
+ stats.All.done(len(stats.ByValidator))
+ for _, set := range stats.ByValidator {
+ 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.getStatsSetByValidator(e.ValidatorID).newBlockProposeEvent(
+ e, payload, history)
+ case evtReceiveBlock:
+ stats.All.newBlockReceiveEvent(
+ e, payload, history, apps[e.ValidatorID])
+ stats.getStatsSetByValidator(e.ValidatorID).newBlockReceiveEvent(
+ e, payload, history, apps[e.ValidatorID])
+ default:
+ return ErrUnknownConsensusEventType
+ }
+ }
+ return nil
+}
+
+func (stats *Stats) getStatsSetByValidator(
+ vID types.ValidatorID) (s *StatsSet) {
+
+ s = stats.ByValidator[vID]
+ if s == nil {
+ s = &StatsSet{}
+ stats.ByValidator[vID] = s
+ }
+ return
+}
+
+func (stats *Stats) summary(history []*test.Event) {
+ // Find average delivered block count among all blocks.
+ totalConfirmedBlocks := 0
+ for _, s := range stats.ByValidator {
+ totalConfirmedBlocks += s.DeliveredBlockCount
+ }
+ averageConfirmedBlocks := totalConfirmedBlocks / len(stats.ByValidator)
+
+ // 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)
+}