Skip to content

Commit e2b0a65

Browse files
author
yihuang
authored
Problem: fire-cli argument parsing logic is unexpected (#1580)
Solution: - switch to click package
1 parent 7aacdb7 commit e2b0a65

File tree

3 files changed

+199
-173
lines changed

3 files changed

+199
-173
lines changed

testground/benchmark/benchmark/stateless.py

Lines changed: 181 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from pathlib import Path
1010
from typing import List
1111

12-
import fire
12+
import click
1313
import requests
1414
import tomlkit
1515

@@ -38,149 +38,193 @@
3838
ECHO_SERVER_PORT = 26659
3939

4040

41-
class CLI:
42-
def gen(
43-
self,
44-
outdir: str,
45-
hostname_template=HOSTNAME_TEMPLATE,
46-
options={},
47-
):
48-
print("options", options)
49-
validators = options.get("validators", 3)
50-
fullnodes = options.get("fullnodes", 7)
51-
num_accounts = options.get("num_accounts", 10)
52-
num_txs = options.get("num_txs", 1000)
53-
config_patch = options.get("config", {})
54-
app_patch = options.get("app", {})
55-
genesis_patch = options.get("genesis", {})
56-
outdir = Path(outdir)
57-
cli = ChainCommand(LOCAL_CRONOSD_PATH)
58-
(outdir / VALIDATOR_GROUP).mkdir(parents=True, exist_ok=True)
59-
(outdir / FULLNODE_GROUP).mkdir(parents=True, exist_ok=True)
60-
61-
peers = []
62-
for i in range(validators):
63-
print("init validator", i)
64-
ip = hostname_template.format(index=i)
65-
peers.append(init_node_local(cli, outdir, VALIDATOR_GROUP, i, ip))
66-
for i in range(fullnodes):
67-
print("init fullnode", i)
68-
ip = hostname_template.format(index=i + validators)
69-
peers.append(init_node_local(cli, outdir, FULLNODE_GROUP, i, ip))
70-
71-
print("prepare genesis")
72-
# use a full node directory to prepare the genesis file
73-
genesis = gen_genesis(cli, outdir / FULLNODE_GROUP / "0", peers, genesis_patch)
74-
75-
print("patch genesis")
76-
# write genesis file and patch config files
77-
for i in range(validators):
78-
patch_configs_local(
79-
peers, genesis, outdir, VALIDATOR_GROUP, i, config_patch, app_patch
80-
)
81-
for i in range(fullnodes):
82-
patch_configs_local(
83-
peers,
84-
genesis,
85-
outdir,
86-
FULLNODE_GROUP,
87-
i,
88-
config_patch,
89-
app_patch,
90-
)
91-
92-
print("write config")
93-
cfg = {
94-
"validators": validators,
95-
"fullnodes": fullnodes,
96-
"num_accounts": num_accounts,
97-
"num_txs": num_txs,
98-
"validator-generate-load": options.get("validator-generate-load", True),
99-
}
100-
(outdir / "config.json").write_text(json.dumps(cfg))
101-
102-
def patchimage(
103-
self,
104-
toimage,
105-
src,
106-
dst="/data",
107-
fromimage="ghcr.io/crypto-org-chain/cronos-testground:latest",
108-
):
109-
"""
110-
combine data directory with an exiting image to produce a new image
111-
"""
112-
with tempfile.TemporaryDirectory() as tmpdir:
113-
tmpdir = Path(tmpdir)
114-
shutil.copytree(src, tmpdir / "out")
115-
content = f"""FROM {fromimage}
41+
@click.group()
42+
def cli():
43+
pass
44+
45+
46+
def validate_json(ctx, param, value):
47+
try:
48+
return json.loads(value)
49+
except json.JSONDecodeError:
50+
raise click.BadParameter("must be a valid JSON string")
51+
52+
53+
@cli.command()
54+
@click.argument("outdir")
55+
@click.option("--hostname-template", default=HOSTNAME_TEMPLATE)
56+
@click.option("--validators", default=3)
57+
@click.option("--fullnodes", default=7)
58+
@click.option("--num-accounts", default=10)
59+
@click.option("--num-txs", default=1000)
60+
@click.option("--config-patch", default="{}", callback=validate_json)
61+
@click.option("--app-patch", default="{}", callback=validate_json)
62+
@click.option("--genesis-patch", default="{}", callback=validate_json)
63+
@click.option("--validator-generate-load/--no-validator-generate-load", default=True)
64+
def gen(**kwargs):
65+
return _gen(**kwargs)
66+
67+
68+
@cli.command()
69+
@click.argument("options", callback=validate_json)
70+
def generic_gen(options: dict):
71+
return _gen(**options)
72+
73+
74+
def _gen(
75+
outdir: str,
76+
hostname_template: str = HOSTNAME_TEMPLATE,
77+
validators: int = 3,
78+
fullnodes: int = 7,
79+
num_accounts: int = 10,
80+
num_txs: int = 1000,
81+
validator_generate_load: bool = True,
82+
config_patch: dict = None,
83+
app_patch: dict = None,
84+
genesis_patch: dict = None,
85+
):
86+
config_patch = config_patch or {}
87+
app_patch = app_patch or {}
88+
genesis_patch = genesis_patch or {}
89+
90+
outdir = Path(outdir)
91+
cli = ChainCommand(LOCAL_CRONOSD_PATH)
92+
(outdir / VALIDATOR_GROUP).mkdir(parents=True, exist_ok=True)
93+
(outdir / FULLNODE_GROUP).mkdir(parents=True, exist_ok=True)
94+
95+
config_patch = (
96+
json.loads(config_patch) if isinstance(config_patch, str) else config_patch
97+
)
98+
app_patch = json.loads(app_patch) if isinstance(app_patch, str) else app_patch
99+
genesis_patch = (
100+
json.loads(genesis_patch) if isinstance(genesis_patch, str) else genesis_patch
101+
)
102+
103+
peers = []
104+
for i in range(validators):
105+
print("init validator", i)
106+
ip = hostname_template.format(index=i)
107+
peers.append(init_node_local(cli, outdir, VALIDATOR_GROUP, i, ip))
108+
for i in range(fullnodes):
109+
print("init fullnode", i)
110+
ip = hostname_template.format(index=i + validators)
111+
peers.append(init_node_local(cli, outdir, FULLNODE_GROUP, i, ip))
112+
113+
print("prepare genesis")
114+
# use a full node directory to prepare the genesis file
115+
genesis = gen_genesis(cli, outdir / FULLNODE_GROUP / "0", peers, genesis_patch)
116+
117+
print("patch genesis")
118+
# write genesis file and patch config files
119+
for i in range(validators):
120+
patch_configs_local(
121+
peers, genesis, outdir, VALIDATOR_GROUP, i, config_patch, app_patch
122+
)
123+
for i in range(fullnodes):
124+
patch_configs_local(
125+
peers,
126+
genesis,
127+
outdir,
128+
FULLNODE_GROUP,
129+
i,
130+
config_patch,
131+
app_patch,
132+
)
133+
134+
print("write config")
135+
cfg = {
136+
"validators": validators,
137+
"fullnodes": fullnodes,
138+
"num_accounts": num_accounts,
139+
"num_txs": num_txs,
140+
"validator-generate-load": validator_generate_load,
141+
}
142+
(outdir / "config.json").write_text(json.dumps(cfg))
143+
144+
145+
@cli.command()
146+
@click.argument("toimage")
147+
@click.argument("src")
148+
@click.option("--dst", default="/data")
149+
@click.option(
150+
"--fromimage", default="ghcr.io/crypto-org-chain/cronos-testground:latest"
151+
)
152+
def patchimage(toimage, src, dst, fromimage):
153+
"""
154+
combine data directory with an exiting image to produce a new image
155+
"""
156+
with tempfile.TemporaryDirectory() as tmpdir:
157+
tmpdir = Path(tmpdir)
158+
shutil.copytree(src, tmpdir / "out")
159+
content = f"""FROM {fromimage}
116160
ADD ./out {dst}
117161
"""
118-
print(content)
119-
(tmpdir / "Dockerfile").write_text(content)
120-
subprocess.run(["docker", "build", "-t", toimage, tmpdir])
121-
122-
def run(
123-
self,
124-
outdir: str = "/outputs",
125-
datadir: str = "/data",
126-
cronosd=CONTAINER_CRONOSD_PATH,
127-
global_seq=None,
128-
):
129-
run_echo_server(ECHO_SERVER_PORT)
130-
131-
datadir = Path(datadir)
132-
cfg = json.loads((datadir / "config.json").read_text())
133-
134-
if global_seq is None:
135-
global_seq = node_index()
136-
137-
validators = cfg["validators"]
138-
group = VALIDATOR_GROUP if global_seq < validators else FULLNODE_GROUP
139-
group_seq = global_seq if group == VALIDATOR_GROUP else global_seq - validators
140-
print("node role", global_seq, group, group_seq)
141-
142-
home = datadir / group / str(group_seq)
143-
144-
# wait for persistent peers to be ready
145-
wait_for_peers(home)
146-
147-
print("start node")
148-
logfile = open(home / "node.log", "ab", buffering=0)
149-
proc = subprocess.Popen(
150-
[cronosd, "start", "--home", str(home)],
151-
stdout=logfile,
152-
)
162+
print(content)
163+
(tmpdir / "Dockerfile").write_text(content)
164+
subprocess.run(["docker", "build", "-t", toimage, tmpdir])
153165

154-
cli = ChainCommand(cronosd)
155-
wait_for_port(26657)
156-
wait_for_port(8545)
157-
wait_for_block(cli, 3)
158166

159-
if group == FULLNODE_GROUP or cfg.get("validator-generate-load", True):
160-
wait_for_w3()
161-
generate_load(
162-
cli, cfg["num_accounts"], cfg["num_txs"], home=home, output="json"
163-
)
167+
@cli.command()
168+
@click.option("--outdir", default="/outputs")
169+
@click.option("--datadir", default="/data")
170+
@click.option("--cronosd", default=CONTAINER_CRONOSD_PATH)
171+
@click.option("--global-seq", default=None)
172+
def run(outdir: str, datadir: str, cronosd, global_seq):
173+
run_echo_server(ECHO_SERVER_PORT)
164174

165-
# node quit when the chain is idle or halted for a while
166-
detect_idle_halted(20, 20)
175+
datadir = Path(datadir)
176+
cfg = json.loads((datadir / "config.json").read_text())
167177

168-
with (home / "block_stats.log").open("w") as logfile:
169-
dump_block_stats(logfile)
178+
if global_seq is None:
179+
global_seq = node_index()
170180

171-
proc.kill()
172-
proc.wait(20)
181+
validators = cfg["validators"]
182+
group = VALIDATOR_GROUP if global_seq < validators else FULLNODE_GROUP
183+
group_seq = global_seq if group == VALIDATOR_GROUP else global_seq - validators
184+
print("node role", global_seq, group, group_seq)
173185

174-
# collect outputs
175-
output = Path("/data.tar.bz2")
176-
with tarfile.open(output, "x:bz2") as tar:
177-
tar.add(home, arcname="data", filter=output_filter(group, group_seq))
178-
outdir = Path(outdir)
179-
if outdir.exists():
180-
assert outdir.is_dir()
181-
filename = outdir / f"{group}_{group_seq}.tar.bz2"
182-
filename.unlink(missing_ok=True)
183-
shutil.copy(output, filename)
186+
home = datadir / group / str(group_seq)
187+
188+
# wait for persistent peers to be ready
189+
wait_for_peers(home)
190+
191+
print("start node")
192+
logfile = open(home / "node.log", "ab", buffering=0)
193+
proc = subprocess.Popen(
194+
[cronosd, "start", "--home", str(home)],
195+
stdout=logfile,
196+
)
197+
198+
cli = ChainCommand(cronosd)
199+
wait_for_port(26657)
200+
wait_for_port(8545)
201+
wait_for_block(cli, 3)
202+
203+
if group == FULLNODE_GROUP or cfg.get("validator-generate-load", True):
204+
wait_for_w3()
205+
generate_load(
206+
cli, cfg["num_accounts"], cfg["num_txs"], home=home, output="json"
207+
)
208+
209+
# node quit when the chain is idle or halted for a while
210+
detect_idle_halted(20, 20)
211+
212+
with (home / "block_stats.log").open("w") as logfile:
213+
dump_block_stats(logfile)
214+
215+
proc.kill()
216+
proc.wait(20)
217+
218+
# collect outputs
219+
output = Path("/data.tar.bz2")
220+
with tarfile.open(output, "x:bz2") as tar:
221+
tar.add(home, arcname="data", filter=output_filter(group, group_seq))
222+
outdir = Path(outdir)
223+
if outdir.exists():
224+
assert outdir.is_dir()
225+
filename = outdir / f"{group}_{group_seq}.tar.bz2"
226+
filename.unlink(missing_ok=True)
227+
shutil.copy(output, filename)
184228

185229

186230
def output_filter(group, group_seq: int):
@@ -321,9 +365,5 @@ def dump_block_stats(fp):
321365
print("block", i, txs, timestamp, file=fp)
322366

323367

324-
def main():
325-
fire.Fire(CLI)
326-
327-
328368
if __name__ == "__main__":
329-
main()
369+
cli()

0 commit comments

Comments
 (0)