diff --git a/defaults.ethlogger.yaml b/defaults.ethlogger.yaml index 765faf7..a7a4bdb 100644 --- a/defaults.ethlogger.yaml +++ b/defaults.ethlogger.yaml @@ -97,5 +97,9 @@ pendingTx: enabled: false collectInterval: 10s retryWaitTime: 10s +peerInfo: + enabled: false + collectInterval: 10s + retryWaitTime: 10s internalMetrics: collectInterval: 1s diff --git a/examples/quorum/docker-compose.yaml b/examples/quorum/docker-compose.yaml index 941475a..fe2fecf 100644 --- a/examples/quorum/docker-compose.yaml +++ b/examples/quorum/docker-compose.yaml @@ -344,6 +344,8 @@ services: - SPLUNK_INTERNAL_INDEX=metrics - SPLUNK_HEC_REJECT_INVALID_CERTS=false - ABI_DIR=/abi + - COLLECT_PENDING_TX=true + - COLLECT_PEER_INFO=true - DEBUG=ethlogger:abi:* volumes: - ./abi:/abi @@ -370,6 +372,8 @@ services: - SPLUNK_HEC_REJECT_INVALID_CERTS=false - COLLECT_BLOCKS=false - ABI_DIR=/abi + - COLLECT_PENDING_TX=true + - COLLECT_PEER_INFO=true - DEBUG=ethlogger:abi:* volumes: - ./abi:/abi diff --git a/src/cliflags.ts b/src/cliflags.ts index 39c450a..560efe4 100644 --- a/src/cliflags.ts +++ b/src/cliflags.ts @@ -53,6 +53,11 @@ export const CLI_FLAGS = { env: 'COLLECT_PENDING_TX', description: 'Enables collection of pending transactions', }), + 'collect-peer-info': flags.boolean({ + allowNo: true, + env: 'COLLECT_PEER_INFO', + description: 'Enables collection of detailed peer information', + }), 'collect-internal-metrics': flags.boolean({ allowNo: true, env: 'COLLECT_INTERNAL_METRICS', diff --git a/src/config.ts b/src/config.ts index cbddea6..7066e40 100644 --- a/src/config.ts +++ b/src/config.ts @@ -39,6 +39,8 @@ export interface EthloggerConfigSchema { nodeInfo: NodeInfoConfigSchema; /** Settings for collecting pending transactions from node */ pendingTx: PendingTxConfigSchema; + /** Settings for collecting peer informataion from the node */ + peerInfo: PeerInfoConfigSchema; /** Settings for internal metrics collection */ internalMetrics: InternalMetricsConfigSchema; } @@ -60,6 +62,7 @@ export interface EthloggerConfig { nodeMetrics: NodeMetricsConfig; nodeInfo: NodeInfoConfig; pendingTx: PendingTxConfig; + peerInfo: PeerInfoConfig; internalMetrics: InternalMetricsConfig; } @@ -239,6 +242,24 @@ export interface PendingTxConfig extends Omit { + collectInterval: Duration; + retryWaitTime: WaitTime; +} + /** * Ethlogger-internal metrics allow for visibility into the operation of ethlogger itself. */ @@ -813,6 +834,15 @@ export async function loadEthloggerConfig(flags: CliFlags, dryRun: boolean = fal collectInterval: parseDuration(defaults.pendingTx?.collectInterval) ?? 30000, retryWaitTime: waitTimeFromConfig(defaults.pendingTx?.retryWaitTime) ?? 30000, }, + peerInfo: { + enabled: + flags['collect-peer-info'] ?? + parseBooleanEnvVar(CLI_FLAGS['collect-peer-info'].env) ?? + defaults.peerInfo?.enabled ?? + false, + collectInterval: parseDuration(defaults.peerInfo?.collectInterval) ?? 10000, + retryWaitTime: waitTimeFromConfig(defaults.peerInfo?.retryWaitTime) ?? 10000, + }, }; const result = config as EthloggerConfig; diff --git a/src/index.ts b/src/index.ts index 84ebb1c..31291e6 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,14 +1,15 @@ import { Command } from '@oclif/command'; import debugModule from 'debug'; import { inspect } from 'util'; +import { ContractInfo } from './abi/contract'; import { AbiRepository } from './abi/repo'; import { BlockWatcher } from './blockwatcher'; import { Checkpoint } from './checkpoint'; import { CLI_FLAGS } from './cliflags'; -import { ConfigError, loadEthloggerConfig, EthloggerConfig } from './config'; -import { ContractInfo } from './abi/contract'; +import { ConfigError, EthloggerConfig, loadEthloggerConfig } from './config'; import { BatchedEthereumClient, EthereumClient } from './eth/client'; import { HttpTransport } from './eth/http'; +import { checkHealthState, HealthStateMonitor } from './health'; import { HecClient } from './hec'; import { introspectTargetNodePlatform } from './introspect'; import { substituteVariablesInHecConfig } from './meta'; @@ -20,7 +21,6 @@ import LRUCache from './utils/lru'; import { ManagedResource, shutdownAll } from './utils/resource'; import { waitForSignal } from './utils/signal'; import { InternalStatsCollector } from './utils/stats'; -import { checkHealthState, HealthStateMonitor } from './health'; const { debug, error, info } = createModuleDebug('cli'); @@ -154,6 +154,7 @@ class Ethlogger extends Command { nodeMetrics: config.nodeMetrics, nodeInfo: config.nodeInfo, pendingTx: config.pendingTx, + peerInfo: config.peerInfo, }); addResource(nodeStatsCollector); internalStatsCollector.addSource(nodeStatsCollector, 'nodeStatsCollector'); diff --git a/src/nodestats.ts b/src/nodestats.ts index 618c74a..6105df1 100644 --- a/src/nodestats.ts +++ b/src/nodestats.ts @@ -1,4 +1,4 @@ -import { NodeInfoConfig, NodeMetricsConfig, PendingTxConfig } from './config'; +import { NodeInfoConfig, NodeMetricsConfig, PendingTxConfig, PeerInfoConfig } from './config'; import { EthereumClient } from './eth/client'; import { Output, OutputMessage } from './output'; import { NodePlatformAdapter } from './platforms'; @@ -18,6 +18,8 @@ const initialCounters = { metricsErrorCount: 0, pendingTxCollectCount: 0, pendingTxErrorCount: 0, + peerInfoCollectCount: 0, + peerInfoErrorCount: 0, }; export class NodeStatsCollector implements ManagedResource { @@ -28,6 +30,7 @@ export class NodeStatsCollector implements ManagedResource { infoCollectDuration: new AggregateMetric(), metricsCollectDuration: new AggregateMetric(), pendingtxCollectDuration: new AggregateMetric(), + peerInfoCollectDuration: new AggregateMetric(), }; constructor( private config: { @@ -37,6 +40,7 @@ export class NodeStatsCollector implements ManagedResource { nodeInfo: NodeInfoConfig; nodeMetrics: NodeMetricsConfig; pendingTx: PendingTxConfig; + peerInfo: PeerInfoConfig; } ) {} @@ -47,6 +51,7 @@ export class NodeStatsCollector implements ManagedResource { this.startCollectingNodeInfo(), this.startCollectingNodeMetrics(), this.startCollectingPendingTransactions(), + this.startCollectingPeerInfo(), ]); this.donePromise = p; await p; @@ -132,6 +137,11 @@ export class NodeStatsCollector implements ManagedResource { return await abort.race(platformAdapter.capturePendingTransactions(ethClient, time)); }; + collectPeerInfo = async (abort: AbortHandle, time: number): Promise => { + const { platformAdapter, ethClient } = this.config; + return await abort.race(platformAdapter.capturePeerInfo!(ethClient, time)); + }; + private async startCollectingNodeInfo() { if (!this.config.nodeInfo.enabled) { debug('Node info collection is disabled'); @@ -192,6 +202,34 @@ export class NodeStatsCollector implements ManagedResource { ); } + private async startCollectingPeerInfo() { + if (!this.config.peerInfo.enabled) { + debug('Peer info collection is disabled'); + return; + } + + const { platformAdapter, ethClient } = this.config; + const peerInfoSupported = await platformAdapter.supportsPeerInfo(ethClient); + if (!peerInfoSupported) { + warn( + 'Collection of peer information is not supported by the ethereum node %s (platform %s)', + ethClient.transport.source, + platformAdapter.name + ); + return; + } + + await this.periodicallyCollect( + this.collectPeerInfo, + 'peer info', + this.config.peerInfo.collectInterval, + this.config.peerInfo.retryWaitTime, + this.aggregates.peerInfoCollectDuration, + 'peerInfoCollectCount', + 'peerInfoErrorCount' + ); + } + public flushStats() { const stats = { ...this.counters, diff --git a/src/platforms/generic.ts b/src/platforms/generic.ts index 965d652..a894213 100644 --- a/src/platforms/generic.ts +++ b/src/platforms/generic.ts @@ -224,7 +224,13 @@ export class GenericNodeAdapter implements NodePlatformAdapter { return fetchPendingTransactions(ethClient, captureTime); } - public async supportsPendingTransactions(): Promise { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async supportsPendingTransactions(_: EthereumClient): Promise { return this.supports?.pendingTransactions ?? false; } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async supportsPeerInfo(_: EthereumClient): Promise { + return false; + } } diff --git a/src/platforms/geth.ts b/src/platforms/geth.ts index f476e22..2c114ad 100644 --- a/src/platforms/geth.ts +++ b/src/platforms/geth.ts @@ -6,7 +6,7 @@ import { NodeInfo, PendingTransactionMessage, NodeMetricsMessage } from '../msgs import { OutputMessage } from '../output'; import { createModuleDebug } from '../utils/debug'; import { durationStringToMs, parseAbbreviatedNumber } from '../utils/parse'; -import { captureDefaultMetrics, GenericNodeAdapter } from './generic'; +import { captureDefaultMetrics, GenericNodeAdapter, checkRpcMethodSupport } from './generic'; const { debug, error } = createModuleDebug('platforms:geth'); @@ -162,12 +162,23 @@ export class GethAdapter extends GenericNodeAdapter { const [defaultMetrics, gethMetrics] = await Promise.all([ captureDefaultMetrics(ethClient, captureTime), captureGethMetrics(ethClient, captureTime), - capturePeers(ethClient, captureTime), ]); return [defaultMetrics, gethMetrics]; } + public async supportsPendingTransactions(ethClient: EthereumClient): Promise { + return await checkRpcMethodSupport(ethClient, gethTxpool()); + } + public async capturePendingTransactions(ethClient: EthereumClient, captureTime: number): Promise { return await captureTxpoolData(ethClient, captureTime); } + + public async supportsPeerInfo(ethClient: EthereumClient) { + return await checkRpcMethodSupport(ethClient, gethPeers()); + } + + public async capturePeerInfo?(ethClient: EthereumClient, captureTime: number): Promise { + return await capturePeers(ethClient, captureTime); + } } diff --git a/src/platforms/index.ts b/src/platforms/index.ts index 310cd65..8beed41 100644 --- a/src/platforms/index.ts +++ b/src/platforms/index.ts @@ -20,4 +20,6 @@ export interface NodePlatformAdapter { captureNodeInfo(ethClient: EthereumClient): Promise; capturePendingTransactions(ethClient: EthereumClient, captureTime: number): Promise; supportsPendingTransactions(ethClient: EthereumClient): Promise; + capturePeerInfo?(ethClient: EthereumClient, captureTime: number): Promise; + supportsPeerInfo(ethClient: EthereumClient): Promise; } diff --git a/test/config.test.ts b/test/config.test.ts index fb7f3b1..530fb4d 100644 --- a/test/config.test.ts +++ b/test/config.test.ts @@ -119,6 +119,11 @@ test('defaults', async () => { }, "type": "hec", }, + "peerInfo": Object { + "collectInterval": 10000, + "enabled": false, + "retryWaitTime": 10000, + }, "pendingTx": Object { "collectInterval": 10000, "enabled": false,