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
@@ -44,6 +44,11 @@ type t = {
44
44
  sourcesState: array<sourceState>,
45
45
  mutable statusStart: Performance.timeRef,
46
46
  mutable status: sourceManagerStatus,
47
+ // Cumulative time spent in each status, rendered into the
48
+ // envio_indexing_idle/source_waiting/source_querying_seconds counters.
49
+ mutable idleSeconds: float,
50
+ mutable waitingForNewBlockSeconds: float,
51
+ mutable queryingSeconds: float,
47
52
  newBlockStallTimeout: int,
48
53
  newBlockStallTimeoutRealtime: int,
49
54
  stalledPollingInterval: int,
@@ -92,6 +97,55 @@ let getRequestStatSamples = (sourceManager: t): array<requestStatSample> => {
92
97
  samples
93
98
  }
94
99
 
100
+ // Per-source known heights for envio_source_known_height. Sources with no
101
+ // observed height yet are skipped.
102
+ type sourceHeightSample = {
103
+ sourceName: string,
104
+ chainId: int,
105
+ height: int,
106
+ }
107
+
108
+ let getSourceHeightSamples = (sourceManager: t): array<sourceHeightSample> => {
109
+ let samples = []
110
+ sourceManager.sourcesState->Array.forEach(sourceState => {
111
+ if sourceState.knownHeight > 0 {
112
+ samples->Array.push({
113
+ sourceName: sourceState.source.name,
114
+ chainId: sourceState.source.chain->ChainMap.Chain.toChainId,
115
+ height: sourceState.knownHeight,
116
+ })
117
+ }
118
+ })
119
+ samples
120
+ }
121
+
122
+ // Each accessor adds the in-progress interval of the current status, so a
123
+ // scrape during a long idle/wait/query isn't stale until the next transition.
124
+ let idleSeconds = (sourceManager: t) =>
125
+ sourceManager.idleSeconds +.
126
+ switch sourceManager.status {
127
+ | Idle => sourceManager.statusStart->Performance.secondsSince
128
+ | _ => 0.
129
+ }
130
+
131
+ let waitingForNewBlockSeconds = (sourceManager: t) =>
132
+ sourceManager.waitingForNewBlockSeconds +.
133
+ switch sourceManager.status {
134
+ | WaitingForNewBlock => sourceManager.statusStart->Performance.secondsSince
135
+ | _ => 0.
136
+ }
137
+
138
+ let queryingSeconds = (sourceManager: t) =>
139
+ sourceManager.queryingSeconds +.
140
+ switch sourceManager.status {
141
+ | Querying => sourceManager.statusStart->Performance.secondsSince
142
+ | _ => 0.
143
+ }
144
+
145
+ // Partition queries currently in flight on this chain's sources. Summed across
146
+ // chains by CrossChainState to enforce the indexer-wide concurrency budget.
147
+ let inFlightCount = sourceManager => sourceManager.fetchingPartitionsCount
148
+
95
149
  let getRateLimitTimeMs = sourceManager =>
96
150
  sourceManager.committedRateLimitTimeMs +.
97
151
  switch sourceManager.activeRateLimitStartMs {
@@ -217,10 +271,6 @@ let make = (
217
271
  | None =>
218
272
  JsError.throwWithMessage("Invalid configuration, no data-source for historical sync provided")
219
273
  }
220
- Prometheus.IndexingConcurrency.set(
221
- ~concurrency=0,
222
- ~chainId=initialActiveSource.chain->ChainMap.Chain.toChainId,
223
- )
224
274
  {
225
275
  sourcesState: sources->Array.map(source => {
226
276
  source,
@@ -243,6 +293,9 @@ let make = (
243
293
  recoveryTimeout,
244
294
  statusStart: Performance.now(),
245
295
  status: Idle,
296
+ idleSeconds: 0.,
297
+ waitingForNewBlockSeconds: 0.,
298
+ queryingSeconds: 0.,
246
299
  hasRealtime,
247
300
  committedRateLimitTimeMs: 0.0,
248
301
  rateLimitWaiters: 0,
@@ -252,15 +305,13 @@ let make = (
252
305
  }
253
306
 
254
307
  let trackNewStatus = (sourceManager: t, ~newStatus) => {
255
- let promCounter = switch sourceManager.status {
256
- | Idle => Prometheus.IndexingIdleTime.counter
257
- | WaitingForNewBlock => Prometheus.IndexingSourceWaitingTime.counter
258
- | Querying => Prometheus.IndexingQueryTime.counter
308
+ let elapsed = sourceManager.statusStart->Performance.secondsSince
309
+ switch sourceManager.status {
310
+ | Idle => sourceManager.idleSeconds = sourceManager.idleSeconds +. elapsed
311
+ | WaitingForNewBlock =>
312
+ sourceManager.waitingForNewBlockSeconds = sourceManager.waitingForNewBlockSeconds +. elapsed
313
+ | Querying => sourceManager.queryingSeconds = sourceManager.queryingSeconds +. elapsed
259
314
  }
260
- promCounter->Prometheus.SafeCounter.handleFloat(
261
- ~labels=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
262
- ~value=sourceManager.statusStart->Performance.secondsSince,
263
- )
264
315
  sourceManager.statusStart = Performance.now()
265
316
  sourceManager.status = newStatus
266
317
  }
@@ -303,20 +354,12 @@ let dispatch = async (
303
354
  // when they were admitted; here we just execute them.
304
355
  sourceManager.fetchingPartitionsCount =
305
356
  sourceManager.fetchingPartitionsCount + queries->Array.length
306
- Prometheus.IndexingConcurrency.set(
307
- ~concurrency=sourceManager.fetchingPartitionsCount,
308
- ~chainId=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
309
- )
310
357
  sourceManager->trackNewStatus(~newStatus=Querying)
311
358
  let _ = await queries
312
359
  ->Array.map(q => {
313
360
  let promise = q->executeQuery
314
361
  let _ = promise->Promise.thenResolve(_ => {
315
362
  sourceManager.fetchingPartitionsCount = sourceManager.fetchingPartitionsCount - 1
316
- Prometheus.IndexingConcurrency.set(
317
- ~concurrency=sourceManager.fetchingPartitionsCount,
318
- ~chainId=sourceManager.activeSource.chain->ChainMap.Chain.toChainId,
319
- )
320
363
  if sourceManager.fetchingPartitionsCount === 0 {
321
364
  sourceManager->trackNewStatus(~newStatus=Idle)
322
365
  }
@@ -461,13 +504,8 @@ let getSourceNewHeight = async (
461
504
  }
462
505
  }
463
506
 
464
- // Update Prometheus only if height increased
465
- if newHeight.contents > initialHeight {
466
- Prometheus.SourceHeight.set(
467
- ~sourceName=source.name,
468
- ~chainId=source.chain->ChainMap.Chain.toChainId,
469
- ~blockNumber=newHeight.contents,
470
- )
507
+ if newHeight.contents > sourceState.knownHeight {
508
+ sourceState.knownHeight = newHeight.contents
471
509
  }
472
510
 
473
511
  newHeight.contents
@@ -736,6 +774,13 @@ let executeQuery = async (
736
774
  )
737
775
  sourceState->recordRequestStats(response.requestStats)
738
776
  sourceState.lastFailedAt = None
777
+
778
+ // The response carries a fresh height for exactly this source, so during a
779
+ // long backfill (when the wait loop doesn't run) it keeps the per-source
780
+ // envio_source_known_height current.
781
+ if response.knownHeight > sourceState.knownHeight {
782
+ sourceState.knownHeight = response.knownHeight
783
+ }
739
784
  responseRef := Some(response)
740
785
  } catch {
741
786
  | Source.RateLimited({resetMs}) =>
@@ -4,7 +4,6 @@ import * as Utils from "../Utils.res.mjs";
4
4
  import * as Source from "./Source.res.mjs";
5
5
  import * as Logging from "../Logging.res.mjs";
6
6
  import * as FetchState from "../FetchState.res.mjs";
7
- import * as Prometheus from "../Prometheus.res.mjs";
8
7
  import * as Performance from "../bindings/Performance.res.mjs";
9
8
  import * as ErrorHandling from "../ErrorHandling.res.mjs";
10
9
  import * as Primitive_int from "@rescript/runtime/lib/es6/Primitive_int.js";
@@ -52,6 +51,70 @@ function getRequestStatSamples(sourceManager) {
52
51
  return samples;
53
52
  }
54
53
 
54
+ function getSourceHeightSamples(sourceManager) {
55
+ let samples = [];
56
+ sourceManager.sourcesState.forEach(sourceState => {
57
+ if (sourceState.knownHeight > 0) {
58
+ samples.push({
59
+ sourceName: sourceState.source.name,
60
+ chainId: sourceState.source.chain,
61
+ height: sourceState.knownHeight
62
+ });
63
+ return;
64
+ }
65
+ });
66
+ return samples;
67
+ }
68
+
69
+ function idleSeconds(sourceManager) {
70
+ let match = sourceManager.status;
71
+ let tmp;
72
+ switch (match) {
73
+ case "Idle" :
74
+ tmp = Performance.secondsSince(sourceManager.statusStart);
75
+ break;
76
+ case "WaitingForNewBlock" :
77
+ case "Querying" :
78
+ tmp = 0;
79
+ break;
80
+ }
81
+ return sourceManager.idleSeconds + tmp;
82
+ }
83
+
84
+ function waitingForNewBlockSeconds(sourceManager) {
85
+ let match = sourceManager.status;
86
+ let tmp;
87
+ switch (match) {
88
+ case "WaitingForNewBlock" :
89
+ tmp = Performance.secondsSince(sourceManager.statusStart);
90
+ break;
91
+ case "Idle" :
92
+ case "Querying" :
93
+ tmp = 0;
94
+ break;
95
+ }
96
+ return sourceManager.waitingForNewBlockSeconds + tmp;
97
+ }
98
+
99
+ function queryingSeconds(sourceManager) {
100
+ let match = sourceManager.status;
101
+ let tmp;
102
+ switch (match) {
103
+ case "Idle" :
104
+ case "WaitingForNewBlock" :
105
+ tmp = 0;
106
+ break;
107
+ case "Querying" :
108
+ tmp = Performance.secondsSince(sourceManager.statusStart);
109
+ break;
110
+ }
111
+ return sourceManager.queryingSeconds + tmp;
112
+ }
113
+
114
+ function inFlightCount(sourceManager) {
115
+ return sourceManager.fetchingPartitionsCount;
116
+ }
117
+
55
118
  function getRateLimitTimeMs(sourceManager) {
56
119
  let startMs = sourceManager.activeRateLimitStartMs;
57
120
  return sourceManager.committedRateLimitTimeMs + (
@@ -159,7 +222,6 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
159
222
  let hasRealtime = sources.some(s => s.sourceFor === "Realtime");
160
223
  let source = sources.find(source => getSourceRole(source.sourceFor, isRealtime, hasRealtime) === "Primary");
161
224
  let initialActiveSource = source !== undefined ? source : Stdlib_JsError.throwWithMessage("Invalid configuration, no data-source for historical sync provided");
162
- Prometheus.IndexingConcurrency.set(0, initialActiveSource.chain);
163
225
  return {
164
226
  sourcesState: sources.map(source => ({
165
227
  source: source,
@@ -172,6 +234,9 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
172
234
  })),
173
235
  statusStart: Performance.now(),
174
236
  status: "Idle",
237
+ idleSeconds: 0,
238
+ waitingForNewBlockSeconds: 0,
239
+ queryingSeconds: 0,
175
240
  newBlockStallTimeout: newBlockStallTimeout,
176
241
  newBlockStallTimeoutRealtime: newBlockStallTimeoutRealtime,
177
242
  stalledPollingInterval: stalledPollingInterval,
@@ -191,20 +256,19 @@ function make(sources, isRealtime, newBlockStallTimeoutOpt, newBlockStallTimeout
191
256
  }
192
257
 
193
258
  function trackNewStatus(sourceManager, newStatus) {
259
+ let elapsed = Performance.secondsSince(sourceManager.statusStart);
194
260
  let match = sourceManager.status;
195
- let promCounter;
196
261
  switch (match) {
197
262
  case "Idle" :
198
- promCounter = Prometheus.IndexingIdleTime.counter;
263
+ sourceManager.idleSeconds = sourceManager.idleSeconds + elapsed;
199
264
  break;
200
265
  case "WaitingForNewBlock" :
201
- promCounter = Prometheus.IndexingSourceWaitingTime.counter;
266
+ sourceManager.waitingForNewBlockSeconds = sourceManager.waitingForNewBlockSeconds + elapsed;
202
267
  break;
203
268
  case "Querying" :
204
- promCounter = Prometheus.IndexingQueryTime.counter;
269
+ sourceManager.queryingSeconds = sourceManager.queryingSeconds + elapsed;
205
270
  break;
206
271
  }
207
- Prometheus.SafeCounter.handleFloat(promCounter, sourceManager.activeSource.chain, Performance.secondsSince(sourceManager.statusStart));
208
272
  sourceManager.statusStart = Performance.now();
209
273
  sourceManager.status = newStatus;
210
274
  }
@@ -232,13 +296,11 @@ async function dispatch(sourceManager, fetchState, executeQuery, waitForNewBlock
232
296
  } else {
233
297
  let queries = action._0;
234
298
  sourceManager.fetchingPartitionsCount = sourceManager.fetchingPartitionsCount + queries.length | 0;
235
- Prometheus.IndexingConcurrency.set(sourceManager.fetchingPartitionsCount, sourceManager.activeSource.chain);
236
299
  trackNewStatus(sourceManager, "Querying");
237
300
  await Promise.all(queries.map(q => {
238
301
  let promise = executeQuery(q);
239
302
  promise.then(param => {
240
303
  sourceManager.fetchingPartitionsCount = sourceManager.fetchingPartitionsCount - 1 | 0;
241
- Prometheus.IndexingConcurrency.set(sourceManager.fetchingPartitionsCount, sourceManager.activeSource.chain);
242
304
  if (sourceManager.fetchingPartitionsCount === 0) {
243
305
  return trackNewStatus(sourceManager, "Idle");
244
306
  }
@@ -361,8 +423,8 @@ async function getSourceNewHeight(sourceManager, sourceState, knownHeight, stall
361
423
  }
362
424
  }
363
425
  };
364
- if (newHeight.contents > initialHeight) {
365
- Prometheus.SourceHeight.set(source.name, source.chain, newHeight.contents);
426
+ if (newHeight.contents > sourceState.knownHeight) {
427
+ sourceState.knownHeight = newHeight.contents;
366
428
  }
367
429
  return newHeight.contents;
368
430
  }
@@ -542,6 +604,9 @@ async function executeQuery(sourceManager, query, knownHeight, isRealtime) {
542
604
  let response = await source.getItemsOrThrow(query.fromBlock, toBlock, query.addressesByContractName, FetchState.deriveContractNameByAddress(query.addressesByContractName), knownHeight, query.partitionId, query.selection, query.itemsTarget, retry, logger$2);
543
605
  recordRequestStats(sourceState, response.requestStats);
544
606
  sourceState.lastFailedAt = undefined;
607
+ if (response.knownHeight > sourceState.knownHeight) {
608
+ sourceState.knownHeight = response.knownHeight;
609
+ }
545
610
  responseRef = response;
546
611
  } catch (raw_error) {
547
612
  let error = Primitive_exceptions.internalToException(raw_error);
@@ -715,6 +780,11 @@ export {
715
780
  make,
716
781
  getActiveSource,
717
782
  getRequestStatSamples,
783
+ getSourceHeightSamples,
784
+ idleSeconds,
785
+ waitingForNewBlockSeconds,
786
+ queryingSeconds,
787
+ inFlightCount,
718
788
  onReorg,
719
789
  dispatch,
720
790
  waitForNewBlock,
@@ -31,6 +31,20 @@ type requestStatSample = {
31
31
 
32
32
  let getRequestStatSamples: t => array<requestStatSample>
33
33
 
34
+ type sourceHeightSample = {
35
+ sourceName: string,
36
+ chainId: int,
37
+ height: int,
38
+ }
39
+
40
+ let getSourceHeightSamples: t => array<sourceHeightSample>
41
+
42
+ let idleSeconds: t => float
43
+ let waitingForNewBlockSeconds: t => float
44
+ let queryingSeconds: t => float
45
+
46
+ let inFlightCount: t => int
47
+
34
48
  let onReorg: (t, ~rollbackTargetBlock: int) => unit
35
49
 
36
50
  let dispatch: (
package/src/tui/Tui.res CHANGED
@@ -157,7 +157,7 @@ module App = {
157
157
  ->IndexerState.chainStates
158
158
  ->Dict.valuesToArray
159
159
  ->Array.map(cs => {
160
- let data = cs->ChainState.toChainData
160
+ let data = cs->ChainState.toMetrics
161
161
  let numEventsProcessed = data.numEventsProcessed
162
162
  let committedProgressBlockNumber = cs->ChainState.committedProgressBlockNumber
163
163
  let timestampCaughtUpToHeadOrEndblock = data.timestampCaughtUpToHeadOrEndblock
@@ -207,7 +207,7 @@ function Tui$App(props) {
207
207
  };
208
208
  }, [getState]);
209
209
  let chains = Object.values(IndexerState.chainStates(state)).map(cs => {
210
- let data = ChainState.toChainData(cs);
210
+ let data = ChainState.toMetrics(cs);
211
211
  let numEventsProcessed = data.numEventsProcessed;
212
212
  let committedProgressBlockNumber = ChainState.committedProgressBlockNumber(cs);
213
213
  let timestampCaughtUpToHeadOrEndblock = data.timestampCaughtUpToHeadOrEndblock;
@@ -266,7 +266,7 @@ function Tui$App(props) {
266
266
  poweredByHyperSync: data.poweredByHyperSync,
267
267
  progress: progress,
268
268
  latestFetchedBlockNumber: latestFetchedBlockNumber,
269
- knownHeight: data.currentBlockHeight,
269
+ knownHeight: data.knownHeight,
270
270
  blockUnit: tmp,
271
271
  rateLimitTimeMs: SourceManager.getRateLimitTimeMs(sourceManager),
272
272
  isRateLimited: SourceManager.isRateLimited(sourceManager),