12#include <boost/asio/high_resolution_timer.hpp>
13#include <boost/algorithm/clamp.hpp>
22#include <contracts.hpp>
42#define CALL(api_name, api_handle, call_name, INVOKE, http_response_code) \
43{std::string("/v1/" #api_name "/" #call_name), \
44 [this](string, string body, url_response_callback cb) mutable { \
46 if (body.empty()) body = "{}"; \
48 cb(http_response_code, fc::variant(result)); \
50 http_plugin::handle_exception(#api_name, #call_name, body, cb); \
54#define INVOKE_V_R_R_R(api_handle, call_name, in_param0, in_param1, in_param2) \
55 const auto& vs = fc::json::json::from_string(body).as<fc::variants>(); \
56 auto status = api_handle->call_name(vs.at(0).as<in_param0>(), vs.at(1).as<in_param1>(), vs.at(2).as<in_param2>()); \
57 sysio::detail::txn_test_gen_status result = { status };
59#define INVOKE_V_R_R(api_handle, call_name, in_param0, in_param1) \
60 const auto& vs = fc::json::json::from_string(body).as<fc::variants>(); \
61 api_handle->call_name(vs.at(0).as<in_param0>(), vs.at(1).as<in_param1>()); \
62 sysio::detail::txn_test_gen_empty result;
64#define INVOKE_V_V(api_handle, call_name) \
65 api_handle->call_name(); \
66 sysio::detail::txn_test_gen_empty result;
68#define CALL_ASYNC(api_name, api_handle, call_name, INVOKE, http_response_code) \
69{std::string("/v1/" #api_name "/" #call_name), \
70 [this](string, string body, url_response_callback cb) mutable { \
71 if (body.empty()) body = "{}"; \
73 auto times_called = std::make_shared<std::atomic<size_t>>(0);\
74 auto result_handler = [times_called{std::move(times_called)}, cb, body](const fc::exception_ptr& e) mutable {\
75 if( ++(*times_called) > 1 ) return;\
78 e->dynamic_rethrow_exception();\
80 http_plugin::handle_exception(#api_name, #call_name, body, cb);\
83 cb(http_response_code, fc::variant(sysio::detail::txn_test_gen_empty())); \
90#define INVOKE_ASYNC_R_R(api_handle, call_name, in_param0, in_param1) \
91 const auto& vs = fc::json::json::from_string(body).as<fc::variants>(); \
92 api_handle->call_name(vs.at(0).as<in_param0>(), vs.at(1).as<in_param1>(), result_handler);
101 std::shared_ptr<boost::asio::high_resolution_timer>
timer;
109 for (
size_t i = 0; i < trxs->size(); ++i) {
110 cp.
accept_transaction( std::make_shared<packed_transaction>(trxs->at(i)), [=](
const std::variant<fc::exception_ptr, transaction_trace_ptr>& result){
112 fc::exception_ptr except_ptr;
113 if (std::holds_alternative<fc::exception_ptr>(result)) {
114 except_ptr = std::get<fc::exception_ptr>(result);
115 }
else if (std::get<transaction_trace_ptr>(result)->except) {
116 except_ptr = std::get<transaction_trace_ptr>(result)->except->dynamic_copy_exception();
120 next(std::get<fc::exception_ptr>(result));
122 if (std::holds_alternative<transaction_trace_ptr>(result) && std::get<transaction_trace_ptr>(result)->receipt) {
123 _total_us += std::get<transaction_trace_ptr>(result)->receipt->cpu_usage_us;
132 auto trxs_copy = std::make_shared<std::decay_t<
decltype(trxs)>>(std::move(trxs));
134 push_next_transaction(trxs_copy, next);
139 ilog(
"create_test_accounts");
140 std::vector<signed_transaction> trxs;
144 name creator(init_name);
191 trx.
sign(creator_priv_key, chainid);
192 trxs.emplace_back(std::move(trx));
203 handler.
code.assign(wasm.begin(), wasm.end());
217 act.
name =
"create"_n;
219 act.
data = sysio_token_serializer.variant_to_binary(
"create",
228 act.
name =
"issue"_n;
230 act.
data = sysio_token_serializer.variant_to_binary(
"issue",
239 act.
name =
"transfer"_n;
241 act.
data = sysio_token_serializer.variant_to_binary(
"transfer",
250 act.
name =
"transfer"_n;
252 act.
data = sysio_token_serializer.variant_to_binary(
"transfer",
262 trx.
sign(txn_test_receiver_C_priv_key, chainid);
263 trxs.emplace_back(std::move(trx));
265 }
catch (
const std::bad_alloc& ) {
267 }
catch (
const boost::interprocess::bad_alloc& ) {
272 }
catch (
const std::exception& e) {
277 push_transactions(std::move(trxs), next);
281 ilog(
"Starting transaction test plugin");
283 return "start_generation already running";
284 if(period < 1 || period > 2500)
285 return "period must be between 1 and 2500";
286 if(batch_size < 1 || batch_size > 250)
287 return "batch_size must be between 1 and 250";
289 return "batch_size must be even";
290 ilog(
"Starting transaction test plugin valid");
297 act_a_to_b.account = newaccountT;
298 act_a_to_b.
name =
"transfer"_n;
300 act_a_to_b.data = sysio_token_serializer.variant_to_binary(
"transfer",
305 act_b_to_a.account = newaccountT;
306 act_b_to_a.
name =
"transfer"_n;
308 act_b_to_a.data = sysio_token_serializer.variant_to_binary(
"transfer",
313 timer_timeout = period;
314 batch = batch_size/2;
317 thread_pool.emplace(
"txntest", thread_pool_size );
318 timer = std::make_shared<boost::asio::high_resolution_timer>(thread_pool->get_executor());
320 ilog(
"Started transaction test plugin; generating ${p} transactions every ${m} ms by ${t} load generation threads",
321 (
"p", batch_size) (
"m", period) (
"t", thread_pool_size));
323 boost::asio::post( thread_pool->get_executor(), [
this]() {
324 arm_timer(boost::asio::high_resolution_timer::clock_type::now());
329 void arm_timer(boost::asio::high_resolution_timer::time_point
s) {
330 timer->expires_at(
s + std::chrono::milliseconds(timer_timeout));
331 boost::asio::post( thread_pool->get_executor(), [
this]() {
332 send_transaction([this](const fc::exception_ptr& e){
334 elog(
"pushing transaction failed: ${e}", (
"e", e->to_detail_string()));
340 timer->async_wait([
this](
const boost::system::error_code& ec) {
343 arm_timer(timer->expires_at());
348 std::vector<signed_transaction> trxs;
349 trxs.reserve(2*batch);
361 if (txn_reference_block_lag >= 0) {
363 if (reference_block_num <= (
uint32_t)txn_reference_block_lag) {
364 reference_block_num = 0;
366 reference_block_num -= (
uint32_t)txn_reference_block_lag;
372 for(
unsigned int i = 0; i < batch; ++i) {
375 trx.
actions.push_back(act_a_to_b);
380 trx.
sign(a_priv_key, chainid);
381 trxs.emplace_back(std::move(trx));
386 trx.
actions.push_back(act_b_to_a);
391 trx.
sign(b_priv_key, chainid);
392 trxs.emplace_back(std::move(trx));
395 }
catch (
const std::bad_alloc& ) {
397 }
catch (
const boost::interprocess::bad_alloc& ) {
401 }
catch (
const std::exception& e) {
405 push_transactions(std::move(trxs), next);
416 ilog(
"Stopping transaction generation test");
419 ilog(
"${d} transactions executed, ${t}us / transaction", (
"d", _txcount)(
"t", _total_us / (
double)_txcount));
420 _txcount = _total_us = 0;
436txn_test_gen_plugin::txn_test_gen_plugin() {}
437txn_test_gen_plugin::~txn_test_gen_plugin() {}
439void txn_test_gen_plugin::set_program_options(options_description&, options_description& cfg) {
441 (
"txn-reference-block-lag", bpo::value<int32_t>()->default_value(0),
"Lag in number of blocks from the head block when selecting the reference block for transactions (-1 means Last Irreversible Block)")
442 (
"txn-test-gen-threads", bpo::value<uint16_t>()->default_value(2),
"Number of worker threads in txn_test_gen thread pool")
443 (
"txn-test-gen-account-prefix", bpo::value<string>()->default_value(
"txn.test."),
"Prefix to use for accounts generated and used by this plugin")
447void txn_test_gen_plugin::plugin_initialize(
const variables_map& options) {
450 my->txn_reference_block_lag = options.at(
"txn-reference-block-lag" ).as<
int32_t>();
451 my->thread_pool_size = options.at(
"txn-test-gen-threads" ).as<
uint16_t>();
452 const std::string thread_pool_account_prefix = options.at(
"txn-test-gen-account-prefix" ).as<std::string>();
456 SYS_ASSERT( my->thread_pool_size > 0, chain::plugin_config_exception,
457 "txn-test-gen-threads ${num} must be greater than 0", (
"num", my->thread_pool_size) );
461void txn_test_gen_plugin::plugin_startup() {
463 CALL_ASYNC(txn_test_gen, my, create_test_accounts,
INVOKE_ASYNC_R_R(my, create_test_accounts, std::string, std::string), 200),
464 CALL(txn_test_gen, my, stop_generation,
INVOKE_V_V(my, stop_generation), 200),
469void txn_test_gen_plugin::plugin_shutdown() {
471 my->stop_generation();
473 catch(
const std::exception& e) {
#define SYS_ASSERT(expr, exc_type, FORMAT,...)
abstract_plugin & get_plugin(const string &name) const
auto post(int priority, Func &&func)
public_key get_public_key() const
static private_key regenerate(const typename KeyType::data_type &data)
Used to generate a useful error report when an exception is thrown.
virtual std::shared_ptr< exception > dynamic_copy_exception() const
static variant from_string(const string &utf8_str, const parse_type ptype=parse_type::legacy_parser, uint32_t max_depth=DEFAULT_MAX_RECURSION_DEPTH)
An order-preserving dictionary of variants.
static std_exception_wrapper from_current_exception(const std::exception &e)
constexpr uint32_t sec_since_epoch() const
uint32_t head_block_num() const
uint32_t last_irreversible_block_num() const
block_id_type get_block_id_for_num(uint32_t block_num) const
time_point head_block_time() const
void accept_transaction(const chain::packed_transaction_ptr &trx, chain::plugin_interface::next_function< chain::transaction_trace_ptr > next)
Defines exception's used by fc.
#define FC_LOG_AND_RETHROW()
void pack(Stream &s, const std::deque< T > &value)
std::shared_ptr< exception > exception_ptr
constexpr microseconds seconds(int64_t s)
@ invalid_operation_exception_code
fc::string format_string(const fc::string &, const variant_object &, bool minimize=false)
const fc::microseconds abi_serializer_max_time
#define FC_REFLECT(TYPE, MEMBERS)
Specializes fc::reflector for TYPE.
unsigned __int64 uint64_t
static yield_function_t create_yield_function(const fc::microseconds &max_serialization_time)
vector< permission_level > authorization
Immutable except for fc::from_variant.
name(std::string_view str)
std::string to_string() const
const signature_type & sign(const private_key_type &key, const chain_id_type &chain_id)
vector< action > context_free_actions
string start_generation(const std::string &salt, const uint64_t &period, const uint64_t &batch_size)
void arm_timer(boost::asio::high_resolution_timer::time_point s)
void send_transaction(std::function< void(const fc::exception_ptr &)> next, uint64_t nonce_prefix)
void push_transactions(std::vector< signed_transaction > &&trxs, const std::function< void(fc::exception_ptr)> &next)
std::shared_ptr< boost::asio::high_resolution_timer > timer
void push_next_transaction(const std::shared_ptr< std::vector< signed_transaction > > &trxs, const std::function< void(const fc::exception_ptr &)> &next)
std::optional< sysio::chain::named_thread_pool > thread_pool
uint16_t thread_pool_size
int32_t txn_reference_block_lag
void create_test_accounts(const std::string &init_name, const std::string &init_priv_key, const std::function< void(const fc::exception_ptr &)> &next)
#define INVOKE_V_R_R_R(api_handle, call_name, in_param0, in_param1, in_param2)
#define CALL_ASYNC(api_name, api_handle, call_name, INVOKE, http_response_code)
#define INVOKE_V_V(api_handle, call_name)
#define INVOKE_ASYNC_R_R(api_handle, call_name, in_param0, in_param1)
#define CALL(api_name, api_handle, call_name, INVOKE, http_response_code)