From 002bdc73145db03491750adbd43e5b7360f58cbd Mon Sep 17 00:00:00 2001 From: thatben Date: Tue, 18 Mar 2025 15:54:55 +0100 Subject: [PATCH] Implements chainmetrics that publishes slotFill events count --- codexcrawler/components/chainmetrics.nim | 45 ++++++++++ codexcrawler/services/metrics.nim | 11 ++- .../components/testchainmetrics.nim | 90 +++++++++++++++++++ tests/codexcrawler/mocks/mockmarketplace.nim | 9 +- tests/codexcrawler/mocks/mockmetrics.nim | 4 + tests/codexcrawler/mocks/mockstate.nim | 2 +- tests/codexcrawler/testcomponents.nim | 9 +- 7 files changed, 158 insertions(+), 12 deletions(-) create mode 100644 codexcrawler/components/chainmetrics.nim create mode 100644 tests/codexcrawler/components/testchainmetrics.nim diff --git a/codexcrawler/components/chainmetrics.nim b/codexcrawler/components/chainmetrics.nim new file mode 100644 index 0000000..3aaf68c --- /dev/null +++ b/codexcrawler/components/chainmetrics.nim @@ -0,0 +1,45 @@ +import pkg/chronicles +import pkg/chronos +import pkg/questionable +import pkg/questionable/results + +import ../state +import ../services/metrics +import ../services/marketplace +import ../component + +logScope: + topics = "ChainMetrics" + +type ChainMetrics* = ref object of Component + state: State + metrics: Metrics + marketplace: MarketplaceService + +proc step(c: ChainMetrics): Future[?!void] {.async: (raises: []).} = + without slotFills =? (await c.marketplace.getRecentSlotFillEvents()), err: + trace "Unable to get recent slotFill events from chain", err = err.msg + return success() # We don't propagate this error. + # The call is allowed to fail and the app should continue as normal. + + c.metrics.setSlotFill(slotFills.len) + return success() + +method start*(c: ChainMetrics): Future[?!void] {.async.} = + info "Starting..." + + proc onStep(): Future[?!void] {.async: (raises: []), gcsafe.} = + return await c.step() + + if c.state.config.marketplaceEnable: + await c.state.whileRunning(onStep, 10.minutes) + + return success() + +method stop*(c: ChainMetrics): Future[?!void] {.async.} = + return success() + +proc new*( + T: type ChainMetrics, state: State, metrics: Metrics, marketplace: MarketplaceService +): ChainMetrics = + ChainMetrics(state: state, metrics: metrics, marketplace: marketplace) diff --git a/codexcrawler/services/metrics.nim b/codexcrawler/services/metrics.nim index 39d0b63..322b80c 100644 --- a/codexcrawler/services/metrics.nim +++ b/codexcrawler/services/metrics.nim @@ -6,6 +6,8 @@ declareGauge(todoNodesGauge, "DHT nodes to be visited") declareGauge(okNodesGauge, "DHT nodes successfully contacted") declareGauge(nokNodesGauge, "DHT nodes failed to contact") +declareGauge(slotFillGauge, "Marketplace recent slots filled") + type OnUpdateMetric = proc(value: int64): void {.gcsafe, raises: [].} @@ -13,6 +15,7 @@ type todoNodes: OnUpdateMetric okNodes: OnUpdateMetric nokNodes: OnUpdateMetric + onSlotFill: OnUpdateMetric proc startServer(metricsAddress: IpAddress, metricsPort: Port) = let metricsAddress = metricsAddress @@ -34,6 +37,9 @@ method setOkNodes*(m: Metrics, value: int) {.base, gcsafe, raises: [].} = method setNokNodes*(m: Metrics, value: int) {.base, gcsafe, raises: [].} = m.nokNodes(value.int64) +method setSlotFill*(m: Metrics, value: int) {.base, gcsafe, raises: [].} = + m.onSlotFill(value.int64) + proc createMetrics*(metricsAddress: IpAddress, metricsPort: Port): Metrics = startServer(metricsAddress, metricsPort) @@ -47,5 +53,8 @@ proc createMetrics*(metricsAddress: IpAddress, metricsPort: Port): Metrics = proc onNok(value: int64) = nokNodesGauge.set(value) + + proc onSlotFill(value: int64) = + slotFillGauge.set(value) - return Metrics(todoNodes: onTodo, okNodes: onOk, nokNodes: onNok) + return Metrics(todoNodes: onTodo, okNodes: onOk, nokNodes: onNok, onSlotFill: onSlotFill) diff --git a/tests/codexcrawler/components/testchainmetrics.nim b/tests/codexcrawler/components/testchainmetrics.nim new file mode 100644 index 0000000..4668e41 --- /dev/null +++ b/tests/codexcrawler/components/testchainmetrics.nim @@ -0,0 +1,90 @@ +import pkg/chronos +import pkg/questionable +import pkg/questionable/results +import pkg/asynctest/chronos/unittest +import std/sequtils + +import ../../../codexcrawler/components/chainmetrics +import ../../../codexcrawler/services/marketplace/market +import ../../../codexcrawler/types +import ../../../codexcrawler/state +import ../mocks/mockstate +import ../mocks/mockmetrics +import ../mocks/mockmarketplace +import ../helpers + +suite "ChainMetrics": + var + state: MockState + metrics: MockMetrics + marketplace: MockMarketplaceService + chain: ChainMetrics + + setup: + state = createMockState() + metrics = createMockMetrics() + marketplace = createMockMarketplaceService() + + metrics.slotFill = -1 + chain = ChainMetrics.new(state, metrics, marketplace) + + (await chain.start()).tryGet() + + teardown: + (await chain.stop()).tryGet() + state.checkAllUnsubscribed() + + proc onStep() {.async.} = + (await state.steppers[0]()).tryGet() + + test "start should start stepper for 10 minutes": + check: + state.delays.len == 1 + state.delays[0] == 10.minutes + + test "onStep is not activated when config.marketplaceEnable is false": + # Recreate chainMetrics, reset mockstate: + (await chain.stop()).tryGet() + state.steppers = @[] + # disable marketplace: + state.config.marketplaceEnable = false + (await chain.start()).tryGet() + + check: + state.steppers.len == 0 + + test "step should not call setSlotFill when getRecentSlotFillEvents fails": + let testValue = -123 + metrics.slotFill = testValue + + marketplace.recentSlotFillEventsReturn = seq[SlotFilled].failure("testfailure") + + await onStep() + + check: + metrics.slotFill == testValue + + test "step should setSlotFill to zero when getRecentSlotFillEvents returns empty seq": + metrics.slotFill = -123 + + marketplace.recentSlotFillEventsReturn = success(newSeq[SlotFilled]()) + + await onStep() + + check: + metrics.slotFill == 0 + + test "step should setSlotFill to the length of seq returned from getRecentSlotFillEvents": + let fills = @[ + SlotFilled(), + SlotFilled(), + SlotFilled(), + SlotFilled() + ] + + marketplace.recentSlotFillEventsReturn = success(fills) + + await onStep() + + check: + metrics.slotFill == fills.len diff --git a/tests/codexcrawler/mocks/mockmarketplace.nim b/tests/codexcrawler/mocks/mockmarketplace.nim index 2f90e6b..bf4a717 100644 --- a/tests/codexcrawler/mocks/mockmarketplace.nim +++ b/tests/codexcrawler/mocks/mockmarketplace.nim @@ -1,11 +1,8 @@ import pkg/ethers import pkg/questionable -import ./marketplace/market -import ./marketplace/marketplace -import ../config -import ../component -import ../state +import ../../../codexcrawler/services/marketplace +import ../../../codexcrawler/services/marketplace/market logScope: topics = "marketplace" @@ -14,7 +11,7 @@ type MockMarketplaceService* = ref object of MarketplaceService recentSlotFillEventsReturn*: ?!seq[SlotFilled] -method getRecentSlotFillEvents*(m: MarketplaceService): Future[?!seq[SlotFilled]] {.async: (raises: []).} = +method getRecentSlotFillEvents*(m: MockMarketplaceService): Future[?!seq[SlotFilled]] {.async: (raises: []).} = return m.recentSlotFillEventsReturn proc createMockMarketplaceService*(): MockMarketplaceService = diff --git a/tests/codexcrawler/mocks/mockmetrics.nim b/tests/codexcrawler/mocks/mockmetrics.nim index 8152e76..3b9a24a 100644 --- a/tests/codexcrawler/mocks/mockmetrics.nim +++ b/tests/codexcrawler/mocks/mockmetrics.nim @@ -4,6 +4,7 @@ type MockMetrics* = ref object of Metrics todo*: int ok*: int nok*: int + slotFill*: int method setTodoNodes*(m: MockMetrics, value: int) = m.todo = value @@ -14,5 +15,8 @@ method setOkNodes*(m: MockMetrics, value: int) = method setNokNodes*(m: MockMetrics, value: int) = m.nok = value +method setSlotFill*(m: MockMetrics, value: int) = + m.slotFill = value + proc createMockMetrics*(): MockMetrics = MockMetrics() diff --git a/tests/codexcrawler/mocks/mockstate.nim b/tests/codexcrawler/mocks/mockstate.nim index 329ca99..e61ae92 100644 --- a/tests/codexcrawler/mocks/mockstate.nim +++ b/tests/codexcrawler/mocks/mockstate.nim @@ -22,7 +22,7 @@ method whileRunning*(s: MockState, step: OnStep, delay: Duration) {.async.} = proc createMockState*(): MockState = MockState( status: ApplicationStatus.Running, - config: Config(dhtEnable: true), + config: Config(dhtEnable: true, marketplaceEnable: true), events: Events( nodesFound: newAsyncDataEvent[seq[Nid]](), newNodesDiscovered: newAsyncDataEvent[seq[Nid]](), diff --git a/tests/codexcrawler/testcomponents.nim b/tests/codexcrawler/testcomponents.nim index 0773ec0..a81a08f 100644 --- a/tests/codexcrawler/testcomponents.nim +++ b/tests/codexcrawler/testcomponents.nim @@ -1,7 +1,8 @@ -import ./components/testnodestore -import ./components/testdhtmetrics -import ./components/testtodolist -import ./components/testtimetracker +import ./components/testchainmetrics import ./components/testcrawler +import ./components/testdhtmetrics +import ./components/testnodestore +import ./components/testtimetracker +import ./components/testtodolist {.warning[UnusedImport]: off.}