Skip to content

Commit f044a05

Browse files
committed
add Solana support in sync-lag-monitor
- change monitor name from head-lag-monitor to sync-lag-monitor - add support for tracking Solana sync lag
1 parent c0e1d28 commit f044a05

File tree

3 files changed

+73
-28
lines changed

3 files changed

+73
-28
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,12 @@ Chainbench will keep the test data up to date by running a background process th
195195

196196
### Monitors
197197
Monitors are separate processes that run during the test to collect or process some additional data and metrics relevant to the test.
198-
For example, head-lag-monitor will collect the latest block information from the node under test, check the timestamp and compare it to current time to calculate how much the node lags behind.
198+
For example, sync-lag-monitor will collect the latest block information from the node under test, check the timestamp and compare it to current time to calculate how much the node lags behind.
199199
You may include monitors in your test by using the `-m` or `--monitor` option and specifying the name of the monitor. At the moment, monitors only work in headless mode.
200200

201201
Here's an example:
202202
```shell
203-
chainbench start --profile evm.light --users 50 --workers 2 --test-time 12h --target https://node-url --headless --autoquit -m head-lag-monitor
203+
chainbench start --profile evm.light --users 50 --workers 2 --test-time 12h --target https://node-url --headless --autoquit -m sync-lag-monitor
204204
```
205205

206206

chainbench/main.py

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@
88
import click
99
import gevent.pool
1010
from click import Context, Parameter
11+
from locust.argument_parser import parse_locustfile_paths
12+
from locust.util.load_locustfile import load_locustfile
1113

