From 75b57171333fae458feb2fbb613a11516c7310c5 Mon Sep 17 00:00:00 2001 From: Antoine Lanco Date: Wed, 18 Dec 2024 16:10:44 +0100 Subject: [PATCH 1/6] Perf/Scenarios: Add locust file and lib --- src/bin_testnet_scenarios/locust.ml | 153 ++++++++++++++++++ src/bin_testnet_scenarios/locust/RPC.md | 42 +++++ .../locust/locustfile.py | 94 +++++++++++ 3 files changed, 289 insertions(+) create mode 100644 src/bin_testnet_scenarios/locust.ml create mode 100644 src/bin_testnet_scenarios/locust/RPC.md create mode 100644 src/bin_testnet_scenarios/locust/locustfile.py diff --git a/src/bin_testnet_scenarios/locust.ml b/src/bin_testnet_scenarios/locust.ml new file mode 100644 index 000000000000..3d004943248e --- /dev/null +++ b/src/bin_testnet_scenarios/locust.ml @@ -0,0 +1,153 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Functori *) +(* *) +(*****************************************************************************) + +type rpc = + | Eth_getBalance + | Eth_accounts + | Eth_blockNumber + | Eth_getBlockByHash + | Eth_getBlockByNumber + | Eth_getBlockTransactionCountByHash + | Eth_getBlockTransactionCountByNumber + | Eth_getUncleByBlockHashAndIndex + | Eth_getUncleByBlockNumberAndIndex + | Eth_getUncleCountByBlockHash + | Eth_getUncleCountByBlockNumber + | Eth_getTransactionByHash + | Eth_getTransactionByBlockHashAndIndex + | Eth_getTransactionByBlockNumberAndIndex + | Eth_getTransactionCount + | Eth_getTransactionReceipt + | Eth_getCode + | Eth_getStorageAt + | Eth_gasPrice + | Eth_chainId + | Eth_getBlockReceipts + | Eth_maxPriorityFeePerGas + | Eth_coinbase + | Eth_estimateGas + | Tez_kernelVersion + | Tez_kernelRootHash + | Net_version + | Txpool_content + | Web3_clientVersion + | Web3_sha3 +(* TODO : Add last rpc https://gitlab.com/tezos/tezos/-/issues/7663 *) + +type config = { + account_address : string; + block_num : string; + block_hash : string; + tx_hash : string; + contract_address : string; + raw_tx : string; + rpc : rpc list; +} + +let rpc_to_string = function + | Eth_getBalance -> "eth_getBalance" + | Eth_accounts -> "eth_accounts" + | Eth_blockNumber -> "eth_blockNumber" + | Eth_getBlockByHash -> "eth_getBlockByHash" + | Eth_getBlockByNumber -> "eth_getBlockByNumber" + | Eth_getBlockTransactionCountByHash -> "eth_getBlockTransactionCountByHash" + | Eth_getBlockTransactionCountByNumber -> + "eth_getBlockTransactionCountByNumber" + | Eth_getUncleByBlockHashAndIndex -> "eth_getUncleByBlockHashAndIndex" + | Eth_getUncleByBlockNumberAndIndex -> "eth_getUncleByBlockNumberAndIndex" + | Eth_getUncleCountByBlockHash -> "eth_getUncleCountByBlockHash" + | Eth_getUncleCountByBlockNumber -> "eth_getUncleCountByBlockNumber" + | Eth_getTransactionByHash -> "eth_getTransactionByHash" + | Eth_getTransactionByBlockHashAndIndex -> + "eth_getTransactionByBlockHashAndIndex" + | Eth_getTransactionByBlockNumberAndIndex -> + "eth_getTransactionByBlockNumberAndIndex" + | Eth_getTransactionCount -> "eth_getTransactionCount" + | Eth_getTransactionReceipt -> "eth_getTransactionReceipt" + | Eth_getCode -> "eth_getCode" + | Eth_getStorageAt -> "eth_getStorageAt" + | Eth_gasPrice -> "eth_gasPrice" + | Eth_chainId -> "eth_chainId" + | Eth_getBlockReceipts -> "eth_getBlockReceipts" + | Eth_maxPriorityFeePerGas -> "eth_maxPriorityFeePerGas" + | Eth_coinbase -> "eth_coinbase" + | Eth_estimateGas -> "eth_estimateGas" + | Tez_kernelVersion -> "tez_kernelVersion" + | Tez_kernelRootHash -> "tez_kernelRootHash" + | Net_version -> "net_version" + | Txpool_content -> "txpool_content" + | Web3_clientVersion -> "web3_clientVersion" + | Web3_sha3 -> "web3_sha3" +(* TODO : Add last rpc https://gitlab.com/tezos/tezos/-/issues/7663 *) + +let path = "src/bin_testnet_scenarios/locust/" + +let run ?(output = "perf") ?(file = "locustfile.py") ?(spawn_rate = 1) + ?(users = 1) ?(time = "5s") endpoint = + let ptime = Client.Time.now () in + let string_time = Ptime.to_rfc3339 ptime in + Process.run + "locust" + [ + "--skip-log"; + "--autostart"; + "--autoquit"; + "1"; + "-t"; + time; + "--users"; + Int.to_string users; + "--spawn-rate"; + Int.to_string spawn_rate; + "-H"; + endpoint; + "-f"; + path // file; + "--csv"; + path // string_time // output; + ] + +let config_to_json c = + let open Ezjsonm in + to_string + @@ dict + [ + ("account_address", string c.account_address); + ("block_num", string c.block_num); + ("block_hash", string c.block_hash); + ("tx_hash", string c.tx_hash); + ("contract_address", string c.contract_address); + ("raw_tx", string c.raw_tx); + ("rpc_list", list (fun i -> `String (rpc_to_string i)) c.rpc); + ] + +let write_config_file config_locust = + let path = path // "config.json" in + let json = config_to_json config_locust in + Lwt_io.with_file ~mode:Output path @@ fun chan -> + Lwt_io.atomic + (fun chan -> + let* () = Lwt_io.write chan json in + Lwt_io.flush chan) + chan + +let read_csv file : string list list = + let split_comma line = String.split_on_char ',' line in + let ic = open_in file in + let rec read_lines acc = + match input_line ic with + | line -> read_lines (split_comma line :: acc) + | exception End_of_file -> + close_in ic ; + acc + in + read_lines [] + +let read_csv file_path = + let l = read_csv file_path in + let fist = List.hd l in + List.nth fist 9 diff --git a/src/bin_testnet_scenarios/locust/RPC.md b/src/bin_testnet_scenarios/locust/RPC.md new file mode 100644 index 000000000000..58df738d7041 --- /dev/null +++ b/src/bin_testnet_scenarios/locust/RPC.md @@ -0,0 +1,42 @@ +# RPC supported : + +- eth_accounts +- eth_blockNumber +- eth_call +- eth_chainId +- eth_coinbase +- eth_estimateGas +- eth_feeHistory +- eth_gasPrice +- eth_getBalance +- eth_getBlockByHash +- eth_getBlockByNumber +- eth_getBlockReceipts +- eth_getBlockTransactionCountByHash +- eth_getBlockTransactionCountByNumber +- eth_getCode +- eth_getProof +- eth_getStorageAt +- eth_getTransactionByBlockHashAndIndex +- eth_getTransactionByBlockNumberAndIndex +- eth_getTransactionByHash +- eth_getTransactionCount +- eth_getTransactionReceipt +- eth_getUncleByBlockHashAndIndex +- eth_getUncleByBlockNumberAndIndex +- eth_getUncleCountByBlockHash +- eth_getUncleCountByBlockNumber +- eth_maxPriorityFeePerGas +- net_version +- web3_clientVersion +- web3_sha3 + +# RPC not yet supported : +- debug_traceTransaction +- eth_call +- eth_sendRawTransaction (Locust can't handle the nonce when it floods transactions.) +- eth_getLogs + +TODO : Add last rpc https://gitlab.com/tezos/tezos/-/issues/7663 + +https://docs.etherlink.com/building-on-etherlink/endpoint-support/ diff --git a/src/bin_testnet_scenarios/locust/locustfile.py b/src/bin_testnet_scenarios/locust/locustfile.py new file mode 100644 index 000000000000..e27f7cf4a697 --- /dev/null +++ b/src/bin_testnet_scenarios/locust/locustfile.py @@ -0,0 +1,94 @@ +from locust import HttpUser, task +from json import JSONDecodeError +import json + +with open('src/bin_testnet_scenarios/locust/config.json', 'r') as file: + data = json.load(file) + +userAddr = data["account_address"] +blockNum = data["block_num"] +blockHash = data["block_hash"] +txHash = data["tx_hash"] +contractAddr = data["contract_address"] +rawTx = data["raw_tx"] +rpc_list = data["rpc_list"] + + +empty_method = ["eth_coinbase"] + +uncleIndex = "0x0" + +rpc_tuple = { + "eth_getBalance": [userAddr, "latest"], + "eth_getBlockByNumber": [blockNum, True], + "eth_getBlockTransactionCountByHash": [blockHash], + "eth_getBlockTransactionCountByNumber": [blockNum], + "eth_getUncleByBlockHashAndIndex": [blockHash, uncleIndex], + "eth_getUncleByBlockNumberAndIndex": [blockNum, uncleIndex], + "eth_getUncleCountByBlockNumber": [blockNum], + "eth_getTransactionByHash": [txHash], + "eth_getBlockByHash": [blockHash, True], + "eth_getUncleCountByBlockHash": [blockHash], + "eth_getTransactionByBlockHashAndIndex": [blockHash, "0x0"], + "eth_getTransactionByBlockNumberAndIndex": [blockNum, "0x0"], + "eth_getTransactionCount": [userAddr, "latest"], + "eth_getTransactionReceipt": [txHash], + "eth_getCode": [contractAddr, "latest"], + "eth_getStorageAt": [contractAddr, "0x0", "latest"], + "eth_getBlockReceipts": ["latest"], + "web3_sha3": ["0x5610a654"], + "eth_estimateGas": [{"from":userAddr,"to":userAddr,"value":"0x1"}], + # TODO : Add last rpc https://gitlab.com/tezos/tezos/-/issues/7663 +} + + +def map_f(x): + if x in rpc_tuple: + return (x,rpc_tuple[x]) + else: + return (x,[]) + +def get_all_rpc(): + l = [] + for k,v in rpc_tuple.items(): + l.append((k,v)) + return l + + +rpc_list = list(map(map_f, rpc_list)) if len(rpc_list)>0 else get_all_rpc() + + + +class RPC(HttpUser): + def make(self, method, params): + headers = {"Content-Type": "application/json"} + json = {"jsonrpc":"2.0","method":method,"id":1} if method in empty_method else {"jsonrpc":"2.0","method":method,"params":params,"id":1} + return self.client.post("/",name=method,headers=headers, json=json, catch_response=True) + + + def rpc_request(self, method, params=None): + """ + Generic function to call any JSON-RPC method. + """ + if params is None: + params = [] + + with self.make(method, params) as response: + try: + response_data = response.json() + if "result" not in response_data: + print(response_data) + response.failure(f"Response did not contain 'result' for method {method} : {response_data}") + else: + return response_data["result"] + except JSONDecodeError: + response.failure(f"Response for {method} could not be decoded as JSON") + except Exception as e: + response.failure(f"Unexpected error for {method}: {str(e)}") + return None + + @task + def rpc(self): + for (rpc,arg) in rpc_list: + self.rpc_request(rpc,arg) + -- GitLab From 4024760b5e381c2f527704308afd5ac45dbc305c Mon Sep 17 00:00:00 2001 From: Antoine Lanco Date: Wed, 18 Dec 2024 16:11:11 +0100 Subject: [PATCH 2/6] Perf/Scenarios: Add faucet function --- src/bin_testnet_scenarios/scenario_helpers.ml | 41 +++++++++++++++++++ .../scenario_helpers.mli | 4 ++ 2 files changed, 45 insertions(+) diff --git a/src/bin_testnet_scenarios/scenario_helpers.ml b/src/bin_testnet_scenarios/scenario_helpers.ml index 1004e4d1df16..cba291759166 100644 --- a/src/bin_testnet_scenarios/scenario_helpers.ml +++ b/src/bin_testnet_scenarios/scenario_helpers.ml @@ -97,3 +97,44 @@ let setup_octez_node ~(testnet : Testnet.t) ?runner ?metrics_port () = let* () = Client.bootstrapped client in Log.info "Node bootstrapped" ; return (client, node) + +type network = Ghostnet | Weeklynet | Other + +let string_contains haystack needle = + let len_h = String.length haystack in + let len_n = String.length needle in + if len_n = 0 then true + else + let rec aux i = + if i > len_h - len_n then false + else if String.sub haystack i len_n = needle then true + else aux (i + 1) + in + aux 0 + +let string_to_network network = + if string_contains network "weeklynet" || string_contains network "Weeklynet" + then Weeklynet + else if + string_contains network "ghostnet" || string_contains network "Ghostnet" + then Ghostnet + else Other + +let faucet ?(amount = 11_000) ~network_string address = + let network = string_to_network network_string in + let amount = string_of_int amount in + let npx_fun network = + Printf.printf "Faucet %s found" network ; + Process.run + "npx" + ["@tacoinfra/get-tez"; address; "--amount"; amount; "--network"; network] + in + let* () = + match network with + | Weeklynet -> npx_fun "weeklynet" + | Ghostnet -> npx_fun "ghostnet" + | Other -> + Printf.printf "No faucet found" ; + unit + in + unit diff --git a/src/bin_testnet_scenarios/scenario_helpers.mli b/src/bin_testnet_scenarios/scenario_helpers.mli index 5f41e26a06b0..3f4ff7f49f02 100644 --- a/src/bin_testnet_scenarios/scenario_helpers.mli +++ b/src/bin_testnet_scenarios/scenario_helpers.mli @@ -43,3 +43,7 @@ val setup_octez_node : ?metrics_port:int -> unit -> (Client.t * Node.t) Lwt.t + +(** [faucet ?amount ~network_string address] Automatically found the address + of the amount in tez. *) +val faucet : ?amount:int -> network_string:string -> string -> unit Lwt.t -- GitLab From 1bf4b5acf7b142e24dd2074275992491689e9bd3 Mon Sep 17 00:00:00 2001 From: Antoine Lanco Date: Wed, 8 Jan 2025 15:57:58 +0100 Subject: [PATCH 3/6] Etherlink: Add chain_id rpc --- etherlink/tezt/lib/rpc.ml | 6 ++++++ etherlink/tezt/lib/rpc.mli | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/etherlink/tezt/lib/rpc.ml b/etherlink/tezt/lib/rpc.ml index 836776978175..012fe3539fea 100644 --- a/etherlink/tezt/lib/rpc.ml +++ b/etherlink/tezt/lib/rpc.ml @@ -139,6 +139,8 @@ module Request = struct parameters = `A [`String address; block_param_to_json block]; } + let eth_getChainId = {method_ = "eth_chainId"; parameters = `A []} + let net_version = {method_ = "net_version"; parameters = `A []} let tez_kernelVersion = {method_ = "tez_kernelVersion"; parameters = `Null} @@ -273,6 +275,10 @@ let net_version ?websocket evm_node = return (decode_or_error (fun json -> JSON.(json |-> "result" |> as_string)) json) +let get_chain_id ?websocket evm_node = + let* json = Evm_node.jsonrpc ?websocket evm_node Request.eth_getChainId in + return (decode_or_error (fun json -> JSON.(json |-> "result" |> as_int)) json) + let get_transaction_by_hash ?websocket ~transaction_hash evm_node = let* json = Evm_node.jsonrpc diff --git a/etherlink/tezt/lib/rpc.mli b/etherlink/tezt/lib/rpc.mli index d7633267702d..bf93142d9789 100644 --- a/etherlink/tezt/lib/rpc.mli +++ b/etherlink/tezt/lib/rpc.mli @@ -49,6 +49,8 @@ module Request : sig val eth_getCode : address:string -> block:block_param -> Evm_node.request + val eth_getChainId : Evm_node.request + val net_version : Evm_node.request val eth_maxPriorityFeePerGas : Evm_node.request @@ -89,6 +91,10 @@ end val net_version : ?websocket:Websocket.t -> Evm_node.t -> (string, error) result Lwt.t +(** [get_chain_id evm_node] calls [eth_getChainId]. *) +val get_chain_id : + ?websocket:Websocket.t -> Evm_node.t -> (int, error) result Lwt.t + (** [get_transaction_by_hash ~transaction_hash evm_node] calls [eth_getTransactionByHash]. *) val get_transaction_by_hash : ?websocket:Websocket.t -> -- GitLab From 254661a9e0858b983c6a957da34e1f058d34a7eb Mon Sep 17 00:00:00 2001 From: Antoine Lanco Date: Wed, 18 Dec 2024 16:11:39 +0100 Subject: [PATCH 4/6] Perf/Scenarios: Add locust scenario --- src/bin_testnet_scenarios/evm_rollup.ml | 119 ++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 6 deletions(-) diff --git a/src/bin_testnet_scenarios/evm_rollup.ml b/src/bin_testnet_scenarios/evm_rollup.ml index 0a12b5f203ae..f7d82062c9fd 100644 --- a/src/bin_testnet_scenarios/evm_rollup.ml +++ b/src/bin_testnet_scenarios/evm_rollup.ml @@ -155,23 +155,130 @@ let stop_or_keep_going ~config ~node = unit else unit -let deploy_evm_rollup ~configuration_path ~(testnet : unit -> Testnet.t) () = +let deploy_evm_rollup ~configuration_path ~testnet = + Test.register ~__FILE__ ~title:"Deploy an EVM rollup" ~tags:["deploy"] + @@ fun () -> let config = get_config (JSON.parse_file configuration_path) in let testnet = testnet () in let* client, node = Scenario_helpers.setup_octez_node ~testnet () in let* operator = Client.gen_and_show_keys client in + let* () = + Scenario_helpers.faucet + ~network_string:testnet.network + operator.public_key_hash + in let* () = check_operator_balance ~node ~client ~mode:config.mode ~operator in let* _rollup_address, _rollup_node, _evm_node = setup_evm_infra ~config ~operator node client in stop_or_keep_going ~config ~node +let rps_perf ~configuration_path ~testnet = + Test.register ~__FILE__ ~title:"Performance RPS EVM" ~tags:["perf"; "rps"] + @@ fun () -> + let config = get_config (JSON.parse_file configuration_path) in + let testnet = testnet () in + let* client, node = Scenario_helpers.setup_octez_node ~testnet () in + + let* operator = Client.gen_and_show_keys client in + let* () = + Scenario_helpers.faucet + ~network_string:testnet.network + operator.public_key_hash + in + let* () = check_operator_balance ~node ~client ~mode:config.mode ~operator in + let* _rollup_address, _sc_rollup_node, evm_node = + setup_evm_infra ~config ~operator node client + in + + let endpoint = Evm_node.endpoint evm_node in + let* contract = Solidity_contracts.loop () in + let data = read_file contract.bin in + let* chain_id_result = Rpc.get_chain_id evm_node in + let chain_id = + match chain_id_result with + | Ok chain_id -> chain_id + | _ -> failwith "Rpc eth_chainId fail" + in + let nonce = 0 in + let gas_price = 1_000_000_000 in + let gas = 30_000_000 in + + let* raw_tx = + Cast.craft_deploy_tx + ~source_private_key:Eth_account.bootstrap_accounts.(0).private_key + ~chain_id + ~nonce + ~gas_price + ~gas + ~data + () + in + + let* raw_tx_to_send = + Cast.craft_deploy_tx + ~source_private_key:Eth_account.bootstrap_accounts.(1).private_key + ~chain_id + ~nonce + ~gas_price + ~gas + ~data + () + in + + let* transaction_hash_result = Rpc.send_raw_transaction ~raw_tx evm_node in + let tx_hash = + match transaction_hash_result with + | Ok tx_hash -> tx_hash + | Error _ -> failwith "Rpc eth_sendRawTransaction fail" + in + + let* receipt = + Helpers.wait_for_transaction_receipt ~evm_node ~transaction_hash:tx_hash () + in + + let contract_address = + match receipt.contractAddress with + | Some address -> address + | None -> failwith "Contract address needed" + in + + let* block_number = Rpc.block_number evm_node in + let block_number = + match block_number with + | Ok number -> number + | Error _ -> failwith "Rpc block_number fail" + in + + let block_id = Int32.to_string block_number in + + let account_address = Eth_account.bootstrap_accounts.(0).address in + + let* block = Eth_cli.get_block ~block_id ~endpoint in + let block_num = Int32.to_string block.number in + let block_hash = block.hash in + + let config_locust : Locust.config = + { + account_address; + block_num; + block_hash; + tx_hash; + contract_address; + raw_tx = raw_tx_to_send; + rpc = []; + } + in + + let* () = Locust.write_config_file config_locust in + + let* () = Locust.run ~spawn_rate:10000 ~users:100 endpoint ~time:"20s" in + + stop_or_keep_going ~config ~node + let register ~testnet = let configuration_path = Cli.get_string ~default:"evm_configuration.json" "evm_configuration" in - Test.register - ~__FILE__ - ~title:"Deploy an EVM rollup" - ~tags:["deploy"] - (deploy_evm_rollup ~configuration_path ~testnet) + deploy_evm_rollup ~configuration_path ~testnet ; + rps_perf ~configuration_path ~testnet -- GitLab From 9965508c867da2cea95074c024c326a6ad0a0a06 Mon Sep 17 00:00:00 2001 From: Antoine Lanco Date: Wed, 18 Dec 2024 16:13:48 +0100 Subject: [PATCH 5/6] Perf/Scenarios: Add locust readme --- src/bin_testnet_scenarios/locust/README.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/bin_testnet_scenarios/locust/README.md diff --git a/src/bin_testnet_scenarios/locust/README.md b/src/bin_testnet_scenarios/locust/README.md new file mode 100644 index 000000000000..d07d4e15bcbc --- /dev/null +++ b/src/bin_testnet_scenarios/locust/README.md @@ -0,0 +1,7 @@ +- Install Locust (V2.32.5) using pip install locust or sudo apt-get install python3-locust. +- Run the following command: + `dune exec src/bin_testnet_scenarios/main.exe -- --file evm_rollup.ml rps -v` +- A folder named with the timestamp (e.g., 2024-12-13T10:11:16-00:00) will be created in the Locust directory. This folder contains the results of the Tezt. +- You can tune the Locust setup in Tezt: + - Time to execution. + - Number of users sending RPC requests. \ No newline at end of file -- GitLab From e96e47997fd5b8d03e414b9b9baafe0883a299ad Mon Sep 17 00:00:00 2001 From: Antoine Lanco Date: Wed, 18 Dec 2024 16:53:07 +0100 Subject: [PATCH 6/6] Perf/Scenarios: Tezt return RPS --- src/bin_testnet_scenarios/evm_rollup.ml | 6 +++- src/bin_testnet_scenarios/locust.ml | 47 ++++++++++++++----------- 2 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/bin_testnet_scenarios/evm_rollup.ml b/src/bin_testnet_scenarios/evm_rollup.ml index f7d82062c9fd..7d7e350892e8 100644 --- a/src/bin_testnet_scenarios/evm_rollup.ml +++ b/src/bin_testnet_scenarios/evm_rollup.ml @@ -272,7 +272,11 @@ let rps_perf ~configuration_path ~testnet = let* () = Locust.write_config_file config_locust in - let* () = Locust.run ~spawn_rate:10000 ~users:100 endpoint ~time:"20s" in + let* output = Locust.run ~spawn_rate:10000 ~users:100 endpoint ~time:"20s" in + + let rps = Locust.read_csv output in + + Format.printf "=========== %s RPS ===========@." rps ; stop_or_keep_going ~config ~node diff --git a/src/bin_testnet_scenarios/locust.ml b/src/bin_testnet_scenarios/locust.ml index 3d004943248e..24c93ef79036 100644 --- a/src/bin_testnet_scenarios/locust.ml +++ b/src/bin_testnet_scenarios/locust.ml @@ -90,26 +90,30 @@ let run ?(output = "perf") ?(file = "locustfile.py") ?(spawn_rate = 1) ?(users = 1) ?(time = "5s") endpoint = let ptime = Client.Time.now () in let string_time = Ptime.to_rfc3339 ptime in - Process.run - "locust" - [ - "--skip-log"; - "--autostart"; - "--autoquit"; - "1"; - "-t"; - time; - "--users"; - Int.to_string users; - "--spawn-rate"; - Int.to_string spawn_rate; - "-H"; - endpoint; - "-f"; - path // file; - "--csv"; - path // string_time // output; - ] + let* () = + Process.run + "locust" + [ + "--skip-log"; + "--autostart"; + "--autoquit"; + "1"; + "-t"; + time; + "--users"; + Int.to_string users; + "--spawn-rate"; + Int.to_string spawn_rate; + "-H"; + endpoint; + "-f"; + path // file; + "--csv"; + path // string_time // output; + ] + in + + return (path // string_time // (output ^ "_stats.csv")) let config_to_json c = let open Ezjsonm in @@ -135,8 +139,9 @@ let write_config_file config_locust = Lwt_io.flush chan) chan +let split_comma line = String.split_on_char ',' line + let read_csv file : string list list = - let split_comma line = String.split_on_char ',' line in let ic = open_in file in let rec read_lines acc = match input_line ic with -- GitLab