Skip to content

Commit 1d9ebe3

Browse files
committed
add more metadata to timescale db listener
- add function to try to get client version of target node/endpoint - add metadata such as chainbench version, target node host, client and version - refactor get_master_command and get_worker_command into a single class and reduce code redundancy
1 parent 5b2cf01 commit 1d9ebe3

File tree

3 files changed

+127
-124
lines changed

3 files changed

+127
-124
lines changed

chainbench/main.py

Lines changed: 6 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,11 @@
1818
)
1919
from chainbench.util.cli import (
2020
ContextData,
21+
LocustOptions,
2122
ensure_results_dir,
2223
get_base_path,
23-
get_master_command,
2424
get_profile_path,
2525
get_profiles,
26-
get_worker_command,
2726
)
2827
from chainbench.util.monitor import monitors
2928
from chainbench.util.notify import NoopNotifier, Notifier
@@ -296,8 +295,7 @@ def start(
296295
for tag in exclude_tags:
297296
custom_exclude_tags.append(tag)
298297

299-
# Start the Locust master
300-
master_command = get_master_command(
298+
locust_options = LocustOptions(
301299
profile_path=profile_path,
302300
host=host,
303301
port=port,
@@ -321,6 +319,9 @@ def start(
321319
method=method,
322320
enable_class_picker=enable_class_picker,
323321
)
322+
# Start the Locust master
323+
master_command = locust_options.get_master_command()
324+
324325
if headless:
325326
click.echo("Starting master in headless mode")
326327
else:
@@ -334,24 +335,7 @@ def start(
334335

335336
# Start the Locust workers
336337
for worker_id in range(workers):
337-
worker_command = get_worker_command(
338-
profile_path=profile_path,
339-
host=host,
340-
port=port,
341-
results_path=results_path,
342-
headless=headless,
343-
target=target,
344-
worker_id=worker_id,
345-
log_level=log_level,
346-
exclude_tags=custom_exclude_tags,
347-
timescale=timescale,
348-
pg_host=pg_host,
349-
pg_port=pg_port,
350-
pg_username=pg_username,
351-
pg_password=pg_password,
352-
use_latest_blocks=use_latest_blocks,
353-
method=method,
354-
)
338+
worker_command = locust_options.get_worker_command(worker_id=worker_id)
355339
worker_args = shlex.split(worker_command, posix=is_posix)
356340
worker_process = subprocess.Popen(worker_args)
357341
ctx.obj.workers.append(worker_process)

chainbench/util/cli.py

Lines changed: 119 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
from datetime import datetime
44
from pathlib import Path
55

6+
from geventhttpclient import URL
7+
8+
from chainbench.util.http import (
9+
HttpClient,
10+
HttpErrorLevel,
11+
HttpStatusError,
12+
JsonRpcError,
13+
)
614
from chainbench.util.notify import NoopNotifier, Notifier
715

816

@@ -58,112 +66,123 @@ def get_timescale_args(
5866
return f" --timescale --pghost={pg_host} --pgport={pg_port}" f" --pgpassword={pg_password} --pguser={pg_username}"
5967

6068

61-
def get_master_command(
62-
profile_path: Path,
63-
host: str,
64-
port: int,
65-
users: int,
66-
workers: int,
67-
spawn_rate: int,
68-
test_time: str,
69-
log_level: str,
70-
results_path: Path,
71-
exclude_tags: list[str],
72-
target: str | None = None,
73-
headless: bool = False,
74-
timescale: bool = False,
75-
pg_host: str | None = None,
76-
pg_port: int | None = None,
77-
pg_username: str | None = None,
78-
pg_password: str | None = None,
79-
override_plan_name: str | None = None,
80-
use_latest_blocks: bool = False,
81-
size: str | None = None,
82-
method: str | None = None,
83-
enable_class_picker: bool = False,
84-
) -> str:
85-
"""Generate master command."""
86-
command = (
87-
f"locust -f {profile_path} --master "
88-
f"--master-bind-host {host} --master-bind-port {port} "
89-
f"--web-host {host} "
90-
f"-u {users} -r {spawn_rate} --run-time {test_time} "
91-
f"--html {results_path}/report.html --csv {results_path}/report.csv "
92-
f"--logfile {results_path}/report.log "
93-
f"--loglevel {log_level} --expect-workers {workers} "
94-
f"--size {size}"
95-
)
96-
97-
if timescale:
98-
command += get_timescale_args(pg_host, pg_port, pg_username, pg_password)
99-
if override_plan_name is not None:
100-
command += f" --override-plan-name {override_plan_name}"
101-
102-
if target is not None:
103-
command += f" --host {target}"
104-
105-
if headless:
106-
command += " --headless"
107-
108-
if len(exclude_tags) > 0:
109-
command += f" --exclude-tags {' '.join(exclude_tags)}"
110-
111-
if use_latest_blocks:
112-
command += " --use-latest-blocks True"
113-
114-
if method is not None:
115-
command += f" --method {method}"
116-
117-
if enable_class_picker:
118-
command += " --class-picker"
119-
return command
120-
121-
122-
def get_worker_command(
123-
profile_path: Path,
124-
host: str,
125-
port: int,
126-
results_path: Path,
127-
log_level: str,
128-
exclude_tags: list[str],
129-
target: str | None = None,
130-
headless: bool = False,
131-
worker_id: int = 0,
132-
timescale: bool = False,
133-
pg_host: str | None = None,
134-
pg_port: int | None = None,
135-
pg_username: str | None = None,
136-
pg_password: str | None = None,
137-
override_plan_name: str | None = None,
138-
use_latest_blocks: bool = False,
139-
method: str | None = None,
140-
) -> str:
141-
"""Generate worker command."""
142-
command = (
143-
f"locust -f {profile_path} --worker --master-host {host} --master-port {port} "
144-
f"--logfile {results_path}/worker_{worker_id}.log --loglevel {log_level}"
145-
)
69+
def get_url_domain(target_url: str) -> str:
70+
"""Get domain from URL."""
71+
return URL(target_url).host
72+
14673

147-
if timescale:
148-
command += get_timescale_args(pg_host, pg_port, pg_username, pg_password)
149-
if override_plan_name is not None:
150-
command += f" --override-plan-name {override_plan_name}"
74+
def get_target_client_version(target_url: str) -> str:
75+
"""Get client version from target URL."""
76+
http = HttpClient(target_url, error_level=HttpErrorLevel.ClientError)
15177

152-
if target is not None:
153-
command += f" --host {target}"
78+
def _get_version_rpc(_method: str, _path: str) -> str:
79+
try:
80+
_response = http.make_rpc_call(method=_method, path=_path)
81+
return str(_response)
82+
except (JsonRpcError, HttpStatusError):
83+
return "unknown"
15484

155-
if headless:
156-
command += " --headless"
85+
rpc_methods = [
86+
"web3_clientVersion",
87+
"getVersion",
88+
("pathfinder_version", "/rpc/pathfinder/v0.1"),
89+
"juno_version",
90+
"nodeVersion",
91+
]
15792

158-
if len(exclude_tags) > 0:
159-
command += f" --exclude-tags {' '.join(exclude_tags)}"
93+
for rpc_method in rpc_methods:
94+
if isinstance(rpc_method, tuple):
95+
method, path = rpc_method
96+
else:
97+
method = rpc_method
98+
path = ""
99+
version = _get_version_rpc(method, path)
100+
if version != "unknown":
101+
return version
102+
103+
try:
104+
response = http.get("eth/v1/node/version")
105+
return response.json["data"]["version"]
106+
except (JsonRpcError, HttpStatusError):
107+
return "unknown"
160108

161-
if use_latest_blocks:
162-
command += " --use-latest-blocks True"
163109

164-
if method is not None:
165-
command += f" --method {method}"
166-
return command
110+
@dataclass
111+
class LocustOptions:
112+
profile_path: Path
113+
host: str
114+
port: int
115+
users: int
116+
workers: int
117+
spawn_rate: int
118+
test_time: str
119+
log_level: str
120+
results_path: Path
121+
exclude_tags: list[str]
122+
target: str
123+
headless: bool = False
124+
timescale: bool = False
125+
pg_host: str | None = None
126+
pg_port: int | None = None
127+
pg_username: str | None = None
128+
pg_password: str | None = None
129+
override_plan_name: str | None = None
130+
use_latest_blocks: bool = False
131+
size: str | None = None
132+
method: str | None = None
133+
enable_class_picker: bool = False
134+
135+
def get_master_command(self) -> str:
136+
"""Generate master command."""
137+
command = (
138+
f"locust -f {self.profile_path} --master "
139+
f"--master-bind-host {self.host} --master-bind-port {self.port} "
140+
f"--web-host {self.host} "
141+
f"-u {self.users} -r {self.spawn_rate} --run-time {self.test_time} "
142+
f"--html {self.results_path}/report.html --csv {self.results_path}/report.csv "
143+
f"--logfile {self.results_path}/report.log "
144+
f"--loglevel {self.log_level} --expect-workers {self.workers} "
145+
f"--size {self.size}"
146+
)
147+
148+
if self.enable_class_picker:
149+
command += " --class-picker"
150+
151+
return self.get_extra_options(command)
152+
153+
def get_worker_command(self, worker_id: int = 0) -> str:
154+
"""Generate worker command."""
155+
command = (
156+
f"locust -f {self.profile_path} --worker --master-host {self.host} --master-port {self.port} "
157+
f"--logfile {self.results_path}/worker_{worker_id}.log --loglevel {self.log_level}"
158+
)
159+
return self.get_extra_options(command)
160+
161+
def get_extra_options(self, command: str):
162+
if self.timescale:
163+
command += get_timescale_args(self.pg_host, self.pg_port, self.pg_username, self.pg_password)
164+
if self.override_plan_name is not None:
165+
command += f" --override-plan-name {self.override_plan_name}"
166+
from importlib import metadata
167+
168+
command += f" --test-version chainbench-{metadata.version('chainbench')}"
169+
command += f' --description "{get_url_domain(self.target)} [{get_target_client_version(self.target)}]"'
170+
171+
if self.target is not None:
172+
command += f" --host {self.target}"
173+
174+
if self.headless:
175+
command += " --headless"
176+
177+
if len(self.exclude_tags) > 0:
178+
command += f" --exclude-tags {' '.join(self.exclude_tags)}"
179+
180+
if self.use_latest_blocks:
181+
command += " --use-latest-blocks True"
182+
183+
if self.method is not None:
184+
command += f" --method {self.method}"
185+
return command
167186

168187

169188
@dataclass

chainbench/util/http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ def _make_body(self, method: str, params: list[t.Any]) -> dict[str, t.Any]:
151151
"id": token_hex(8),
152152
}
153153

154-
def make_rpc_call(self, method: str, params: list[t.Any] | None = None) -> t.Any:
154+
def make_rpc_call(self, method: str, params: list[t.Any] | None = None, path: str = "") -> t.Any:
155155
if params is None:
156156
params = []
157157

158-
response = self.post(data=self._make_body(method, params))
158+
response = self.post(path=path, data=self._make_body(method, params))
159159

160160
logger.debug(f"Making call to {self._host} with method {method} and params {params}")
161161
logger.debug(f"Response: {response.json}")

0 commit comments

Comments
 (0)