12-
from chainbench.user import get_subclass_tasks
14+
from chainbench.user import EvmUser, SolanaUser, get_subclass_tasks
1315
from chainbench.user.common import all_method_classes, all_methods
1416
from chainbench.util.cli import (
1517
ContextData,
@@ -148,7 +150,7 @@ def validate_profile(ctx: Context, param: Parameter, value: str) -> str:
148150
default=[],
149151
help="Add a monitor to collect additional data or metrics. "
150152
"You may specify this option multiple times for different monitors",
151-
type=click.Choice(["head-lag-monitor"], case_sensitive=False),
153+
type=click.Choice(["sync-lag-monitor"], case_sensitive=False),
152154
multiple=True,
153155
)
154156
@click.option(
@@ -251,18 +253,16 @@ def start(
251253
click.echo(f"Profile path {profile_path} does not exist.")
252254
sys.exit(1)
253255

256+
user_classes = {}
257+
for locustfile in parse_locustfile_paths([profile_path.__str__()]):
258+
_, _user_classes, _ = load_locustfile(locustfile)
259+
for key, value in _user_classes.items():
260+
user_classes[key] = value
261+
test_data_types = set()
262+
for user_class in user_classes.values():
263+
test_data_types.add(type(getattr(user_class, "test_data")).__name__)
264+
254265
if test_by_directory:
255-
from locust.argument_parser import parse_locustfile_paths
256-
from locust.util.load_locustfile import load_locustfile
257-
258-
user_classes = {}
259-
test_data_types = set()
260-
for locustfile in parse_locustfile_paths([profile_path.__str__()]):
261-
_, _user_classes, _ = load_locustfile(locustfile)
262-
for key, value in _user_classes.items():
263-
user_classes[key] = value
264-
for user_class in user_classes.values():
265-
test_data_types.add(type(getattr(user_class, "test_data")).__name__)
266266
if len(test_data_types) > 1:
267267
click.echo(
268268
"Error occurred: Multiple test data types detected. "
@@ -384,9 +384,14 @@ def start(
384384
tags=["loudspeaker"],
385385
)
386386

387+
if list(test_data_types)[0] == "SolanaTestData":
388+
user_class = SolanaUser
389+
else:
390+
user_class = EvmUser
391+
387392
unique_monitors: set[str] = set(monitor)
388393
for m in unique_monitors:
389-
p = Process(target=monitors[m], args=(target, results_path, test_time))
394+
p = Process(target=monitors[m], args=(user_class, target, results_path, test_time))
390395
click.echo(f"Starting monitor {m}")
391396
p.start()
392397
ctx.obj.monitors.append(p)
@@ -505,7 +510,7 @@ def shapes() -> None:
505510
)
506511
def methods() -> None:
507512
for method_class in all_method_classes:
508-
click.echo(f"\nMethods for {method_class.__name__}:")
513+
click.echo(f"\nMethods for {method_class.__name__}: ")
509514
task_list = get_subclass_tasks(method_class)
510515
for task in task_list:
511516
click.echo(f"- {method_class.task_to_method(task.name)}")

chainbench/util/monitor.py

Lines changed: 51 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import csv
22
import logging
3+
import typing
34
from datetime import datetime, timedelta
45
from pathlib import Path
56
from time import sleep
67

78
from locust.util.timespan import parse_timespan
89
from orjson import JSONDecodeError
910

11+
from ..user import SolanaUser
1012
from .http import HttpClient
1113

1214
logger = logging.getLogger(__name__)
@@ -24,43 +26,81 @@ def calculate_lag(current_timestamp: datetime, block_timestamp: datetime) -> int
2426
return max(int((current_timestamp - block_timestamp).total_seconds()), 0)
2527

2628

27-
def head_lag_monitor(endpoint: str, result_path: Path, duration: str):
28-
data = {
29-
"id": 1,
29+
# EVM
30+
def eth_get_block_by_number(http_client: HttpClient) -> dict:
31+
body = {
3032
"jsonrpc": "2.0",
33+
"id": 1,
3134
"method": "eth_getBlockByNumber",
3235
"params": ["latest", False],
3336
}
37+
response = http_client.post(data=body)
38+
return response.json["result"]
39+
40+
41+
# Solana
42+
43+
44+
def get_slot(http_client: HttpClient) -> int:
45+
response = http_client.post(data={"jsonrpc": "2.0", "id": 1, "method": "getSlot", "params": []})
46+
return response.json["result"]
47+
48+
49+
def get_block(http_client: HttpClient, slot: int) -> dict:
50+
body = {
51+
"jsonrpc": "2.0",
52+
"id": 1,
53+
"method": "getBlock",
54+
"params": [
55+
slot,
56+
{
57+
"encoding": "jsonParsed",
58+
"transactionDetails": "none",
59+
"rewards": False,
60+
"maxSupportedTransactionVersion": 0,
61+
},
62+
],
63+
}
64+
response = http_client.post(data=body)
65+
return response.json["result"]
66+
67+
68+
def sync_lag_monitor(user_class: typing.Any, endpoint: str, result_path: Path, duration: str):
3469
end_time = datetime.now() + timedelta(seconds=parse_timespan(duration))
3570
http = HttpClient(endpoint)
3671
with open(
37-
file=f"{result_path}/head_lag.csv",
72+
file=f"{result_path}/sync_lag.csv",
3873
mode="a",
3974
encoding="utf-8-sig",
4075
newline="",
4176
) as csv_file:
42-
logger.info("Start monitoring head lag")
77+
logger.info("Start monitoring sync lag")
4378
csv_writer = csv.writer(csv_file)
4479
csv_writer.writerow(["timestamp", "lag (s)", "block number"])
4580
while datetime.now() < end_time:
4681
current_timestamp = datetime.now()
47-
response = http.post(data=data)
4882
try:
49-
block_timestamp = datetime.fromtimestamp(int(response.json["result"]["timestamp"], 0))
50-
block_number = int(response.json["result"]["number"], 0)
83+
if user_class == SolanaUser:
84+
block_number = get_slot(http)
85+
block = get_block(http, block_number)
86+
block_timestamp = datetime.fromtimestamp(block["blockTime"])
87+
else:
88+
block = eth_get_block_by_number(http)
89+
block_timestamp = datetime.fromtimestamp(int(block["timestamp"], 0))
90+
block_number = int(block["number"], 0)
5191
csv_writer.writerow(
5292
[
5393
current_timestamp,
5494
f"{calculate_lag(current_timestamp, block_timestamp)}",
5595
block_number,
5696
]
5797
)
58-
logger.info("Written 1 row to head_lag.csv")
98+
logger.info("Written 1 row to sync_lag.csv")
5999
sleep(10)
60100
except (KeyError, JSONDecodeError):
61101
logger.error("Error decoding JSON or key not found")
62102
sleep(1)
63-
logger.info("Finished monitoring head lag")
103+
logger.info("Finished monitoring sync lag")
64104

65105

66-
monitors = {"head-lag-monitor": head_lag_monitor}
106+
monitors = {"sync-lag-monitor": sync_lag_monitor}

0 commit comments

Comments
 (0)