diff --git a/libraries/chain/controller.cpp b/libraries/chain/controller.cpp index 4b269c337dc..4148d209294 100644 --- a/libraries/chain/controller.cpp +++ b/libraries/chain/controller.cpp @@ -322,7 +322,7 @@ struct controller_impl { cfg.reversible_cache_size, false, cfg.db_map_mode ), blog( cfg.blog ), fork_db( cfg.state_dir ), - wasmif( cfg.wasm_runtime, cfg.eosvmoc_tierup, db, cfg.state_dir, cfg.eosvmoc_config ), + wasmif( cfg.wasm_runtime, cfg.eosvmoc_tierup, db, cfg.state_dir, cfg.eosvmoc_config, !cfg.profile_accounts.empty() ), resource_limits( db, [&s]() { return s.get_deep_mind_logger(); }), authorization( s, db ), protocol_features( std::move(pfs), [&s]() { return s.get_deep_mind_logger(); } ), @@ -3190,6 +3190,10 @@ bool controller::contracts_console()const { return my->conf.contracts_console; } +bool controller::is_profiling(account_name account) const { + return my->conf.profile_accounts.find(account) != my->conf.profile_accounts.end(); +} + chain_id_type controller::get_chain_id()const { return my->chain_id; } diff --git a/libraries/chain/include/eosio/chain/controller.hpp b/libraries/chain/include/eosio/chain/controller.hpp index 7766f17effe..e3f8cbdd108 100644 --- a/libraries/chain/include/eosio/chain/controller.hpp +++ b/libraries/chain/include/eosio/chain/controller.hpp @@ -99,6 +99,8 @@ namespace eosio { namespace chain { flat_set resource_greylist; flat_set trusted_producers; uint32_t greylist_limit = chain::config::maximum_elastic_resource_multiplier; + + flat_set profile_accounts; }; enum class block_status { @@ -288,6 +290,8 @@ namespace eosio { namespace chain { bool contracts_console()const; + bool is_profiling(account_name name) const; + chain_id_type get_chain_id()const; db_read_mode get_read_mode()const; diff --git a/libraries/chain/include/eosio/chain/wasm_interface.hpp b/libraries/chain/include/eosio/chain/wasm_interface.hpp index 8b08a4edb58..48b6c365dfb 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface.hpp @@ -44,7 +44,7 @@ namespace eosio { namespace chain { } } - wasm_interface(vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const boost::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config); + wasm_interface(vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const boost::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile); ~wasm_interface(); //call before dtor to skip what can be minutes of dtor overhead with some runtimes; can cause leaks diff --git a/libraries/chain/include/eosio/chain/wasm_interface_private.hpp b/libraries/chain/include/eosio/chain/wasm_interface_private.hpp index 1330ca40cc2..dfffce17f1f 100644 --- a/libraries/chain/include/eosio/chain/wasm_interface_private.hpp +++ b/libraries/chain/include/eosio/chain/wasm_interface_private.hpp @@ -58,13 +58,17 @@ namespace eosio { namespace chain { }; #endif - wasm_interface_impl(wasm_interface::vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const boost::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config) : db(d), wasm_runtime_time(vm) { + wasm_interface_impl(wasm_interface::vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const boost::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile) : db(d), wasm_runtime_time(vm) { #ifdef EOSIO_EOS_VM_RUNTIME_ENABLED if(vm == wasm_interface::vm_type::eos_vm) runtime_interface = std::make_unique>(); #endif #ifdef EOSIO_EOS_VM_JIT_RUNTIME_ENABLED - if(vm == wasm_interface::vm_type::eos_vm_jit) + if(vm == wasm_interface::vm_type::eos_vm_jit && profile) { + eosio::vm::set_profile_interval_us(200); + runtime_interface = std::make_unique(); + } + if(vm == wasm_interface::vm_type::eos_vm_jit && !profile) runtime_interface = std::make_unique>(); #endif #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED @@ -86,7 +90,7 @@ namespace eosio { namespace chain { if(is_shutting_down) for(wasm_cache_index::iterator it = wasm_instantiation_cache.begin(); it != wasm_instantiation_cache.end(); ++it) wasm_instantiation_cache.modify(it, [](wasm_cache_entry& e) { - e.module.release(); + e.module.release()->fast_shutdown(); }); } diff --git a/libraries/chain/include/eosio/chain/webassembly/eos-vm.hpp b/libraries/chain/include/eosio/chain/webassembly/eos-vm.hpp index 6f4bf2ceccd..03af69ba096 100644 --- a/libraries/chain/include/eosio/chain/webassembly/eos-vm.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/eos-vm.hpp @@ -10,6 +10,7 @@ //eos-vm includes #include +#include namespace eosio { namespace chain { namespace webassembly { namespace eos_vm_runtime { @@ -18,7 +19,7 @@ struct apply_options; }} template -using eos_vm_backend_t = eosio::vm::backend; +using eos_vm_backend_t = eosio::vm::backend; template using eos_vm_null_backend_t = eosio::vm::backend; @@ -34,6 +35,10 @@ void validate(const bytes& code, const wasm_config& cfg, const whitelisted_intri struct apply_options; +struct profile_config { + boost::container::flat_set accounts_to_profile; +}; + template class eos_vm_runtime : public eosio::chain::wasm_runtime_interface { public: @@ -54,4 +59,14 @@ class eos_vm_runtime : public eosio::chain::wasm_runtime_interface { friend class eos_vm_instantiated_module; }; +class eos_vm_profile_runtime : public eosio::chain::wasm_runtime_interface { + public: + eos_vm_profile_runtime(); + bool inject_module(IR::Module&) override; + std::unique_ptr instantiate_module(const char* code_bytes, size_t code_size, std::vector, + const digest_type& code_hash, const uint8_t& vm_type, const uint8_t& vm_version) override; + + void immediately_exit_currently_running_module() override; +}; + }}}}// eosio::chain::webassembly::eos_vm_runtime diff --git a/libraries/chain/include/eosio/chain/webassembly/runtime_interface.hpp b/libraries/chain/include/eosio/chain/webassembly/runtime_interface.hpp index 882ea2169da..b75b53adf2c 100644 --- a/libraries/chain/include/eosio/chain/webassembly/runtime_interface.hpp +++ b/libraries/chain/include/eosio/chain/webassembly/runtime_interface.hpp @@ -13,6 +13,7 @@ class apply_context; class wasm_instantiated_module_interface { public: virtual void apply(apply_context& context) = 0; + virtual void fast_shutdown() {} virtual ~wasm_instantiated_module_interface(); }; diff --git a/libraries/chain/wasm_interface.cpp b/libraries/chain/wasm_interface.cpp index 131cec695fd..ca6fca2e764 100644 --- a/libraries/chain/wasm_interface.cpp +++ b/libraries/chain/wasm_interface.cpp @@ -33,8 +33,8 @@ namespace eosio { namespace chain { - wasm_interface::wasm_interface(vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const boost::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config) - : my( new wasm_interface_impl(vm, eosvmoc_tierup, d, data_dir, eosvmoc_config) ) {} + wasm_interface::wasm_interface(vm_type vm, bool eosvmoc_tierup, const chainbase::database& d, const boost::filesystem::path data_dir, const eosvmoc::config& eosvmoc_config, bool profile) + : my( new wasm_interface_impl(vm, eosvmoc_tierup, d, data_dir, eosvmoc_config, profile) ) {} wasm_interface::~wasm_interface() {} diff --git a/libraries/chain/webassembly/runtimes/eos-vm.cpp b/libraries/chain/webassembly/runtimes/eos-vm.cpp index b6502ea1a1a..fe378bd0318 100644 --- a/libraries/chain/webassembly/runtimes/eos-vm.cpp +++ b/libraries/chain/webassembly/runtimes/eos-vm.cpp @@ -156,6 +156,72 @@ class eos_vm_instantiated_module : public wasm_instantiated_module_interface { std::unique_ptr _instantiated_module; }; +class eos_vm_profiling_module : public wasm_instantiated_module_interface { + using backend_t = eosio::vm::backend; + public: + eos_vm_profiling_module(std::unique_ptr mod, const char * code, std::size_t code_size) : + _instantiated_module(std::move(mod)), + _original_code(code, code + code_size) {} + + + void apply(apply_context& context) override { + _instantiated_module->set_wasm_allocator(&context.control.get_wasm_allocator()); + apply_options opts; + if(context.control.is_builtin_activated(builtin_protocol_feature_t::configurable_wasm_limits)) { + const wasm_config& config = context.control.get_global_properties().wasm_configuration; + opts = {config.max_pages, config.max_call_depth}; + } + auto fn = [&]() { + eosio::chain::webassembly::interface iface(context); + _instantiated_module->initialize(&iface, opts); + _instantiated_module->call( + iface, "env", "apply", + context.get_receiver().to_uint64_t(), + context.get_action().account.to_uint64_t(), + context.get_action().name.to_uint64_t()); + }; + profile_data* prof = start(context); + try { + scoped_profile profile_runner(prof); + checktime_watchdog wd(context.trx_context.transaction_timer); + _instantiated_module->timed_run(wd, fn); + } catch(eosio::vm::timeout_exception&) { + context.trx_context.checktime(); + } catch(eosio::vm::wasm_memory_exception& e) { + FC_THROW_EXCEPTION(wasm_execution_error, "access violation"); + } catch(eosio::vm::exception& e) { + FC_THROW_EXCEPTION(wasm_execution_error, "eos-vm system failure"); + } + } + + void fast_shutdown() override { + _prof.clear(); + } + + profile_data* start(apply_context& context) { + name account = context.get_receiver(); + if(!context.control.is_profiling(account)) return nullptr; + if(auto it = _prof.find(account); it != _prof.end()) { + return it->second.get(); + } else { + auto code_sequence = context.control.db().get(account).code_sequence; + std::string basename = account.to_string() + "." + std::to_string(code_sequence); + auto prof = std::make_unique(basename + ".profile", *_instantiated_module); + auto [pos,_] = _prof.insert(std::pair{ account, std::move(prof)}); + std::ofstream outfile(basename + ".wasm"); + outfile.write(_original_code.data(), _original_code.size()); + return pos->second.get(); + } + return nullptr; + } + + private: + + std::unique_ptr _instantiated_module; + boost::container::flat_map> _prof; + std::vector _original_code; +}; + template eos_vm_runtime::eos_vm_runtime() {} @@ -189,4 +255,30 @@ std::unique_ptr eos_vm_runtime::instan template class eos_vm_runtime; template class eos_vm_runtime; +eos_vm_profile_runtime::eos_vm_profile_runtime() {} + +void eos_vm_profile_runtime::immediately_exit_currently_running_module() { + throw wasm_exit{}; +} + +bool eos_vm_profile_runtime::inject_module(IR::Module& module) { + return false; +} + +std::unique_ptr eos_vm_profile_runtime::instantiate_module(const char* code_bytes, size_t code_size, std::vector, + const digest_type&, const uint8_t&, const uint8_t&) { + + using backend_t = eosio::vm::backend; + try { + wasm_code_ptr code((uint8_t*)code_bytes, code_size); + apply_options options = { .max_pages = 65536, + .max_call_depth = 0 }; + std::unique_ptr bkend = std::make_unique(code, code_size, nullptr, options); + eos_vm_host_functions_t::resolve(bkend->get_module()); + return std::make_unique(std::move(bkend), code_bytes, code_size); + } catch(eosio::vm::exception& e) { + FC_THROW_EXCEPTION(wasm_execution_error, "Error building eos-vm interp: ${e}", ("e", e.what())); + } +} + }}}} diff --git a/libraries/eos-vm b/libraries/eos-vm index 883e9712404..530e758c580 160000 --- a/libraries/eos-vm +++ b/libraries/eos-vm @@ -1 +1 @@ -Subproject commit 883e9712404970cb21c164ad2d54a7ba6283d4de +Subproject commit 530e758c580ce6f0d842ab63f262539ab257edcc diff --git a/libraries/rodeos/embedded_rodeos.cpp b/libraries/rodeos/embedded_rodeos.cpp index e8f764f5ad8..8fa3d295bcd 100644 --- a/libraries/rodeos/embedded_rodeos.cpp +++ b/libraries/rodeos/embedded_rodeos.cpp @@ -193,7 +193,7 @@ extern "C" rodeos_bool rodeos_write_deltas(rodeos_error* error, rodeos_db_snapsh extern "C" rodeos_filter* rodeos_create_filter(rodeos_error* error, uint64_t name, const char* wasm_filename) { return handle_exceptions(error, nullptr, [&]() -> rodeos_filter* { // - return std::make_unique(eosio::name{ name }, wasm_filename).release(); + return std::make_unique(eosio::name{ name }, wasm_filename, false).release(); }); } diff --git a/libraries/rodeos/include/b1/rodeos/filter.hpp b/libraries/rodeos/include/b1/rodeos/filter.hpp index 4254f472342..d7eac2cb078 100644 --- a/libraries/rodeos/include/b1/rodeos/filter.hpp +++ b/libraries/rodeos/include/b1/rodeos/filter.hpp @@ -21,7 +21,7 @@ namespace b1::rodeos::filter { struct callbacks; using rhf_t = registered_host_functions; -using backend_t = eosio::vm::backend; +using backend_t = eosio::vm::backend; #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED struct eosvmoc_tier { diff --git a/libraries/rodeos/include/b1/rodeos/rodeos.hpp b/libraries/rodeos/include/b1/rodeos/rodeos.hpp index 387bffa66b2..611717c8b23 100644 --- a/libraries/rodeos/include/b1/rodeos/rodeos.hpp +++ b/libraries/rodeos/include/b1/rodeos/rodeos.hpp @@ -3,6 +3,7 @@ #include #include #include +#include #include namespace b1::rodeos { @@ -74,8 +75,9 @@ struct rodeos_filter { eosio::name name = {}; std::unique_ptr backend = {}; std::unique_ptr filter_state = {}; + std::unique_ptr prof = {}; - rodeos_filter(eosio::name name, const std::string& wasm_filename + rodeos_filter(eosio::name name, const std::string& wasm_filename, bool profile #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED , const boost::filesystem::path& eosvmoc_path = "", diff --git a/libraries/rodeos/rodeos.cpp b/libraries/rodeos/rodeos.cpp index dcb81b623f2..f8ed0d22eba 100644 --- a/libraries/rodeos/rodeos.cpp +++ b/libraries/rodeos/rodeos.cpp @@ -261,7 +261,7 @@ void rodeos_db_snapshot::write_deltas(const ship_protocol::get_blocks_result_v1& std::once_flag registered_filter_callbacks; -rodeos_filter::rodeos_filter(eosio::name name, const std::string& wasm_filename +rodeos_filter::rodeos_filter(eosio::name name, const std::string& wasm_filename, bool profile #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED , const boost::filesystem::path& eosvmoc_path, @@ -286,6 +286,9 @@ rodeos_filter::rodeos_filter(eosio::name name, const std::string& wasm_filename backend = std::make_unique(code, nullptr); filter_state = std::make_unique(); filter::rhf_t::resolve(backend->get_module()); + if (profile) { + prof = std::make_unique(wasm_filename + ".profile", *backend); + } #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED if (eosvmoc_enable) { try { @@ -329,6 +332,7 @@ void rodeos_filter::process(rodeos_db_snapshot& snapshot, const ship_protocol::g backend->set_wasm_allocator(&filter_state->wa); backend->initialize(&cb); try { + eosio::vm::scoped_profile profile_runner(prof.get()); (*backend)(cb, "env", "apply", uint64_t(0), uint64_t(0), uint64_t(0)); if (!filter_state->console.empty()) diff --git a/plugins/chain_plugin/chain_plugin.cpp b/plugins/chain_plugin/chain_plugin.cpp index b39bc98a389..0694a0f9802 100644 --- a/plugins/chain_plugin/chain_plugin.cpp +++ b/plugins/chain_plugin/chain_plugin.cpp @@ -261,6 +261,8 @@ void chain_plugin::set_program_options(options_description& cli, options_descrip #endif })->default_value(eosio::chain::config::default_wasm_runtime, default_wasm_runtime_str), wasm_runtime_opt.c_str() ) + ("profile-account", boost::program_options::value>()->composing(), + "The name of an account whose code will be profiled") ("abi-serializer-max-time-ms", bpo::value()->default_value(config::default_abi_serializer_max_time_us / 1000), "Override default maximum ABI serialization time allowed in ms") ("chain-state-db-size-mb", bpo::value()->default_value(config::default_state_size / (1024 * 1024)), "Maximum size (in MiB) of the chain state database") @@ -750,6 +752,8 @@ void chain_plugin::plugin_initialize(const variables_map& options) { if( options.count( "wasm-runtime" )) my->wasm_runtime = options.at( "wasm-runtime" ).as(); + LOAD_VALUE_SET( options, "profile-account", my->chain_config->profile_accounts ); + if(options.count("abi-serializer-max-time-ms")) { my->abi_serializer_max_time_us = fc::microseconds(options.at("abi-serializer-max-time-ms").as() * 1000); my->chain_config->abi_serializer_max_time_us = my->abi_serializer_max_time_us; diff --git a/programs/rodeos/cloner_plugin.cpp b/programs/rodeos/cloner_plugin.cpp index 77a91a5fc5f..f3879053963 100644 --- a/programs/rodeos/cloner_plugin.cpp +++ b/programs/rodeos/cloner_plugin.cpp @@ -40,8 +40,9 @@ struct cloner_config : ship_client::connection_config { uint32_t skip_to = 0; uint32_t stop_before = 0; bool exit_on_filter_wasm_error = false; - eosio::name filter_name = {}; // todo: remove - std::string filter_wasm = {}; // todo: remove + eosio::name filter_name = {}; // todo: remove + std::string filter_wasm = {}; // todo: remove + bool profile = false; #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED eosio::chain::eosvmoc::config eosvmoc_config; @@ -85,7 +86,7 @@ struct cloner_session : ship_client::connection_callbacks, std::enable_shared_fr cloner_session(cloner_plugin_impl* my) : my(my), config(my->config) { // todo: remove if (!config->filter_wasm.empty()) - filter = std::make_unique(config->filter_name, config->filter_wasm + filter = std::make_unique(config->filter_name, config->filter_wasm, config->profile #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED , app().data_dir(), config->eosvmoc_config, config->eosvmoc_tierup @@ -242,6 +243,7 @@ void cloner_plugin::set_program_options(options_description& cli, options_descri // todo: remove op("filter-name", bpo::value(), "Filter name"); op("filter-wasm", bpo::value(), "Filter wasm"); + op("profile-filter", bpo::bool_switch(), "Enable filter profiling"); #ifdef EOSIO_EOS_VM_OC_RUNTIME_ENABLED op("eos-vm-oc-cache-size-mb", @@ -274,6 +276,7 @@ void cloner_plugin::plugin_initialize(const variables_map& options) { if (options.count("filter-name") && options.count("filter-wasm")) { my->config->filter_name = eosio::name{ options["filter-name"].as() }; my->config->filter_wasm = options["filter-wasm"].as(); + my->config->profile = options["profile-filter"].as(); } else if (options.count("filter-name") || options.count("filter-wasm")) { throw std::runtime_error("filter-name and filter-wasm must be used together"); } diff --git a/tests/rodeos_test.py b/tests/rodeos_test.py index 15e123c0630..8ece87ed853 100755 --- a/tests/rodeos_test.py +++ b/tests/rodeos_test.py @@ -126,7 +126,8 @@ def __enter__(self): def __exit__(self, exc_type, exc_value, traceback): if self.rodeos is not None: - self.rodeos.kill() + self.rodeos.send_signal(signal.SIGINT) + self.rodeos.wait() if self.rodeosStdout is not None: self.rodeosStdout.close() if self.rodeosStderr is not None: