@subsquid/batch-processor 0.0.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.
- package/README.md +4 -0
- package/lib/database.d.ts +38 -0
- package/lib/database.d.ts.map +1 -0
- package/lib/database.js +3 -0
- package/lib/database.js.map +1 -0
- package/lib/datasource.d.ts +8 -0
- package/lib/datasource.d.ts.map +1 -0
- package/lib/datasource.js +3 -0
- package/lib/datasource.js.map +1 -0
- package/lib/index.d.ts +4 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +20 -0
- package/lib/index.js.map +1 -0
- package/lib/metrics.d.ts +21 -0
- package/lib/metrics.d.ts.map +1 -0
- package/lib/metrics.js +92 -0
- package/lib/metrics.js.map +1 -0
- package/lib/run.d.ts +36 -0
- package/lib/run.d.ts.map +1 -0
- package/lib/run.js +168 -0
- package/lib/run.js.map +1 -0
- package/lib/util.d.ts +6 -0
- package/lib/util.d.ts.map +1 -0
- package/lib/util.js +43 -0
- package/lib/util.js.map +1 -0
- package/package.json +31 -0
- package/src/database.ts +53 -0
- package/src/datasource.ts +9 -0
- package/src/index.ts +3 -0
- package/src/metrics.ts +111 -0
- package/src/run.ts +197 -0
- package/src/util.ts +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database is responsible for providing a persistent storage for data handlers
|
|
3
|
+
* and keeping the processor progress and status.
|
|
4
|
+
*/
|
|
5
|
+
export type Database<S> = FinalDatabase<S> | HotDatabase<S>;
|
|
6
|
+
export interface FinalTxInfo {
|
|
7
|
+
prevHead: HashAndHeight;
|
|
8
|
+
nextHead: HashAndHeight;
|
|
9
|
+
isOnTop: boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface FinalDatabase<S> {
|
|
12
|
+
supportsHotBlocks?: false;
|
|
13
|
+
connect(): Promise<HashAndHeight>;
|
|
14
|
+
transact(info: FinalTxInfo, cb: (store: S) => Promise<void>): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export interface HotTxInfo {
|
|
17
|
+
finalizedHead: HashAndHeight;
|
|
18
|
+
baseHead: HashAndHeight;
|
|
19
|
+
newBlocks: HashAndHeight[];
|
|
20
|
+
}
|
|
21
|
+
export interface HotDatabase<S> {
|
|
22
|
+
supportsHotBlocks: true;
|
|
23
|
+
connect(): Promise<HotDatabaseState>;
|
|
24
|
+
transact(info: FinalTxInfo, cb: (store: S) => Promise<void>): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* @deprecated
|
|
27
|
+
*/
|
|
28
|
+
transactHot(info: HotTxInfo, cb: (store: S, block: HashAndHeight) => Promise<void>): Promise<void>;
|
|
29
|
+
transactHot2?(info: HotTxInfo, cb: (store: S, blockSliceStart: number, blockSliceEnd: number) => Promise<void>): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export interface HotDatabaseState extends HashAndHeight {
|
|
32
|
+
top: HashAndHeight[];
|
|
33
|
+
}
|
|
34
|
+
export interface HashAndHeight {
|
|
35
|
+
height: number;
|
|
36
|
+
hash: string;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=database.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;AAG3D,MAAM,WAAW,WAAW;IACxB,QAAQ,EAAE,aAAa,CAAA;IACvB,QAAQ,EAAE,aAAa,CAAA;IACvB,OAAO,EAAE,OAAO,CAAA;CACnB;AAGD,MAAM,WAAW,aAAa,CAAC,CAAC;IAC5B,iBAAiB,CAAC,EAAE,KAAK,CAAA;IACzB,OAAO,IAAI,OAAO,CAAC,aAAa,CAAC,CAAA;IACjC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;CAC9E;AAGD,MAAM,WAAW,SAAS;IACtB,aAAa,EAAE,aAAa,CAAA;IAC5B,QAAQ,EAAE,aAAa,CAAA;IACvB,SAAS,EAAE,aAAa,EAAE,CAAA;CAC7B;AAGD,MAAM,WAAW,WAAW,CAAC,CAAC;IAC1B,iBAAiB,EAAE,IAAI,CAAA;IACvB,OAAO,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAAA;IACpC,QAAQ,CAAC,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3E;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,aAAa,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAElG,YAAY,CAAC,CACT,IAAI,EAAE,SAAS,EACf,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,GAChF,OAAO,CAAC,IAAI,CAAC,CAAA;CACnB;AAGD,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACnD,GAAG,EAAE,aAAa,EAAE,CAAA;CACvB;AAGD,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,MAAM,CAAA;CACf"}
|
package/lib/database.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FiniteRange } from '@subsquid/util-internal-range';
|
|
2
|
+
export interface DataSource<B> {
|
|
3
|
+
getFinalizedHeight(): Promise<number>;
|
|
4
|
+
getBlockHash(height: number): Promise<string | undefined>;
|
|
5
|
+
getBlockStream(fromBlock?: number): AsyncIterable<B[]>;
|
|
6
|
+
getBlocksCountInRange?(range: FiniteRange): number;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=datasource.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datasource.d.ts","sourceRoot":"","sources":["../src/datasource.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,+BAA+B,CAAA;AAG9D,MAAM,WAAW,UAAU,CAAC,CAAC;IACzB,kBAAkB,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;IACrC,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAA;IACzD,cAAc,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,aAAa,CAAC,CAAC,EAAE,CAAC,CAAA;IACtD,qBAAqB,CAAC,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAAA;CACrD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"datasource.js","sourceRoot":"","sources":["../src/datasource.ts"],"names":[],"mappings":""}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,cAAc,cAAc,CAAA;AAC5B,cAAc,OAAO,CAAA"}
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./database"), exports);
|
|
18
|
+
__exportStar(require("./datasource"), exports);
|
|
19
|
+
__exportStar(require("./run"), exports);
|
|
20
|
+
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6CAA0B;AAC1B,+CAA4B;AAC5B,wCAAqB"}
|
package/lib/metrics.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare class Metrics {
|
|
2
|
+
private chainHeight;
|
|
3
|
+
private lastBlock;
|
|
4
|
+
private mappingSpeed;
|
|
5
|
+
private mappingItemSpeed;
|
|
6
|
+
private blockProgress;
|
|
7
|
+
setChainHeight(height: number): void;
|
|
8
|
+
setLastProcessedBlock(height: number): void;
|
|
9
|
+
updateProgress(processed: number, left: number, time?: bigint): void;
|
|
10
|
+
registerBatch(batchSize: number, batchItemSize: number, batchMappingStartTime: bigint, batchMappingEndTime: bigint): void;
|
|
11
|
+
getChainHeight(): number;
|
|
12
|
+
getLastProcessedBlock(): number;
|
|
13
|
+
getSyncSpeed(): number;
|
|
14
|
+
getSyncEtaSeconds(): number;
|
|
15
|
+
getSyncRatio(): number;
|
|
16
|
+
getMappingSpeed(): number;
|
|
17
|
+
getMappingItemSpeed(): number;
|
|
18
|
+
getStatusLine(): string;
|
|
19
|
+
install(): void;
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"AAKA,qBAAa,OAAO;IAChB,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,gBAAgB,CAA6B;IACrD,OAAO,CAAC,aAAa,CAAkD;IAEvE,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAIpC,qBAAqB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAK3C,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI;IAKpE,aAAa,CACT,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,MAAM,EACrB,qBAAqB,EAAE,MAAM,EAC7B,mBAAmB,EAAE,MAAM,GAC5B,IAAI;IAKP,cAAc,IAAI,MAAM;IAIxB,qBAAqB,IAAI,MAAM;IAI/B,YAAY,IAAI,MAAM;IAItB,iBAAiB,IAAI,MAAM;IAI3B,YAAY,IAAI,MAAM;IAItB,eAAe,IAAI,MAAM;IAIzB,mBAAmB,IAAI,MAAM;IAI7B,aAAa,IAAI,MAAM;IAQvB,OAAO,IAAI,IAAI;CA+BlB"}
|
package/lib/metrics.js
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Metrics = void 0;
|
|
4
|
+
const util_internal_counters_1 = require("@subsquid/util-internal-counters");
|
|
5
|
+
const prom_client_1 = require("prom-client");
|
|
6
|
+
const util_1 = require("./util");
|
|
7
|
+
class Metrics {
|
|
8
|
+
constructor() {
|
|
9
|
+
this.chainHeight = -1;
|
|
10
|
+
this.lastBlock = -1;
|
|
11
|
+
this.mappingSpeed = new util_internal_counters_1.Speed({ windowSize: 5 });
|
|
12
|
+
this.mappingItemSpeed = new util_internal_counters_1.Speed({ windowSize: 5 });
|
|
13
|
+
this.blockProgress = new util_internal_counters_1.Progress({ initialValue: 0, windowSize: 20 });
|
|
14
|
+
}
|
|
15
|
+
setChainHeight(height) {
|
|
16
|
+
this.chainHeight = Math.max(height, this.lastBlock);
|
|
17
|
+
}
|
|
18
|
+
setLastProcessedBlock(height) {
|
|
19
|
+
this.lastBlock = height;
|
|
20
|
+
this.chainHeight = Math.max(this.chainHeight, this.lastBlock);
|
|
21
|
+
}
|
|
22
|
+
updateProgress(processed, left, time) {
|
|
23
|
+
this.blockProgress.setTargetValue(processed + left);
|
|
24
|
+
this.blockProgress.setCurrentValue(processed, time);
|
|
25
|
+
}
|
|
26
|
+
registerBatch(batchSize, batchItemSize, batchMappingStartTime, batchMappingEndTime) {
|
|
27
|
+
this.mappingSpeed.push(batchSize, batchMappingStartTime, batchMappingEndTime);
|
|
28
|
+
this.mappingItemSpeed.push(batchItemSize || 1, batchMappingStartTime, batchMappingEndTime);
|
|
29
|
+
}
|
|
30
|
+
getChainHeight() {
|
|
31
|
+
return this.chainHeight;
|
|
32
|
+
}
|
|
33
|
+
getLastProcessedBlock() {
|
|
34
|
+
return this.lastBlock;
|
|
35
|
+
}
|
|
36
|
+
getSyncSpeed() {
|
|
37
|
+
return this.blockProgress.speed();
|
|
38
|
+
}
|
|
39
|
+
getSyncEtaSeconds() {
|
|
40
|
+
return this.blockProgress.eta();
|
|
41
|
+
}
|
|
42
|
+
getSyncRatio() {
|
|
43
|
+
return this.blockProgress.ratio();
|
|
44
|
+
}
|
|
45
|
+
getMappingSpeed() {
|
|
46
|
+
return this.mappingSpeed.speed();
|
|
47
|
+
}
|
|
48
|
+
getMappingItemSpeed() {
|
|
49
|
+
return this.mappingItemSpeed.speed();
|
|
50
|
+
}
|
|
51
|
+
getStatusLine() {
|
|
52
|
+
return `${this.lastBlock} / ${this.chainHeight}, ` +
|
|
53
|
+
`rate: ${Math.round(this.getSyncSpeed())} blocks/sec, ` +
|
|
54
|
+
`mapping: ${Math.round(this.getMappingSpeed())} blocks/sec, ` +
|
|
55
|
+
`${Math.round(this.getMappingItemSpeed())} items/sec, ` +
|
|
56
|
+
`eta: ${(0, util_1.timeInterval)(this.getSyncEtaSeconds())}`;
|
|
57
|
+
}
|
|
58
|
+
install() {
|
|
59
|
+
new prom_client_1.Gauge({
|
|
60
|
+
name: 'sqd_processor_chain_height',
|
|
61
|
+
help: 'Chain height of the data source',
|
|
62
|
+
collect: collect(() => this.getChainHeight())
|
|
63
|
+
});
|
|
64
|
+
new prom_client_1.Gauge({
|
|
65
|
+
name: 'sqd_processor_last_block',
|
|
66
|
+
help: 'Last processed block',
|
|
67
|
+
collect: collect(() => this.getLastProcessedBlock())
|
|
68
|
+
});
|
|
69
|
+
new prom_client_1.Gauge({
|
|
70
|
+
name: 'sqd_processor_mapping_blocks_per_second',
|
|
71
|
+
help: 'Mapping performance',
|
|
72
|
+
collect: collect(() => this.getMappingSpeed())
|
|
73
|
+
});
|
|
74
|
+
new prom_client_1.Gauge({
|
|
75
|
+
name: 'sqd_processor_sync_eta_seconds',
|
|
76
|
+
help: 'Estimated time until all required blocks will be processed or until the chain height will be reached',
|
|
77
|
+
collect: collect(() => this.getSyncEtaSeconds())
|
|
78
|
+
});
|
|
79
|
+
new prom_client_1.Gauge({
|
|
80
|
+
name: 'sqd_processor_sync_ratio',
|
|
81
|
+
help: 'Percentage of processed blocks',
|
|
82
|
+
collect: collect(() => this.getSyncRatio())
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
exports.Metrics = Metrics;
|
|
87
|
+
function collect(fn) {
|
|
88
|
+
return function () {
|
|
89
|
+
this.set(fn());
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.js","sourceRoot":"","sources":["../src/metrics.ts"],"names":[],"mappings":"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"}
|
package/lib/run.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Database, HashAndHeight } from './database';
|
|
2
|
+
import { DataSource } from './datasource';
|
|
3
|
+
export interface DataHandlerContext<Block, Store> {
|
|
4
|
+
/**
|
|
5
|
+
* Storage interface provided by the database
|
|
6
|
+
*/
|
|
7
|
+
store: Store;
|
|
8
|
+
/**
|
|
9
|
+
* List of blocks to map and process
|
|
10
|
+
*/
|
|
11
|
+
blocks: Block[];
|
|
12
|
+
/**
|
|
13
|
+
* Signals, that the processor is near the head of the chain.
|
|
14
|
+
*/
|
|
15
|
+
isHead: boolean;
|
|
16
|
+
}
|
|
17
|
+
interface BlockBase {
|
|
18
|
+
header: HashAndHeight;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Run data processing.
|
|
22
|
+
*
|
|
23
|
+
* This method assumes full control over the current OS process as
|
|
24
|
+
* it terminates the entire program in case of error or
|
|
25
|
+
* at the end of data processing.
|
|
26
|
+
*
|
|
27
|
+
* @param src - data source to ingest data from
|
|
28
|
+
*
|
|
29
|
+
* @param db - database is responsible for providing storage API to data handler
|
|
30
|
+
* and persisting mapping progress and status.
|
|
31
|
+
*
|
|
32
|
+
* @param dataHandler - The data handler, see {@link DataHandlerContext} for an API available to the handler.
|
|
33
|
+
*/
|
|
34
|
+
export declare function run<Block extends BlockBase, Store>(src: DataSource<Block>, db: Database<Store>, dataHandler: (ctx: DataHandlerContext<Block, Store>) => Promise<void>): void;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=run.d.ts.map
|
package/lib/run.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"AAIA,OAAO,EAAC,QAAQ,EAAE,aAAa,EAAC,MAAM,YAAY,CAAA;AAClD,OAAO,EAAC,UAAU,EAAC,MAAM,cAAc,CAAA;AAQvC,MAAM,WAAW,kBAAkB,CAAC,KAAK,EAAE,KAAK;IAC5C;;OAEG;IACH,KAAK,EAAE,KAAK,CAAA;IACZ;;OAEG;IACH,MAAM,EAAE,KAAK,EAAE,CAAA;IACf;;OAEG;IACH,MAAM,EAAE,OAAO,CAAA;CAClB;AAGD,UAAU,SAAS;IACf,MAAM,EAAE,aAAa,CAAA;CACxB;AAGD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,GAAG,CAAC,KAAK,SAAS,SAAS,EAAE,KAAK,EAC9C,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,EACtB,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,EACnB,WAAW,EAAE,CAAC,GAAG,EAAE,kBAAkB,CAAC,KAAK,EAAE,KAAK,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,GACtE,IAAI,CAMN"}
|
package/lib/run.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.run = void 0;
|
|
27
|
+
const logger_1 = require("@subsquid/logger");
|
|
28
|
+
const util_internal_1 = require("@subsquid/util-internal");
|
|
29
|
+
const util_internal_prometheus_server_1 = require("@subsquid/util-internal-prometheus-server");
|
|
30
|
+
const prom = __importStar(require("prom-client"));
|
|
31
|
+
const metrics_1 = require("./metrics");
|
|
32
|
+
const util_1 = require("./util");
|
|
33
|
+
const log = (0, logger_1.createLogger)('sqd:batch-processor');
|
|
34
|
+
/**
|
|
35
|
+
* Run data processing.
|
|
36
|
+
*
|
|
37
|
+
* This method assumes full control over the current OS process as
|
|
38
|
+
* it terminates the entire program in case of error or
|
|
39
|
+
* at the end of data processing.
|
|
40
|
+
*
|
|
41
|
+
* @param src - data source to ingest data from
|
|
42
|
+
*
|
|
43
|
+
* @param db - database is responsible for providing storage API to data handler
|
|
44
|
+
* and persisting mapping progress and status.
|
|
45
|
+
*
|
|
46
|
+
* @param dataHandler - The data handler, see {@link DataHandlerContext} for an API available to the handler.
|
|
47
|
+
*/
|
|
48
|
+
function run(src, db, dataHandler) {
|
|
49
|
+
(0, util_internal_1.runProgram)(() => {
|
|
50
|
+
return new Processor(src, db, dataHandler).run();
|
|
51
|
+
}, err => {
|
|
52
|
+
log.fatal(err);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
exports.run = run;
|
|
56
|
+
class Processor {
|
|
57
|
+
constructor(src, db, handler) {
|
|
58
|
+
this.src = src;
|
|
59
|
+
this.db = db;
|
|
60
|
+
this.handler = handler;
|
|
61
|
+
this.metrics = new metrics_1.Metrics();
|
|
62
|
+
this.hasStatusNews = false;
|
|
63
|
+
this.chainHeight = new util_internal_1.Throttler(() => this.src.getFinalizedHeight(), 30000);
|
|
64
|
+
}
|
|
65
|
+
async run() {
|
|
66
|
+
let state = await this.db.connect();
|
|
67
|
+
if (state.height >= 0) {
|
|
68
|
+
log.info(`last processed final block was ${state.height}`);
|
|
69
|
+
}
|
|
70
|
+
await this.assertWeAreOnTheSameChain(state);
|
|
71
|
+
await this.initMetrics(state);
|
|
72
|
+
for await (let blocks of this.src.getBlockStream(state.height + 1)) {
|
|
73
|
+
if (blocks.length > 0) {
|
|
74
|
+
state = await this.processBatch(state, blocks);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
this.reportFinalStatus();
|
|
78
|
+
}
|
|
79
|
+
async assertWeAreOnTheSameChain(state) {
|
|
80
|
+
if (state.height < 0)
|
|
81
|
+
return;
|
|
82
|
+
let hash = await this.src.getBlockHash(state.height);
|
|
83
|
+
if (state.hash === hash)
|
|
84
|
+
return;
|
|
85
|
+
throw new Error(`already indexed block ${(0, util_1.formatHead)(state)} was not found on chain`);
|
|
86
|
+
}
|
|
87
|
+
async initMetrics(state) {
|
|
88
|
+
await this.updateProgressMetrics(await this.chainHeight.get(), state);
|
|
89
|
+
let port = process.env.PROCESSOR_PROMETHEUS_PORT || process.env.PROMETHEUS_PORT;
|
|
90
|
+
if (port == null)
|
|
91
|
+
return;
|
|
92
|
+
prom.collectDefaultMetrics();
|
|
93
|
+
this.metrics.install();
|
|
94
|
+
let server = await (0, util_internal_prometheus_server_1.createPrometheusServer)(prom.register, port);
|
|
95
|
+
log.info(`prometheus metrics are served on port ${server.port}`);
|
|
96
|
+
}
|
|
97
|
+
updateProgressMetrics(chainHeight, state, time) {
|
|
98
|
+
this.metrics.setChainHeight(chainHeight);
|
|
99
|
+
this.metrics.setLastProcessedBlock(state.height);
|
|
100
|
+
let left;
|
|
101
|
+
let processed;
|
|
102
|
+
if (this.src.getBlocksCountInRange) {
|
|
103
|
+
left = this.src.getBlocksCountInRange({
|
|
104
|
+
from: this.metrics.getLastProcessedBlock() + 1,
|
|
105
|
+
to: this.metrics.getChainHeight()
|
|
106
|
+
});
|
|
107
|
+
processed = this.src.getBlocksCountInRange({
|
|
108
|
+
from: 0,
|
|
109
|
+
to: this.metrics.getChainHeight()
|
|
110
|
+
}) - left;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
left = this.metrics.getChainHeight() - this.metrics.getLastProcessedBlock();
|
|
114
|
+
processed = this.metrics.getLastProcessedBlock();
|
|
115
|
+
}
|
|
116
|
+
this.metrics.updateProgress(processed, left, time);
|
|
117
|
+
}
|
|
118
|
+
async processBatch(prevHead, blocks) {
|
|
119
|
+
let chainHeight = await this.chainHeight.get();
|
|
120
|
+
let nextHead = {
|
|
121
|
+
hash: (0, util_internal_1.last)(blocks).header.hash,
|
|
122
|
+
height: (0, util_internal_1.last)(blocks).header.height
|
|
123
|
+
};
|
|
124
|
+
let isOnTop = nextHead.height >= chainHeight;
|
|
125
|
+
let mappingStartTime = process.hrtime.bigint();
|
|
126
|
+
await this.db.transact({
|
|
127
|
+
prevHead,
|
|
128
|
+
nextHead,
|
|
129
|
+
isOnTop
|
|
130
|
+
}, store => {
|
|
131
|
+
return this.handler({
|
|
132
|
+
store,
|
|
133
|
+
blocks,
|
|
134
|
+
isHead: isOnTop
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
let mappingEndTime = process.hrtime.bigint();
|
|
138
|
+
this.updateProgressMetrics(chainHeight, nextHead, mappingEndTime);
|
|
139
|
+
this.metrics.registerBatch(blocks.length, (0, util_1.getItemsCount)(blocks), mappingStartTime, mappingEndTime);
|
|
140
|
+
this.reportStatus();
|
|
141
|
+
return nextHead;
|
|
142
|
+
}
|
|
143
|
+
reportStatus() {
|
|
144
|
+
if (this.statusReportTimer == null) {
|
|
145
|
+
log.info(this.metrics.getStatusLine());
|
|
146
|
+
this.statusReportTimer = setTimeout(() => {
|
|
147
|
+
this.statusReportTimer = undefined;
|
|
148
|
+
if (this.hasStatusNews) {
|
|
149
|
+
this.hasStatusNews = false;
|
|
150
|
+
this.reportStatus();
|
|
151
|
+
}
|
|
152
|
+
}, 5000);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
this.hasStatusNews = true;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
reportFinalStatus() {
|
|
159
|
+
if (this.statusReportTimer != null) {
|
|
160
|
+
clearTimeout(this.statusReportTimer);
|
|
161
|
+
}
|
|
162
|
+
if (this.hasStatusNews) {
|
|
163
|
+
this.hasStatusNews = false;
|
|
164
|
+
log.info(this.metrics.getStatusLine());
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=run.js.map
|
package/lib/run.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../src/run.ts"],"names":[],"mappings":"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"}
|
package/lib/util.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { HashAndHeight } from './database';
|
|
2
|
+
export declare function timeInterval(seconds: number): string;
|
|
3
|
+
export declare function getItemsCount(blocks: any[]): number;
|
|
4
|
+
export declare function formatHead(head: HashAndHeight): string;
|
|
5
|
+
export declare function shortHash(hash: string): string;
|
|
6
|
+
//# sourceMappingURL=util.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,aAAa,EAAC,MAAM,YAAY,CAAA;AAGxC,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAWpD;AAGD,wBAAgB,aAAa,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAWnD;AAGD,wBAAgB,UAAU,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAEtD;AAGD,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAM9C"}
|
package/lib/util.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shortHash = exports.formatHead = exports.getItemsCount = exports.timeInterval = void 0;
|
|
4
|
+
function timeInterval(seconds) {
|
|
5
|
+
if (seconds < 60) {
|
|
6
|
+
return Math.round(seconds) + 's';
|
|
7
|
+
}
|
|
8
|
+
let minutes = Math.ceil(seconds / 60);
|
|
9
|
+
if (minutes < 60) {
|
|
10
|
+
return minutes + 'm';
|
|
11
|
+
}
|
|
12
|
+
let hours = Math.floor(minutes / 60);
|
|
13
|
+
minutes = minutes - hours * 60;
|
|
14
|
+
return hours + 'h ' + minutes + 'm';
|
|
15
|
+
}
|
|
16
|
+
exports.timeInterval = timeInterval;
|
|
17
|
+
function getItemsCount(blocks) {
|
|
18
|
+
let count = 0;
|
|
19
|
+
for (let block of blocks) {
|
|
20
|
+
for (let key in block) {
|
|
21
|
+
let val = block[key];
|
|
22
|
+
if (Array.isArray(val)) {
|
|
23
|
+
count += val.length;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return count;
|
|
28
|
+
}
|
|
29
|
+
exports.getItemsCount = getItemsCount;
|
|
30
|
+
function formatHead(head) {
|
|
31
|
+
return `${head.height}#${shortHash(head.hash)}`;
|
|
32
|
+
}
|
|
33
|
+
exports.formatHead = formatHead;
|
|
34
|
+
function shortHash(hash) {
|
|
35
|
+
if (hash.startsWith('0x')) {
|
|
36
|
+
return hash.slice(2, 7);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
return hash.slice(0, 5);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.shortHash = shortHash;
|
|
43
|
+
//# sourceMappingURL=util.js.map
|
package/lib/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAGA,SAAgB,YAAY,CAAC,OAAe;IACxC,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QACf,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,GAAG,CAAA;IACpC,CAAC;IACD,IAAI,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,GAAC,EAAE,CAAC,CAAA;IACnC,IAAI,OAAO,GAAG,EAAE,EAAE,CAAC;QACf,OAAQ,OAAO,GAAC,GAAG,CAAA;IACvB,CAAC;IACD,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,EAAE,CAAC,CAAA;IACpC,OAAO,GAAG,OAAO,GAAG,KAAK,GAAG,EAAE,CAAA;IAC9B,OAAO,KAAK,GAAG,IAAI,GAAG,OAAO,GAAG,GAAG,CAAA;AACvC,CAAC;AAXD,oCAWC;AAGD,SAAgB,aAAa,CAAC,MAAa;IACvC,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,KAAK,IAAI,KAAK,IAAI,MAAM,EAAE,CAAC;QACvB,KAAK,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC;YACpB,IAAI,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;YACpB,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;gBACrB,KAAK,IAAI,GAAG,CAAC,MAAM,CAAA;YACvB,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAA;AAChB,CAAC;AAXD,sCAWC;AAGD,SAAgB,UAAU,CAAC,IAAmB;IAC1C,OAAO,GAAG,IAAI,CAAC,MAAM,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAA;AACnD,CAAC;AAFD,gCAEC;AAGD,SAAgB,SAAS,CAAC,IAAY;IAClC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3B,CAAC;SAAM,CAAC;QACJ,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3B,CAAC;AACL,CAAC;AAND,8BAMC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@subsquid/batch-processor",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "ETL processor",
|
|
5
|
+
"license": "GPL-3.0-or-later",
|
|
6
|
+
"repository": "git@github.com:subsquid/squid.git",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public",
|
|
9
|
+
"registry": "https://registry.npmjs.org/"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"lib",
|
|
13
|
+
"src"
|
|
14
|
+
],
|
|
15
|
+
"main": "lib/index.js",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@subsquid/logger": "^1.3.3",
|
|
18
|
+
"@subsquid/util-internal": "^3.2.0",
|
|
19
|
+
"@subsquid/util-internal-counters": "^1.3.2",
|
|
20
|
+
"@subsquid/util-internal-prometheus-server": "^1.3.0",
|
|
21
|
+
"@subsquid/util-internal-range": "^0.3.0",
|
|
22
|
+
"prom-client": "^14.2.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/node": "^18.18.14",
|
|
26
|
+
"typescript": "~5.3.2"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "rm -rf lib && tsc"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/database.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database is responsible for providing a persistent storage for data handlers
|
|
3
|
+
* and keeping the processor progress and status.
|
|
4
|
+
*/
|
|
5
|
+
export type Database<S> = FinalDatabase<S> | HotDatabase<S>
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export interface FinalTxInfo {
|
|
9
|
+
prevHead: HashAndHeight
|
|
10
|
+
nextHead: HashAndHeight
|
|
11
|
+
isOnTop: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
export interface FinalDatabase<S> {
|
|
16
|
+
supportsHotBlocks?: false
|
|
17
|
+
connect(): Promise<HashAndHeight>
|
|
18
|
+
transact(info: FinalTxInfo, cb: (store: S) => Promise<void>): Promise<void>
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
export interface HotTxInfo {
|
|
23
|
+
finalizedHead: HashAndHeight
|
|
24
|
+
baseHead: HashAndHeight
|
|
25
|
+
newBlocks: HashAndHeight[]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
export interface HotDatabase<S> {
|
|
30
|
+
supportsHotBlocks: true
|
|
31
|
+
connect(): Promise<HotDatabaseState>
|
|
32
|
+
transact(info: FinalTxInfo, cb: (store: S) => Promise<void>): Promise<void>
|
|
33
|
+
/**
|
|
34
|
+
* @deprecated
|
|
35
|
+
*/
|
|
36
|
+
transactHot(info: HotTxInfo, cb: (store: S, block: HashAndHeight) => Promise<void>): Promise<void>
|
|
37
|
+
|
|
38
|
+
transactHot2?(
|
|
39
|
+
info: HotTxInfo,
|
|
40
|
+
cb: (store: S, blockSliceStart: number, blockSliceEnd: number) => Promise<void>
|
|
41
|
+
): Promise<void>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
export interface HotDatabaseState extends HashAndHeight {
|
|
46
|
+
top: HashAndHeight[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
export interface HashAndHeight {
|
|
51
|
+
height: number
|
|
52
|
+
hash: string
|
|
53
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type {FiniteRange} from '@subsquid/util-internal-range'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export interface DataSource<B> {
|
|
5
|
+
getFinalizedHeight(): Promise<number>
|
|
6
|
+
getBlockHash(height: number): Promise<string | undefined>
|
|
7
|
+
getBlockStream(fromBlock?: number): AsyncIterable<B[]>
|
|
8
|
+
getBlocksCountInRange?(range: FiniteRange): number
|
|
9
|
+
}
|
package/src/index.ts
ADDED
package/src/metrics.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {Progress, Speed} from '@subsquid/util-internal-counters'
|
|
2
|
+
import {Gauge} from 'prom-client'
|
|
3
|
+
import {timeInterval} from './util'
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
export class Metrics {
|
|
7
|
+
private chainHeight = -1
|
|
8
|
+
private lastBlock = -1
|
|
9
|
+
private mappingSpeed = new Speed({windowSize: 5})
|
|
10
|
+
private mappingItemSpeed = new Speed({windowSize: 5})
|
|
11
|
+
private blockProgress = new Progress({initialValue: 0, windowSize: 20})
|
|
12
|
+
|
|
13
|
+
setChainHeight(height: number): void {
|
|
14
|
+
this.chainHeight = Math.max(height, this.lastBlock)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setLastProcessedBlock(height: number): void {
|
|
18
|
+
this.lastBlock = height
|
|
19
|
+
this.chainHeight = Math.max(this.chainHeight, this.lastBlock)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
updateProgress(processed: number, left: number, time?: bigint): void {
|
|
23
|
+
this.blockProgress.setTargetValue(processed + left)
|
|
24
|
+
this.blockProgress.setCurrentValue(processed, time)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
registerBatch(
|
|
28
|
+
batchSize: number,
|
|
29
|
+
batchItemSize: number,
|
|
30
|
+
batchMappingStartTime: bigint,
|
|
31
|
+
batchMappingEndTime: bigint,
|
|
32
|
+
): void {
|
|
33
|
+
this.mappingSpeed.push(batchSize, batchMappingStartTime, batchMappingEndTime)
|
|
34
|
+
this.mappingItemSpeed.push(batchItemSize || 1, batchMappingStartTime, batchMappingEndTime)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getChainHeight(): number {
|
|
38
|
+
return this.chainHeight
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getLastProcessedBlock(): number {
|
|
42
|
+
return this.lastBlock
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getSyncSpeed(): number {
|
|
46
|
+
return this.blockProgress.speed()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
getSyncEtaSeconds(): number {
|
|
50
|
+
return this.blockProgress.eta()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getSyncRatio(): number {
|
|
54
|
+
return this.blockProgress.ratio()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getMappingSpeed(): number {
|
|
58
|
+
return this.mappingSpeed.speed()
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
getMappingItemSpeed(): number {
|
|
62
|
+
return this.mappingItemSpeed.speed()
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
getStatusLine(): string {
|
|
66
|
+
return `${this.lastBlock} / ${this.chainHeight}, ` +
|
|
67
|
+
`rate: ${Math.round(this.getSyncSpeed())} blocks/sec, ` +
|
|
68
|
+
`mapping: ${Math.round(this.getMappingSpeed())} blocks/sec, ` +
|
|
69
|
+
`${Math.round(this.getMappingItemSpeed())} items/sec, ` +
|
|
70
|
+
`eta: ${timeInterval(this.getSyncEtaSeconds())}`
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
install(): void {
|
|
74
|
+
new Gauge({
|
|
75
|
+
name: 'sqd_processor_chain_height',
|
|
76
|
+
help: 'Chain height of the data source',
|
|
77
|
+
collect: collect(() => this.getChainHeight())
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
new Gauge({
|
|
81
|
+
name: 'sqd_processor_last_block',
|
|
82
|
+
help: 'Last processed block',
|
|
83
|
+
collect: collect(() => this.getLastProcessedBlock())
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
new Gauge({
|
|
87
|
+
name: 'sqd_processor_mapping_blocks_per_second',
|
|
88
|
+
help: 'Mapping performance',
|
|
89
|
+
collect: collect(() => this.getMappingSpeed())
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
new Gauge({
|
|
93
|
+
name: 'sqd_processor_sync_eta_seconds',
|
|
94
|
+
help: 'Estimated time until all required blocks will be processed or until the chain height will be reached',
|
|
95
|
+
collect: collect(() => this.getSyncEtaSeconds())
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
new Gauge({
|
|
99
|
+
name: 'sqd_processor_sync_ratio',
|
|
100
|
+
help: 'Percentage of processed blocks',
|
|
101
|
+
collect: collect(() => this.getSyncRatio())
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
function collect(fn: () => number) {
|
|
108
|
+
return function(this: Gauge<string>) {
|
|
109
|
+
this.set(fn())
|
|
110
|
+
}
|
|
111
|
+
}
|
package/src/run.ts
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import {createLogger} from '@subsquid/logger'
|
|
2
|
+
import {last, runProgram, Throttler} from '@subsquid/util-internal'
|
|
3
|
+
import {createPrometheusServer} from '@subsquid/util-internal-prometheus-server'
|
|
4
|
+
import * as prom from 'prom-client'
|
|
5
|
+
import {Database, HashAndHeight} from './database'
|
|
6
|
+
import {DataSource} from './datasource'
|
|
7
|
+
import {Metrics} from './metrics'
|
|
8
|
+
import {formatHead, getItemsCount} from './util'
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
const log = createLogger('sqd:batch-processor')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export interface DataHandlerContext<Block, Store> {
|
|
15
|
+
/**
|
|
16
|
+
* Storage interface provided by the database
|
|
17
|
+
*/
|
|
18
|
+
store: Store
|
|
19
|
+
/**
|
|
20
|
+
* List of blocks to map and process
|
|
21
|
+
*/
|
|
22
|
+
blocks: Block[]
|
|
23
|
+
/**
|
|
24
|
+
* Signals, that the processor is near the head of the chain.
|
|
25
|
+
*/
|
|
26
|
+
isHead: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
interface BlockBase {
|
|
31
|
+
header: HashAndHeight
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run data processing.
|
|
37
|
+
*
|
|
38
|
+
* This method assumes full control over the current OS process as
|
|
39
|
+
* it terminates the entire program in case of error or
|
|
40
|
+
* at the end of data processing.
|
|
41
|
+
*
|
|
42
|
+
* @param src - data source to ingest data from
|
|
43
|
+
*
|
|
44
|
+
* @param db - database is responsible for providing storage API to data handler
|
|
45
|
+
* and persisting mapping progress and status.
|
|
46
|
+
*
|
|
47
|
+
* @param dataHandler - The data handler, see {@link DataHandlerContext} for an API available to the handler.
|
|
48
|
+
*/
|
|
49
|
+
export function run<Block extends BlockBase, Store>(
|
|
50
|
+
src: DataSource<Block>,
|
|
51
|
+
db: Database<Store>,
|
|
52
|
+
dataHandler: (ctx: DataHandlerContext<Block, Store>) => Promise<void>
|
|
53
|
+
): void {
|
|
54
|
+
runProgram(() => {
|
|
55
|
+
return new Processor(src, db, dataHandler).run()
|
|
56
|
+
}, err => {
|
|
57
|
+
log.fatal(err)
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class Processor<B extends BlockBase, S> {
|
|
63
|
+
private metrics = new Metrics()
|
|
64
|
+
private chainHeight: Throttler<number>
|
|
65
|
+
private statusReportTimer?: any
|
|
66
|
+
private hasStatusNews = false
|
|
67
|
+
|
|
68
|
+
constructor(
|
|
69
|
+
private src: DataSource<B>,
|
|
70
|
+
private db: Database<S>,
|
|
71
|
+
private handler: (ctx: DataHandlerContext<B, S>) => Promise<void>
|
|
72
|
+
) {
|
|
73
|
+
this.chainHeight = new Throttler(() => this.src.getFinalizedHeight(), 30_000)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async run(): Promise<void> {
|
|
77
|
+
let state = await this.db.connect()
|
|
78
|
+
if (state.height >= 0) {
|
|
79
|
+
log.info(`last processed final block was ${state.height}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
await this.assertWeAreOnTheSameChain(state)
|
|
83
|
+
await this.initMetrics(state)
|
|
84
|
+
|
|
85
|
+
for await (let blocks of this.src.getBlockStream(state.height + 1)) {
|
|
86
|
+
if (blocks.length > 0) {
|
|
87
|
+
state = await this.processBatch(state, blocks)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.reportFinalStatus()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private async assertWeAreOnTheSameChain(state: HashAndHeight): Promise<void> {
|
|
95
|
+
if (state.height < 0) return
|
|
96
|
+
let hash = await this.src.getBlockHash(state.height)
|
|
97
|
+
if (state.hash === hash) return
|
|
98
|
+
throw new Error(
|
|
99
|
+
`already indexed block ${formatHead(state)} was not found on chain`
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private async initMetrics(state: HashAndHeight): Promise<void> {
|
|
104
|
+
await this.updateProgressMetrics(await this.chainHeight.get(), state)
|
|
105
|
+
let port = process.env.PROCESSOR_PROMETHEUS_PORT || process.env.PROMETHEUS_PORT
|
|
106
|
+
if (port == null) return
|
|
107
|
+
prom.collectDefaultMetrics()
|
|
108
|
+
this.metrics.install()
|
|
109
|
+
let server = await createPrometheusServer(prom.register, port)
|
|
110
|
+
log.info(`prometheus metrics are served on port ${server.port}`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private updateProgressMetrics(chainHeight: number, state: HashAndHeight, time?: bigint): void {
|
|
114
|
+
this.metrics.setChainHeight(chainHeight)
|
|
115
|
+
this.metrics.setLastProcessedBlock(state.height)
|
|
116
|
+
let left: number
|
|
117
|
+
let processed: number
|
|
118
|
+
if (this.src.getBlocksCountInRange) {
|
|
119
|
+
left = this.src.getBlocksCountInRange({
|
|
120
|
+
from: this.metrics.getLastProcessedBlock() + 1,
|
|
121
|
+
to: this.metrics.getChainHeight()
|
|
122
|
+
})
|
|
123
|
+
processed = this.src.getBlocksCountInRange({
|
|
124
|
+
from: 0,
|
|
125
|
+
to: this.metrics.getChainHeight()
|
|
126
|
+
}) - left
|
|
127
|
+
} else {
|
|
128
|
+
left = this.metrics.getChainHeight() - this.metrics.getLastProcessedBlock()
|
|
129
|
+
processed = this.metrics.getLastProcessedBlock()
|
|
130
|
+
}
|
|
131
|
+
this.metrics.updateProgress(processed, left, time)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
private async processBatch(prevHead: HashAndHeight, blocks: B[]): Promise<HashAndHeight> {
|
|
135
|
+
let chainHeight = await this.chainHeight.get()
|
|
136
|
+
|
|
137
|
+
let nextHead = {
|
|
138
|
+
hash: last(blocks).header.hash,
|
|
139
|
+
height: last(blocks).header.height
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let isOnTop = nextHead.height >= chainHeight
|
|
143
|
+
|
|
144
|
+
let mappingStartTime = process.hrtime.bigint()
|
|
145
|
+
|
|
146
|
+
await this.db.transact({
|
|
147
|
+
prevHead,
|
|
148
|
+
nextHead,
|
|
149
|
+
isOnTop
|
|
150
|
+
}, store => {
|
|
151
|
+
return this.handler({
|
|
152
|
+
store,
|
|
153
|
+
blocks,
|
|
154
|
+
isHead: isOnTop
|
|
155
|
+
})
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
let mappingEndTime = process.hrtime.bigint()
|
|
159
|
+
|
|
160
|
+
this.updateProgressMetrics(chainHeight, nextHead, mappingEndTime)
|
|
161
|
+
this.metrics.registerBatch(
|
|
162
|
+
blocks.length,
|
|
163
|
+
getItemsCount(blocks),
|
|
164
|
+
mappingStartTime,
|
|
165
|
+
mappingEndTime
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
this.reportStatus()
|
|
169
|
+
|
|
170
|
+
return nextHead
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private reportStatus(): void {
|
|
174
|
+
if (this.statusReportTimer == null) {
|
|
175
|
+
log.info(this.metrics.getStatusLine())
|
|
176
|
+
this.statusReportTimer = setTimeout(() => {
|
|
177
|
+
this.statusReportTimer = undefined
|
|
178
|
+
if (this.hasStatusNews) {
|
|
179
|
+
this.hasStatusNews = false
|
|
180
|
+
this.reportStatus()
|
|
181
|
+
}
|
|
182
|
+
}, 5000)
|
|
183
|
+
} else {
|
|
184
|
+
this.hasStatusNews = true
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private reportFinalStatus(): void {
|
|
189
|
+
if (this.statusReportTimer != null) {
|
|
190
|
+
clearTimeout(this.statusReportTimer)
|
|
191
|
+
}
|
|
192
|
+
if (this.hasStatusNews) {
|
|
193
|
+
this.hasStatusNews = false
|
|
194
|
+
log.info(this.metrics.getStatusLine())
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {HashAndHeight} from './database'
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
export function timeInterval(seconds: number): string {
|
|
5
|
+
if (seconds < 60) {
|
|
6
|
+
return Math.round(seconds) + 's'
|
|
7
|
+
}
|
|
8
|
+
let minutes = Math.ceil(seconds/60)
|
|
9
|
+
if (minutes < 60) {
|
|
10
|
+
return minutes+'m'
|
|
11
|
+
}
|
|
12
|
+
let hours = Math.floor(minutes / 60)
|
|
13
|
+
minutes = minutes - hours * 60
|
|
14
|
+
return hours + 'h ' + minutes + 'm'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export function getItemsCount(blocks: any[]): number {
|
|
19
|
+
let count = 0
|
|
20
|
+
for (let block of blocks) {
|
|
21
|
+
for (let key in block) {
|
|
22
|
+
let val = block[key]
|
|
23
|
+
if (Array.isArray(val)) {
|
|
24
|
+
count += val.length
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return count
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export function formatHead(head: HashAndHeight): string {
|
|
33
|
+
return `${head.height}#${shortHash(head.hash)}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
export function shortHash(hash: string): string {
|
|
38
|
+
if (hash.startsWith('0x')) {
|
|
39
|
+
return hash.slice(2, 7)
|
|
40
|
+
} else {
|
|
41
|
+
return hash.slice(0, 5)
|
|
42
|
+
}
|
|
43
|
+
}
|