UNPKG

@ledgerhq/coin-modules-monitoring

Version:
185 lines 8.66 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = default_1; const impl_1 = require("@ledgerhq/live-common/bridge/impl"); const test_helpers_1 = require("@ledgerhq/cryptoassets/cal-client/test-helpers"); const cryptoassets_1 = require("@ledgerhq/cryptoassets"); const rxjs_1 = require("rxjs"); const accountId_1 = require("@ledgerhq/ledger-wallet-framework/account/accountId"); const LiveConfig_1 = require("@ledgerhq/live-config/LiveConfig"); const sharedConfig_1 = require("@ledgerhq/live-common/config/sharedConfig"); const load_all_coins_1 = require("@ledgerhq/live-common/coin-modules/load-all-coins"); const currencies_1 = __importDefault(require("./currencies")); const datadog_1 = require("./datadog"); const measure_1 = require("./measure"); function formatDist(dist, unit = "", decimals = 3) { const round = (v) => Math.round(v * Math.pow(10, decimals)) / Math.pow(10, decimals); return `min=${round(dist.min)}${unit}, average=${round(dist.average)}${unit}, max=${round(dist.max)}${unit}, p90=${round(dist.p90)}${unit}, p99=${round(dist.p99)}${unit}`; } function formatCalls(total, calls) { return (`Total: ${total} ` + Object.entries(calls) .map(([domain, nbOfCalls]) => `- ${domain}: ${nbOfCalls}`) .join(" ")); } function formatDuration(ms) { if (ms < 1000) return `${ms.toFixed(0)} ms`; const totalSeconds = Math.floor(ms / 1000); const remainingMs = ms % 1000; const seconds = totalSeconds % 60; const minutes = Math.floor(totalSeconds / 60) % 60; const hours = Math.floor(totalSeconds / 3600); const parts = []; if (hours) parts.push(`${hours}h`); if (minutes) parts.push(`${minutes}m`); if (seconds || (!hours && !minutes)) { if (remainingMs > 0 && ms < 60_000) { parts.push(`${seconds}.${remainingMs.toString().padStart(3, "0")}s`); } else { parts.push(`${seconds}s`); } } return parts.join(" "); } function hasCauseProperty(object) { return (typeof object === "object" && object !== null && "cause" in object && typeof object.cause === "object" && object.cause !== null); } function objectWithoutKeys(obj, excludeKeys) { const set = new Set(excludeKeys); return Object.fromEntries(Object.entries(obj).filter(([key]) => !set.has(key))); } function logErrorProperties(error) { const errorProperties = objectWithoutKeys(error, ["message", "stack", "cause"]); console.error(` └─ Error properties:`, errorProperties); if (hasCauseProperty(error)) { const causeProperties = objectWithoutKeys(error.cause, ["stack", "cause"]); console.error(` └─ Error cause properties:`, causeProperties); } } function prettyLog(i, nbOfAccounts, scanDuration, syncDuration, scanCalls, scanRoutes, scanCpu, scanMem, syncCalls, syncRoutes, syncCpu, syncMem) { const totalDuration = scanDuration + syncDuration; console.log(`\n[${i} / ${nbOfAccounts}] ✅ Completed in ${formatDuration(totalDuration)}`); console.log(` ┌─ 🔎 Scan`); console.log(` │ • Calls: ${formatCalls(scanCalls, scanRoutes)}`); console.log(` │ • CPU : ${formatDist(scanCpu, "%")}`); console.log(` │ • MEM : ${formatDist(scanMem, " MB")}`); console.log(` └─ 🔄 Sync`); console.log(` • Calls: ${formatCalls(syncCalls, syncRoutes)}`); console.log(` • CPU : ${formatDist(syncCpu, "%")}`); console.log(` • MEM : ${formatDist(syncMem, " MB")}\n`); } function toEmptyAccount(currency, info) { const id = (0, accountId_1.encodeAccountId)({ type: "js", version: "2", currencyId: currency.id, xpubOrAddress: info.xpub ?? info.address, derivationMode: info.derivationMode ?? "", }); return { id, currency, freshAddress: info.address, xpub: info.xpub, derivationMode: info.derivationMode ?? "", operations: [], pendingOperations: [], balanceHistoryCache: {}, blockHeight: 0, }; } function getSync(currency) { const bridge = (0, impl_1.getAccountBridgeByFamily)(currency.family); return async (account) => (0, rxjs_1.firstValueFrom)((await bridge) .sync(account, { paginationConfig: {} }) .pipe((0, rxjs_1.reduce)((a, f) => f(a), account))); } async function default_1(currencyIds, accountTypes) { (0, load_all_coins_1.registerAllCoins)(); LiveConfig_1.LiveConfig.setConfig(sharedConfig_1.liveConfig); // Setup CAL client store for monitoring (automatically set as global store) (0, test_helpers_1.setupCalClientStore)(); const result = { entries: [], failed: false, }; const nbOfAccounts = currencyIds .flatMap(currencyId => Object.keys(currencies_1.default[currencyId].accounts)) .filter(currencyAccountType => accountTypes.some(type => type === currencyAccountType)).length; let i = 0; console.log(`Monitoring ${nbOfAccounts} account(s) within ${currencyIds.join(", ")}`); for (const currencyId of currencyIds) { const monitored = currencies_1.default[currencyId]; const currency = (0, cryptoassets_1.getCryptoCurrencyById)(currencyId); const sync = getSync(currency); for (const accountType of accountTypes) { const info = monitored.accounts[accountType]; if (!info) { console.log(`\nSkipping currency = "${currencyId}", no account = "${accountType}"`); continue; } console.log(`\n[${++i} / ${nbOfAccounts}] Start (currency = "${currencyId}" account = "${accountType}")`); try { const startScan = Date.now(); const { result: initialAccount, totalCalls: scanCalls, callsByDomain: scanRoutes, cpu: scanCpu, memory: scanMem, } = await (0, measure_1.measureCalls)(() => sync(toEmptyAccount(currency, info))); const endScan = Date.now(); const startSync = Date.now(); const { totalCalls: syncCalls, callsByDomain: syncRoutes, cpu: syncCpu, memory: syncMem, } = await (0, measure_1.measureCalls)(() => sync(initialAccount)); const endSync = Date.now(); const scanDuration = endScan - startScan; const syncDuration = endSync - startSync; const { xpubOrAddress } = (0, accountId_1.decodeAccountId)(initialAccount.id); prettyLog(i, nbOfAccounts, scanDuration, syncDuration, scanCalls, scanRoutes, scanCpu, scanMem, syncCalls, syncRoutes, syncCpu, syncMem); result.entries.push({ duration: scanDuration, currencyName: currency.id, coinModuleName: currency.family, operationType: "scan", accountType: accountType, transactions: initialAccount.operationsCount, accountAddressOrXpub: xpubOrAddress, totalNetworkCalls: scanCalls, networkCallsByDomain: scanRoutes, cpu: scanCpu, memory: scanMem, }, { duration: syncDuration, currencyName: currency.id, coinModuleName: currency.family, operationType: "sync", accountType: accountType, transactions: initialAccount.operationsCount, accountAddressOrXpub: xpubOrAddress, totalNetworkCalls: syncCalls, networkCallsByDomain: syncRoutes, cpu: syncCpu, memory: syncMem, }); } catch (err) { console.error(`Skipping failing run. Error: ${err instanceof Error ? err.stack ?? err.message : err}`); // We may miss some parameters added to the error on runtime // We display only the message on the top for convenience and better readability // So we added this log to have the full object if (err instanceof Error) { logErrorProperties(err); } result.failed = true; } } } await (0, datadog_1.submitLogs)(result.entries); return result; } //# sourceMappingURL=run.js.map