-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Str-961: Introduce load testing env in fn-tests. (#639)
* fntests: introduce locust as a load testing tool. * Drafty structure for the load testing. * Disable lint for entry.py because of monkey-patching. * Minor adjustments and comments. * Refactor, prettify, lints, docstings.
- Loading branch information
1 parent
9bae5f8
commit a0d413d
Showing
11 changed files
with
1,401 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from dataclasses import dataclass | ||
|
||
import flexitest | ||
|
||
from load.job import StrataLoadJob | ||
|
||
|
||
@dataclass(frozen=True) | ||
class LoadConfig: | ||
""" | ||
Config for the load service. | ||
""" | ||
|
||
jobs: list[StrataLoadJob] | ||
"""A set of jobs that emit the load towards the host.""" | ||
|
||
host: str | ||
"""The host that will accept load requests.""" | ||
|
||
spawn_rate: int | ||
"""The rate at which all the jobs will emit the load.""" | ||
|
||
|
||
class LoadConfigBuilder: | ||
""" | ||
An abstract builder of the `LoadConfig`. | ||
""" | ||
|
||
jobs: list[StrataLoadJob] = [] | ||
"""A set of jobs that emit the load towards the host.""" | ||
|
||
spawn_rate: int = 10 | ||
"""The rate at which all the jobs will emit the load.""" | ||
|
||
service_name: str | None = None | ||
"""The name of the service to emit the load.""" | ||
|
||
def __init__(self): | ||
if not self.service_name: | ||
raise Exception("LoadConfigBuilder: missing service_name attribute.") | ||
|
||
def with_jobs(self, jobs: list[StrataLoadJob]): | ||
self.jobs.extend(jobs) | ||
return self | ||
|
||
def with_rate(self, rate: int): | ||
self.spawn_rate = rate | ||
return self | ||
|
||
def __call__(self, svcs) -> LoadConfig: | ||
if not self.jobs: | ||
raise Exception("LoadConfigBuilder: load jobs list is empty") | ||
|
||
host = self.host_url(svcs) | ||
# Patch jobs by the host. | ||
for job in self.jobs: | ||
job.host = host | ||
|
||
return LoadConfig(self.jobs, host, self.spawn_rate) | ||
|
||
def host_url(self, _svcs: dict[str, flexitest.Service]) -> str: | ||
raise NotImplementedError() | ||
|
||
@property | ||
def name(self): | ||
return self.service_name | ||
|
||
|
||
class RethLoadConfigBuilder(LoadConfigBuilder): | ||
service_name: str = "reth" | ||
spawn_rate: int = 20 | ||
|
||
def host_url(self, svcs: dict[str, flexitest.Service]) -> str: | ||
reth = svcs["reth"] | ||
web3_port = reth.get_prop("eth_rpc_http_port") | ||
return f"http://localhost:{web3_port}" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import web3 | ||
import web3.middleware | ||
from locust import HttpUser | ||
|
||
|
||
class StrataLoadJob(HttpUser): | ||
""" | ||
A common layer for all the load jobs in the load tests. | ||
""" | ||
|
||
pass | ||
|
||
|
||
# TODO(load): configure the structured logging as we do in the tests. | ||
class BaseRethLoadJob(StrataLoadJob): | ||
fund_amount: int = 1_000_000_000_000_000_000_000 # 1000 ETH | ||
|
||
def on_start(self): | ||
root_w3, genesis_acc = self.w3_with_genesis_acc() | ||
self._root_w3 = root_w3 | ||
self._genesis_acc = genesis_acc | ||
|
||
def w3_with_genesis_acc(self): | ||
""" | ||
Return w3 with prefunded "root" account as specified in the chain config. | ||
""" | ||
return self._init_w3( | ||
lambda w3: w3.eth.account.from_key( | ||
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" | ||
) | ||
) | ||
|
||
def w3_with_new_acc(self): | ||
""" | ||
Return w3 with a fresh account. | ||
Also, funds this account, so it's able to sign and send some txns. | ||
""" | ||
w3, new_acc = self._init_w3(lambda w3: w3.eth.account.create()) | ||
self._fund_account(new_acc.address) | ||
|
||
return w3, new_acc | ||
|
||
def _init_w3(self, init): | ||
# Reuse the http session by locust internals, so the stats are measured correctly. | ||
w3 = web3.Web3(web3.Web3.HTTPProvider(self.host, session=self.client)) | ||
# Init the account according to lambda | ||
account = init(w3) | ||
# Set the account onto web3 and init the signing middleware. | ||
w3.address = account.address | ||
w3.middleware_onion.add(web3.middleware.SignAndSendRawMiddlewareBuilder.build(account)) | ||
|
||
return w3, account | ||
|
||
def _fund_account(self, acc): | ||
print(f"FUNDING ACCOUNT {acc}") | ||
source = self._root_w3.address | ||
tx_hash = self._root_w3.eth.send_transaction( | ||
{"to": acc, "value": hex(self.fund_amount), "gas": hex(100000), "from": source} | ||
) | ||
|
||
tx_receipt = self._root_w3.eth.wait_for_transaction_receipt(tx_hash, timeout=120) | ||
print(f"FUNDING SUCCESS: {tx_receipt}") | ||
|
||
def _balance(self, acc): | ||
return self._root_w3.eth.get_balance(acc) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from .reth import * |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from locust import task | ||
|
||
from load.job import BaseRethLoadJob | ||
|
||
|
||
class EthJob(BaseRethLoadJob): | ||
def on_start(self): | ||
super().on_start() | ||
|
||
w3, new_acc = self.w3_with_new_acc() | ||
self.w3 = w3 | ||
self.new_acc = new_acc | ||
|
||
b = self._balance(new_acc.address) | ||
print(f"BALANCE AFTER START: {b}") | ||
|
||
@task | ||
def get_block(self): | ||
print("GET_BLOCK REQUEST") | ||
for i in range(1, 10): | ||
block = self.w3.eth.get_block(hex(i)) | ||
num = block["number"] | ||
txn_cnt = len(block["transactions"]) | ||
hash = block["hash"] | ||
print(f"BLOCK DATA \t\t\t\t\t {hash}, {num}, {txn_cnt}") | ||
|
||
@task | ||
def block_num(self): | ||
print("BLOCK_NUM REQUEST") | ||
# Pure json-rpc without web3 with middleware. | ||
method = "eth_blockNumber" | ||
params = [] | ||
payload = {"jsonrpc": "2.0", "method": method, "params": params, "id": 1} | ||
headers = {"Content-type": "application/json"} | ||
# response = session.post(self.host, json=payload, headers=headers) | ||
response = self.client.post("", json=payload, headers=headers) | ||
# print(f"raw json response: {response.json()}") | ||
print("BLOCK_NUMBER: {}".format(response.json()["result"])) | ||
|
||
@task(5) | ||
def send(self): | ||
print("TRANSFER TRANSACTION") | ||
|
||
source = self.w3.address | ||
dest = self.w3.to_checksum_address("0x0000000000000000000000000000000000000001") | ||
to_transfer = 1_000_000_000_000_000_000 | ||
try: | ||
tx_hash = self.w3.eth.send_transaction( | ||
{"to": dest, "value": hex(to_transfer), "gas": hex(100000), "from": source} | ||
) | ||
print(f"transfer transaction hash: {tx_hash}") | ||
except Exception as e: | ||
print(e) |
Oops, something went wrong.