Skip to content

Commit

Permalink
V3.0.0 (#17)
Browse files Browse the repository at this point in the history
* Labels refactoring and additional error handling.

* Update README.md
  • Loading branch information
namikmesic authored Sep 27, 2022
1 parent 0b42f98 commit b903183
Show file tree
Hide file tree
Showing 13 changed files with 243 additions and 278 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
The exporter is used to scrape metrics from blockchain RPC endpoints. The purpose of this exporter is to perform black-box testing on RPC endpoints.
## Metrics
Exporter currently supports all EVM-compatible chains. In addition, there is limited support for the following chains:
- Cardano
- Conflux
- Solana
- Bitcoin
- Dogecoin
- Filecoin
- Starkware
- Cardano (wss)
- Conflux (wss)
- Solana (https & wss)
- Bitcoin (https)
- Dogecoin (https)
- Filecoin (https)
- Starkware (https)

# Disclaimer
Please note that this tool is in the early development stage and should not be used to influence critical business decisions.
Expand Down
16 changes: 10 additions & 6 deletions config/exporter_example/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ connection_parameters:
ping_timeout: 3 # Liveness ping timeout
collector: "evm" # This will load different collectors based on what mode exporter will run with Supported modes are: "evm", "solana", "conflux", "cardano", "bitcoin"
endpoints: # List of endpoints with their metadata.
- ws_url: wss://example-rpc-1.com/ws # RPC Endpoint websocket endpoint (Must start with wss://)
https_url: https://example-rpc-1.com/rpc # RPC Endpoint https endpoint (Must be valid https:// domain)
- url: wss://example-rpc-1.com/ws # RPC Endpoint websocket endpoint (Must start with wss:// or https://)
provider: Provider1 # Provider (Must be present in allowed providers list. Please check src/settings.py line 24) The purpose is to make sure we do not have same providers spelled differently
- ws_url: wss://example-rpc-2.com/ws
https_url: https://example-rpc-2.com/rpc
- url: wss://example-rpc-2.com/ws
provider: Provider2
- ws_url: wss://example-rpc-3.com/ws
https_url: https://example-rpc-3.com/rpc
- url: wss://example-rpc-3.com/ws
provider: Provider3
# Solana specific
- url: https://example-solana-rpc-1.com/rpc
subscribe_url: wss://example-solana-rpc-1.com/ws
provider: Provider3





##
32 changes: 11 additions & 21 deletions src/collectors/bitcoin.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,30 @@
import asyncio
from bitcoinrpc import BitcoinRPC
from helpers import strip_url
from helpers import strip_url, validate_protocol, generate_labels_from_metadata
from time import perf_counter
from settings import cfg, logger
from settings import logger


class bitcoin_collector():

def __init__(self, https_url, provider):
self.https_url = https_url
self.labels = [
'https_url', 'provider', 'blockchain', 'network_name',
'network_type'
]
self.labels_values = [
https_url, provider, cfg.blockchain, cfg.network_name,
cfg.network_type
]
def __init__(self, rpc_metadata):
validate_protocol(rpc_metadata['url'], "https")
self.url = rpc_metadata['url']
self.labels, self.labels_values = generate_labels_from_metadata(rpc_metadata)

async def _probe(self, metrics):
try:
async with BitcoinRPC(self.https_url, "admin", "admin") as rpc:
async with BitcoinRPC(self.url, "admin", "admin") as rpc:
start = perf_counter()
chain_info = await rpc.getblockchaininfo()
latency = (perf_counter() - start) * 1000

metrics['ws_rpc_health'].add_metric(self.labels_values, True)
metrics['ws_rpc_latency'].add_metric(self.labels_values,
latency)
metrics['ws_rpc_block_height'].add_metric(
self.labels_values, chain_info['headers'])
metrics['ws_rpc_total_difficulty'].add_metric(
self.labels_values, chain_info['difficulty'])
metrics['ws_rpc_latency'].add_metric(self.labels_values, latency)
metrics['ws_rpc_block_height'].add_metric(self.labels_values, chain_info['headers'])
metrics['ws_rpc_total_difficulty'].add_metric(self.labels_values, chain_info['difficulty'])
except Exception as exc:
logger.error("Failed probing {} with error: {}".format(
strip_url(self.url), exc))
logger.error("Failed probing {} with error: {}".format(strip_url(self.url), exc))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)

