202 lines
7.3 KiB
TypeScript
202 lines
7.3 KiB
TypeScript
import { InfluxDB, Point, WriteApi } from '@influxdata/influxdb-client';
|
|
import { IMetricsProvider } from '../domain/interfaces.js';
|
|
import { logger } from '../logging/index.js';
|
|
|
|
export class InfluxMetricsProvider implements IMetricsProvider {
|
|
private writeApi: WriteApi | null = null;
|
|
private influx: InfluxDB | null = null;
|
|
|
|
constructor(url: string, token: string, org: string, bucket: string) {
|
|
try {
|
|
this.influx = new InfluxDB({ url, token });
|
|
this.writeApi = this.influx.getWriteApi(org, bucket, 'ms', {
|
|
writeFailed(error) {
|
|
logger.error('InfluxDB metrics point write failed', error);
|
|
},
|
|
});
|
|
|
|
logger.info('InfluxDB metrics provider initialized successfully');
|
|
} catch (error) {
|
|
logger.error(
|
|
'Failed to initialize InfluxDB metrics provider. System will run with degraded/no time-series metrics reporting.',
|
|
error,
|
|
);
|
|
}
|
|
}
|
|
|
|
private writePoint(point: Point): void {
|
|
if (!this.writeApi) {
|
|
logger.debug('InfluxDB write client not initialized, discarding point', {
|
|
measurement: point.toLineProtocol(),
|
|
});
|
|
return;
|
|
}
|
|
try {
|
|
this.writeApi.writePoint(point);
|
|
} catch (error) {
|
|
logger.error('Error writing point to InfluxDB', error);
|
|
}
|
|
}
|
|
|
|
recordWalletDetectionLatency(latencyMs: number, wallet: string): void {
|
|
const p = new Point('wallet_detection_latency')
|
|
.tag('wallet', wallet)
|
|
.floatField('latency_ms', latencyMs);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordSignalProcessingLatency(latencyMs: number): void {
|
|
const p = new Point('signal_processing_latency').floatField('latency_ms', latencyMs);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordQuoteLatency(latencyMs: number, provider: string): void {
|
|
const p = new Point('quote_latency').tag('provider', provider).floatField('latency_ms', latencyMs);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordExecutionLatency(latencyMs: number, scenario: number): void {
|
|
const p = new Point('execution_latency')
|
|
.tag('scenario', `${scenario}ms`)
|
|
.floatField('latency_ms', latencyMs);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordPrice(tokenMint: string, priceSol: number): void {
|
|
const p = new Point('token_price').tag('token_mint', tokenMint).floatField('price_sol', priceSol);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordPortfolioBalance(balanceSol: number, scenario: number): void {
|
|
const p = new Point('portfolio_balance')
|
|
.tag('scenario', `${scenario}ms`)
|
|
.floatField('balance_sol', balanceSol);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordTradePnl(pnlSol: number, scenario: number, tokenMint: string): void {
|
|
const p = new Point('trade_pnl')
|
|
.tag('scenario', `${scenario}ms`)
|
|
.tag('token_mint', tokenMint)
|
|
.floatField('pnl_sol', pnlSol);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordExcursion(mfeSol: number, maeSol: number, scenario: number, tokenMint: string): void {
|
|
const p = new Point('trade_excursion')
|
|
.tag('scenario', `${scenario}ms`)
|
|
.tag('token_mint', tokenMint)
|
|
.floatField('mfe_sol', mfeSol)
|
|
.floatField('mae_sol', maeSol);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordQueueDepth(depth: number): void {
|
|
const p = new Point('queue_depth').intField('depth', depth);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordQueueRetries(retries: number, signature: string): void {
|
|
const p = new Point('queue_retries').tag('signature', signature).intField('retries', retries);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordQueue429s(signature: string): void {
|
|
const p = new Point('queue_429s').tag('signature', signature).intField('count', 1);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordWalletEventReceived(wallet: string): void {
|
|
const p = new Point('wallet_event_received').tag('wallet', wallet).intField('count', 1);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordWalletQueueDepth(wallet: string, depth: number): void {
|
|
const p = new Point('wallet_queue_depth').tag('wallet', wallet).intField('depth', depth);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordOldestQueuedAge(wallet: string, ageMs: number): void {
|
|
const p = new Point('oldest_queued_age').tag('wallet', wallet).floatField('age_ms', ageMs);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordDroppedOldestCount(wallet: string): void {
|
|
const p = new Point('dropped_oldest_count').tag('wallet', wallet).intField('count', 1);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordTransactionRelevance(relevant: boolean): void {
|
|
const p = new Point('transaction_relevance').tag('relevant', String(relevant)).intField('count', 1);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordRpcRequestRate(requestsPerSecond: number): void {
|
|
const p = new Point('rpc_request_rate').floatField('req_per_sec', requestsPerSecond);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordQueueProcessed(wallet: string): void {
|
|
const p = new Point('queue_processed').tag('wallet', wallet).intField('count', 1);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordRpcFetchError(wallet: string): void {
|
|
const p = new Point('rpc_fetch_error').tag('wallet', wallet).intField('count', 1);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordWalletState(wallet: string, isHot: boolean): void {
|
|
const p = new Point('wallet_state').tag('wallet', wallet).intField('is_hot', isHot ? 1 : 0);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordWalletIncomingRate(wallet: string, rate: number): void {
|
|
const p = new Point('wallet_incoming_rate').tag('wallet', wallet).floatField('rate', rate);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
recordWalletProcessedRate(wallet: string, rate: number): void {
|
|
const p = new Point('wallet_processed_rate').tag('wallet', wallet).floatField('rate', rate);
|
|
this.writePoint(p);
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
if (this.writeApi) {
|
|
logger.info('Closing InfluxDB metrics writer...');
|
|
try {
|
|
await this.writeApi.close();
|
|
logger.info('InfluxDB metrics writer closed.');
|
|
} catch (error) {
|
|
logger.error('Error closing InfluxDB metrics writer', error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// In-Memory/Mock Metrics Provider for local tests and offline dev
|
|
export class MockMetricsProvider implements IMetricsProvider {
|
|
recordWalletDetectionLatency(_latencyMs: number, _wallet: string): void {}
|
|
recordSignalProcessingLatency(_latencyMs: number): void {}
|
|
recordQuoteLatency(_latencyMs: number, _provider: string): void {}
|
|
recordExecutionLatency(_latencyMs: number, _scenario: number): void {}
|
|
recordPrice(_tokenMint: string, _priceSol: number): void {}
|
|
recordPortfolioBalance(_balanceSol: number, _scenario: number): void {}
|
|
recordTradePnl(_pnlSol: number, _scenario: number, _tokenMint: string): void {}
|
|
recordExcursion(_mfeSol: number, _maeSol: number, _scenario: number, _tokenMint: string): void {}
|
|
recordQueueDepth(_depth: number): void {}
|
|
recordQueueRetries(_retries: number, _signature: string): void {}
|
|
recordQueue429s(_signature: string): void {}
|
|
recordWalletEventReceived(_wallet: string): void {}
|
|
recordWalletQueueDepth(_wallet: string, _depth: number): void {}
|
|
recordOldestQueuedAge(_wallet: string, _ageMs: number): void {}
|
|
recordDroppedOldestCount(_wallet: string): void {}
|
|
recordTransactionRelevance(_relevant: boolean): void {}
|
|
recordRpcRequestRate(_requestsPerSecond: number): void {}
|
|
recordQueueProcessed(_wallet: string): void {}
|
|
recordRpcFetchError(_wallet: string): void {}
|
|
recordWalletState(_wallet: string, _isHot: boolean): void {}
|
|
recordWalletIncomingRate(_wallet: string, _rate: number): void {}
|
|
recordWalletProcessedRate(_wallet: string, _rate: number): void {}
|
|
}
|