mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-07-31 20:12:06 +00:00
Compare CLI enhancements for verifying uniswap-watcher entities (#188)
* Update compare CLI to verify only updated entities * Show time difference between GQL requests
This commit is contained in:
parent
87224a4673
commit
00a9c247f5
@ -58,17 +58,18 @@
|
|||||||
|
|
||||||
* For comparing queries in a range of blocks:
|
* For comparing queries in a range of blocks:
|
||||||
|
|
||||||
* Config file should have the names of queries to be fired.
|
* Config file should have the names of queries to be fired along with the corresponding entity names.
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[queries]
|
[queries]
|
||||||
queryDir = "../graph-test-watcher/src/gql/queries"
|
queryDir = "../graph-test-watcher/src/gql/queries"
|
||||||
names = [
|
[queries.names]
|
||||||
"author",
|
author = "Author"
|
||||||
"blog"
|
blog = "Blog"
|
||||||
]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
The queries will be fired if the corresponding entities are updated.
|
||||||
|
|
||||||
* Run the CLI:
|
* Run the CLI:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@ -86,10 +87,9 @@
|
|||||||
|
|
||||||
[queries]
|
[queries]
|
||||||
queryDir = "../graph-test-watcher/src/gql/queries"
|
queryDir = "../graph-test-watcher/src/gql/queries"
|
||||||
names = [
|
[queries.names]
|
||||||
"author",
|
author = "Author"
|
||||||
"blog"
|
blog = "Blog"
|
||||||
]
|
|
||||||
|
|
||||||
[watcher]
|
[watcher]
|
||||||
configPath = "../../graph-test-watcher/environments/local.toml"
|
configPath = "../../graph-test-watcher/environments/local.toml"
|
||||||
|
@ -4,9 +4,9 @@
|
|||||||
|
|
||||||
[queries]
|
[queries]
|
||||||
queryDir = "../../graph-test-watcher/src/gql/queries"
|
queryDir = "../../graph-test-watcher/src/gql/queries"
|
||||||
names = []
|
|
||||||
blockDelayInMs = 250
|
blockDelayInMs = 250
|
||||||
queryLimits = {}
|
queryLimits = {}
|
||||||
|
[queries.names]
|
||||||
|
|
||||||
[watcher]
|
[watcher]
|
||||||
configPath = "../../graph-test-watcher/environments/local.toml"
|
configPath = "../../graph-test-watcher/environments/local.toml"
|
||||||
|
@ -17,9 +17,11 @@ export class Client {
|
|||||||
_queryDir: string;
|
_queryDir: string;
|
||||||
_cache: Cache | undefined;
|
_cache: Cache | undefined;
|
||||||
_endpoint: string;
|
_endpoint: string;
|
||||||
|
_timeDiff: boolean;
|
||||||
|
|
||||||
constructor (config: Config, queryDir: string) {
|
constructor (config: Config, timeDiff: boolean, queryDir: string) {
|
||||||
this._config = config;
|
this._config = config;
|
||||||
|
this._timeDiff = timeDiff;
|
||||||
this._queryDir = path.resolve(process.cwd(), queryDir);
|
this._queryDir = path.resolve(process.cwd(), queryDir);
|
||||||
|
|
||||||
const { gqlEndpoint, cache } = config;
|
const { gqlEndpoint, cache } = config;
|
||||||
@ -35,8 +37,12 @@ export class Client {
|
|||||||
return this._endpoint;
|
return this._endpoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getResult (queryName: string, params: { [key: string]: any }): Promise<any> {
|
async getResult (queryName: string, params: { [key: string]: any }): Promise<{ time: number, data: any }> {
|
||||||
return this._getCachedOrFetch(queryName, params);
|
const startTime = new Date();
|
||||||
|
const data = await this._getCachedOrFetch(queryName, params);
|
||||||
|
const time = (new Date()).getTime() - startTime.getTime();
|
||||||
|
|
||||||
|
return { time, data };
|
||||||
}
|
}
|
||||||
|
|
||||||
async getIds (queryName: string, blockNumber: number): Promise<string[]> {
|
async getIds (queryName: string, blockNumber: number): Promise<string[]> {
|
||||||
@ -80,8 +86,9 @@ export class Client {
|
|||||||
params
|
params
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if request cached in db, if cache is enabled.
|
// Check if request cached in db
|
||||||
if (this._cache) {
|
// If cache is enabled and timeDiff is disabled.
|
||||||
|
if (this._cache && !this._timeDiff) {
|
||||||
const [value, found] = await this._cache.get(keyObj) || [undefined, false];
|
const [value, found] = await this._cache.get(keyObj) || [undefined, false];
|
||||||
if (found) {
|
if (found) {
|
||||||
return value;
|
return value;
|
||||||
|
@ -21,7 +21,9 @@ const log = debug('vulcanize:compare-blocks');
|
|||||||
export const main = async (): Promise<void> => {
|
export const main = async (): Promise<void> => {
|
||||||
const argv = await yargs.parserConfiguration({
|
const argv = await yargs.parserConfiguration({
|
||||||
'parse-numbers': false
|
'parse-numbers': false
|
||||||
}).options({
|
}).env(
|
||||||
|
'COMPARE'
|
||||||
|
).options({
|
||||||
configFile: {
|
configFile: {
|
||||||
alias: 'cf',
|
alias: 'cf',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -53,13 +55,18 @@ export const main = async (): Promise<void> => {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
describe: 'Fetch ids and compare multiple entities',
|
describe: 'Fetch ids and compare multiple entities',
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
timeDiff: {
|
||||||
|
type: 'boolean',
|
||||||
|
describe: 'Compare time taken between GQL queries',
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
}).argv;
|
}).argv;
|
||||||
|
|
||||||
const { startBlock, endBlock, rawJson, queryDir, fetchIds, configFile } = argv;
|
const { startBlock, endBlock, rawJson, queryDir, fetchIds, configFile, timeDiff } = argv;
|
||||||
const config: Config = await getConfig(configFile);
|
const config: Config = await getConfig(configFile);
|
||||||
const snakeNamingStrategy = new SnakeNamingStrategy();
|
const snakeNamingStrategy = new SnakeNamingStrategy();
|
||||||
const clients = await getClients(config, queryDir);
|
const clients = await getClients(config, timeDiff, queryDir);
|
||||||
const queryNames = config.queries.names;
|
const queryNames = config.queries.names;
|
||||||
let diffFound = false;
|
let diffFound = false;
|
||||||
let blockDelay = wait(0);
|
let blockDelay = wait(0);
|
||||||
@ -88,32 +95,39 @@ export const main = async (): Promise<void> => {
|
|||||||
|
|
||||||
for (let blockNumber = startBlock; blockNumber <= endBlock; blockNumber++) {
|
for (let blockNumber = startBlock; blockNumber <= endBlock; blockNumber++) {
|
||||||
const block = { number: blockNumber };
|
const block = { number: blockNumber };
|
||||||
let updatedEntityIds: string[][] = [];
|
const updatedEntityIds: { [entityName: string]: string[] } = {};
|
||||||
|
const updatedEntities: Set<string> = new Set();
|
||||||
let ipldStateByBlock = {};
|
let ipldStateByBlock = {};
|
||||||
|
assert(db);
|
||||||
console.time(`time:compare-block-${blockNumber}`);
|
console.time(`time:compare-block-${blockNumber}`);
|
||||||
|
|
||||||
if (fetchIds) {
|
if (fetchIds) {
|
||||||
// Fetch entity ids updated at block.
|
// Fetch entity ids updated at block.
|
||||||
console.time(`time:fetch-updated-ids-${blockNumber}`);
|
console.time(`time:fetch-updated-ids-${blockNumber}`);
|
||||||
|
|
||||||
const updatedEntityIdPromises = queryNames.map(
|
for (const entityName of Object.values(queryNames)) {
|
||||||
queryName => {
|
updatedEntityIds[entityName] = await db.getEntityIdsAtBlockNumber(
|
||||||
assert(db);
|
|
||||||
|
|
||||||
return db.getEntityIdsAtBlockNumber(
|
|
||||||
blockNumber,
|
blockNumber,
|
||||||
snakeNamingStrategy.tableName(queryName, '')
|
snakeNamingStrategy.tableName(entityName, '')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
console.timeEnd(`time:fetch-updated-ids-${blockNumber}`);
|
||||||
|
} else {
|
||||||
|
for (const entityName of Object.values(queryNames)) {
|
||||||
|
const isUpdated = await db.isEntityUpdatedAtBlockNumber(
|
||||||
|
blockNumber,
|
||||||
|
snakeNamingStrategy.tableName(entityName, '')
|
||||||
);
|
);
|
||||||
|
|
||||||
updatedEntityIds = await Promise.all(updatedEntityIdPromises);
|
if (isUpdated) {
|
||||||
console.timeEnd(`time:fetch-updated-ids-${blockNumber}`);
|
updatedEntities.add(entityName);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.watcher.verifyState) {
|
if (config.watcher.verifyState) {
|
||||||
assert(db);
|
assert(db);
|
||||||
const [block] = await db?.getBlocksAtHeight(blockNumber, false);
|
const [block] = await db.getBlocksAtHeight(blockNumber, false);
|
||||||
assert(subgraphGQLClient);
|
assert(subgraphGQLClient);
|
||||||
const contractIPLDsByBlock = await getIPLDsByBlock(subgraphGQLClient, subgraphContracts, block.blockHash);
|
const contractIPLDsByBlock = await getIPLDsByBlock(subgraphGQLClient, subgraphContracts, block.blockHash);
|
||||||
|
|
||||||
@ -130,7 +144,7 @@ export const main = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await blockDelay;
|
await blockDelay;
|
||||||
for (const [index, queryName] of queryNames.entries()) {
|
for (const [queryName, entityName] of Object.entries(queryNames)) {
|
||||||
try {
|
try {
|
||||||
log(`At block ${blockNumber} for query ${queryName}:`);
|
log(`At block ${blockNumber} for query ${queryName}:`);
|
||||||
let resultDiff = '';
|
let resultDiff = '';
|
||||||
@ -140,16 +154,17 @@ export const main = async (): Promise<void> => {
|
|||||||
|
|
||||||
if (queryLimit) {
|
if (queryLimit) {
|
||||||
// Take only last `queryLimit` entity ids to compare in GQL.
|
// Take only last `queryLimit` entity ids to compare in GQL.
|
||||||
const idsLength = updatedEntityIds[index].length;
|
const idsLength = updatedEntityIds[entityName].length;
|
||||||
updatedEntityIds[index].splice(0, idsLength - queryLimit);
|
updatedEntityIds[entityName].splice(0, idsLength - queryLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const id of updatedEntityIds[index]) {
|
for (const id of updatedEntityIds[entityName]) {
|
||||||
const { diff, result1: result } = await compareQuery(
|
const { diff, result1: result } = await compareQuery(
|
||||||
clients,
|
clients,
|
||||||
queryName,
|
queryName,
|
||||||
{ block, id },
|
{ block, id },
|
||||||
rawJson
|
rawJson,
|
||||||
|
timeDiff
|
||||||
);
|
);
|
||||||
|
|
||||||
if (config.watcher.verifyState) {
|
if (config.watcher.verifyState) {
|
||||||
@ -166,13 +181,16 @@ export const main = async (): Promise<void> => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (updatedEntities.has(entityName)) {
|
||||||
({ diff: resultDiff } = await compareQuery(
|
({ diff: resultDiff } = await compareQuery(
|
||||||
clients,
|
clients,
|
||||||
queryName,
|
queryName,
|
||||||
{ block },
|
{ block },
|
||||||
rawJson
|
rawJson,
|
||||||
|
timeDiff
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (resultDiff) {
|
if (resultDiff) {
|
||||||
log('Results mismatch:', resultDiff);
|
log('Results mismatch:', resultDiff);
|
||||||
|
@ -13,7 +13,9 @@ const log = debug('vulcanize:compare-entity');
|
|||||||
export const main = async (): Promise<void> => {
|
export const main = async (): Promise<void> => {
|
||||||
const argv = await yargs.parserConfiguration({
|
const argv = await yargs.parserConfiguration({
|
||||||
'parse-numbers': false
|
'parse-numbers': false
|
||||||
}).options({
|
}).env(
|
||||||
|
'COMPARE'
|
||||||
|
).options({
|
||||||
configFile: {
|
configFile: {
|
||||||
alias: 'cf',
|
alias: 'cf',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@ -50,6 +52,11 @@ export const main = async (): Promise<void> => {
|
|||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
describe: 'Whether to print out raw diff object',
|
describe: 'Whether to print out raw diff object',
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
timeDiff: {
|
||||||
|
type: 'boolean',
|
||||||
|
describe: 'Compare time taken between GQL queries',
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
}).argv;
|
}).argv;
|
||||||
|
|
||||||
@ -63,9 +70,9 @@ export const main = async (): Promise<void> => {
|
|||||||
hash: argv.blockHash
|
hash: argv.blockHash
|
||||||
};
|
};
|
||||||
|
|
||||||
const clients = await getClients(config, argv.queryDir);
|
const clients = await getClients(config, argv.timeDiff, argv.queryDir);
|
||||||
|
|
||||||
const { diff } = await compareQuery(clients, queryName, { id, block }, argv.rawJson);
|
const { diff } = await compareQuery(clients, queryName, { id, block }, argv.rawJson, argv.timeDiff);
|
||||||
|
|
||||||
if (diff) {
|
if (diff) {
|
||||||
log(diff);
|
log(diff);
|
||||||
|
@ -10,6 +10,7 @@ import fs from 'fs-extra';
|
|||||||
import { diffString, diff } from 'json-diff';
|
import { diffString, diff } from 'json-diff';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import omitDeep from 'omit-deep';
|
import omitDeep from 'omit-deep';
|
||||||
|
import debug from 'debug';
|
||||||
|
|
||||||
import { Config as CacheConfig, getCache } from '@cerc-io/cache';
|
import { Config as CacheConfig, getCache } from '@cerc-io/cache';
|
||||||
import { GraphQLClient } from '@cerc-io/ipld-eth-client';
|
import { GraphQLClient } from '@cerc-io/ipld-eth-client';
|
||||||
@ -18,6 +19,8 @@ import { gql } from '@apollo/client/core';
|
|||||||
import { Client } from './client';
|
import { Client } from './client';
|
||||||
import { DEFAULT_LIMIT } from '../../database';
|
import { DEFAULT_LIMIT } from '../../database';
|
||||||
|
|
||||||
|
const log = debug('vulcanize:compare-utils');
|
||||||
|
|
||||||
const IPLD_STATE_QUERY = `
|
const IPLD_STATE_QUERY = `
|
||||||
query getState($blockHash: String!, $contractAddress: String!, $kind: String){
|
query getState($blockHash: String!, $contractAddress: String!, $kind: String){
|
||||||
getState(blockHash: $blockHash, contractAddress: $contractAddress, kind: $kind){
|
getState(blockHash: $blockHash, contractAddress: $contractAddress, kind: $kind){
|
||||||
@ -42,7 +45,7 @@ interface EndpointConfig {
|
|||||||
|
|
||||||
interface QueryConfig {
|
interface QueryConfig {
|
||||||
queryDir: string;
|
queryDir: string;
|
||||||
names: string[];
|
names: { [queryName: string]: string };
|
||||||
blockDelayInMs: number;
|
blockDelayInMs: number;
|
||||||
queryLimits: { [queryName: string]: number }
|
queryLimits: { [queryName: string]: number }
|
||||||
}
|
}
|
||||||
@ -100,15 +103,23 @@ export const compareQuery = async (
|
|||||||
},
|
},
|
||||||
queryName: string,
|
queryName: string,
|
||||||
params: { [key: string]: any },
|
params: { [key: string]: any },
|
||||||
rawJson: boolean
|
rawJson: boolean,
|
||||||
|
timeDiff: boolean
|
||||||
): Promise<CompareResult> => {
|
): Promise<CompareResult> => {
|
||||||
const { client1, client2 } = clients;
|
const { client1, client2 } = clients;
|
||||||
|
|
||||||
const [result1, result2] = await Promise.all([
|
const [
|
||||||
|
{ data: result1, time: time1 },
|
||||||
|
{ data: result2, time: time2 }
|
||||||
|
] = await Promise.all([
|
||||||
client1.getResult(queryName, params),
|
client1.getResult(queryName, params),
|
||||||
client2.getResult(queryName, params)
|
client2.getResult(queryName, params)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
if (timeDiff) {
|
||||||
|
log(`time:utils#compareQuery-${queryName}-${JSON.stringify(params)}-gql1-[${time1}ms]-gql2-[${time2}ms]-diff-[${time1 - time2}ms]`);
|
||||||
|
}
|
||||||
|
|
||||||
// Getting the diff of two result objects.
|
// Getting the diff of two result objects.
|
||||||
const resultDiff = compareObjects(result1, result2, rawJson);
|
const resultDiff = compareObjects(result1, result2, rawJson);
|
||||||
|
|
||||||
@ -119,7 +130,7 @@ export const compareQuery = async (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getClients = async (config: Config, queryDir?: string):Promise<{
|
export const getClients = async (config: Config, timeDiff: boolean, queryDir?: string):Promise<{
|
||||||
client1: Client,
|
client1: Client,
|
||||||
client2: Client
|
client2: Client
|
||||||
}> => {
|
}> => {
|
||||||
@ -145,12 +156,12 @@ export const getClients = async (config: Config, queryDir?: string):Promise<{
|
|||||||
const client1 = new Client({
|
const client1 = new Client({
|
||||||
gqlEndpoint: gqlEndpoint1,
|
gqlEndpoint: gqlEndpoint1,
|
||||||
cache: endpoint === 'gqlEndpoint1' ? cache : undefined
|
cache: endpoint === 'gqlEndpoint1' ? cache : undefined
|
||||||
}, queryDir);
|
}, timeDiff, queryDir);
|
||||||
|
|
||||||
const client2 = new Client({
|
const client2 = new Client({
|
||||||
gqlEndpoint: gqlEndpoint2,
|
gqlEndpoint: gqlEndpoint2,
|
||||||
cache: endpoint === 'gqlEndpoint2' ? cache : undefined
|
cache: endpoint === 'gqlEndpoint2' ? cache : undefined
|
||||||
}, queryDir);
|
}, timeDiff, queryDir);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
client1,
|
client1,
|
||||||
|
@ -112,6 +112,18 @@ export class Database {
|
|||||||
return entities.map((entity: any) => entity.id);
|
return entities.map((entity: any) => entity.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async isEntityUpdatedAtBlockNumber (blockNumber: number, tableName: string): Promise<boolean> {
|
||||||
|
const repo = this._conn.getRepository(tableName);
|
||||||
|
|
||||||
|
const count = await repo.count({
|
||||||
|
where: {
|
||||||
|
blockNumber
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
async getEntityWithRelations<Entity> (queryRunner: QueryRunner, entity: (new () => Entity), id: string, relationsMap: Map<any, { [key: string]: any }>, block: BlockHeight = {}, depth = 1): Promise<Entity | undefined> {
|
async getEntityWithRelations<Entity> (queryRunner: QueryRunner, entity: (new () => Entity), id: string, relationsMap: Map<any, { [key: string]: any }>, block: BlockHeight = {}, depth = 1): Promise<Entity | undefined> {
|
||||||
let { hash: blockHash, number: blockNumber } = block;
|
let { hash: blockHash, number: blockNumber } = block;
|
||||||
const repo = queryRunner.manager.getRepository(entity);
|
const repo = queryRunner.manager.getRepository(entity);
|
||||||
|
Loading…
Reference in New Issue
Block a user