envio 3.3.0 → 3.3.1-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/package.json +6 -7
  2. package/src/ChainFetching.res +4 -32
  3. package/src/ChainFetching.res.mjs +2 -12
  4. package/src/ChainState.res +73 -53
  5. package/src/ChainState.res.mjs +61 -30
  6. package/src/ChainState.resi +13 -17
  7. package/src/Config.res +5 -0
  8. package/src/Config.res.mjs +1 -0
  9. package/src/CrossChainState.res +2 -8
  10. package/src/CrossChainState.res.mjs +6 -8
  11. package/src/CrossChainState.resi +1 -0
  12. package/src/EffectState.res +183 -44
  13. package/src/EffectState.res.mjs +134 -26
  14. package/src/EffectState.resi +26 -3
  15. package/src/EventProcessing.res +14 -9
  16. package/src/EventProcessing.res.mjs +7 -7
  17. package/src/FetchState.res +18 -29
  18. package/src/FetchState.res.mjs +20 -37
  19. package/src/IndexerState.res +280 -31
  20. package/src/IndexerState.res.mjs +234 -25
  21. package/src/IndexerState.resi +22 -0
  22. package/src/LoadLayer.res +19 -65
  23. package/src/LoadLayer.res.mjs +13 -55
  24. package/src/Main.res +42 -23
  25. package/src/Main.res.mjs +32 -35
  26. package/src/Metrics.res +943 -66
  27. package/src/Metrics.res.mjs +367 -50
  28. package/src/Persistence.res +3 -0
  29. package/src/PgStorage.res +3 -8
  30. package/src/PgStorage.res.mjs +3 -4
  31. package/src/PruneStaleHistory.res +1 -1
  32. package/src/PruneStaleHistory.res.mjs +1 -2
  33. package/src/Rollback.res +4 -5
  34. package/src/Rollback.res.mjs +2 -3
  35. package/src/SimulateItems.res.mjs +3 -21
  36. package/src/TestIndexer.res.mjs +4 -23
  37. package/src/TestIndexerProxyStorage.res +1 -0
  38. package/src/TestIndexerProxyStorage.res.mjs +1 -1
  39. package/src/Writing.res +3 -5
  40. package/src/Writing.res.mjs +3 -6
  41. package/src/bindings/ClickHouse.res +101 -17
  42. package/src/bindings/ClickHouse.res.mjs +58 -19
  43. package/src/bindings/NodeJs.res +64 -0
  44. package/src/bindings/NodeJs.res.mjs +6 -0
  45. package/src/sources/HyperSyncClient.res +5 -11
  46. package/src/sources/SourceManager.res +72 -27
  47. package/src/sources/SourceManager.res.mjs +81 -11
  48. package/src/sources/SourceManager.resi +14 -0
  49. package/src/tui/Tui.res +1 -1
  50. package/src/tui/Tui.res.mjs +2 -2
  51. package/src/Prometheus.res +0 -789
  52. package/src/Prometheus.res.mjs +0 -847
  53. package/src/bindings/PromClient.res +0 -72
  54. package/src/bindings/PromClient.res.mjs +0 -38
@@ -74,7 +74,7 @@ let runEventHandlerOrThrow = async (
74
74
  }
75
75
  let handlerDuration = timeBeforeHandler->Performance.secondsSince
76
76
  let eventConfig = eventItem.onEventRegistration.eventConfig
