diff options
Diffstat (limited to 'integration_test/stats.go')
-rw-r--r-- | integration_test/stats.go | 176 |
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) +} |