def probe(self, metrics):
Expand Down
15 changes: 8 additions & 7 deletions src/collectors/cardano.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
from settings import cfg, logger
from settings import logger
import json
from collectors.ws import websocket_collector
from helpers import strip_url
from helpers import strip_url, validate_protocol, generate_labels_from_metadata


class cardano_collector():

def __init__(self, url, provider):
self.labels = ['url', 'provider', 'blockchain', 'network_name', 'network_type']
self.labels_values = [url, provider, cfg.blockchain, cfg.network_name, cfg.network_type]
self.url = url
self.ws_collector = websocket_collector(url, provider)
def __init__(self, rpc_metadata):
validate_protocol(rpc_metadata['url'], "wss")
self.url = rpc_metadata['url']

self.labels, self.labels_values = generate_labels_from_metadata(rpc_metadata)
self.ws_collector = websocket_collector(self.url)

def _get_block_height(self):
blk_height_payload = {
Expand Down
38 changes: 19 additions & 19 deletions src/collectors/conflux.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
from settings import cfg, logger
from conflux_web3 import Web3
import asyncio
from helpers import strip_url
from helpers import strip_url, validate_protocol, generate_labels_from_metadata
from collectors.ws import websocket_collector


class conflux_collector():

def __init__(self, websocket_url, https_url, provider):
self.client = Web3(Web3.WebsocketProvider(websocket_url, websocket_timeout=cfg.response_timeout))
self.labels = [
'websocket_url', 'https_url', 'provider', 'blockchain', 'network_name', 'network_type', 'evmChainID'
]
self.labels_values = [
websocket_url, https_url, provider, cfg.blockchain, cfg.network_name, cfg.network_type,
str(cfg.chain_id)
]
self.websocket_url = websocket_url
self.ws_collector = websocket_collector(websocket_url,
def __init__(self, rpc_metadata):
validate_protocol(rpc_metadata['url'], "wss")
self.url = rpc_metadata['url']
self.labels, self.labels_values = generate_labels_from_metadata(rpc_metadata)
self.client = Web3(Web3.WebsocketProvider(self.url, websocket_timeout=cfg.response_timeout))
self.labels, self.labels_values = generate_labels_from_metadata(rpc_metadata)

self.ws_collector = websocket_collector(self.url,
sub_payload={
"method": "cfx_subscribe",
"jsonrpc": "2.0",
Expand All @@ -26,7 +23,6 @@ def __init__(self, websocket_url, https_url, provider):
})
self.ws_collector.setDaemon(True)
self.ws_collector.start()
self.first_time = False

def probe(self, metrics):
try:
Expand All @@ -37,16 +33,20 @@ def probe(self, metrics):
metrics['ws_rpc_latency'].add_metric(self.labels_values, self.ws_collector.get_latency())
metrics['ws_rpc_block_height'].add_metric(self.labels_values, self.client.cfx.epoch_number)

metrics['ws_rpc_difficulty'].add_metric(
self.labels_values,
self.client.cfx.get_block_by_hash(self.client.cfx.get_best_block_hash())['difficulty'])
try:
difficulty = self.client.cfx.get_block_by_hash(self.client.cfx.get_best_block_hash())['difficulty']
metrics['ws_rpc_difficulty'].add_metric(self.labels_values, difficulty)
except TypeError:
logger.error(
"RPC Endpoint sent faulty response type when querying for difficulty. This is most likely issue with RPC endpoint."
)

metrics['ws_rpc_gas_price'].add_metric(self.labels_values, self.client.cfx.gas_price)
else:
logger.info("Client is not connected to {}".format(strip_url(self.websocket_url)))
logger.info("Client is not connected to {}".format(strip_url(self.url)))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except asyncio.exceptions.TimeoutError:
logger.info("Client timed out for {}".format(strip_url(self.websocket_url)))
logger.info("Client timed out for {}".format(strip_url(self.url)))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except Exception as exc:
logger.error("Failed probing {} with error: {}".format(strip_url(self.url), exc))
logger.error("Failed probing {} with error: {}".format(strip_url(self.url), exc))
38 changes: 13 additions & 25 deletions src/collectors/dogecoin.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,34 @@
from helpers import strip_url
from settings import cfg, logger
from helpers import strip_url, validate_protocol, generate_labels_from_metadata
from settings import logger
from time import perf_counter
import requests


class doge_collector():

def __init__(self, https_url, provider):
self.https_url = https_url
self.labels = [
'https_url', 'provider', 'blockchain', 'network_name',
'network_type'
]
self.labels_values = [
https_url, provider, cfg.blockchain, cfg.network_name,
cfg.network_type
]
def __init__(self, rpc_metadata):
validate_protocol(rpc_metadata['url'], "https")
self.url = rpc_metadata['url']
self.labels, self.labels_values = generate_labels_from_metadata(rpc_metadata)

def probe(self, metrics):
try:
payload = {'version': '1.1', 'method': "getinfo", 'id': 1}
start = perf_counter()
response = requests.post(self.https_url, json=payload).json()
response = requests.post(self.url, json=payload).json()
latency = (perf_counter() - start) * 1000

if response:
metrics['ws_rpc_health'].add_metric(self.labels_values, True)
metrics['ws_rpc_latency'].add_metric(self.labels_values,
latency)
metrics['ws_rpc_block_height'].add_metric(
self.labels_values, response['result']['blocks'])
metrics['ws_rpc_total_difficulty'].add_metric(
self.labels_values, response['result']['difficulty'])
metrics['ws_rpc_latency'].add_metric(self.labels_values, latency)
metrics['ws_rpc_block_height'].add_metric(self.labels_values, response['result']['blocks'])
metrics['ws_rpc_total_difficulty'].add_metric(self.labels_values, response['result']['difficulty'])
else:
logger.error("Bad response from client {}: {}".format(
strip_url(self.https_url), exc))
logger.error("Bad response from client {}: {}".format(strip_url(self.url), exc))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except requests.RequestException as exc:
logger.error("Health check failed for {}: {}".format(
strip_url(self.https_url), exc))
logger.error("Health check failed for {}: {}".format(strip_url(self.url), exc))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except Exception as exc:
logger.error("Health check failed for {}: {}".format(
strip_url(self.https_url), exc))
logger.error("Health check failed for {}: {}".format(strip_url(self.url), exc))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
44 changes: 26 additions & 18 deletions src/collectors/evm.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
from settings import cfg, logger
from web3 import Web3
import asyncio
from helpers import strip_url
from helpers import strip_url, validate_protocol, generate_labels_from_metadata
from collectors.ws import websocket_collector
from websockets.exceptions import WebSocketException


class evm_collector():

def __init__(self, websocket_url, https_url, provider):
self.client = Web3(Web3.WebsocketProvider(websocket_url, websocket_timeout=cfg.response_timeout))
self.labels = [
'websocket_url', 'https_url', 'provider', 'blockchain', 'network_name', 'network_type', 'evmChainID'
]
self.labels_values = [
websocket_url, https_url, provider, cfg.blockchain, cfg.network_name, cfg.network_type,
str(cfg.chain_id)
]
self.websocket_url = websocket_url
self.ws_collector = websocket_collector(websocket_url,
def __init__(self, rpc_metadata):
validate_protocol(rpc_metadata['url'], "wss")
self.url = rpc_metadata['url']
self.client = Web3(Web3.WebsocketProvider(self.url, websocket_timeout=cfg.response_timeout))

self.labels, self.labels_values = generate_labels_from_metadata(rpc_metadata)

self.ws_collector = websocket_collector(self.url,
sub_payload={
"method": "eth_subscribe",
"jsonrpc": "2.0",
"id": cfg.chain_id,
"id": rpc_metadata['chain_id'],
"params": ["newHeads"]
})
self.net_peer_enabled = True
self.ws_collector.setDaemon(True)
self.ws_collector.start()

def probe(self, metrics):
def probe(self, metrics):
try:
if self.client.isConnected():
metrics['ws_rpc_health'].add_metric(self.labels_values, True)
Expand All @@ -39,17 +38,26 @@ def probe(self, metrics):
self.client.eth.get_block('latest')['totalDifficulty'])
metrics['ws_rpc_difficulty'].add_metric(self.labels_values,
self.client.eth.get_block('latest')['difficulty'])
metrics['ws_rpc_net_peer_count'].add_metric(self.labels_values, self.client.net.peer_count)

try:
if self.net_peer_enabled:
metrics['ws_rpc_net_peer_count'].add_metric(self.labels_values, self.client.net.peer_count)
except ValueError:
logger.error(
"Net peer function is not supported for this chain, the collector will ignore this from this point on."
)
self.net_peer_enabled = False

metrics['ws_rpc_gas_price'].add_metric(self.labels_values, self.client.eth.gas_price)
metrics['ws_rpc_max_priority_fee'].add_metric(self.labels_values, self.client.eth.max_priority_fee)
else:
logger.info("Client is not connected to {}".format(strip_url(self.websocket_url)))
logger.info("Client is not connected to {}".format(strip_url(self.url)))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except asyncio.exceptions.TimeoutError as exc:
logger.info("Client timed out for {}: {}".format(strip_url(self.websocket_url), exc))
logger.info("Client timed out for {}: {}".format(strip_url(self.url), exc))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except WebSocketException as exc:
logger.info("Websocket client exception {}: {}".format(strip_url(self.websocket_url), exc))
logger.info("Websocket client exception {}: {}".format(strip_url(self.url), exc))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except Exception as exc:
logger.error("Failed probing {} with error: {}".format(strip_url(self.url), exc))
Expand Down
45 changes: 16 additions & 29 deletions src/collectors/filecoin.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,35 @@
from helpers import strip_url
from settings import cfg, logger
from helpers import strip_url, validate_protocol, generate_labels_from_metadata
from settings import logger
from time import perf_counter
import requests


class filecoin_collector():

def __init__(self, https_url, provider):
self.https_url = https_url
self.labels = [
'https_url', 'provider', 'blockchain', 'network_name',
'network_type'
]
self.labels_values = [
https_url, provider, cfg.blockchain, cfg.network_name,
cfg.network_type
]
def __init__(self, rpc_metadata):
validate_protocol(rpc_metadata['url'], "https")
self.url = rpc_metadata['url']

self.labels, self.labels_values = generate_labels_from_metadata(rpc_metadata)

def probe(self, metrics):
try:
payload = {
'jsonrpc': '2.0',
'method': "Filecoin.ChainHead",
'id': 1
}
payload = {'jsonrpc': '2.0', 'method': "Filecoin.ChainHead", 'id': 1}
start = perf_counter()
response = requests.post(self.https_url, json=payload).json()
response = requests.post(self.url, json=payload)
latency = (perf_counter() - start) * 1000

if response:
if response.ok:
metrics['ws_rpc_health'].add_metric(self.labels_values, True)
metrics['ws_rpc_latency'].add_metric(self.labels_values,
latency)
metrics['ws_rpc_block_height'].add_metric(
self.labels_values, response['result']['Height'])
metrics['ws_rpc_latency'].add_metric(self.labels_values, latency)
metrics['ws_rpc_block_height'].add_metric(self.labels_values, response.json()['result']['Height'])
else:
logger.error("Bad response from client {}: {}".format(
strip_url(self.https_url), exc))
logger.error("Bad response from client while fetching Filecoin.ChainHead method for {}: {}".format(
strip_url(self.url), response))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except requests.RequestException as exc:
logger.error("Health check failed for {}: {}".format(
strip_url(self.https_url), exc))
logger.error("Health check failed for {}: {}".format(strip_url(self.url), exc))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
except Exception as e:
logger.error("Health check failed for {}: {}".format(
strip_url(self.https_url), e))
logger.error("Health check failed for {}: {}".format(strip_url(self.url), e))
metrics['ws_rpc_health'].add_metric(self.labels_values, False)
Loading

0 comments on commit b903183

Please sign in to comment.