77
- Prometheus.ProcessingHandler.increment(
77
+ indexerState->IndexerState.recordHandlerDuration(
78
78
  ~contract=eventConfig.contractName,
79
79
  ~event=eventConfig.name,
80
80
  ~duration=handlerDuration,
@@ -163,16 +163,18 @@ let preloadBatchOrThrow = async (
163
163
  let item = batch.items->Array.getUnsafe(itemIdx.contents + idx)
164
164
  switch item {
165
165
  | Event(_) =>
166
- let {handler, eventConfig: {contractName, name: eventName}} =
167
- (item->Internal.castUnsafeEventItem).onEventRegistration
166
+ let {handler, eventConfig: {contractName, name: eventName}} = (
167
+ item->Internal.castUnsafeEventItem
168
+ ).onEventRegistration
168
169
  switch handler {
169
170
  | None => ()
170
171
  | Some(handler) =>
171
172
  try {
172
- let timerRef = Prometheus.PreloadHandler.startOperation(
173
- ~contract=contractName,
174
- ~event=eventName,
175
- )
173
+ let timerRef =
174
+ indexerState->IndexerState.startPreloadHandler(
175
+ ~contract=contractName,
176
+ ~event=eventName,
177
+ )
176
178
  promises->Array.push(
177
179
  handler({
178
180
  event: item->Ecosystem.getItemEvent(~ecosystem=config.ecosystem),
@@ -189,7 +191,8 @@ let preloadBatchOrThrow = async (
189
191
  }),
190
192
  })
191
193
  ->Promise.thenResolve(_ => {
192
- timerRef->Prometheus.PreloadHandler.endOperation(
194
+ indexerState->IndexerState.endPreloadHandler(
195
+ timerRef,
193
196
  ~contract=contractName,
194
197
  ~event=eventName,
195
198
  )
@@ -270,6 +273,7 @@ let runBatchHandlersOrThrow = async (
270
273
  let registerProcessEventBatchMetrics = (
271
274
  ~logger,
272
275
  ~batch: Batch.t,
276
+ ~indexerState,
273
277
  ~loadDuration,
274
278
  ~handlerDuration,
275
279
  ) => {
@@ -282,7 +286,7 @@ let registerProcessEventBatchMetrics = (
282
286
  })
283
287
  })
284
288
 
285
- Prometheus.ProcessingBatch.registerMetrics(~loadDuration, ~handlerDuration)
289
+ indexerState->IndexerState.recordBatchDurations(~loadDuration, ~handlerDuration)
286
290
  }
287
291
 
288
292
  type logPartitionInfo = {
@@ -380,6 +384,7 @@ let processEventBatch = async (
380
384
  registerProcessEventBatchMetrics(
381
385
  ~logger,
382
386
  ~batch,
387
+ ~indexerState,
383
388
  ~loadDuration=loaderDuration,
384
389
  ~handlerDuration,
385
390
  )
@@ -6,12 +6,12 @@ import * as Writing from "./Writing.res.mjs";
6
6
  import * as Internal from "./Internal.res.mjs";
7
7
  import * as Ecosystem from "./Ecosystem.res.mjs";
8
8
  import * as ChainState from "./ChainState.res.mjs";
9
- import * as Prometheus from "./Prometheus.res.mjs";
10
9
  import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
11
10
  import * as Performance from "./bindings/Performance.res.mjs";
12
11
  import * as Persistence from "./Persistence.res.mjs";
13
12
  import * as Stdlib_Dict from "@rescript/runtime/lib/es6/Stdlib_Dict.js";
14
13
  import * as UserContext from "./UserContext.res.mjs";
14
+ import * as IndexerState from "./IndexerState.res.mjs";
15
15
  import * as ErrorHandling from "./ErrorHandling.res.mjs";
16
16
  import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
17
17
 
@@ -66,7 +66,7 @@ async function runEventHandlerOrThrow(item, checkpointId, handler, indexerState,
66
66
  }
67
67
  let handlerDuration = Performance.secondsSince(timeBeforeHandler);
68
68
  let eventConfig = item.onEventRegistration.eventConfig;
69
- return Prometheus.ProcessingHandler.increment(eventConfig.contractName, eventConfig.name, handlerDuration);
69
+ return IndexerState.recordHandlerDuration(indexerState, eventConfig.contractName, eventConfig.name, handlerDuration);
70
70
  }
71
71
 
72
72
  async function runHandlerOrThrow(item, checkpointId, indexerState, loadManager, persistence, config, chains) {
@@ -121,7 +121,7 @@ async function preloadBatchOrThrow(batch, loadManager, persistence, config, inde
121
121
  let contractName = match$1.contractName;
122
122
  let eventName = match$1.name;
123
123
  try {
124
- let timerRef = Prometheus.PreloadHandler.startOperation(contractName, eventName);
124
+ let timerRef = IndexerState.startPreloadHandler(indexerState, contractName, eventName);
125
125
  promises.push(Utils.$$Promise.silentCatch(handler({
126
126
  event: Ecosystem.getItemEvent(item, config.ecosystem),
127
127
  context: UserContext.getHandlerContext({
@@ -135,7 +135,7 @@ async function preloadBatchOrThrow(batch, loadManager, persistence, config, inde
135
135
  config: config,
136
136
  isResolved: false
137
137
  })
138
- }).then(() => Prometheus.PreloadHandler.endOperation(timerRef, contractName, eventName))));
138
+ }).then(() => IndexerState.endPreloadHandler(indexerState, timerRef, contractName, eventName))));
139
139
  } catch (exn) {
140
140
 
141
141
  }
@@ -176,14 +176,14 @@ async function runBatchHandlersOrThrow(batch, indexerState, loadManager, persist
176
176
  }
177
177
  }
178
178
 
179
- function registerProcessEventBatchMetrics(logger, batch, loadDuration, handlerDuration) {
179
+ function registerProcessEventBatchMetrics(logger, batch, indexerState, loadDuration, handlerDuration) {
180
180
  Stdlib_Dict.forEachWithKey(batch.progressedChainsById, (chainAfterBatch, chainId) => Logging.childTrace(logger, {
181
181
  msg: "Finished processing",
182
182
  chainId: Stdlib_Int.fromString(chainId, undefined),
183
183
  batchSize: chainAfterBatch.batchSize,
184
184
  progress: chainAfterBatch.progressBlockNumber
185
185
  }));
186
- Prometheus.ProcessingBatch.registerMetrics(loadDuration, handlerDuration);
186
+ IndexerState.recordBatchDurations(indexerState, loadDuration, handlerDuration);
187
187
  }
188
188
 
189
189
  async function materializeBatchEvents(batch, chainStates, ecosystem) {
@@ -231,7 +231,7 @@ async function processEventBatch(batch, indexerState, loadManager, persistence,
231
231
  let elapsedTimeAfterProcessing = Performance.secondsSince(timeRef);
232
232
  Writing.commitBatch(indexerState, batch);
233
233
  let handlerDuration = elapsedTimeAfterProcessing - elapsedTimeAfterLoaders;
234
- registerProcessEventBatchMetrics(logger, batch, elapsedTimeAfterLoaders, handlerDuration);
234
+ registerProcessEventBatchMetrics(logger, batch, indexerState, elapsedTimeAfterLoaders, handlerDuration);
235
235
  return {
236
236
  TAG: "Ok",
237
237
  _0: undefined
@@ -909,19 +909,6 @@ let updateInternal = (
909
909
  firstEventBlock: fetchState.firstEventBlock,
910
910
  }
911
911
 
912
- Prometheus.IndexingPartitions.set(
913
- ~partitionsCount=optimizedPartitions->OptimizedPartitions.count,
914
- ~chainId=fetchState.chainId,
915
- )
916
- Prometheus.IndexingBufferSize.set(
917
- ~bufferSize=updatedFetchState.buffer->Array.length,
918
- ~chainId=fetchState.chainId,
919
- )
920
- Prometheus.IndexingBufferBlockNumber.set(
921
- ~blockNumber=updatedFetchState->bufferBlockNumber,
922
- ~chainId=fetchState.chainId,
923
- )
924
-
925
912
  updatedFetchState
926
913
  }
927
914
 
@@ -2340,22 +2327,13 @@ let make = (
2340
2327
  firstEventBlock,
2341
2328
  }
2342
2329
 
2343
- Prometheus.IndexingPartitions.set(
2344
- ~partitionsCount=optimizedPartitions->OptimizedPartitions.count,
2345
- ~chainId,
2346
- )
2347
- Prometheus.IndexingBufferSize.set(~bufferSize=buffer->Array.length, ~chainId)
2348
- Prometheus.IndexingBufferBlockNumber.set(~blockNumber=fetchState->bufferBlockNumber, ~chainId)
2349
- switch endBlock {
2350
- | Some(endBlock) => Prometheus.IndexingEndBlock.set(~endBlock, ~chainId)
2351
- | None => ()
2352
- }
2353
-
2354
2330
  fetchState
2355
2331
  }
2356
2332
 
2357
2333
  let bufferSize = ({buffer}: t) => buffer->Array.length
2358
2334
 
2335
+ let partitionsCount = ({optimizedPartitions}: t) => optimizedPartitions->OptimizedPartitions.count
2336
+
2359
2337
  let rollbackPendingQueries = (mutPendingQueries: array<pendingQuery>, ~targetBlockNumber) => {
2360
2338
  // - Remove queries where fromBlock > target
2361
2339
  // - Cap fetchedBlock at target where fetchedBlock > target
@@ -2556,14 +2534,26 @@ let isFetchingAtHead = ({endBlock, blockLag, knownHeight} as fetchState: t) => {
2556
2534
  }
2557
2535
  }
2558
2536
 
2559
- let isReadyToEnterReorgThreshold = ({endBlock, blockLag, buffer, knownHeight} as fetchState: t) => {
2537
+ // Frontier at (or within `tolerance` of) the lagged head with no processable
2538
+ // items left — the moment the chain can cross into the reorg threshold.
2539
+ // `tolerance` absorbs the head advancing between a chain catching up and this
2540
+ // check, so it applies only when the (moving) head is the target: a finite
2541
+ // endBlock at or below the lagged head is an exact target and is reached without
2542
+ // tolerance. Uses bufferReadyCount, not an empty buffer: items stuck above the
2543
+ // frontier behind a lagging partition's gap are reorg-safe (all <= head -
2544
+ // blockLag) and must not defer entry, or a many-partition chain never enters.
2545
+ let isReadyToEnterReorgThreshold = (
2546
+ ~tolerance,
2547
+ {endBlock, blockLag, knownHeight} as fetchState: t,
2548
+ ) => {
2560
2549
  let bufferBlockNumber = fetchState->bufferBlockNumber
2550
+ let laggedHead = knownHeight - blockLag
2561
2551
  knownHeight !== 0 &&
2562
2552
  switch endBlock {
2563
- | Some(endBlock) if bufferBlockNumber >= endBlock => true
2564
- | _ => bufferBlockNumber >= knownHeight - blockLag
2553
+ | Some(endBlock) if endBlock <= laggedHead => bufferBlockNumber >= endBlock
2554
+ | _ => bufferBlockNumber >= laggedHead - tolerance
2565
2555
  } &&
2566
- buffer->Utils.Array.isEmpty
2556
+ fetchState->bufferReadyCount == 0
2567
2557
  }
2568
2558
 
2569
2559
  // Lower progress percentage = further behind = higher priority. Progress is
@@ -2630,7 +2620,6 @@ let getProgressBlockNumberAt = ({buffer} as fetchState: t, ~index) => {
2630
2620
 
2631
2621
  let updateKnownHeight = (fetchState: t, ~knownHeight) => {
2632
2622
  if knownHeight > fetchState.knownHeight {
2633
- Prometheus.IndexingKnownHeight.set(~blockNumber=knownHeight, ~chainId=fetchState.chainId)
2634
2623
  fetchState->updateInternal(~knownHeight)
2635
2624
  } else {
2636
2625
  fetchState
@@ -3,7 +3,6 @@
3
3
  import * as Utils from "./Utils.res.mjs";
4
4
  import * as Js_math from "@rescript/runtime/lib/es6/Js_math.js";
5
5
  import * as Logging from "./Logging.res.mjs";
6
- import * as Prometheus from "./Prometheus.res.mjs";
7
6
  import * as Stdlib_Int from "@rescript/runtime/lib/es6/Stdlib_Int.js";
8
7
  import * as Stdlib_Array from "@rescript/runtime/lib/es6/Stdlib_Array.js";
9
8
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
@@ -679,34 +678,21 @@ function updateInternal(fetchState, optimizedPartitionsOpt, mutItems, mutItemsSo
679
678
  } else {
680
679
  latestOnBlockBlockNumber = knownHeight;
681
680
  }
682
- let updatedFetchState_startBlock = fetchState.startBlock;
683
- let updatedFetchState_endBlock = fetchState.endBlock;
684
- let updatedFetchState_normalSelection = fetchState.normalSelection;
685
- let updatedFetchState_contractConfigs = fetchState.contractConfigs;
686
- let updatedFetchState_chainId = fetchState.chainId;
687
- let updatedFetchState_buffer = blockItems.length !== 0 ? mergeIntoBuffer(base, blockItems) : base;
688
- let updatedFetchState_maxOnBlockBufferSize = fetchState.maxOnBlockBufferSize;
689
- let updatedFetchState_onBlockRegistrations = fetchState.onBlockRegistrations;
690
- let updatedFetchState_firstEventBlock = fetchState.firstEventBlock;
691
- let updatedFetchState = {
681
+ return {
692
682
  optimizedPartitions: optimizedPartitions,
693
- startBlock: updatedFetchState_startBlock,
694
- endBlock: updatedFetchState_endBlock,
695
- normalSelection: updatedFetchState_normalSelection,
696
- contractConfigs: updatedFetchState_contractConfigs,
697
- chainId: updatedFetchState_chainId,
683
+ startBlock: fetchState.startBlock,
684
+ endBlock: fetchState.endBlock,
685
+ normalSelection: fetchState.normalSelection,
686
+ contractConfigs: fetchState.contractConfigs,
687
+ chainId: fetchState.chainId,
698
688
  latestOnBlockBlockNumber: latestOnBlockBlockNumber,
699
689
  blockLag: blockLag,
700
- buffer: updatedFetchState_buffer,
701
- maxOnBlockBufferSize: updatedFetchState_maxOnBlockBufferSize,
702
- onBlockRegistrations: updatedFetchState_onBlockRegistrations,
690
+ buffer: blockItems.length !== 0 ? mergeIntoBuffer(base, blockItems) : base,
691
+ maxOnBlockBufferSize: fetchState.maxOnBlockBufferSize,
692
+ onBlockRegistrations: fetchState.onBlockRegistrations,
703
693
  knownHeight: knownHeight,
704
- firstEventBlock: updatedFetchState_firstEventBlock
694
+ firstEventBlock: fetchState.firstEventBlock
705
695
  };
706
- Prometheus.IndexingPartitions.set(optimizedPartitions.idsInAscOrder.length, fetchState.chainId);
707
- Prometheus.IndexingBufferSize.set(updatedFetchState_buffer.length, fetchState.chainId);
708
- Prometheus.IndexingBufferBlockNumber.set(bufferBlockNumber(updatedFetchState), fetchState.chainId);
709
- return updatedFetchState;
710
696
  }
711
697
 
712
698
  function warnDifferentContractType(fetchState, existingContract, dc) {
@@ -1525,7 +1511,7 @@ function make$1(startBlock, endBlock, onEventRegistrations, contractConfigs, add
1525
1511
  } else {
1526
1512
  latestOnBlockBlockNumber = progressBlockNumber;
1527
1513
  }
1528
- let fetchState = {
1514
+ return {
1529
1515
  optimizedPartitions: optimizedPartitions,
1530
1516
  startBlock: startBlock,
1531
1517
  endBlock: endBlock,
@@ -1540,19 +1526,16 @@ function make$1(startBlock, endBlock, onEventRegistrations, contractConfigs, add
1540
1526
  knownHeight: knownHeight,
1541
1527
  firstEventBlock: firstEventBlock
1542
1528
  };
1543
- Prometheus.IndexingPartitions.set(optimizedPartitions.idsInAscOrder.length, chainId);
1544
- Prometheus.IndexingBufferSize.set(buffer.length, chainId);
1545
- Prometheus.IndexingBufferBlockNumber.set(bufferBlockNumber(fetchState), chainId);
1546
- if (endBlock !== undefined) {
1547
- Prometheus.IndexingEndBlock.set(endBlock, chainId);
1548
- }
1549
- return fetchState;
1550
1529
  }
1551
1530
 
1552
1531
  function bufferSize(param) {
1553
1532
  return param.buffer.length;
1554
1533
  }
1555
1534
 
1535
+ function partitionsCount(param) {
1536
+ return param.optimizedPartitions.idsInAscOrder.length;
1537
+ }
1538
+
1556
1539
  function rollbackPendingQueries(mutPendingQueries, targetBlockNumber) {
1557
1540
  let adjusted = [];
1558
1541
  for (let qIdx = 0, qIdx_finish = mutPendingQueries.length; qIdx < qIdx_finish; ++qIdx) {
@@ -1750,15 +1733,15 @@ function isFetchingAtHead(fetchState) {
1750
1733
  }
1751
1734
  }
1752
1735
 
1753
- function isReadyToEnterReorgThreshold(fetchState) {
1736
+ function isReadyToEnterReorgThreshold(tolerance, fetchState) {
1754
1737
  let knownHeight = fetchState.knownHeight;
1755
- let blockLag = fetchState.blockLag;
1756
1738
  let endBlock = fetchState.endBlock;
1757
1739
  let bufferBlockNumber$1 = bufferBlockNumber(fetchState);
1740
+ let laggedHead = knownHeight - fetchState.blockLag | 0;
1758
1741
  if (knownHeight !== 0 && (
1759
- endBlock !== undefined && bufferBlockNumber$1 >= endBlock ? true : bufferBlockNumber$1 >= (knownHeight - blockLag | 0)
1742
+ endBlock !== undefined && endBlock <= laggedHead ? bufferBlockNumber$1 >= endBlock : bufferBlockNumber$1 >= (laggedHead - tolerance | 0)
1760
1743
  )) {
1761
- return Utils.$$Array.isEmpty(fetchState.buffer);
1744
+ return bufferReadyCount(fetchState) === 0;
1762
1745
  } else {
1763
1746
  return false;
1764
1747
  }
@@ -1824,7 +1807,6 @@ function getProgressBlockNumberAt(fetchState, index) {
1824
1807
 
1825
1808
  function updateKnownHeight(fetchState, knownHeight) {
1826
1809
  if (knownHeight > fetchState.knownHeight) {
1827
- Prometheus.IndexingKnownHeight.set(knownHeight, fetchState.chainId);
1828
1810
  return updateInternal(fetchState, undefined, undefined, undefined, undefined, knownHeight);
1829
1811
  } else {
1830
1812
  return fetchState;
@@ -1876,6 +1858,7 @@ export {
1876
1858
  getReadyItemsCount,
1877
1859
  make$1 as make,
1878
1860
  bufferSize,
1861
+ partitionsCount,
1879
1862
  rollbackPendingQueries,
1880
1863
  rollback,
1881
1864
  resetPendingQueries,
@@ -28,6 +28,49 @@ module EntityTables = {
28
28
  }
29
29
  }
30
30
 
31
+ // Per-(contract, event) handler counters rendered into the
32
+ // envio_processing_handler_* and envio_preload_handler_* metrics.
33
+ type handlerStat = {
34
+ contract: string,
35
+ event: string,
36
+ mutable processingSeconds: float,
37
+ // Floats: cumulative counters outgrow int32.
38
+ mutable processingCount: float,
39
+ // Wall-clock preload time: overlapping preloads of the same handler count once.
40
+ mutable preloadSeconds: float,
41
+ mutable preloadCount: float,
42
+ // Cumulative per-call preload time; exceeds preloadSeconds under parallel execution.
43
+ mutable preloadSecondsTotal: float,
44
+ mutable preloadPendingCount: int,
45
+ mutable preloadPendingTimerRef: Performance.timeRef,
46
+ }
47
+
48
+ // Per-(storage, operation) load counters rendered into the
49
+ // envio_storage_load_* metrics.
50
+ type storageLoadStat = {
51
+ operation: string,
52
+ storage: string,
53
+ // Wall-clock load time: overlapping loads of the same operation count once.
54
+ mutable seconds: float,
55
+ mutable secondsTotal: float,
56
+ mutable count: float,
57
+ mutable whereSize: float,
58
+ mutable size: float,
59
+ mutable pendingCount: int,
60
+ mutable pendingTimerRef: Performance.timeRef,
61
+ }
62
+
63
+ type storageWriteStat = {
64
+ storage: string,
65
+ mutable seconds: float,
66
+ mutable count: int,
67
+ }
68
+
69
+ type historyPruneStat = {
70
+ mutable seconds: float,
71
+ mutable count: int,
72
+ }
73
+
31
74
  type t = {
32
75
  config: Config.t,
33
76
  persistence: Persistence.t,
@@ -82,6 +125,16 @@ type t = {
82
125
  mutable epoch: int,
83
126
  // None off the simulate path.
84
127
  simulateDeadInputTracker: option<SimulateDeadInputTracker.t>,
128
+ // --- Metric counters, rendered by Metrics at scrape time. ---
129
+ mutable preloadSeconds: float,
130
+ mutable processingSeconds: float,
131
+ handlerStats: dict<handlerStat>,
132
+ storageLoadStats: dict<storageLoadStat>,
133
+ storageWriteStats: dict<storageWriteStat>,
134
+ historyPruneStats: dict<historyPruneStat>,
135
+ mutable rollbackSeconds: float,
136
+ mutable rollbackCount: int,
137
+ mutable rollbackEventsCount: float,
85
138
  }
86
139
 
87
140
  let make = (
@@ -144,6 +197,15 @@ let make = (
144
197
  isStopped: false,
145
198
  epoch: 0,
146
199
  simulateDeadInputTracker: SimulateDeadInputTracker.makeFromConfig(config),
200
+ preloadSeconds: 0.,
201
+ processingSeconds: 0.,
202
+ handlerStats: Dict.make(),
203
+ storageLoadStats: Dict.make(),
204
+ storageWriteStats: Dict.make(),
205
+ historyPruneStats: Dict.make(),
206
+ rollbackSeconds: 0.,
207
+ rollbackCount: 0,
208
+ rollbackEventsCount: 0.,
147
209
  }
148
210
  }
149
211
 
@@ -181,16 +243,6 @@ let makeFromDbState = (
181
243
  )
182
244
  }
183
245
 
184
- Prometheus.ProcessingMaxBatchSize.set(~maxBatchSize=config.batchSize)
185
- Prometheus.ReorgThreshold.set(~isInReorgThreshold)
186
- initialState.cache->Utils.Dict.forEach(({effectName, count, scope}) => {
187
- Prometheus.EffectCacheCount.set(
188
- ~count,
189
- ~effectName,
190
- ~scope=scope->Internal.EffectCache.scopeToString,
191
- )
192
- })
193
-
194
246
  // updateSyncTimeOnRestart wipes the saved timestamp so a restart re-enters
195
247
  // backfill mode for all chains.
196
248
  let isRealtime =
@@ -216,27 +268,7 @@ let makeFromDbState = (
216
268
  )
217
269
  })
218
270
 
219
- // Set initial progress metrics from DB state so dashboards reflect
220
- // the persisted state immediately on restart
221
- let allChainsReady = ref(initialState.chains->Array.length > 0)
222
- chainStates->Utils.Dict.forEach(cs => {
223
- let chainId = (cs->ChainState.chainConfig).id
224
- Prometheus.ProgressBlockNumber.set(
225
- ~blockNumber=cs->ChainState.committedProgressBlockNumber,
226
- ~chainId,
227
- )
228
- Prometheus.ProgressReady.init(~chainId)
229
- if cs->ChainState.isReady {
230
- Prometheus.ProgressReady.set(~chainId)
231
- } else {
232
- allChainsReady := false
233
- }
234
- })
235
- if allChainsReady.contents {
236
- Prometheus.ProgressReady.setAllReady()
237
- }
238
-
239
- make(
271
+ let state = make(
240
272
  ~config,
241
273
  ~persistence,
242
274
  ~chainStates,
@@ -249,6 +281,10 @@ let makeFromDbState = (
249
281
  ~exitAfterFirstEventBlock,
250
282
  ~onError,
251
283
  )
284
+ initialState.cache->Utils.Dict.forEach(({effectName, count, scope}) => {
285
+ state.effectState->EffectState.setUnregisteredCacheCount(~effectName, ~scope, ~count)
286
+ })
287
+ state
252
288
  }
253
289
 
254
290
  // A fetch response or new-block waiter is stale once the indexer stopped or the
@@ -384,6 +420,219 @@ let epoch = (state: t) => state.epoch
384
420
  let lastPrunedAtMillis = (state: t) => state.lastPrunedAtMillis
385
421
  let simulateDeadInputTracker = (state: t) => state.simulateDeadInputTracker
386
422
 
423
+ // Read-only snapshot of every metric; the single window into the mutable
424
+ // counters for the /metrics endpoint, the TUI and the console API.
425
+ let toMetrics = (state: t): Metrics.t => {
426
+ let chainStates = state.crossChainState->CrossChainState.chainStates
427
+ let sourceRequests = []
428
+ let sourceHeights = []
429
+ chainStates->Utils.Dict.forEach(cs => {
430
+ let sourceManager = cs->ChainState.sourceManager
431
+ sourceManager
432
+ ->SourceManager.getRequestStatSamples
433
+ ->Array.forEach(s =>
434
+ sourceRequests->Array.push({
435
+ Metrics.source: s.sourceName,
436
+ chainId: s.chainId,
437
+ method: s.method,
438
+ count: s.count,
439
+ seconds: s.seconds,
440
+ })
441
+ )
442
+ sourceManager
443
+ ->SourceManager.getSourceHeightSamples
444
+ ->Array.forEach(s =>
445
+ sourceHeights->Array.push({
446
+ Metrics.source: s.sourceName,
447
+ chainId: s.chainId,
448
+ height: s.height,
449
+ })
450
+ )
451
+ })
452
+ let historyPrunes = []
453
+ state.historyPruneStats->Utils.Dict.forEachWithKey((s, entityName) =>
454
+ historyPrunes->Array.push({
455
+ Metrics.entity: entityName,
456
+ seconds: s.seconds,
457
+ count: s.count,
458
+ })
459
+ )
460
+ {
461
+ startTime: state.indexerStartTime,
462
+ targetBufferSize: state.crossChainState->CrossChainState.targetBufferSize,
463
+ isInReorgThreshold: state.crossChainState->CrossChainState.isInReorgThreshold,
464
+ rollbackEnabled: state.config.shouldRollbackOnReorg,
465
+ maxBatchSize: state.config.batchSize,
466
+ preloadSeconds: state.preloadSeconds,
467
+ processingSeconds: state.processingSeconds,
468
+ rollbackSeconds: state.rollbackSeconds,
469
+ rollbackCount: state.rollbackCount,
470
+ rollbackEventsCount: state.rollbackEventsCount,
471
+ chains: chainStates->Dict.valuesToArray->Array.map(ChainState.toMetrics),
472
+ handlers: state.handlerStats
473
+ ->Dict.valuesToArray
474
+ ->Array.map(s => {
475
+ Metrics.contract: s.contract,
476
+ event: s.event,
477
+ processingSeconds: s.processingSeconds,
478
+ processingCount: s.processingCount,
479
+ preloadSeconds: s.preloadSeconds,
480
+ preloadCount: s.preloadCount,
481
+ preloadSecondsTotal: s.preloadSecondsTotal,
482
+ }),
483
+ effects: state.effectState->EffectState.toMetrics,
484
+ storageLoads: state.storageLoadStats
485
+ ->Dict.valuesToArray
486
+ ->Array.map(s => {
487
+ Metrics.operation: s.operation,
488
+ storage: s.storage,
489
+ seconds: s.seconds,
490
+ secondsTotal: s.secondsTotal,
491
+ count: s.count,
492
+ whereSize: s.whereSize,
493
+ size: s.size,
494
+ }),
495
+ storageWrites: state.storageWriteStats
496
+ ->Dict.valuesToArray
497
+ ->Array.map(s => {
498
+ Metrics.storage: s.storage,
499
+ seconds: s.seconds,
500
+ count: s.count,
501
+ }),
502
+ historyPrunes,
503
+ sourceRequests,
504
+ sourceHeights,
505
+ }
506
+ }
507
+
508
+ // --- Metric counters. ---
509
+
510
+ let recordBatchDurations = (state: t, ~loadDuration, ~handlerDuration) => {
511
+ state.preloadSeconds = state.preloadSeconds +. loadDuration
512
+ state.processingSeconds = state.processingSeconds +. handlerDuration
513
+ }
514
+
515
+ let getHandlerStat = (state: t, ~contract, ~event) => {
516
+ // Length-prefixed so names containing the separator can't collide.
517
+ let key = contract->String.length->Int.toString ++ ":" ++ contract ++ event
518
+ switch state.handlerStats->Utils.Dict.dangerouslyGetNonOption(key) {
519
+ | Some(stat) => stat
520
+ | None =>
521
+ let stat: handlerStat = {
522
+ contract,
523
+ event,
524
+ processingSeconds: 0.,
525
+ processingCount: 0.,
526
+ preloadSeconds: 0.,
527
+ preloadCount: 0.,
528
+ preloadSecondsTotal: 0.,
529
+ preloadPendingCount: 0,
530
+ preloadPendingTimerRef: %raw(`null`),
531
+ }
532
+ state.handlerStats->Dict.set(key, stat)
533
+ stat
534
+ }
535
+ }
536
+
537
+ let recordHandlerDuration = (state: t, ~contract, ~event, ~duration) => {
538
+ let stat = state->getHandlerStat(~contract, ~event)
539
+ stat.processingSeconds = stat.processingSeconds +. duration
540
+ stat.processingCount = stat.processingCount +. 1.
541
+ }
542
+
543
+ let startPreloadHandler = (state: t, ~contract, ~event) => {
544
+ let stat = state->getHandlerStat(~contract, ~event)
545
+ if stat.preloadPendingCount === 0 {
546
+ stat.preloadPendingTimerRef = Performance.now()
547
+ }
548
+ stat.preloadPendingCount = stat.preloadPendingCount + 1
549
+ Performance.now()
550
+ }
551
+
552
+ let endPreloadHandler = (state: t, timerRef, ~contract, ~event) => {
553
+ let stat = state->getHandlerStat(~contract, ~event)
554
+ stat.preloadPendingCount = stat.preloadPendingCount - 1
555
+ if stat.preloadPendingCount === 0 {
556
+ stat.preloadSeconds =
557
+ stat.preloadSeconds +. stat.preloadPendingTimerRef->Performance.secondsSince
558
+ }
559
+ stat.preloadSecondsTotal = stat.preloadSecondsTotal +. timerRef->Performance.secondsSince
560
+ stat.preloadCount = stat.preloadCount +. 1.
561
+ }
562
+
563
+ let getStorageLoadStat = (state: t, ~storage, ~operation) => {
564
+ // Length-prefixed so names containing the separator can't collide.
565
+ let key = storage->String.length->Int.toString ++ ":" ++ storage ++ operation
566
+ switch state.storageLoadStats->Utils.Dict.dangerouslyGetNonOption(key) {
567
+ | Some(stat) => stat
568
+ | None =>
569
+ let stat: storageLoadStat = {
570
+ operation,
571
+ storage,
572
+ seconds: 0.,
573
+ secondsTotal: 0.,
574
+ count: 0.,
575
+ whereSize: 0.,
576
+ size: 0.,
577
+ pendingCount: 0,
578
+ pendingTimerRef: %raw(`null`),
579
+ }
580
+ state.storageLoadStats->Dict.set(key, stat)
581
+ stat
582
+ }
583
+ }
584
+
585
+ let startStorageLoad = (state: t, ~storage, ~operation) => {
586
+ let stat = state->getStorageLoadStat(~storage, ~operation)
587
+ if stat.pendingCount === 0 {
588
+ stat.pendingTimerRef = Performance.now()
589
+ }
590
+ stat.pendingCount = stat.pendingCount + 1
591
+ Performance.now()
592
+ }
593
+
594
+ let endStorageLoad = (state: t, timerRef, ~storage, ~operation, ~whereSize, ~size) => {
595
+ let stat = state->getStorageLoadStat(~storage, ~operation)
596
+ stat.pendingCount = stat.pendingCount - 1
597
+ if stat.pendingCount === 0 {
598
+ stat.seconds = stat.seconds +. stat.pendingTimerRef->Performance.secondsSince
599
+ }
600
+ stat.secondsTotal = stat.secondsTotal +. timerRef->Performance.secondsSince
601
+ stat.count = stat.count +. 1.
602
+ stat.whereSize = stat.whereSize +. whereSize->Int.toFloat
603
+ stat.size = stat.size +. size->Int.toFloat
604
+ }
605
+
606
+ let recordStorageWrite = (state: t, ~storage, ~timeSeconds) => {
607
+ let stat = switch state.storageWriteStats->Utils.Dict.dangerouslyGetNonOption(storage) {
608
+ | Some(stat) => stat
609
+ | None =>
610
+ let stat: storageWriteStat = {storage, seconds: 0., count: 0}
611
+ state.storageWriteStats->Dict.set(storage, stat)
612
+ stat
613
+ }
614
+ stat.seconds = stat.seconds +. timeSeconds
615
+ stat.count = stat.count + 1
616
+ }
617
+
618
+ let recordHistoryPrune = (state: t, ~entityName, ~timeSeconds) => {
619
+ let stat = switch state.historyPruneStats->Utils.Dict.dangerouslyGetNonOption(entityName) {
620
+ | Some(stat) => stat
621
+ | None =>
622
+ let stat: historyPruneStat = {seconds: 0., count: 0}
623
+ state.historyPruneStats->Dict.set(entityName, stat)
624
+ stat
625
+ }
626
+ stat.seconds = stat.seconds +. timeSeconds
627
+ stat.count = stat.count + 1
628
+ }
629
+
630
+ let recordRollbackSuccess = (state: t, ~timeSeconds, ~rollbackedProcessedEvents) => {
631
+ state.rollbackSeconds = state.rollbackSeconds +. timeSeconds
632
+ state.rollbackCount = state.rollbackCount + 1
633
+ state.rollbackEventsCount = state.rollbackEventsCount +. rollbackedProcessedEvents
634
+ }
635
+
387
636
  // --- Store domain operations. ---
388
637
 
389
638
  // Queue a processed batch for writing and advance the processing frontier.