diff --git a/plugins/metrics/fpc.go b/plugins/metrics/fpc.go index 5614bd12dbaccca631be69381ba45636eff4f317..eb97c82e1c3ce712da6c35238122d555dff0a24e 100644 --- a/plugins/metrics/fpc.go +++ b/plugins/metrics/fpc.go @@ -3,10 +3,40 @@ package metrics import ( "sync/atomic" + "github.com/iotaledger/hive.go/syncutils" + "github.com/iotaledger/goshimmer/packages/vote" ) var activeConflicts uint64 +var finalizedConflictCount uint64 +var failedConflictCount uint64 +var averageRoundsToFinalize float64 +var avLock syncutils.RWMutex + +// ActiveConflicts returns the number of currently active conflicts. +func ActiveConflicts() uint64 { + return atomic.LoadUint64(&activeConflicts) +} + +// FinalizedConflict returns the number of finalized conflicts since the start of the node. +func FinalizedConflict() uint64 { + return atomic.LoadUint64(&finalizedConflictCount) +} + +// FailedConflicts returns the number of failed conflicts since the start of the node. +func FailedConflicts() uint64 { + return atomic.LoadUint64(&failedConflictCount) +} + +// AverageRoundsToFinalize returns the average number fo rounds it takes to finalize conflicts since the start of the node. +func AverageRoundsToFinalize() float64 { + avLock.RLock() + defer avLock.RUnlock() + return averageRoundsToFinalize +} + +//// logic broken into "process..." functions to be able to write unit tests //// func processRoundStats(stats vote.RoundStats) { // get the number of active conflicts @@ -14,7 +44,17 @@ func processRoundStats(stats vote.RoundStats) { atomic.StoreUint64(&activeConflicts, numActive) } -// ActiveConflicts returns the number of currently active conflicts. -func ActiveConflicts() uint64 { - return atomic.LoadUint64(&activeConflicts) +func processFinalized(ctx vote.Context) { + avLock.Lock() + defer avLock.Unlock() + // calculate sum of all rounds, including the currently finalized + sumRounds := averageRoundsToFinalize*(float64)(atomic.LoadUint64(&finalizedConflictCount)) + (float64)(ctx.Rounds) + // increase finalized counter + atomic.AddUint64(&finalizedConflictCount, 1) + // calculate new average + averageRoundsToFinalize = sumRounds / (float64)(atomic.LoadUint64(&finalizedConflictCount)) +} + +func processFailed(ctx vote.Context) { + atomic.AddUint64(&failedConflictCount, 1) } diff --git a/plugins/metrics/fpc_test.go b/plugins/metrics/fpc_test.go index f0a634b7342d495e3dab71778c773774659cd166..27627062f14d3a2643f5c561c9db82d4a41cadd8 100644 --- a/plugins/metrics/fpc_test.go +++ b/plugins/metrics/fpc_test.go @@ -3,11 +3,57 @@ package metrics import ( "testing" + "github.com/iotaledger/goshimmer/packages/vote" + "github.com/magiconair/properties/assert" ) func TestActiveConflicts(t *testing.T) { // initialized to 0 assert.Equal(t, ActiveConflicts(), (uint64)(0)) + stats := vote.RoundStats{ + ActiveVoteContexts: map[string]*vote.Context{ + "test1": &vote.Context{}, + "test2": &vote.Context{}, + "test3": &vote.Context{}, + }, + } + processRoundStats(stats) + assert.Equal(t, ActiveConflicts(), (uint64)(3)) +} + +func TestFailedConflicts(t *testing.T) { + // initialized to 0 + assert.Equal(t, FailedConflicts(), (uint64)(0)) + // simulate 10 failed conflicts + for i := 0; i < 10; i++ { + processFailed(vote.Context{}) + } + assert.Equal(t, FailedConflicts(), (uint64)(10)) + // simulate 10 failed conflicts + for i := 0; i < 10; i++ { + processFailed(vote.Context{}) + } + assert.Equal(t, FailedConflicts(), (uint64)(20)) +} +func TestFinalize(t *testing.T) { + assert.Equal(t, AverageRoundsToFinalize(), 0.0) + assert.Equal(t, FinalizedConflict(), (uint64)(0)) + // simulate 5 finalized conflicts with 5 rounds + for i := 0; i < 5; i++ { + processFinalized(vote.Context{ + Rounds: 5, + }) + } + assert.Equal(t, FinalizedConflict(), (uint64)(5)) + // simulate 5 finalized conflicts with 10 rounds + for i := 0; i < 5; i++ { + processFinalized(vote.Context{ + Rounds: 10, + }) + } + assert.Equal(t, FinalizedConflict(), (uint64)(10)) + // => average should be 7.5 + assert.Equal(t, AverageRoundsToFinalize(), 7.5) } diff --git a/plugins/metrics/message_test.go b/plugins/metrics/message_test.go index 76cf84d2dbb05b424894fb5dd0ac50bf436c6b7e..e326e813f1dec33f625fabc818c65b6754cf0f2e 100644 --- a/plugins/metrics/message_test.go +++ b/plugins/metrics/message_test.go @@ -53,25 +53,7 @@ func TestReceivedMPSUpdatedEvent(t *testing.T) { wg.Wait() } -func TestMPSPerPayloadSingle(t *testing.T) { - // it is empty initially - assert.Equal(t, MPSPerPayload(), map[payload.Type]uint64{}) - assert.Equal(t, MessageTotalCount(), (uint64)(0)) - // simulate attaching 10 value payloads in 0s < t < 1s - for i := 0; i < 10; i++ { - increasePerPayloadMPSCounter(valuepayload.Type) - } - // test measurement - measureMPSPerPayload() - assert.Equal(t, MPSPerPayload(), map[payload.Type]uint64{valuepayload.Type: 10}) - assert.Equal(t, MessageTotalCount(), (uint64)(10)) - // test counter reset on measurement - measureMPSPerPayload() - assert.Equal(t, MPSPerPayload(), map[payload.Type]uint64{valuepayload.Type: 0}) - assert.Equal(t, MessageTotalCount(), (uint64)(10)) -} - -func TestMPSPerPayloadMultiple(t *testing.T) { +func TestMPSPerPayload(t *testing.T) { // it is empty initially assert.Equal(t, MPSPerPayload(), map[payload.Type]uint64{}) assert.Equal(t, MessageTotalCount(), (uint64)(0)) diff --git a/plugins/metrics/plugin.go b/plugins/metrics/plugin.go index 3cce3be7800945ead604dbc3335214c3b1fab16d..c6c4a360e6c36ac0ecc54288d911d04917f206da 100644 --- a/plugins/metrics/plugin.go +++ b/plugins/metrics/plugin.go @@ -53,6 +53,16 @@ func configure(_ *node.Plugin) { processRoundStats(roundStats) })) + // a conflict has been finalized + valuetransfers.Voter().Events().Finalized.Attach(events.NewClosure(func(ev *vote.OpinionEvent) { + processFinalized(ev.Ctx) + })) + + // consensus failure in conflict resolution + valuetransfers.Voter().Events().Failed.Attach(events.NewClosure(func(ev *vote.OpinionEvent) { + processFailed(ev.Ctx) + })) + //// Events coming from metrics package //// metrics.Events().FPCInboundBytes.Attach(events.NewClosure(func(amountBytes uint64) {