UNPKG

@ledgerhq/coin-modules-monitoring

Version:
243 lines (214 loc) 7.11 kB
import { spawn } from "child_process"; import path from "path"; import { EventEmitter } from "node:events"; import commandLineArgs, { CommandLineOptions } from "command-line-args"; import packageJson from "../package.json"; import currencies, { type AccountType } from "./currencies"; import run from "./run"; // Mute `MaxListenersExceededWarning` // TODO: check if emitters can be better handled EventEmitter.defaultMaxListeners = 0; const VALID_ACCOUNT_TYPES = ["pristine", "average", "big"] as const; const VALID_CURRENCIES = Object.keys(currencies); function isValidAccountType(value: string): value is AccountType { return VALID_ACCOUNT_TYPES.some(type => type === value); } function validateAccountTypes(value: string): AccountType[] { const types = value.split(",").map(t => t.trim()); const invalidTypes = types.filter(t => !isValidAccountType(t)); if (invalidTypes.length > 0) { throw new Error( `Invalid account types: ${invalidTypes.join(", ")}. Valid types are: ${VALID_ACCOUNT_TYPES.join(", ")}`, ); } return types.filter(isValidAccountType); } function validateCurrencies(value: string): string[] { const currencyList = value.split(",").map(c => c.trim()); const invalidCurrencies = currencyList.filter(c => !VALID_CURRENCIES.includes(c)); if (invalidCurrencies.length > 0) { throw new Error( `Invalid currencies: ${invalidCurrencies.join(", ")}. Valid currencies are: ${VALID_CURRENCIES.join(", ")}`, ); } return currencyList; } const mainOptionDefinitions = [ { name: "command", defaultOption: true }, { name: "help", alias: "h", type: Boolean }, { name: "version", alias: "v", type: Boolean }, ]; const monitorOptionDefinitions = [ { name: "help", alias: "h", type: Boolean }, { name: "account-types", alias: "t", type: String, defaultValue: "pristine,average,big", }, { name: "currencies", alias: "c", type: String, defaultValue: "all", }, { name: "isolated", alias: "i", type: Boolean, defaultValue: false, }, ]; function printHelp() { console.log(`coin-modules-monitoring v${packageJson.version}`); console.log("Monitor cryptocurrency modules and push metrics to Datadog"); console.log(""); console.log("Usage: pnpm start <command> [options]"); console.log(""); console.log("Commands:"); console.log(" monitor Monitor specified currencies and account types"); console.log(""); console.log("Global Options:"); console.log(" -h, --help Show help"); console.log(" -v, --version Show version"); console.log(""); console.log("Monitor Command Options:"); console.log( ` -t, --account-types <types> Comma-separated account types (${VALID_ACCOUNT_TYPES.join(", ")})`, ); console.log(" Default: pristine,average,big"); console.log( ` -c, --currencies <currencies> Comma-separated currencies (${VALID_CURRENCIES.slice(0, 5).join(", ")}...)`, ); console.log(" Default: all"); console.log( " -i, --isolated Run each currency/account combination in isolated processes", ); console.log(" Default: false (run in same process)"); console.log(""); console.log("Examples:"); console.log(" pnpm start monitor"); console.log(" pnpm start monitor -c bitcoin,ethereum -t pristine,average"); console.log(" pnpm start monitor --currencies all --account-types big"); console.log( " pnpm start monitor --currencies bitcoin,etherem --account-types pristine,big --isolated", ); } function printVersion() { console.log(packageJson.version); } async function runIsolatedMonitor(currency: string, accountType: string): Promise<void> { return new Promise((resolve, reject) => { const child = spawn( "node", [ "--import", path.join(__dirname, "..", "register.mjs"), path.join(__dirname, "cli.js"), "monitor", "--currencies", currency, "--account-types", accountType, ], { stdio: "inherit", env: process.env, }, ); child.on("close", code => { if (code === 0) { resolve(); } else { reject(new Error(`Process exited with code ${code}`)); } }); child.on("error", reject); }); } async function runMonitorCommand(options: CommandLineOptions) { if (options.help) { printHelp(); process.exit(0); } const accountTypes = validateAccountTypes(options["account-types"]); const filteredCurrencies = options.currencies === "all" ? VALID_CURRENCIES : validateCurrencies(options.currencies); const monitoredCurrencies = filteredCurrencies.filter(c => { const currency = currencies[c]; if (currency?.skip) { console.log(`Skipping "${c}". Reason: ${currency.skip}`); } return !currency.skip; }); if (!monitoredCurrencies.length) { console.log("No currencies to monitor. Exit now."); process.exit(0); } if (options.isolated) { let atLeastOneProcessFailed = false; for (const currency of monitoredCurrencies) { for (const accountType of accountTypes) { try { await runIsolatedMonitor(currency, accountType); } catch (error) { if (!atLeastOneProcessFailed) { atLeastOneProcessFailed = true; } console.error( `Process failed for currency ${currency} and account type ${accountType} with error ${error}`, ); console.log("Global process will continue..."); } } } process.exit(atLeastOneProcessFailed ? 1 : 0); } try { const result = await run(monitoredCurrencies, accountTypes); if (!result.entries.length) { console.log("No resulted entries. Exit now."); } else { console.log("========== SUMMARY ========== \n"); console.table( result.entries.map(log => ({ address: log.accountAddressOrXpub, type: log.accountType, transactions: log.transactions, currency: log.currencyName, module: log.coinModuleName, operation: log.operationType, "duration (ms)": log.duration, "network calls": log.totalNetworkCalls, })), ); } process.exit(result.failed ? 1 : 0); } catch (error) { console.error(error); process.exit(1); } } const mainOptions = commandLineArgs(mainOptionDefinitions, { stopAtFirstUnknown: true, }); if (mainOptions.version) { printVersion(); process.exit(0); } if (mainOptions.help || !mainOptions.command) { printHelp(); process.exit(0); } switch (mainOptions.command) { case "monitor": { const argv = mainOptions._unknown || []; const monitorOptions = commandLineArgs(monitorOptionDefinitions, { argv }); runMonitorCommand(monitorOptions); break; } default: console.error(`Unknown command: ${mainOptions.command}`); console.error("Run 'pnpm start --help' for usage information."); process.exit(1); }