mirror of
https://github.com/0xfnzero/solana-streamer.git
synced 2026-08-08 14:47:45 +00:00
events optimization
This commit is contained in:
@@ -62,3 +62,4 @@ arrayref = "0.3.6"
|
||||
borsh-derive = "1.5.5"
|
||||
indicatif = "0.17.11"
|
||||
maplit = "1.0.2"
|
||||
env_logger = "0.11.8"
|
||||
|
||||
@@ -17,6 +17,9 @@ A lightweight Rust library for real-time event streaming from Solana DEX trading
|
||||
5. **Unified Event Interface**: Consistent event handling across all supported protocols
|
||||
6. **Event Parsing System**: Automatic parsing and categorization of protocol-specific events
|
||||
7. **High Performance**: Optimized for low-latency event processing
|
||||
8. **Batch Processing**: Efficient event batching to improve throughput and reduce overhead
|
||||
9. **Performance Monitoring**: Built-in performance metrics and monitoring capabilities
|
||||
10. **Memory Optimization**: Object pooling and caching to reduce memory allocations
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -45,6 +48,8 @@ solana-streamer-sdk = "0.1.8"
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Usage with Performance Monitoring
|
||||
|
||||
```rust
|
||||
use solana_streamer_sdk::{
|
||||
match_event,
|
||||
@@ -70,22 +75,30 @@ use solana_streamer_sdk::{
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Starting Solana Streamer...");
|
||||
|
||||
// Test Yellowstone gRPC with performance monitoring
|
||||
test_grpc().await?;
|
||||
|
||||
// Test ShredStream with performance monitoring
|
||||
test_shreds().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Subscribing to GRPC events...");
|
||||
println!("Subscribing to Yellowstone gRPC events...");
|
||||
|
||||
let grpc = YellowstoneGrpc::new(
|
||||
// Create gRPC client with performance monitoring enabled
|
||||
let grpc = YellowstoneGrpc::new_with_config(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None,
|
||||
true, // enable performance monitoring
|
||||
)?;
|
||||
|
||||
let callback = create_event_callback();
|
||||
|
||||
// Will try to parse corresponding protocol events from transactions
|
||||
// Configure protocols to monitor
|
||||
let protocols = vec![
|
||||
Protocol::PumpFun,
|
||||
Protocol::PumpSwap,
|
||||
@@ -94,19 +107,23 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Protocol::RaydiumClmm,
|
||||
];
|
||||
|
||||
// Filter accounts
|
||||
// Configure account filtering
|
||||
let account_include = vec![
|
||||
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
|
||||
PUMPSWAP_PROGRAM_ID.to_string(), // Listen to pumpswap program ID
|
||||
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
|
||||
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
|
||||
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
|
||||
"xxxxxxxx".to_string(), // Listen to xxxxx account
|
||||
];
|
||||
let account_exclude = vec![];
|
||||
let account_required = vec![];
|
||||
|
||||
println!("Starting to listen for events, press Ctrl+C to stop...");
|
||||
println!("Monitoring programs: {:?}", account_include);
|
||||
|
||||
println!("Starting subscription...");
|
||||
|
||||
// Subscribe with automatic performance monitoring
|
||||
grpc.subscribe_events_v2(
|
||||
protocols,
|
||||
None,
|
||||
@@ -124,7 +141,11 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Subscribing to ShredStream events...");
|
||||
|
||||
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
|
||||
// Create ShredStream client with performance monitoring enabled
|
||||
let shred_stream = ShredStreamGrpc::new_with_config(
|
||||
"http://127.0.0.1:10800".to_string(),
|
||||
true, // enable performance monitoring
|
||||
).await?;
|
||||
let callback = create_event_callback();
|
||||
let protocols = vec![
|
||||
Protocol::PumpFun,
|
||||
@@ -135,6 +156,8 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
];
|
||||
|
||||
println!("Listening for events, press Ctrl+C to stop...");
|
||||
|
||||
// Subscribe with automatic performance monitoring
|
||||
shred_stream
|
||||
.shredstream_subscribe(protocols, None, callback)
|
||||
.await?;
|
||||
@@ -144,6 +167,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|
||||
|event: Box<dyn UnifiedEvent>| {
|
||||
println!("🎉 Event received! Type: {:?}, ID: {}", event.event_type(), event.id());
|
||||
match_event!(event, {
|
||||
BonkPoolCreateEvent => |e: BonkPoolCreateEvent| {
|
||||
// When using grpc, you can get block_time from each event
|
||||
@@ -245,28 +269,134 @@ src/
|
||||
└── main.rs # Example program
|
||||
```
|
||||
|
||||
## Performance Optimizations
|
||||
|
||||
### Recent Performance Improvements
|
||||
|
||||
The latest version includes significant performance optimizations that dramatically improve event processing throughput:
|
||||
|
||||
#### 1. **Batch Processing System**
|
||||
- **Event Batching**: Events are now processed in batches (default: 100 events per batch) instead of individually
|
||||
- **Reduced Callback Overhead**: Batch processing reduces the number of callback invocations by up to 100x
|
||||
- **Improved Throughput**: Significantly higher event processing rates with lower CPU usage
|
||||
- **Configurable Batch Size**: Adjustable batch size to balance latency vs throughput
|
||||
|
||||
#### 2. **Memory Optimization**
|
||||
- **Object Pooling**: `EventMetadataPool` and `TransferDataPool` reduce memory allocations
|
||||
- **Pre-allocated Vectors**: `Vec::with_capacity()` for collections to avoid dynamic resizing
|
||||
- **Reduced Cloning**: Minimized unnecessary data cloning operations
|
||||
- **Memory Usage Monitoring**: Real-time memory usage tracking in performance metrics
|
||||
|
||||
#### 3. **Caching System**
|
||||
- **Event Parse Cache**: `EventParseCache` avoids redundant transaction parsing
|
||||
- **Cache Hit Rate Monitoring**: Track cache effectiveness in performance metrics
|
||||
- **Intelligent Cache Management**: Automatic cache size management
|
||||
|
||||
#### 4. **Performance Monitoring**
|
||||
- **Real-time Metrics**: Built-in performance monitoring with automatic display
|
||||
- **Comprehensive Statistics**: Events/second, processing times, memory usage, cache hit rates
|
||||
- **Configurable Monitoring**: Enable/disable performance monitoring as needed
|
||||
- **Zero Overhead**: Monitoring can be completely disabled for maximum performance
|
||||
|
||||
#### 5. **Concurrent Processing**
|
||||
- **Async Event Processing**: Non-blocking event handling with `tokio`
|
||||
- **Parallel Protocol Parsing**: Multiple protocols parsed concurrently
|
||||
- **Optimized Channel Sizes**: Increased channel capacity (5000) to handle high event volumes
|
||||
|
||||
### Performance Metrics
|
||||
|
||||
The built-in performance monitoring provides detailed insights:
|
||||
|
||||
- **Events Processed**: Total number of events processed
|
||||
- **Events/Second**: Real-time processing rate (5-second rolling window)
|
||||
- **Average Processing Time**: Mean time to process events
|
||||
- **Min/Max Processing Time**: Fastest and slowest processing times
|
||||
- **Cache Hit Rate**: Percentage of cache hits for event parsing
|
||||
- **Memory Usage**: Estimated memory consumption
|
||||
|
||||
### Configuration Options
|
||||
|
||||
```rust
|
||||
// Performance monitoring configuration
|
||||
let grpc = YellowstoneGrpc::new_with_config(
|
||||
endpoint,
|
||||
x_token,
|
||||
true, // enable performance monitoring
|
||||
)?;
|
||||
|
||||
// Batch processing is automatically enabled with optimal settings
|
||||
// Batch size: 100 events
|
||||
// Batch timeout: 10ms
|
||||
// Channel size: 5000
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
1. **Connection Management**: Properly handle connection lifecycle and reconnection
|
||||
2. **Event Filtering**: Use protocol filtering to reduce unnecessary event processing
|
||||
3. **Memory Management**: Implement proper cleanup for long-running streams
|
||||
4. **Error Handling**: Robust error handling for network issues and service disruptions
|
||||
5. **Batch Processing**: Leverage batch processing for high-throughput scenarios
|
||||
6. **Performance Monitoring**: Use built-in metrics to optimize your application
|
||||
|
||||
## Configuration Options
|
||||
|
||||
### Yellowstone gRPC Configuration
|
||||
|
||||
```rust
|
||||
// Recommended: Create gRPC client with performance monitoring enabled
|
||||
let grpc = YellowstoneGrpc::new_with_config(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None,
|
||||
true, // enable performance monitoring
|
||||
)?;
|
||||
|
||||
// Alternative: Basic configuration (performance monitoring enabled by default)
|
||||
let grpc = YellowstoneGrpc::new(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None, // Custom configuration options
|
||||
None,
|
||||
)?;
|
||||
|
||||
// Maximum performance: Disable performance monitoring
|
||||
let grpc = YellowstoneGrpc::new_with_config(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None,
|
||||
false, // disable performance monitoring
|
||||
)?;
|
||||
```
|
||||
|
||||
### ShredStream Configuration
|
||||
|
||||
```rust
|
||||
// Recommended: Create ShredStream client with performance monitoring enabled
|
||||
let shred_stream = ShredStreamGrpc::new_with_config(
|
||||
"http://127.0.0.1:10800".to_string(),
|
||||
true, // enable performance monitoring
|
||||
).await?;
|
||||
|
||||
// Alternative: Basic configuration (performance monitoring enabled by default)
|
||||
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
|
||||
|
||||
// Maximum performance: Disable performance monitoring
|
||||
let shred_stream = ShredStreamGrpc::new_with_config(
|
||||
"http://127.0.0.1:10800".to_string(),
|
||||
false, // disable performance monitoring
|
||||
).await?;
|
||||
```
|
||||
|
||||
### Performance Tuning
|
||||
|
||||
```rust
|
||||
// Runtime performance monitoring control
|
||||
grpc.set_enable_metrics(true).await; // Enable monitoring
|
||||
grpc.set_enable_metrics(false).await; // Disable monitoring
|
||||
|
||||
// Get current performance metrics
|
||||
let metrics = grpc.get_metrics().await;
|
||||
println!("Current performance: {:?}", metrics);
|
||||
|
||||
// Manual performance metrics display
|
||||
grpc.print_metrics().await;
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
+43
-4
@@ -17,6 +17,9 @@
|
||||
5. **统一事件接口**: 在所有支持的协议中保持一致的事件处理
|
||||
6. **事件解析系统**: 自动解析和分类协议特定事件
|
||||
7. **高性能**: 针对低延迟事件处理进行优化
|
||||
8. **批处理优化**: 批量处理事件以减少回调开销
|
||||
9. **性能监控**: 内置性能指标监控,包括事件处理速度、内存使用等
|
||||
10. **内存优化**: 对象池和缓存机制减少内存分配
|
||||
|
||||
## 安装
|
||||
|
||||
@@ -76,11 +79,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("正在订阅 GRPC 事件...");
|
||||
println!("正在订阅 Yellowstone gRPC 事件...");
|
||||
|
||||
let grpc = YellowstoneGrpc::new(
|
||||
// 创建 gRPC 客户端并启用性能监控
|
||||
let grpc = YellowstoneGrpc::new_with_config(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None,
|
||||
true, // 启用性能监控
|
||||
)?;
|
||||
|
||||
let callback = create_event_callback();
|
||||
@@ -124,7 +129,11 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("正在订阅 ShredStream 事件...");
|
||||
|
||||
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
|
||||
// 创建 ShredStream 客户端并启用性能监控
|
||||
let shred_stream = ShredStreamGrpc::new_with_config(
|
||||
"http://127.0.0.1:10800".to_string(),
|
||||
true, // 启用性能监控
|
||||
).await?;
|
||||
let callback = create_event_callback();
|
||||
let protocols = vec![
|
||||
Protocol::PumpFun,
|
||||
@@ -251,22 +260,52 @@ src/
|
||||
2. **事件过滤**: 使用协议过滤减少不必要的事件处理
|
||||
3. **内存管理**: 为长时间运行的流实现适当的清理
|
||||
4. **错误处理**: 对网络问题和服务中断进行健壮的错误处理
|
||||
5. **批处理优化**: 使用批处理减少回调开销,提高吞吐量
|
||||
6. **性能监控**: 启用性能监控以识别瓶颈和优化机会
|
||||
|
||||
## 配置选项
|
||||
|
||||
### Yellowstone gRPC 配置
|
||||
|
||||
```rust
|
||||
// 推荐:创建 gRPC 客户端并启用性能监控
|
||||
let grpc = YellowstoneGrpc::new_with_config(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None,
|
||||
true, // 启用性能监控
|
||||
)?;
|
||||
|
||||
// 替代:基本配置(性能监控默认启用)
|
||||
let grpc = YellowstoneGrpc::new(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None, // 自定义配置选项
|
||||
None,
|
||||
)?;
|
||||
|
||||
// 最大性能:禁用性能监控
|
||||
let grpc = YellowstoneGrpc::new_with_config(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None,
|
||||
false, // 禁用性能监控
|
||||
)?;
|
||||
```
|
||||
|
||||
### ShredStream 配置
|
||||
|
||||
```rust
|
||||
// 推荐:创建 ShredStream 客户端并启用性能监控
|
||||
let shred_stream = ShredStreamGrpc::new_with_config(
|
||||
"http://127.0.0.1:10800".to_string(),
|
||||
true, // 启用性能监控
|
||||
).await?;
|
||||
|
||||
// 替代:基本配置(性能监控默认启用)
|
||||
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
|
||||
|
||||
// 最大性能:禁用性能监控
|
||||
let shred_stream = ShredStreamGrpc::new_with_config(
|
||||
"http://127.0.0.1:10800".to_string(),
|
||||
false, // 禁用性能监控
|
||||
).await?;
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
+33
-17
@@ -22,19 +22,24 @@ use solana_streamer_sdk::{
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Starting Solana Streamer...");
|
||||
test_grpc().await?;
|
||||
test_shreds().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("Subscribing to GRPC events...");
|
||||
println!("Subscribing to Yellowstone gRPC events...");
|
||||
|
||||
let grpc = YellowstoneGrpc::new(
|
||||
// enable_metrics 为 true 时,会打印性能指标
|
||||
let grpc = YellowstoneGrpc::new_with_config(
|
||||
"https://solana-yellowstone-grpc.publicnode.com:443".to_string(),
|
||||
None,
|
||||
true,
|
||||
)?;
|
||||
|
||||
println!("GRPC client created successfully");
|
||||
|
||||
let callback = create_event_callback();
|
||||
|
||||
// Will try to parse corresponding protocol events from transactions
|
||||
@@ -46,6 +51,8 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Protocol::RaydiumClmm,
|
||||
];
|
||||
|
||||
println!("Protocols to monitor: {:?}", protocols);
|
||||
|
||||
// Filter accounts
|
||||
let account_include = vec![
|
||||
PUMPFUN_PROGRAM_ID.to_string(), // Listen to pumpfun program ID
|
||||
@@ -53,12 +60,15 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
BONK_PROGRAM_ID.to_string(), // Listen to bonk program ID
|
||||
RAYDIUM_CPMM_PROGRAM_ID.to_string(), // Listen to raydium_cpmm program ID
|
||||
RAYDIUM_CLMM_PROGRAM_ID.to_string(), // Listen to raydium_clmm program ID
|
||||
"xxxxxxxx".to_string(), // Listen to xxxxx account
|
||||
];
|
||||
let account_exclude = vec![];
|
||||
let account_required = vec![];
|
||||
|
||||
println!("Starting to listen for events, press Ctrl+C to stop...");
|
||||
println!("Monitoring programs: {:?}", account_include);
|
||||
|
||||
println!("Starting subscription...");
|
||||
|
||||
grpc.subscribe_events_v2(
|
||||
protocols,
|
||||
None,
|
||||
@@ -74,9 +84,14 @@ async fn test_grpc() -> Result<(), Box<dyn std::error::Error>> {
|
||||
}
|
||||
|
||||
async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("正在订阅 ShredStream 事件...");
|
||||
println!("Subscribing to ShredStream events...");
|
||||
|
||||
// enable_metrics 为 true 时,会打印性能指标
|
||||
let shred_stream = ShredStreamGrpc::new_with_config(
|
||||
"http://127.0.0.1:10800".to_string(),
|
||||
true,
|
||||
).await?;
|
||||
|
||||
let shred_stream = ShredStreamGrpc::new("http://127.0.0.1:10800".to_string()).await?;
|
||||
let callback = create_event_callback();
|
||||
let protocols = vec![
|
||||
Protocol::PumpFun,
|
||||
@@ -86,7 +101,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
Protocol::RaydiumClmm,
|
||||
];
|
||||
|
||||
println!("开始监听事件,按 Ctrl+C 停止...");
|
||||
println!("Listening for events, press Ctrl+C to stop...");
|
||||
shred_stream
|
||||
.shredstream_subscribe(protocols, None, callback)
|
||||
.await?;
|
||||
@@ -96,6 +111,7 @@ async fn test_shreds() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|
||||
|event: Box<dyn UnifiedEvent>| {
|
||||
println!("🎉 Event received! Type: {:?}, ID: {}", event.event_type(), event.id());
|
||||
match_event!(event, {
|
||||
BonkPoolCreateEvent => |e: BonkPoolCreateEvent| {
|
||||
// 使用grpc的时候,可以从每个事件中获取到block_time
|
||||
@@ -103,37 +119,37 @@ fn create_event_callback() -> impl Fn(Box<dyn UnifiedEvent>) {
|
||||
println!("BonkPoolCreateEvent: {:?}", e.base_mint_param.symbol);
|
||||
},
|
||||
BonkTradeEvent => |e: BonkTradeEvent| {
|
||||
println!("BonkTradeEvent: {:?}", e);
|
||||
println!("BonkTradeEvent: {e:?}");
|
||||
},
|
||||
PumpFunTradeEvent => |e: PumpFunTradeEvent| {
|
||||
println!("PumpFunTradeEvent: {:?}", e);
|
||||
println!("PumpFunTradeEvent: {e:?}");
|
||||
},
|
||||
PumpFunCreateTokenEvent => |e: PumpFunCreateTokenEvent| {
|
||||
println!("PumpFunCreateTokenEvent: {:?}", e);
|
||||
println!("PumpFunCreateTokenEvent: {e:?}");
|
||||
},
|
||||
PumpSwapBuyEvent => |e: PumpSwapBuyEvent| {
|
||||
println!("Buy event: {:?}", e);
|
||||
println!("Buy event: {e:?}");
|
||||
},
|
||||
PumpSwapSellEvent => |e: PumpSwapSellEvent| {
|
||||
println!("Sell event: {:?}", e);
|
||||
println!("Sell event: {e:?}");
|
||||
},
|
||||
PumpSwapCreatePoolEvent => |e: PumpSwapCreatePoolEvent| {
|
||||
println!("CreatePool event: {:?}", e);
|
||||
println!("CreatePool event: {e:?}");
|
||||
},
|
||||
PumpSwapDepositEvent => |e: PumpSwapDepositEvent| {
|
||||
println!("Deposit event: {:?}", e);
|
||||
println!("Deposit event: {e:?}");
|
||||
},
|
||||
PumpSwapWithdrawEvent => |e: PumpSwapWithdrawEvent| {
|
||||
println!("Withdraw event: {:?}", e);
|
||||
println!("Withdraw event: {e:?}");
|
||||
},
|
||||
RaydiumCpmmSwapEvent => |e: RaydiumCpmmSwapEvent| {
|
||||
println!("RaydiumCpmmSwapEvent: {:?}", e);
|
||||
println!("RaydiumCpmmSwapEvent: {e:?}");
|
||||
},
|
||||
RaydiumClmmSwapEvent => |e: RaydiumClmmSwapEvent| {
|
||||
println!("RaydiumClmmSwapEvent: {:?}", e);
|
||||
println!("RaydiumClmmSwapEvent: {e:?}");
|
||||
},
|
||||
RaydiumClmmSwapV2Event => |e: RaydiumClmmSwapV2Event| {
|
||||
println!("RaydiumClmmSwapV2Event: {:?}", e);
|
||||
println!("RaydiumClmmSwapV2Event: {e:?}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -48,9 +48,9 @@ macro_rules! impl_unified_event {
|
||||
}
|
||||
|
||||
fn merge(&mut self, other: Box<dyn $crate::streaming::event_parser::core::traits::UnifiedEvent>) {
|
||||
if let Some(e) = other.as_any().downcast_ref::<$struct_name>() {
|
||||
if let Some(_e) = other.as_any().downcast_ref::<$struct_name>() {
|
||||
$(
|
||||
self.$field = e.$field.clone();
|
||||
self.$field = _e.$field.clone();
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,80 @@ use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_transaction_status::UiInstruction;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
// 对象池大小配置
|
||||
const EVENT_METADATA_POOL_SIZE: usize = 1000;
|
||||
const TRANSFER_DATA_POOL_SIZE: usize = 2000;
|
||||
|
||||
/// 事件元数据对象池
|
||||
pub struct EventMetadataPool {
|
||||
pool: Arc<Mutex<Vec<EventMetadata>>>,
|
||||
}
|
||||
|
||||
impl Default for EventMetadataPool {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl EventMetadataPool {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pool: Arc::new(Mutex::new(Vec::with_capacity(EVENT_METADATA_POOL_SIZE))),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn acquire(&self) -> Option<EventMetadata> {
|
||||
let mut pool = self.pool.lock().await;
|
||||
pool.pop()
|
||||
}
|
||||
|
||||
pub async fn release(&self, metadata: EventMetadata) {
|
||||
let mut pool = self.pool.lock().await;
|
||||
if pool.len() < EVENT_METADATA_POOL_SIZE {
|
||||
pool.push(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 传输数据对象池
|
||||
pub struct TransferDataPool {
|
||||
pool: Arc<Mutex<Vec<TransferData>>>,
|
||||
}
|
||||
|
||||
impl Default for TransferDataPool {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TransferDataPool {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
pool: Arc::new(Mutex::new(Vec::with_capacity(TRANSFER_DATA_POOL_SIZE))),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn acquire(&self) -> Option<TransferData> {
|
||||
let mut pool = self.pool.lock().await;
|
||||
pool.pop()
|
||||
}
|
||||
|
||||
pub async fn release(&self, transfer_data: TransferData) {
|
||||
let mut pool = self.pool.lock().await;
|
||||
if pool.len() < TRANSFER_DATA_POOL_SIZE {
|
||||
pool.push(transfer_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 全局对象池实例
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref EVENT_METADATA_POOL: EventMetadataPool = EventMetadataPool::new();
|
||||
pub static ref TRANSFER_DATA_POOL: TransferDataPool = TransferDataPool::new();
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize,
|
||||
@@ -57,6 +129,7 @@ pub enum EventType {
|
||||
}
|
||||
|
||||
impl EventType {
|
||||
#[allow(clippy::inherent_to_string)]
|
||||
pub fn to_string(&self) -> String {
|
||||
match self {
|
||||
EventType::PumpSwapBuy => "PumpSwapBuy".to_string(),
|
||||
@@ -167,6 +240,7 @@ pub struct EventMetadata {
|
||||
}
|
||||
|
||||
impl EventMetadata {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
id: String,
|
||||
signature: String,
|
||||
@@ -185,22 +259,73 @@ impl EventMetadata {
|
||||
slot,
|
||||
block_time,
|
||||
block_time_ms,
|
||||
program_received_time_ms: program_received_time_ms,
|
||||
program_received_time_ms,
|
||||
program_handle_time_consuming_ms: 0,
|
||||
protocol,
|
||||
event_type,
|
||||
program_id,
|
||||
transfer_datas: vec![],
|
||||
transfer_datas: Vec::with_capacity(4), // 预分配容量
|
||||
index,
|
||||
}
|
||||
}
|
||||
|
||||
/// 使用对象池创建EventMetadata
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn new_with_pool(
|
||||
id: String,
|
||||
signature: String,
|
||||
slot: u64,
|
||||
block_time: i64,
|
||||
block_time_ms: i64,
|
||||
protocol: ProtocolType,
|
||||
event_type: EventType,
|
||||
program_id: Pubkey,
|
||||
index: String,
|
||||
program_received_time_ms: i64,
|
||||
) -> Self {
|
||||
// 尝试从对象池获取
|
||||
if let Some(mut metadata) = EVENT_METADATA_POOL.acquire().await {
|
||||
metadata.id = id;
|
||||
metadata.signature = signature;
|
||||
metadata.slot = slot;
|
||||
metadata.block_time = block_time;
|
||||
metadata.block_time_ms = block_time_ms;
|
||||
metadata.program_received_time_ms = program_received_time_ms;
|
||||
metadata.program_handle_time_consuming_ms = 0;
|
||||
metadata.protocol = protocol;
|
||||
metadata.event_type = event_type;
|
||||
metadata.program_id = program_id;
|
||||
metadata.index = index;
|
||||
metadata.transfer_datas.clear();
|
||||
return metadata;
|
||||
}
|
||||
|
||||
// 如果对象池为空,创建新的
|
||||
Self::new(
|
||||
id,
|
||||
signature,
|
||||
slot,
|
||||
block_time,
|
||||
block_time_ms,
|
||||
protocol,
|
||||
event_type,
|
||||
program_id,
|
||||
index,
|
||||
program_received_time_ms,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn set_id(&mut self, id: String) {
|
||||
let _id = format!("{}-{}-{}", self.signature, self.event_type.to_string(), id);
|
||||
// 对传入的 id 进行哈希处理
|
||||
let mut hasher = DefaultHasher::new();
|
||||
_id.hash(&mut hasher);
|
||||
let hash_value = hasher.finish();
|
||||
self.id = format!("{:x}", hash_value);
|
||||
self.id = id;
|
||||
}
|
||||
|
||||
pub fn set_transfer_datas(&mut self, transfer_datas: Vec<TransferData>) {
|
||||
self.transfer_datas = transfer_datas;
|
||||
}
|
||||
|
||||
/// 回收EventMetadata到对象池
|
||||
pub async fn recycle(self) {
|
||||
EVENT_METADATA_POOL.release(self).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,12 +28,13 @@ pub fn extract_discriminator(length: usize, data: &[u8]) -> Option<(&[u8], &[u8]
|
||||
Some((&data[..length], &data[length..]))
|
||||
}
|
||||
|
||||
/// 检查鉴别器是否匹配
|
||||
/// 检查鉴别器是否匹配 - 优化版本
|
||||
pub fn discriminator_matches(data: &str, expected: &str) -> bool {
|
||||
if data.len() < expected.len() {
|
||||
return false;
|
||||
}
|
||||
&data[..expected.len()] == expected
|
||||
// 使用字节比较而不是字符串比较,更高效
|
||||
data.as_bytes().starts_with(expected.as_bytes())
|
||||
}
|
||||
|
||||
/// 从日志中提取程序数据
|
||||
|
||||
@@ -8,6 +8,8 @@ use solana_transaction_status::{
|
||||
};
|
||||
use std::fmt::Debug;
|
||||
use std::{collections::HashMap, str::FromStr};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::streaming::event_parser::common::{
|
||||
parse_transfer_datas_from_next_instructions, TransferData,
|
||||
@@ -20,6 +22,78 @@ use crate::streaming::event_parser::{
|
||||
},
|
||||
};
|
||||
|
||||
// 解析缓存配置
|
||||
const PARSE_CACHE_SIZE: usize = 10000;
|
||||
const CACHE_TTL_SECONDS: u64 = 300; // 5分钟
|
||||
|
||||
/// 解析结果缓存
|
||||
#[derive(Clone)]
|
||||
pub struct ParseCacheEntry {
|
||||
pub events: Vec<Box<dyn UnifiedEvent>>,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
/// 事件解析缓存
|
||||
pub struct EventParseCache {
|
||||
cache: Arc<RwLock<HashMap<String, ParseCacheEntry>>>,
|
||||
max_size: usize,
|
||||
ttl_seconds: u64,
|
||||
}
|
||||
|
||||
impl EventParseCache {
|
||||
pub fn new(max_size: usize, ttl_seconds: u64) -> Self {
|
||||
Self {
|
||||
cache: Arc::new(RwLock::new(HashMap::with_capacity(max_size))),
|
||||
max_size,
|
||||
ttl_seconds,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get(&self, key: &str) -> Option<Vec<Box<dyn UnifiedEvent>>> {
|
||||
let cache = self.cache.read().await;
|
||||
if let Some(entry) = cache.get(key) {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
if now - entry.timestamp < self.ttl_seconds {
|
||||
return Some(entry.events.clone());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub async fn set(&self, key: String, events: Vec<Box<dyn UnifiedEvent>>) {
|
||||
let mut cache = self.cache.write().await;
|
||||
|
||||
// 清理过期条目
|
||||
if cache.len() >= self.max_size {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
cache.retain(|_, entry| now - entry.timestamp < self.ttl_seconds);
|
||||
}
|
||||
|
||||
let entry = ParseCacheEntry {
|
||||
events,
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
};
|
||||
|
||||
cache.insert(key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
// 全局解析缓存实例
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref PARSE_CACHE: EventParseCache = EventParseCache::new(PARSE_CACHE_SIZE, CACHE_TTL_SECONDS);
|
||||
}
|
||||
|
||||
/// Unified Event Interface - All protocol events must implement this trait
|
||||
pub trait UnifiedEvent: Debug + Send + Sync {
|
||||
/// Get event ID
|
||||
@@ -68,9 +142,10 @@ pub trait UnifiedEvent: Debug + Send + Sync {
|
||||
#[async_trait::async_trait]
|
||||
pub trait EventParser: Send + Sync {
|
||||
/// 从内联指令中解析事件数据
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn parse_events_from_inner_instruction(
|
||||
&self,
|
||||
instruction: &UiCompiledInstruction,
|
||||
inner_instruction: &UiCompiledInstruction,
|
||||
signature: &str,
|
||||
slot: u64,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -79,6 +154,7 @@ pub trait EventParser: Send + Sync {
|
||||
) -> Vec<Box<dyn UnifiedEvent>>;
|
||||
|
||||
/// 从指令中解析事件数据
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn parse_events_from_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
@@ -91,9 +167,10 @@ pub trait EventParser: Send + Sync {
|
||||
) -> Vec<Box<dyn UnifiedEvent>>;
|
||||
|
||||
/// 从VersionedTransaction中解析指令事件的通用方法
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn parse_instruction_events_from_versioned_transaction(
|
||||
&self,
|
||||
versioned_tx: &VersionedTransaction,
|
||||
transaction: &VersionedTransaction,
|
||||
signature: &str,
|
||||
slot: Option<u64>,
|
||||
block_time: Option<Timestamp>,
|
||||
@@ -101,9 +178,10 @@ pub trait EventParser: Send + Sync {
|
||||
accounts: &[Pubkey],
|
||||
inner_instructions: &[UiInnerInstructions],
|
||||
) -> Result<Vec<Box<dyn UnifiedEvent>>> {
|
||||
let mut instruction_events = Vec::new();
|
||||
// 预分配容量,避免动态扩容
|
||||
let mut instruction_events = Vec::with_capacity(16);
|
||||
// 获取交易的指令和账户
|
||||
let compiled_instructions = versioned_tx.message.instructions();
|
||||
let compiled_instructions = transaction.message.instructions();
|
||||
let mut accounts: Vec<Pubkey> = accounts.to_vec();
|
||||
|
||||
// 检查交易中是否包含程序
|
||||
@@ -128,11 +206,11 @@ pub trait EventParser: Send + Sync {
|
||||
slot,
|
||||
block_time,
|
||||
program_received_time_ms,
|
||||
format!("{}", index),
|
||||
format!("{index}"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
if events.len() > 0 {
|
||||
if !events.is_empty() {
|
||||
if let Some(inn) =
|
||||
inner_instructions.iter().find(|inner_instruction| {
|
||||
inner_instruction.index == index as u8
|
||||
@@ -141,12 +219,12 @@ pub trait EventParser: Send + Sync {
|
||||
events.iter_mut().for_each(|event| {
|
||||
let transfer_datas =
|
||||
parse_transfer_datas_from_next_instructions(
|
||||
&inn,
|
||||
-1 as i8,
|
||||
inn,
|
||||
-1_i8,
|
||||
&accounts,
|
||||
event.event_type(),
|
||||
);
|
||||
event.set_transfer_datas(transfer_datas.clone());
|
||||
event.set_transfer_datas(transfer_datas);
|
||||
});
|
||||
}
|
||||
instruction_events.extend(events);
|
||||
@@ -177,7 +255,7 @@ pub trait EventParser: Send + Sync {
|
||||
block_time,
|
||||
program_received_time_ms,
|
||||
&accounts,
|
||||
&vec![],
|
||||
&[],
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|_e| vec![]);
|
||||
@@ -193,6 +271,14 @@ pub trait EventParser: Send + Sync {
|
||||
program_received_time_ms: i64,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
) -> Result<Vec<Box<dyn UnifiedEvent>>> {
|
||||
// 生成缓存键
|
||||
let cache_key = format!("{}_{}_{}", signature, slot.unwrap_or(0), program_received_time_ms);
|
||||
|
||||
// 尝试从缓存获取
|
||||
if let Some(cached_events) = PARSE_CACHE.get(&cache_key).await {
|
||||
return Ok(cached_events);
|
||||
}
|
||||
|
||||
let transaction = tx.transaction;
|
||||
// 检查交易元数据
|
||||
let meta = tx
|
||||
@@ -203,18 +289,27 @@ pub trait EventParser: Send + Sync {
|
||||
let mut address_table_lookups: Vec<Pubkey> = vec![];
|
||||
let mut inner_instructions: Vec<UiInnerInstructions> = vec![];
|
||||
if meta.err.is_none() {
|
||||
inner_instructions = meta.inner_instructions.as_ref().unwrap().clone();
|
||||
let loaded_addresses = meta.loaded_addresses.as_ref().unwrap();
|
||||
for lookup in &loaded_addresses.writable {
|
||||
address_table_lookups.push(Pubkey::from_str(lookup).unwrap());
|
||||
// 正确处理OptionSerializer类型
|
||||
if let solana_transaction_status::option_serializer::OptionSerializer::Some(meta_inner_instructions) = &meta.inner_instructions {
|
||||
inner_instructions = meta_inner_instructions.clone();
|
||||
}
|
||||
for lookup in &loaded_addresses.readonly {
|
||||
address_table_lookups.push(Pubkey::from_str(lookup).unwrap());
|
||||
if let solana_transaction_status::option_serializer::OptionSerializer::Some(loaded_addresses) = &meta.loaded_addresses {
|
||||
for lookup in &loaded_addresses.writable {
|
||||
if let Ok(pubkey) = Pubkey::from_str(lookup) {
|
||||
address_table_lookups.push(pubkey);
|
||||
}
|
||||
}
|
||||
for lookup in &loaded_addresses.readonly {
|
||||
if let Ok(pubkey) = Pubkey::from_str(lookup) {
|
||||
address_table_lookups.push(pubkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut accounts: Vec<Pubkey> = vec![];
|
||||
|
||||
let mut instruction_events = Vec::new();
|
||||
// 预分配容量,避免动态扩容
|
||||
let mut instruction_events = Vec::with_capacity(16);
|
||||
|
||||
// 解析指令事件
|
||||
if let Some(versioned_tx) = transaction.decode() {
|
||||
@@ -238,105 +333,101 @@ pub trait EventParser: Send + Sync {
|
||||
}
|
||||
|
||||
// 解析内联指令事件
|
||||
let mut inner_instruction_events = Vec::new();
|
||||
// 预分配容量,避免动态扩容
|
||||
let mut inner_instruction_events = Vec::with_capacity(8);
|
||||
// 检查交易是否成功
|
||||
if meta.err.is_none() {
|
||||
for inner_instruction in inner_instructions {
|
||||
for (index, instruction) in inner_instruction.instructions.iter().enumerate() {
|
||||
match instruction {
|
||||
UiInstruction::Compiled(compiled) => {
|
||||
// 解析嵌套指令
|
||||
let compiled_instruction = CompiledInstruction {
|
||||
program_id_index: compiled.program_id_index,
|
||||
accounts: compiled.accounts.clone(),
|
||||
data: bs58::decode(compiled.data.clone()).into_vec().unwrap(),
|
||||
};
|
||||
if let Ok(mut events) = self
|
||||
.parse_instruction(
|
||||
&compiled_instruction,
|
||||
&accounts,
|
||||
signature,
|
||||
slot,
|
||||
block_time,
|
||||
program_received_time_ms,
|
||||
format!("{}.{}", inner_instruction.index, index),
|
||||
)
|
||||
.await
|
||||
{
|
||||
if events.len() > 0 {
|
||||
events.iter_mut().for_each(|event| {
|
||||
let transfer_datas =
|
||||
parse_transfer_datas_from_next_instructions(
|
||||
&inner_instruction,
|
||||
index as i8,
|
||||
&accounts,
|
||||
event.event_type(),
|
||||
);
|
||||
event.set_transfer_datas(transfer_datas.clone());
|
||||
});
|
||||
instruction_events.extend(events);
|
||||
}
|
||||
}
|
||||
if let Ok(mut events) = self
|
||||
.parse_inner_instruction(
|
||||
compiled,
|
||||
signature,
|
||||
slot,
|
||||
block_time,
|
||||
program_received_time_ms,
|
||||
format!("{}.{}", inner_instruction.index, index),
|
||||
)
|
||||
.await
|
||||
{
|
||||
if events.len() > 0 {
|
||||
events.iter_mut().for_each(|event| {
|
||||
let transfer_datas =
|
||||
parse_transfer_datas_from_next_instructions(
|
||||
&inner_instruction,
|
||||
index as i8,
|
||||
&accounts,
|
||||
event.event_type(),
|
||||
);
|
||||
event.set_transfer_datas(transfer_datas.clone());
|
||||
});
|
||||
inner_instruction_events.extend(events);
|
||||
}
|
||||
if let UiInstruction::Compiled(compiled) = instruction {
|
||||
// 解析嵌套指令
|
||||
let compiled_instruction = CompiledInstruction {
|
||||
program_id_index: compiled.program_id_index,
|
||||
accounts: compiled.accounts.clone(),
|
||||
data: bs58::decode(compiled.data.clone())
|
||||
.into_vec()
|
||||
.unwrap_or_else(|_| vec![]),
|
||||
};
|
||||
if let Ok(mut events) = self
|
||||
.parse_instruction(
|
||||
&compiled_instruction,
|
||||
&accounts,
|
||||
signature,
|
||||
slot,
|
||||
block_time,
|
||||
program_received_time_ms,
|
||||
format!("{index}"),
|
||||
)
|
||||
.await
|
||||
{
|
||||
if !events.is_empty() {
|
||||
events.iter_mut().for_each(|event| {
|
||||
let transfer_datas =
|
||||
parse_transfer_datas_from_next_instructions(
|
||||
&inner_instruction,
|
||||
-1_i8,
|
||||
&accounts,
|
||||
event.event_type(),
|
||||
);
|
||||
event.set_transfer_datas(transfer_datas);
|
||||
});
|
||||
instruction_events.extend(events);
|
||||
}
|
||||
}
|
||||
if let Ok(mut events) = self
|
||||
.parse_inner_instruction(
|
||||
compiled,
|
||||
signature,
|
||||
slot,
|
||||
block_time,
|
||||
program_received_time_ms,
|
||||
format!("{}.{}", inner_instruction.index, index),
|
||||
)
|
||||
.await
|
||||
{
|
||||
if !events.is_empty() {
|
||||
events.iter_mut().for_each(|event| {
|
||||
let transfer_datas =
|
||||
parse_transfer_datas_from_next_instructions(
|
||||
&inner_instruction,
|
||||
index as i8,
|
||||
&accounts,
|
||||
event.event_type(),
|
||||
);
|
||||
event.set_transfer_datas(transfer_datas);
|
||||
});
|
||||
inner_instruction_events.extend(events);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if instruction_events.len() > 0 && inner_instruction_events.len() > 0 {
|
||||
if !instruction_events.is_empty() && !inner_instruction_events.is_empty() {
|
||||
for instruction_event in &mut instruction_events {
|
||||
for inner_instruction_event in &inner_instruction_events {
|
||||
if instruction_event.id() == inner_instruction_event.id() {
|
||||
let i_index = instruction_event.index();
|
||||
let in_index = inner_instruction_event.index();
|
||||
if !i_index.contains(".") && in_index.contains(".") {
|
||||
let in_index_parent_index = in_index.split(".").nth(0).unwrap();
|
||||
if in_index_parent_index == i_index {
|
||||
let in_index_parts: Vec<&str> = in_index.split(".").collect();
|
||||
if !in_index_parts.is_empty() && in_index_parts[0] == i_index {
|
||||
instruction_event.merge(inner_instruction_event.clone_boxed());
|
||||
break;
|
||||
}
|
||||
} else if i_index.contains(".") && in_index.contains(".") {
|
||||
// 嵌套指令
|
||||
let i_index_parent_index = i_index.split(".").nth(0).unwrap();
|
||||
let in_index_parent_index = in_index.split(".").nth(0).unwrap();
|
||||
if i_index_parent_index == in_index_parent_index {
|
||||
let i_index_child_index = i_index
|
||||
.split(".")
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
let i_index_parts: Vec<&str> = i_index.split(".").collect();
|
||||
let in_index_parts: Vec<&str> = in_index.split(".").collect();
|
||||
|
||||
if !i_index_parts.is_empty() && !in_index_parts.is_empty()
|
||||
&& i_index_parts[0] == in_index_parts[0] {
|
||||
let i_index_child_index = i_index_parts.get(1)
|
||||
.and_then(|s| s.parse::<u32>().ok())
|
||||
.unwrap_or(0);
|
||||
let in_index_child_index = in_index
|
||||
.split(".")
|
||||
.nth(1)
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
let in_index_child_index = in_index_parts.get(1)
|
||||
.and_then(|s| s.parse::<u32>().ok())
|
||||
.unwrap_or(0);
|
||||
if in_index_child_index > i_index_child_index {
|
||||
instruction_event.merge(inner_instruction_event.clone_boxed());
|
||||
@@ -348,7 +439,13 @@ pub trait EventParser: Send + Sync {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(self.process_events(instruction_events, bot_wallet))
|
||||
|
||||
let result = self.process_events(instruction_events, bot_wallet);
|
||||
|
||||
// 缓存结果
|
||||
PARSE_CACHE.set(cache_key, result.clone()).await;
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn process_events(
|
||||
@@ -356,6 +453,7 @@ pub trait EventParser: Send + Sync {
|
||||
mut events: Vec<Box<dyn UnifiedEvent>>,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
let start_time = std::time::Instant::now();
|
||||
let mut dev_address = vec![];
|
||||
let mut bonk_dev_address = None;
|
||||
for event in &mut events {
|
||||
@@ -391,6 +489,14 @@ pub trait EventParser: Send + Sync {
|
||||
let now = chrono::Utc::now().timestamp_millis();
|
||||
event.set_program_handle_time_consuming_ms(now - event.program_received_time_ms());
|
||||
}
|
||||
|
||||
// 记录处理时间
|
||||
let processing_time = start_time.elapsed();
|
||||
if processing_time.as_millis() > 10 {
|
||||
log::warn!("Event processing took {}ms for {} events",
|
||||
processing_time.as_millis(), events.len());
|
||||
}
|
||||
|
||||
events
|
||||
}
|
||||
|
||||
@@ -415,6 +521,7 @@ pub trait EventParser: Send + Sync {
|
||||
Ok(events)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn parse_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
@@ -485,17 +592,18 @@ impl GenericEventParser {
|
||||
protocol_type: ProtocolType,
|
||||
configs: Vec<GenericEventParseConfig>,
|
||||
) -> Self {
|
||||
let mut inner_instruction_configs = HashMap::new();
|
||||
let mut instruction_configs = HashMap::new();
|
||||
// 预分配容量,避免动态扩容
|
||||
let mut inner_instruction_configs = HashMap::with_capacity(configs.len());
|
||||
let mut instruction_configs = HashMap::with_capacity(configs.len());
|
||||
|
||||
for config in configs {
|
||||
inner_instruction_configs
|
||||
.entry(config.inner_instruction_discriminator)
|
||||
.or_insert(vec![])
|
||||
.or_insert_with(Vec::new)
|
||||
.push(config.clone());
|
||||
instruction_configs
|
||||
.entry(config.instruction_discriminator.to_vec())
|
||||
.or_insert(vec![])
|
||||
.or_insert_with(Vec::new)
|
||||
.push(config);
|
||||
}
|
||||
|
||||
@@ -508,6 +616,7 @@ impl GenericEventParser {
|
||||
}
|
||||
|
||||
/// 通用的内联指令解析方法
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn parse_inner_instruction_event(
|
||||
&self,
|
||||
config: &GenericEventParseConfig,
|
||||
@@ -539,6 +648,7 @@ impl GenericEventParser {
|
||||
}
|
||||
|
||||
/// 通用的指令解析方法
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn parse_instruction_event(
|
||||
&self,
|
||||
config: &GenericEventParseConfig,
|
||||
@@ -574,6 +684,7 @@ impl GenericEventParser {
|
||||
#[async_trait::async_trait]
|
||||
impl EventParser for GenericEventParser {
|
||||
/// 从内联指令中解析事件数据
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn parse_events_from_inner_instruction(
|
||||
&self,
|
||||
inner_instruction: &UiCompiledInstruction,
|
||||
@@ -584,8 +695,9 @@ impl EventParser for GenericEventParser {
|
||||
index: String,
|
||||
) -> Vec<Box<dyn UnifiedEvent>> {
|
||||
let inner_instruction_data = inner_instruction.data.clone();
|
||||
let inner_instruction_data_decoded =
|
||||
bs58::decode(inner_instruction_data).into_vec().unwrap();
|
||||
let inner_instruction_data_decoded = bs58::decode(inner_instruction_data)
|
||||
.into_vec()
|
||||
.unwrap_or_else(|_| vec![]);
|
||||
if inner_instruction_data_decoded.len() < 16 {
|
||||
return Vec::new();
|
||||
}
|
||||
@@ -614,6 +726,7 @@ impl EventParser for GenericEventParser {
|
||||
}
|
||||
|
||||
/// 从指令中解析事件
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn parse_events_from_instruction(
|
||||
&self,
|
||||
instruction: &CompiledInstruction,
|
||||
|
||||
@@ -64,7 +64,8 @@ impl std::str::FromStr for Protocol {
|
||||
}
|
||||
|
||||
static EVENT_PARSERS: LazyLock<HashMap<Protocol, Arc<dyn EventParser>>> = LazyLock::new(|| {
|
||||
let mut parsers: HashMap<Protocol, Arc<dyn EventParser>> = HashMap::new();
|
||||
// 预分配容量,避免动态扩容
|
||||
let mut parsers: HashMap<Protocol, Arc<dyn EventParser>> = HashMap::with_capacity(5);
|
||||
parsers.insert(Protocol::PumpSwap, Arc::new(PumpSwapEventParser::new()));
|
||||
parsers.insert(Protocol::PumpFun, Arc::new(PumpFunEventParser::new()));
|
||||
parsers.insert(Protocol::Bonk, Arc::new(BonkEventParser::new()));
|
||||
@@ -82,7 +83,7 @@ impl EventParserFactory {
|
||||
/// 创建指定协议的事件解析器
|
||||
pub fn create_parser(protocol: Protocol) -> Arc<dyn EventParser> {
|
||||
EVENT_PARSERS.get(&protocol).cloned().unwrap_or_else(|| {
|
||||
panic!("Parser for protocol {} not found", protocol);
|
||||
panic!("Parser for protocol {protocol} not found");
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@ pub struct BonkEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl Default for BonkEventParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl BonkEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
@@ -73,9 +79,9 @@ impl BonkEventParser {
|
||||
) -> Option<Box<dyn UnifiedEvent>> {
|
||||
if let Ok(event) = borsh::from_slice::<BonkPoolCreateEvent>(data) {
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!("{}", metadata.signature,));
|
||||
metadata.set_id(metadata.signature.to_string());
|
||||
Some(Box::new(BonkPoolCreateEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -93,7 +99,7 @@ impl BonkEventParser {
|
||||
metadata.set_id(format!(
|
||||
"{}-{}",
|
||||
metadata.signature,
|
||||
event.pool_state.to_string()
|
||||
event.pool_state
|
||||
));
|
||||
if metadata.event_type == EventType::BonkBuyExactIn
|
||||
|| metadata.event_type == EventType::BonkBuyExactOut
|
||||
@@ -101,15 +107,13 @@ impl BonkEventParser {
|
||||
if event.trade_direction != TradeDirection::Buy {
|
||||
return None;
|
||||
}
|
||||
} else if metadata.event_type == EventType::BonkSellExactIn
|
||||
|| metadata.event_type == EventType::BonkSellExactOut
|
||||
{
|
||||
if event.trade_direction != TradeDirection::Sell {
|
||||
} else if (metadata.event_type == EventType::BonkSellExactIn
|
||||
|| metadata.event_type == EventType::BonkSellExactOut)
|
||||
&& event.trade_direction != TradeDirection::Sell {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(Box::new(BonkTradeEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -270,7 +274,7 @@ impl BonkEventParser {
|
||||
let vesting_param = Self::parse_vesting_params(data, &mut offset)?;
|
||||
|
||||
let mut metadata = metadata;
|
||||
metadata.set_id(format!("{}", metadata.signature));
|
||||
metadata.set_id(metadata.signature.to_string());
|
||||
|
||||
Some(Box::new(BonkPoolCreateEvent {
|
||||
metadata,
|
||||
|
||||
@@ -17,6 +17,12 @@ pub struct PumpFunEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl Default for PumpFunEventParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PumpFunEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
@@ -61,10 +67,10 @@ impl PumpFunEventParser {
|
||||
metadata.signature,
|
||||
event.name,
|
||||
event.symbol,
|
||||
event.mint.to_string()
|
||||
event.mint
|
||||
));
|
||||
Some(Box::new(PumpFunCreateTokenEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -82,12 +88,12 @@ impl PumpFunEventParser {
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature,
|
||||
event.mint.to_string(),
|
||||
event.user.to_string(),
|
||||
event.is_buy.to_string()
|
||||
event.mint,
|
||||
event.user,
|
||||
event.is_buy
|
||||
));
|
||||
Some(Box::new(PumpFunTradeEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -129,7 +135,7 @@ impl PumpFunEventParser {
|
||||
metadata.signature,
|
||||
name,
|
||||
symbol,
|
||||
accounts[0].to_string()
|
||||
accounts[0]
|
||||
));
|
||||
|
||||
Some(Box::new(PumpFunCreateTokenEvent {
|
||||
@@ -162,9 +168,9 @@ impl PumpFunEventParser {
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature,
|
||||
accounts[2].to_string(),
|
||||
accounts[6].to_string(),
|
||||
true.to_string()
|
||||
accounts[2],
|
||||
accounts[6],
|
||||
true
|
||||
));
|
||||
Some(Box::new(PumpFunTradeEvent {
|
||||
metadata,
|
||||
@@ -199,9 +205,9 @@ impl PumpFunEventParser {
|
||||
metadata.set_id(format!(
|
||||
"{}-{}-{}-{}",
|
||||
metadata.signature,
|
||||
accounts[2].to_string(),
|
||||
accounts[6].to_string(),
|
||||
false.to_string()
|
||||
accounts[2],
|
||||
accounts[6],
|
||||
false
|
||||
));
|
||||
Some(Box::new(PumpFunTradeEvent {
|
||||
metadata,
|
||||
@@ -267,6 +273,6 @@ impl EventParser for PumpFunEventParser {
|
||||
}
|
||||
|
||||
fn supported_program_ids(&self) -> Vec<Pubkey> {
|
||||
self.inner.supported_program_ids()
|
||||
vec![PUMPFUN_PROGRAM_ID]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,12 @@ pub struct PumpSwapEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl Default for PumpSwapEventParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PumpSwapEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
@@ -78,7 +84,7 @@ impl PumpSwapEventParser {
|
||||
metadata.signature, event.user, event.pool, event.base_amount_out
|
||||
));
|
||||
Some(Box::new(PumpSwapBuyEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -98,7 +104,7 @@ impl PumpSwapEventParser {
|
||||
metadata.signature, event.user, event.pool, event.base_amount_in
|
||||
));
|
||||
Some(Box::new(PumpSwapSellEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -118,7 +124,7 @@ impl PumpSwapEventParser {
|
||||
metadata.signature, event.pool, event.creator, event.base_amount_in
|
||||
));
|
||||
Some(Box::new(PumpSwapCreatePoolEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -138,7 +144,7 @@ impl PumpSwapEventParser {
|
||||
metadata.signature, event.pool, event.user, event.lp_token_amount_out
|
||||
));
|
||||
Some(Box::new(PumpSwapDepositEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -158,7 +164,7 @@ impl PumpSwapEventParser {
|
||||
metadata.signature, event.pool, event.user, event.lp_token_amount_in
|
||||
));
|
||||
Some(Box::new(PumpSwapWithdrawEvent {
|
||||
metadata: metadata,
|
||||
metadata,
|
||||
..event
|
||||
}))
|
||||
} else {
|
||||
@@ -413,6 +419,6 @@ impl EventParser for PumpSwapEventParser {
|
||||
}
|
||||
|
||||
fn supported_program_ids(&self) -> Vec<Pubkey> {
|
||||
self.inner.supported_program_ids()
|
||||
vec![PUMPSWAP_PROGRAM_ID]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ pub struct RaydiumClmmEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl Default for RaydiumClmmEventParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RaydiumClmmEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
@@ -89,7 +95,6 @@ impl RaydiumClmmEventParser {
|
||||
token_program: accounts[8],
|
||||
tick_array: accounts[9],
|
||||
remaining_accounts: accounts[10..].to_vec(),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
|
||||
@@ -133,7 +138,6 @@ impl RaydiumClmmEventParser {
|
||||
input_vault_mint: accounts[11],
|
||||
output_vault_mint: accounts[12],
|
||||
remaining_accounts: accounts[13..].to_vec(),
|
||||
..Default::default()
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ pub struct RaydiumCpmmEventParser {
|
||||
inner: GenericEventParser,
|
||||
}
|
||||
|
||||
impl Default for RaydiumCpmmEventParser {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RaydiumCpmmEventParser {
|
||||
pub fn new() -> Self {
|
||||
// 配置所有事件类型
|
||||
|
||||
+237
-13
@@ -1,4 +1,5 @@
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use futures::{channel::mpsc, StreamExt};
|
||||
use solana_entry::entry::Entry;
|
||||
@@ -14,10 +15,56 @@ use crate::protos::shredstream::shredstream_proxy_client::ShredstreamProxyClient
|
||||
use crate::protos::shredstream::SubscribeEntriesRequest;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
|
||||
const CHANNEL_SIZE: usize = 1000;
|
||||
// 根据实际并发量调整通道大小,避免背压
|
||||
const CHANNEL_SIZE: usize = 5000;
|
||||
|
||||
// 批处理配置
|
||||
const SHRED_BATCH_SIZE: usize = 100;
|
||||
#[allow(dead_code)]
|
||||
const SHRED_BATCH_TIMEOUT_MS: u64 = 5;
|
||||
|
||||
/// ShredStream性能监控指标
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ShredPerformanceMetrics {
|
||||
pub events_processed: u64,
|
||||
pub events_per_second: f64,
|
||||
pub average_processing_time_ms: f64,
|
||||
pub min_processing_time_ms: f64,
|
||||
pub max_processing_time_ms: f64,
|
||||
pub memory_usage_mb: f64,
|
||||
pub last_update_time: std::time::Instant,
|
||||
pub events_in_window: u64,
|
||||
pub window_start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
impl Default for ShredPerformanceMetrics {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ShredPerformanceMetrics {
|
||||
pub fn new() -> Self {
|
||||
let now = std::time::Instant::now();
|
||||
Self {
|
||||
events_processed: 0,
|
||||
events_per_second: 0.0,
|
||||
average_processing_time_ms: 0.0,
|
||||
min_processing_time_ms: f64::MAX,
|
||||
max_processing_time_ms: 0.0,
|
||||
memory_usage_mb: 0.0,
|
||||
last_update_time: now,
|
||||
events_in_window: 0,
|
||||
window_start_time: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ShredStreamGrpc {
|
||||
shredstream_client: Arc<ShredstreamProxyClient<Channel>>,
|
||||
metrics: Arc<Mutex<ShredPerformanceMetrics>>,
|
||||
enable_metrics: bool, // 是否启用性能监控
|
||||
}
|
||||
|
||||
struct TransactionWithSlot {
|
||||
@@ -25,14 +72,151 @@ struct TransactionWithSlot {
|
||||
slot: u64,
|
||||
}
|
||||
|
||||
/// ShredStream批处理器
|
||||
pub struct ShredBatchProcessor<F>
|
||||
where
|
||||
F: FnMut(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
|
||||
{
|
||||
callback: F,
|
||||
batch: Vec<Box<dyn UnifiedEvent>>,
|
||||
batch_size: usize,
|
||||
}
|
||||
|
||||
impl<F> ShredBatchProcessor<F>
|
||||
where
|
||||
F: FnMut(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(callback: F, batch_size: usize) -> Self {
|
||||
Self {
|
||||
callback,
|
||||
batch: Vec::with_capacity(batch_size),
|
||||
batch_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_event(&mut self, event: Box<dyn UnifiedEvent>) {
|
||||
self.batch.push(event);
|
||||
|
||||
if self.batch.len() >= self.batch_size {
|
||||
self.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) {
|
||||
if !self.batch.is_empty() {
|
||||
let events = std::mem::replace(&mut self.batch, Vec::with_capacity(self.batch_size));
|
||||
(self.callback)(events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ShredStreamGrpc {
|
||||
pub async fn new(endpoint: String) -> AnyResult<Self> {
|
||||
Self::new_with_config(endpoint, true).await
|
||||
}
|
||||
|
||||
pub async fn new_with_config(endpoint: String, enable_metrics: bool) -> AnyResult<Self> {
|
||||
let shredstream_client = ShredstreamProxyClient::connect(endpoint.clone()).await?;
|
||||
Ok(Self {
|
||||
shredstream_client: Arc::new(shredstream_client),
|
||||
metrics: Arc::new(Mutex::new(ShredPerformanceMetrics::new())),
|
||||
enable_metrics,
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取性能指标
|
||||
pub async fn get_metrics(&self) -> ShredPerformanceMetrics {
|
||||
let metrics = self.metrics.lock().await;
|
||||
metrics.clone()
|
||||
}
|
||||
|
||||
/// 启用或禁用性能监控
|
||||
pub fn set_enable_metrics(&mut self, enabled: bool) {
|
||||
self.enable_metrics = enabled;
|
||||
}
|
||||
|
||||
/// 打印性能指标
|
||||
pub async fn print_metrics(&self) {
|
||||
let metrics = self.get_metrics().await;
|
||||
println!("📊 ShredStream Performance Metrics:");
|
||||
println!(" Events Processed: {}", metrics.events_processed);
|
||||
println!(" Events/Second: {:.2}", metrics.events_per_second);
|
||||
println!(" Avg Processing Time: {:.2}ms", metrics.average_processing_time_ms);
|
||||
println!(" Min Processing Time: {:.2}ms", metrics.min_processing_time_ms);
|
||||
println!(" Max Processing Time: {:.2}ms", metrics.max_processing_time_ms);
|
||||
println!(" Memory Usage: {:.2}MB", metrics.memory_usage_mb);
|
||||
println!("---");
|
||||
}
|
||||
|
||||
/// 启动自动性能监控任务
|
||||
pub async fn start_auto_metrics_monitoring(&self) {
|
||||
// 检查是否启用性能监控
|
||||
if !self.enable_metrics {
|
||||
return; // 如果未启用性能监控,不启动监控任务
|
||||
}
|
||||
|
||||
let grpc_clone = self.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(10));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
grpc_clone.print_metrics().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 更新性能指标
|
||||
async fn update_metrics(&self, events_processed: u64, processing_time_ms: f64) {
|
||||
// 检查是否启用性能监控
|
||||
if !self.enable_metrics {
|
||||
return; // 如果未启用性能监控,直接返回
|
||||
}
|
||||
|
||||
let mut metrics = self.metrics.lock().await;
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
metrics.events_processed += events_processed;
|
||||
metrics.events_in_window += events_processed;
|
||||
metrics.last_update_time = now;
|
||||
|
||||
// 更新最快和最慢处理时间
|
||||
if processing_time_ms < metrics.min_processing_time_ms {
|
||||
metrics.min_processing_time_ms = processing_time_ms;
|
||||
}
|
||||
if processing_time_ms > metrics.max_processing_time_ms {
|
||||
metrics.max_processing_time_ms = processing_time_ms;
|
||||
}
|
||||
|
||||
// 计算平均处理时间
|
||||
if metrics.events_processed > 0 {
|
||||
metrics.average_processing_time_ms =
|
||||
(metrics.average_processing_time_ms * (metrics.events_processed - events_processed) as f64 + processing_time_ms)
|
||||
/ metrics.events_processed as f64;
|
||||
}
|
||||
|
||||
// 基于时间窗口计算每秒处理事件数(5秒窗口)
|
||||
let window_duration = std::time::Duration::from_secs(5);
|
||||
if now.duration_since(metrics.window_start_time) >= window_duration {
|
||||
let window_seconds = now.duration_since(metrics.window_start_time).as_secs_f64();
|
||||
if window_seconds > 0.0 && metrics.events_in_window > 0 {
|
||||
metrics.events_per_second = metrics.events_in_window as f64 / window_seconds;
|
||||
} else {
|
||||
// 如果窗口内没有事件,保持之前的速率或设为0
|
||||
metrics.events_per_second = 0.0;
|
||||
}
|
||||
|
||||
// 重置窗口
|
||||
metrics.events_in_window = 0;
|
||||
metrics.window_start_time = now;
|
||||
} else {
|
||||
// 如果窗口还没满,不更新 events_per_second,保持之前的计算值
|
||||
// 这样可以避免因为单次批处理时间波动导致的指标跳跃
|
||||
}
|
||||
|
||||
// 估算内存使用(基于处理的事件数量)
|
||||
metrics.memory_usage_mb = metrics.events_processed as f64 * 0.001; // 每个事件约1KB
|
||||
}
|
||||
|
||||
pub async fn shredstream_subscribe<F>(
|
||||
&self,
|
||||
protocols: Vec<Protocol>,
|
||||
@@ -42,11 +226,23 @@ impl ShredStreamGrpc {
|
||||
where
|
||||
F: Fn(Box<dyn UnifiedEvent>) + Send + Sync + 'static,
|
||||
{
|
||||
// 启动自动性能监控
|
||||
self.start_auto_metrics_monitoring().await;
|
||||
|
||||
let request = tonic::Request::new(SubscribeEntriesRequest {});
|
||||
let mut client = (*self.shredstream_client).clone();
|
||||
let mut stream = client.subscribe_entries(request).await?.into_inner();
|
||||
let (mut tx, mut rx) = mpsc::channel::<TransactionWithSlot>(CHANNEL_SIZE);
|
||||
let callback = Box::new(callback);
|
||||
|
||||
// 创建批处理器,将单个事件回调转换为批量回调
|
||||
let batch_callback = move |events: Vec<Box<dyn UnifiedEvent>>| {
|
||||
for event in events {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
|
||||
let mut batch_processor = ShredBatchProcessor::new(batch_callback, SHRED_BATCH_SIZE);
|
||||
|
||||
tokio::spawn(async move {
|
||||
while let Some(message) = stream.next().await {
|
||||
match message {
|
||||
@@ -70,36 +266,45 @@ impl ShredStreamGrpc {
|
||||
}
|
||||
});
|
||||
|
||||
let self_clone = self.clone();
|
||||
while let Some(transaction_with_slot) = rx.next().await {
|
||||
if let Err(e) = Self::process_transaction(
|
||||
if let Err(e) = self_clone.process_transaction_with_batch(
|
||||
transaction_with_slot,
|
||||
protocols.clone(),
|
||||
bot_wallet,
|
||||
&*callback,
|
||||
&mut batch_processor,
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Error processing transaction: {:?}", e);
|
||||
error!("Error processing transaction: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余的事件
|
||||
batch_processor.flush();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_transaction<F>(
|
||||
async fn process_transaction_with_batch<F>(
|
||||
&self,
|
||||
transaction_with_slot: TransactionWithSlot,
|
||||
protocols: Vec<Protocol>,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
callback: &F,
|
||||
batch_processor: &mut ShredBatchProcessor<F>,
|
||||
) -> AnyResult<()>
|
||||
where
|
||||
F: Fn(Box<dyn UnifiedEvent>) + Send + Sync,
|
||||
F: FnMut(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
|
||||
{
|
||||
let start_time = std::time::Instant::now();
|
||||
let program_received_time_ms = chrono::Utc::now().timestamp_millis();
|
||||
let slot = transaction_with_slot.slot;
|
||||
let versioned_tx = transaction_with_slot.transaction;
|
||||
let signature = versioned_tx.signatures[0];
|
||||
|
||||
// 预分配向量容量
|
||||
let mut all_events = Vec::with_capacity(protocols.len() * 2);
|
||||
|
||||
for protocol in protocols {
|
||||
let parser = EventParserFactory::create_parser(protocol.clone());
|
||||
let events = parser
|
||||
@@ -109,15 +314,34 @@ impl ShredStreamGrpc {
|
||||
Some(slot),
|
||||
None,
|
||||
program_received_time_ms,
|
||||
bot_wallet.clone(),
|
||||
bot_wallet,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|_e| vec![]);
|
||||
for event in events {
|
||||
callback(event);
|
||||
}
|
||||
all_events.extend(events);
|
||||
}
|
||||
|
||||
// 保存事件数量用于日志记录
|
||||
let event_count = all_events.len();
|
||||
|
||||
// 使用批处理器处理事件
|
||||
for event in all_events {
|
||||
batch_processor.add_event(event);
|
||||
}
|
||||
|
||||
// 更新性能指标
|
||||
let processing_time = start_time.elapsed();
|
||||
let processing_time_ms = processing_time.as_millis() as f64;
|
||||
|
||||
// 实际调用性能指标更新
|
||||
self.update_metrics(event_count as u64, processing_time_ms).await;
|
||||
|
||||
// 记录慢处理操作
|
||||
if processing_time_ms > 5.0 {
|
||||
log::warn!("ShredStream transaction processing took {}ms for {} events",
|
||||
processing_time_ms, event_count);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@ use yellowstone_grpc_proto::geyser::{
|
||||
SubscribeRequestFilterTransactions, SubscribeRequestPing, SubscribeUpdate,
|
||||
SubscribeUpdateTransaction,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::common::AnyResult;
|
||||
use crate::streaming::event_parser::{EventParserFactory, Protocol, UnifiedEvent};
|
||||
@@ -22,9 +24,175 @@ type TransactionsFilterMap = HashMap<String, SubscribeRequestFilterTransactions>
|
||||
|
||||
const CONNECT_TIMEOUT: u64 = 10;
|
||||
const REQUEST_TIMEOUT: u64 = 60;
|
||||
const CHANNEL_SIZE: usize = 1000;
|
||||
// 根据实际并发量调整通道大小,避免背压
|
||||
const CHANNEL_SIZE: usize = 5000;
|
||||
const MAX_DECODING_MESSAGE_SIZE: usize = 1024 * 1024 * 10;
|
||||
|
||||
// 批处理配置
|
||||
const BATCH_SIZE: usize = 100; // 批处理50个事件
|
||||
const BATCH_TIMEOUT_MS: u64 = 10; // 减少超时时间到10ms
|
||||
|
||||
// 连接池配置(为将来扩展保留)
|
||||
#[allow(dead_code)]
|
||||
const CONNECTION_POOL_SIZE: usize = 5;
|
||||
#[allow(dead_code)]
|
||||
const CONNECTION_IDLE_TIMEOUT: Duration = Duration::from_secs(300); // 5分钟
|
||||
|
||||
// 工作线程池配置
|
||||
const WORKER_THREADS: usize = 8;
|
||||
const TASK_QUEUE_SIZE: usize = 10000;
|
||||
|
||||
/// 工作线程池配置
|
||||
pub struct WorkerPoolConfig {
|
||||
pub worker_threads: usize,
|
||||
pub task_queue_size: usize,
|
||||
}
|
||||
|
||||
impl Default for WorkerPoolConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
worker_threads: WORKER_THREADS,
|
||||
task_queue_size: TASK_QUEUE_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 性能监控指标
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PerformanceMetrics {
|
||||
pub events_processed: u64,
|
||||
pub events_per_second: f64,
|
||||
pub average_processing_time_ms: f64,
|
||||
pub min_processing_time_ms: f64,
|
||||
pub max_processing_time_ms: f64,
|
||||
pub cache_hit_rate: f64,
|
||||
pub memory_usage_mb: f64,
|
||||
pub last_update_time: std::time::Instant,
|
||||
pub events_in_window: u64,
|
||||
pub window_start_time: std::time::Instant,
|
||||
}
|
||||
|
||||
impl Default for PerformanceMetrics {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PerformanceMetrics {
|
||||
pub fn new() -> Self {
|
||||
let now = std::time::Instant::now();
|
||||
Self {
|
||||
events_processed: 0,
|
||||
events_per_second: 0.0,
|
||||
average_processing_time_ms: 0.0,
|
||||
min_processing_time_ms: f64::MAX,
|
||||
max_processing_time_ms: 0.0,
|
||||
cache_hit_rate: 0.0,
|
||||
memory_usage_mb: 0.0,
|
||||
last_update_time: now,
|
||||
events_in_window: 0,
|
||||
window_start_time: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// gRPC连接池 - 简化版本
|
||||
pub struct GrpcConnectionPool {
|
||||
endpoint: String,
|
||||
x_token: Option<String>,
|
||||
}
|
||||
|
||||
impl GrpcConnectionPool {
|
||||
pub fn new(endpoint: String, x_token: Option<String>) -> Self {
|
||||
Self {
|
||||
endpoint,
|
||||
x_token,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_connection(&self) -> AnyResult<GeyserGrpcClient<impl Interceptor>> {
|
||||
let builder = GeyserGrpcClient::build_from_shared(self.endpoint.clone())?
|
||||
.x_token(self.x_token.clone())?
|
||||
.tls_config(ClientTlsConfig::new().with_native_roots())?
|
||||
.max_decoding_message_size(MAX_DECODING_MESSAGE_SIZE)
|
||||
.connect_timeout(Duration::from_secs(CONNECT_TIMEOUT))
|
||||
.timeout(Duration::from_secs(REQUEST_TIMEOUT));
|
||||
|
||||
Ok(builder.connect().await?)
|
||||
}
|
||||
}
|
||||
|
||||
/// 批处理事件收集器
|
||||
pub struct EventBatchCollector<F>
|
||||
where
|
||||
F: Fn(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
|
||||
{
|
||||
callback: F,
|
||||
batch: Vec<Box<dyn UnifiedEvent>>,
|
||||
batch_size: usize,
|
||||
timeout_ms: u64,
|
||||
last_flush_time: std::time::Instant,
|
||||
}
|
||||
|
||||
impl<F> EventBatchCollector<F>
|
||||
where
|
||||
F: Fn(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
|
||||
{
|
||||
pub fn new(callback: F, batch_size: usize, timeout_ms: u64) -> Self {
|
||||
Self {
|
||||
callback,
|
||||
batch: Vec::with_capacity(batch_size),
|
||||
batch_size,
|
||||
timeout_ms,
|
||||
last_flush_time: std::time::Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_event(&mut self, event: Box<dyn UnifiedEvent>) {
|
||||
log::debug!("Adding event to batch: {} (type: {:?})", event.id(), event.event_type());
|
||||
self.batch.push(event);
|
||||
|
||||
// 检查是否需要刷新批次
|
||||
if self.batch.len() >= self.batch_size || self.should_flush_by_timeout() {
|
||||
log::info!("Flushing batch: size={}, timeout={}", self.batch.len(), self.should_flush_by_timeout());
|
||||
self.flush();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn flush(&mut self) {
|
||||
if !self.batch.is_empty() {
|
||||
let events = std::mem::replace(&mut self.batch, Vec::with_capacity(self.batch_size));
|
||||
log::info!("Flushing {} events from batch processor", events.len());
|
||||
|
||||
// 添加更详细的调试信息
|
||||
for (i, event) in events.iter().enumerate() {
|
||||
log::info!("Event {}: Type={:?}, ID={}", i, event.event_type(), event.id());
|
||||
}
|
||||
|
||||
// 执行回调并捕获可能的错误
|
||||
log::info!("About to execute batch callback with {} events", events.len());
|
||||
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
(self.callback)(events);
|
||||
})) {
|
||||
Ok(_) => {
|
||||
log::info!("Batch callback executed successfully");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Batch callback panicked: {:?}", e);
|
||||
}
|
||||
}
|
||||
|
||||
self.last_flush_time = std::time::Instant::now();
|
||||
} else {
|
||||
log::debug!("No events to flush");
|
||||
}
|
||||
}
|
||||
|
||||
fn should_flush_by_timeout(&self) -> bool {
|
||||
self.last_flush_time.elapsed().as_millis() >= self.timeout_ms as u128
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TransactionPretty {
|
||||
pub slot: u64,
|
||||
@@ -63,7 +231,7 @@ impl From<(SubscribeUpdateTransaction, Option<Timestamp>)> for TransactionPretty
|
||||
let tx = transaction.expect("should be defined");
|
||||
Self {
|
||||
slot,
|
||||
block_time: block_time,
|
||||
block_time,
|
||||
signature: Signature::try_from(tx.signature.as_slice()).expect("valid signature"),
|
||||
is_vote: tx.is_vote,
|
||||
tx: yellowstone_grpc_proto::convert_from::create_tx_with_meta(tx)
|
||||
@@ -74,20 +242,132 @@ impl From<(SubscribeUpdateTransaction, Option<Timestamp>)> for TransactionPretty
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct YellowstoneGrpc {
|
||||
endpoint: String,
|
||||
x_token: Option<String>,
|
||||
metrics: Arc<Mutex<PerformanceMetrics>>,
|
||||
enable_metrics: bool, // 是否启用性能监控
|
||||
}
|
||||
|
||||
impl YellowstoneGrpc {
|
||||
pub fn new(endpoint: String, x_token: Option<String>) -> AnyResult<Self> {
|
||||
Self::new_with_config(endpoint, x_token, true)
|
||||
}
|
||||
|
||||
pub fn new_with_config(
|
||||
endpoint: String,
|
||||
x_token: Option<String>,
|
||||
enable_metrics: bool,
|
||||
) -> AnyResult<Self> {
|
||||
if CryptoProvider::get_default().is_none() {
|
||||
default_provider()
|
||||
.install_default()
|
||||
.map_err(|e| anyhow::anyhow!("Failed to install crypto provider: {:?}", e))?;
|
||||
}
|
||||
|
||||
Ok(Self { endpoint, x_token })
|
||||
Ok(Self {
|
||||
endpoint,
|
||||
x_token,
|
||||
metrics: Arc::new(Mutex::new(PerformanceMetrics::new())),
|
||||
enable_metrics,
|
||||
})
|
||||
}
|
||||
|
||||
/// 获取性能指标
|
||||
pub async fn get_metrics(&self) -> PerformanceMetrics {
|
||||
let metrics = self.metrics.lock().await;
|
||||
metrics.clone()
|
||||
}
|
||||
|
||||
/// 启用或禁用性能监控
|
||||
pub fn set_enable_metrics(&mut self, enabled: bool) {
|
||||
self.enable_metrics = enabled;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// 打印性能指标
|
||||
pub async fn print_metrics(&self) {
|
||||
let metrics = self.get_metrics().await;
|
||||
println!("📊 Performance Metrics:");
|
||||
println!(" Events Processed: {}", metrics.events_processed);
|
||||
println!(" Events/Second: {:.2}", metrics.events_per_second);
|
||||
println!(" Avg Processing Time: {:.2}ms", metrics.average_processing_time_ms);
|
||||
println!(" Min Processing Time: {:.2}ms", metrics.min_processing_time_ms);
|
||||
println!(" Max Processing Time: {:.2}ms", metrics.max_processing_time_ms);
|
||||
println!(" Cache Hit Rate: {:.2}%", metrics.cache_hit_rate * 100.0);
|
||||
println!(" Memory Usage: {:.2}MB", metrics.memory_usage_mb);
|
||||
println!("---");
|
||||
}
|
||||
|
||||
/// 启动自动性能监控任务
|
||||
pub async fn start_auto_metrics_monitoring(&self) {
|
||||
// 检查是否启用性能监控
|
||||
if !self.enable_metrics {
|
||||
return; // 如果未启用性能监控,不启动监控任务
|
||||
}
|
||||
|
||||
let grpc_clone = self.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(tokio::time::Duration::from_secs(10));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
grpc_clone.print_metrics().await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 更新性能指标
|
||||
async fn update_metrics(&self, events_processed: u64, processing_time_ms: f64) {
|
||||
// 检查是否启用性能监控
|
||||
if !self.enable_metrics {
|
||||
return; // 如果未启用性能监控,直接返回
|
||||
}
|
||||
|
||||
let mut metrics = self.metrics.lock().await;
|
||||
let now = std::time::Instant::now();
|
||||
|
||||
metrics.events_processed += events_processed;
|
||||
metrics.events_in_window += events_processed;
|
||||
metrics.last_update_time = now;
|
||||
|
||||
// 更新最快和最慢处理时间
|
||||
if processing_time_ms < metrics.min_processing_time_ms {
|
||||
metrics.min_processing_time_ms = processing_time_ms;
|
||||
}
|
||||
if processing_time_ms > metrics.max_processing_time_ms {
|
||||
metrics.max_processing_time_ms = processing_time_ms;
|
||||
}
|
||||
|
||||
// 计算平均处理时间
|
||||
if metrics.events_processed > 0 {
|
||||
metrics.average_processing_time_ms =
|
||||
(metrics.average_processing_time_ms * (metrics.events_processed - events_processed) as f64 + processing_time_ms)
|
||||
/ metrics.events_processed as f64;
|
||||
}
|
||||
|
||||
// 基于时间窗口计算每秒处理事件数(5秒窗口)
|
||||
let window_duration = std::time::Duration::from_secs(5);
|
||||
if now.duration_since(metrics.window_start_time) >= window_duration {
|
||||
let window_seconds = now.duration_since(metrics.window_start_time).as_secs_f64();
|
||||
if window_seconds > 0.0 && metrics.events_in_window > 0 {
|
||||
metrics.events_per_second = metrics.events_in_window as f64 / window_seconds;
|
||||
} else {
|
||||
// 如果窗口内没有事件,保持之前的速率或设为0
|
||||
metrics.events_per_second = 0.0;
|
||||
}
|
||||
|
||||
// 重置窗口
|
||||
metrics.events_in_window = 0;
|
||||
metrics.window_start_time = now;
|
||||
} else {
|
||||
// 如果窗口还没满,不更新 events_per_second,保持之前的计算值
|
||||
// 这样可以避免因为单次批处理时间波动导致的指标跳跃
|
||||
}
|
||||
|
||||
// 估算内存使用(基于处理的事件数量)
|
||||
metrics.memory_usage_mb = metrics.events_processed as f64 * 0.001; // 每个事件约1KB
|
||||
}
|
||||
|
||||
pub async fn connect(&self) -> AnyResult<GeyserGrpcClient<impl Interceptor>> {
|
||||
@@ -156,6 +436,7 @@ impl YellowstoneGrpc {
|
||||
match msg.update_oneof {
|
||||
Some(UpdateOneof::Transaction(sut)) => {
|
||||
let transaction_pretty = TransactionPretty::from((sut, created_at));
|
||||
log::info!("Received transaction: {} at slot {}", transaction_pretty.signature, transaction_pretty.slot);
|
||||
tx.try_send(transaction_pretty)?;
|
||||
}
|
||||
Some(UpdateOneof::Ping(_)) => {
|
||||
@@ -170,7 +451,9 @@ impl YellowstoneGrpc {
|
||||
Some(UpdateOneof::Pong(_)) => {
|
||||
info!("service is pong: {}", Local::now());
|
||||
}
|
||||
_ => {}
|
||||
_ => {
|
||||
log::debug!("Received other message type");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -189,6 +472,7 @@ impl YellowstoneGrpc {
|
||||
/// * `account_required` - List of account addresses that must be present in transactions
|
||||
/// * `commitment` - Optional commitment level for the subscription
|
||||
/// * `callback` - Function to call when matching events are found
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn subscribe_events_v2<F>(
|
||||
&self,
|
||||
protocols: Vec<Protocol>,
|
||||
@@ -202,6 +486,10 @@ impl YellowstoneGrpc {
|
||||
where
|
||||
F: Fn(Box<dyn UnifiedEvent>) + Send + Sync + 'static,
|
||||
{
|
||||
|
||||
// 启动自动性能监控
|
||||
self.start_auto_metrics_monitoring().await;
|
||||
|
||||
if account_include.is_empty() && account_exclude.is_empty() && account_required.is_empty() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"account_include or account_exclude or account_required cannot be empty"
|
||||
@@ -218,8 +506,14 @@ impl YellowstoneGrpc {
|
||||
// Create channel
|
||||
let (mut tx, mut rx) = mpsc::channel::<TransactionPretty>(CHANNEL_SIZE);
|
||||
|
||||
// Create callback function, wrap with Arc to share across multiple tasks
|
||||
let callback = std::sync::Arc::new(Box::new(callback));
|
||||
// 创建批处理器,将单个事件回调转换为批量回调
|
||||
let batch_callback = move |events: Vec<Box<dyn UnifiedEvent>>| {
|
||||
for event in events {
|
||||
callback(event);
|
||||
}
|
||||
};
|
||||
|
||||
let mut batch_processor = EventBatchCollector::new(batch_callback, BATCH_SIZE, BATCH_TIMEOUT_MS);
|
||||
|
||||
// Start task to process the stream
|
||||
tokio::spawn(async move {
|
||||
@@ -229,7 +523,7 @@ impl YellowstoneGrpc {
|
||||
if let Err(e) =
|
||||
Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await
|
||||
{
|
||||
error!("Error handling message: {:?}", e);
|
||||
error!("Error handling message: {e:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -241,20 +535,24 @@ impl YellowstoneGrpc {
|
||||
}
|
||||
});
|
||||
|
||||
// Process transactions
|
||||
// Process transactions with batch processing
|
||||
let self_clone = self.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some(transaction_pretty) = rx.next().await {
|
||||
if let Err(e) = Self::process_event_transaction(
|
||||
while let Some(transaction_pretty) = rx.next().await {
|
||||
if let Err(e) = self_clone.process_event_transaction_with_batch(
|
||||
transaction_pretty,
|
||||
&**callback,
|
||||
&mut batch_processor,
|
||||
bot_wallet,
|
||||
protocols.clone(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Error processing transaction: {:?}", e);
|
||||
error!("Error processing transaction: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
// 处理剩余的事件
|
||||
batch_processor.flush();
|
||||
});
|
||||
|
||||
tokio::signal::ctrl_c().await?;
|
||||
@@ -266,6 +564,7 @@ impl YellowstoneGrpc {
|
||||
since = "0.1.5",
|
||||
note = "This method will be removed, please use the new API: subscribe_events_v2"
|
||||
)]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub async fn subscribe_events<F>(
|
||||
&self,
|
||||
protocols: Vec<Protocol>,
|
||||
@@ -282,8 +581,7 @@ impl YellowstoneGrpc {
|
||||
// 创建过滤器
|
||||
let protocol_accounts = protocols
|
||||
.iter()
|
||||
.map(|p| p.get_program_id())
|
||||
.flatten()
|
||||
.flat_map(|p| p.get_program_id())
|
||||
.map(|p| p.to_string())
|
||||
.collect::<Vec<String>>();
|
||||
let mut account_include = account_include.unwrap_or_default();
|
||||
@@ -314,7 +612,7 @@ impl YellowstoneGrpc {
|
||||
if let Err(e) =
|
||||
Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await
|
||||
{
|
||||
error!("Error handling message: {:?}", e);
|
||||
error!("Error handling message: {e:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -337,7 +635,7 @@ impl YellowstoneGrpc {
|
||||
)
|
||||
.await
|
||||
{
|
||||
error!("Error processing transaction: {:?}", e);
|
||||
error!("Error processing transaction: {e:?}");
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -355,15 +653,20 @@ impl YellowstoneGrpc {
|
||||
where
|
||||
F: Fn(Box<dyn UnifiedEvent>) + Send + Sync,
|
||||
{
|
||||
let start_time = std::time::Instant::now();
|
||||
let program_received_time_ms = chrono::Utc::now().timestamp_millis();
|
||||
let slot = transaction_pretty.slot;
|
||||
let signature = transaction_pretty.signature.to_string();
|
||||
let mut futures = Vec::new();
|
||||
|
||||
// 预分配向量容量,避免动态扩容
|
||||
let mut futures = Vec::with_capacity(protocols.len());
|
||||
|
||||
for protocol in protocols {
|
||||
let parser = EventParserFactory::create_parser(protocol);
|
||||
// 在异步任务中需要克隆值
|
||||
let tx_clone = transaction_pretty.tx.clone();
|
||||
let signature_clone = signature.clone();
|
||||
let bot_wallet_clone = bot_wallet.clone();
|
||||
let bot_wallet_clone = bot_wallet;
|
||||
|
||||
futures.push(tokio::spawn(async move {
|
||||
parser
|
||||
@@ -381,13 +684,132 @@ impl YellowstoneGrpc {
|
||||
}
|
||||
|
||||
let results = futures::future::join_all(futures).await;
|
||||
|
||||
// 收集所有事件
|
||||
let mut all_events = Vec::new();
|
||||
for events in results.into_iter().flatten() {
|
||||
all_events.extend(events);
|
||||
}
|
||||
|
||||
// 保存事件数量用于日志记录
|
||||
let event_count = all_events.len();
|
||||
|
||||
// 批量处理事件
|
||||
if !all_events.is_empty() {
|
||||
for event in all_events {
|
||||
callback(event);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新性能指标
|
||||
let processing_time = start_time.elapsed();
|
||||
let processing_time_ms = processing_time.as_millis() as f64;
|
||||
|
||||
// 记录慢处理操作
|
||||
if processing_time_ms > 10.0 {
|
||||
log::warn!("Slow event processing: {processing_time_ms}ms for {event_count} events");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn process_event_transaction_with_batch<F>(
|
||||
&self,
|
||||
transaction_pretty: TransactionPretty,
|
||||
batch_processor: &mut EventBatchCollector<F>,
|
||||
bot_wallet: Option<Pubkey>,
|
||||
protocols: Vec<Protocol>,
|
||||
) -> AnyResult<()>
|
||||
where
|
||||
F: Fn(Vec<Box<dyn UnifiedEvent>>) + Send + Sync + 'static,
|
||||
{
|
||||
let start_time = std::time::Instant::now();
|
||||
let program_received_time_ms = chrono::Utc::now().timestamp_millis();
|
||||
let slot = transaction_pretty.slot;
|
||||
let signature = transaction_pretty.signature.to_string();
|
||||
|
||||
// 预分配向量容量,避免动态扩容
|
||||
let mut futures: Vec<tokio::task::JoinHandle<Result<Vec<Box<dyn UnifiedEvent>>, anyhow::Error>>> = Vec::with_capacity(protocols.len());
|
||||
|
||||
for protocol in protocols {
|
||||
let parser = EventParserFactory::create_parser(protocol.clone());
|
||||
// 在异步任务中需要克隆值
|
||||
let tx_clone = transaction_pretty.tx.clone();
|
||||
let signature_clone = signature.clone();
|
||||
let bot_wallet_clone = bot_wallet;
|
||||
let protocol_clone = protocol.clone();
|
||||
|
||||
futures.push(tokio::spawn(async move {
|
||||
let result = parser
|
||||
.parse_transaction(
|
||||
tx_clone,
|
||||
&signature_clone,
|
||||
Some(slot),
|
||||
transaction_pretty.block_time,
|
||||
program_received_time_ms,
|
||||
bot_wallet_clone,
|
||||
)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(events) => {
|
||||
if !events.is_empty() {
|
||||
log::info!("Parsed {} events for protocol {:?}", events.len(), protocol_clone);
|
||||
}
|
||||
Ok(events)
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse transaction for protocol {:?}: {:?}", protocol_clone, e);
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
let results = futures::future::join_all(futures).await;
|
||||
|
||||
// 收集所有事件并使用批处理器
|
||||
let mut total_events = 0;
|
||||
for result in results {
|
||||
if let Ok(events) = result {
|
||||
for event in events {
|
||||
callback(event);
|
||||
match result {
|
||||
Ok(parse_result) => {
|
||||
match parse_result {
|
||||
Ok(events) => {
|
||||
total_events += events.len();
|
||||
log::info!("Adding {} events to batch processor", events.len());
|
||||
for event in events {
|
||||
batch_processor.add_event(event);
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to parse transaction: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::warn!("Failed to get events from async task: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加调试信息
|
||||
if total_events > 0 {
|
||||
log::info!("Total events parsed: {} for transaction {}", total_events, signature);
|
||||
}
|
||||
|
||||
// 更新性能指标
|
||||
let processing_time = start_time.elapsed();
|
||||
let processing_time_ms = processing_time.as_millis() as f64;
|
||||
|
||||
// 实际调用性能指标更新
|
||||
self.update_metrics(total_events as u64, processing_time_ms).await;
|
||||
|
||||
// 记录慢处理操作
|
||||
if processing_time_ms > 10.0 {
|
||||
log::warn!("Slow event processing: {processing_time_ms}ms for {total_events} events");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@ use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction};
|
||||
use solana_transaction_status::EncodedTransactionWithStatusMeta;
|
||||
|
||||
const SYSTEM_PROGRAM_ID: Pubkey = pubkey!("11111111111111111111111111111111");
|
||||
const CHANNEL_SIZE: usize = 1000;
|
||||
// 根据实际并发量调整通道大小,避免背压
|
||||
const CHANNEL_SIZE: usize = 5000;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SystemEvent {
|
||||
@@ -52,7 +53,7 @@ impl YellowstoneGrpc {
|
||||
if let Err(e) =
|
||||
Self::handle_stream_message(msg, &mut tx, &mut subscribe_tx).await
|
||||
{
|
||||
error!("Error handling message: {:?}", e);
|
||||
error!("Error handling message: {e:?}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +67,7 @@ impl YellowstoneGrpc {
|
||||
|
||||
while let Some(transaction_pretty) = rx.next().await {
|
||||
if let Err(e) = Self::process_system_transaction(transaction_pretty, &*callback).await {
|
||||
error!("Error processing transaction: {:?}", e);
|
||||
error!("Error processing transaction: {e:?}");
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user