diff --git a/Cargo.lock b/Cargo.lock index f5826e8306..e2877c3506 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -234,6 +234,7 @@ dependencies = [ "fdlimit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "finally-block 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.27 (registry+https://github.com/rust-lang/crates.io-index)", + "journaldb 0.1.0", "kvdb 0.1.0", "kvdb-rocksdb 0.1.0", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -241,7 +242,9 @@ dependencies = [ "panic_hook 0.1.0", "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.4.0 (git+https://github.com/CodeChain-io/rust-codechain-primitives.git)", + "rocksdb 0.4.5", "rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", @@ -412,12 +415,14 @@ name = "codechain-merkle" version = "0.1.0" dependencies = [ "codechain-crypto 0.1.0 (git+https://github.com/CodeChain-io/rust-codechain-crypto.git)", + "codechain-types 0.1.0", "elastic-array 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "hashdb 0.1.1", "memorydb 0.1.1", "primitives 0.4.0 (git+https://github.com/CodeChain-io/rust-codechain-primitives.git)", "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.1", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "trie-standardmap 0.1.0", ] @@ -576,6 +581,7 @@ dependencies = [ "primitives 0.4.0 (git+https://github.com/CodeChain-io/rust-codechain-primitives.git)", "rlp 0.2.1", "rlp_derive 0.1.0", + "rustc-hex 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.101 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.53 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1061,6 +1067,7 @@ dependencies = [ name = "hashdb" version = "0.1.1" dependencies = [ + "codechain-types 0.1.0", "elastic-array 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.4.0 (git+https://github.com/CodeChain-io/rust-codechain-primitives.git)", ] @@ -1202,6 +1209,7 @@ name = "journaldb" version = "0.1.0" dependencies = [ "codechain-crypto 0.1.0 (git+https://github.com/CodeChain-io/rust-codechain-crypto.git)", + "codechain-types 0.1.0", "hashdb 0.1.1", "kvdb 0.1.0", "kvdb-memorydb 0.1.0", @@ -1314,6 +1322,7 @@ dependencies = [ name = "kvdb" version = "0.1.0" dependencies = [ + "codechain-types 0.1.0", "elastic-array 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "error-chain 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", "primitives 0.4.0 (git+https://github.com/CodeChain-io/rust-codechain-primitives.git)", @@ -1332,6 +1341,7 @@ dependencies = [ name = "kvdb-rocksdb" version = "0.1.0" dependencies = [ + "codechain-types 0.1.0", "elastic-array 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", "interleaved-ordered 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "kvdb 0.1.0", @@ -1341,7 +1351,7 @@ dependencies = [ "primitives 0.4.0 (git+https://github.com/CodeChain-io/rust-codechain-primitives.git)", "regex 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "rlp 0.2.1", - "rocksdb 0.4.5 (git+https://github.com/paritytech/rust-rocksdb?rev=ecf06adf3148ab10f6f7686b724498382ff4f36e)", + "rocksdb 0.4.5", "tempdir 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -2230,22 +2240,20 @@ dependencies = [ [[package]] name = "rocksdb" version = "0.4.5" -source = "git+https://github.com/paritytech/rust-rocksdb?rev=ecf06adf3148ab10f6f7686b724498382ff4f36e#ecf06adf3148ab10f6f7686b724498382ff4f36e" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "local-encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rocksdb-sys 0.3.0 (git+https://github.com/paritytech/rust-rocksdb?rev=ecf06adf3148ab10f6f7686b724498382ff4f36e)", + "rocksdb-sys 0.3.0", ] [[package]] name = "rocksdb-sys" version = "0.3.0" -source = "git+https://github.com/paritytech/rust-rocksdb?rev=ecf06adf3148ab10f6f7686b724498382ff4f36e#ecf06adf3148ab10f6f7686b724498382ff4f36e" dependencies = [ "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "local-encoding 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "snappy-sys 0.1.0 (git+https://github.com/paritytech/rust-snappy)", + "snappy-sys 0.1.0 (git+https://github.com/paritytech/rust-snappy?rev=40ac9a0d9fd613e7f38df800a11a589b7296da73)", ] [[package]] @@ -2502,7 +2510,7 @@ dependencies = [ [[package]] name = "snappy-sys" version = "0.1.0" -source = "git+https://github.com/paritytech/rust-snappy#40ac9a0d9fd613e7f38df800a11a589b7296da73" +source = "git+https://github.com/paritytech/rust-snappy?rev=40ac9a0d9fd613e7f38df800a11a589b7296da73#40ac9a0d9fd613e7f38df800a11a589b7296da73" dependencies = [ "cc 1.0.29 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3402,8 +3410,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum remove_dir_all 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3488ba1b9a2084d38645c4c08276a1752dcbf2c7130d74f1569681ad5d2799c5" "checksum reqwest 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ab52e462d1e15891441aeefadff68bdea005174328ce3da0a314f2ad313ec837" "checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c" -"checksum rocksdb 0.4.5 (git+https://github.com/paritytech/rust-rocksdb?rev=ecf06adf3148ab10f6f7686b724498382ff4f36e)" = "" -"checksum rocksdb-sys 0.3.0 (git+https://github.com/paritytech/rust-rocksdb?rev=ecf06adf3148ab10f6f7686b724498382ff4f36e)" = "" "checksum rotor 0.6.3 (git+https://github.com/tailhook/rotor)" = "" "checksum rpassword 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d127299b02abda51634f14025aec43ae87a7aa7a95202b6a868ec852607d1451" "checksum rust-crypto 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" @@ -3436,7 +3442,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum smallvec 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f90c5e5fe535e48807ab94fc611d323935f39d4660c52b26b96446a7b33aef10" "checksum smallvec 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "211a489e65e94b103926d2054ae515a1cdb5d515ea0ef414fee23b7e043ce748" "checksum snap 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "174451758f7045084ae92070f18e5d8e5c53a716f4172a9c6b17ce03e7b82573" -"checksum snappy-sys 0.1.0 (git+https://github.com/paritytech/rust-snappy)" = "" +"checksum snappy-sys 0.1.0 (git+https://github.com/paritytech/rust-snappy?rev=40ac9a0d9fd613e7f38df800a11a589b7296da73)" = "" "checksum socket2 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "06dc9f86ee48652b7c80f3d254e3b9accb67a928c562c64d10d7b016d3d98dab" "checksum spin 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "44363f6f51401c34e7be73db0db371c04705d35efbe9f7d6082e03a921a32c55" "checksum spmc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cd1f11d1fb5fd41834e55ce0b85a186efbf2f2afd9fdb09e2c8d72f9bff1ad1a" diff --git a/Cargo.toml b/Cargo.toml index 0a5ba08425..4810eee846 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ codechain-crypto = { git = "https://github.com/CodeChain-io/rust-codechain-crypt codechain-discovery = { path = "discovery" } codechain-logger = { path = "util/logger" } codechain-key = { path = "key" } +journaldb = { path = "./util/journaldb" } codechain-keystore = { path = "keystore" } codechain-merkle = { path = "util/merkle" } codechain-network = { path = "network" } @@ -35,6 +36,7 @@ ctrlc = { git = "https://github.com/paritytech/rust-ctrlc.git" } fdlimit = "0.1" finally-block = "0.1" futures = "0.1" +rustc-hex = "1.0" kvdb = { path = "util/kvdb" } kvdb-rocksdb = { path = "util/kvdb-rocksdb" } log = "0.4.6" @@ -51,10 +53,14 @@ serde_json = "1.0" tokio-core = "0.1.17" toml = "0.4" cidr = "0.0.4" +rocksdb = { git = "https://github.com/paritytech/rust-rocksdb", rev = "ecf06adf3148ab10f6f7686b724498382ff4f36e" } [build-dependencies] vergen = "2" +[patch."https://github.com/paritytech/rust-rocksdb"] +rocksdb = { path = "./rust-rocksdb/" } + [[bin]] path = "codechain/main.rs" name = "codechain" @@ -74,4 +80,5 @@ members = [ "sync", "types", "vm", + "rust-rocksdb", ] diff --git a/codechain/codechain.yml b/codechain/codechain.yml index 43dba554be..4c1f96aef9 100644 --- a/codechain/codechain.yml +++ b/codechain/codechain.yml @@ -379,3 +379,72 @@ subcommands: - accountId - commit-hash: about: Print the commit hash at the build time. + - perf-write: + about: Check the write performance + args: + - db-dir: + short: d + long: db-dir + help: The db directory + takes_value: true + - number: + short: n + long: number + help: Set the number of db values + takes_value: true + - generate-data: + about: Generate large trie for test + args: + - number: + short: n + long: number + help: Set the number of db values + takes_value: true + - read-db: + about: Read a value from DB + args: + - db-dir: + short: d + long: db-dir + help: The db directory + takes_value: true + - key: + short: k + long: key + help: The key to find a value in DB + takes_value: true + - read-toplevel: + about: Read a value from Toplevel state + args: + - prepare: + short: p + long: prepare + help: if key is n, read 0..n keys before reading the key + takes_value: true + - db-dir: + short: d + long: db-dir + help: The db directory + takes_value: true + - key: + short: k + long: key + help: The key to find a value in DB + takes_value: true + - key-str: + long: key-str + help: The key to find a value in DB + takes_value: true + - execute-block: + about: Execute a block + args: + - db-dir: + short: d + long: db-dir + help: The db directory + takes_value: true + - block-num: + short: n + long: block-num + help: The number of a block + takes_value: true diff --git a/codechain/main.rs b/codechain/main.rs index 98a5f34ee9..2f00a897b3 100644 --- a/codechain/main.rs +++ b/codechain/main.rs @@ -14,6 +14,8 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +extern crate rocksdb; +extern crate rustc_hex; #[macro_use] extern crate clap; extern crate futures; @@ -35,6 +37,7 @@ extern crate codechain_keystore as ckeystore; #[macro_use] extern crate codechain_logger as clogger; extern crate cidr; +extern crate codechain_crypto as ccrypto; extern crate codechain_network as cnetwork; extern crate codechain_rpc as crpc; extern crate codechain_state as cstate; @@ -45,6 +48,7 @@ extern crate ctrlc; extern crate env_logger; extern crate fdlimit; extern crate finally_block; +extern crate journaldb; extern crate kvdb; extern crate kvdb_rocksdb; extern crate never_type; diff --git a/codechain/subcommand/execute_block_command.rs b/codechain/subcommand/execute_block_command.rs new file mode 100644 index 0000000000..2ca4d5742b --- /dev/null +++ b/codechain/subcommand/execute_block_command.rs @@ -0,0 +1,158 @@ +use ccore::{ + AccountProvider, BlockChainTrait, BlockId, ClientConfig, ClientService, ImportBlock, Miner, Scheme, TimeGapParams, +}; +use ckey::NetworkId; +use ckeystore::{accounts_dir::RootDiskDirectory, KeyStore}; +use clap::ArgMatches; +use clogger::{self, LoggerConfig}; +use cnetwork::{Filters, NetworkConfig, NetworkService, RoutingTable, SocketAddr}; +use config::ChainType; +use ctimer::TimerLoop; +use ctypes::DebugRead; +use kvdb::KeyValueDB; +use kvdb_rocksdb::{Database, DatabaseConfig}; +use parking_lot::Mutex; +use rocksdb::Options; +use std::{ + path::Path, + str::FromStr, + sync::Arc, + time::{Duration, Instant}, + u32, +}; + +pub const NUM_COLUMNS: Option = Some(6); + +pub fn run_execute_block_command(matches: &ArgMatches) -> Result<(), String> { + clogger::init(&LoggerConfig::new(777), None).expect("Logger must be successfully initialized"); + + let db_dir = matches.value_of("db-dir").unwrap(); + let block_num: u64 = matches.value_of("block-num").unwrap().parse().unwrap(); + // let key = matches.value_of("key").unwrap().to_string().from_hex().unwrap(); + + let timer_loop = TimerLoop::new(2); + + let time_gap_params = TimeGapParams { + allowed_past_gap: Duration::from_millis(3000), + allowed_future_gap: Duration::from_millis(3000), + }; + let scheme = ChainType::Corgi.scheme()?; + scheme.engine.register_time_gap_config_to_worker(time_gap_params); + let client_config: ClientConfig = Default::default(); + let (db, opts) = open_db(db_dir)?; + // let db = open_db(&config.operating, &client_config)?; + + let keys_path = "./keys".to_string(); + let ap = prepare_account_provider(&keys_path)?; + let miner = new_miner(&scheme, ap.clone(), Arc::clone(&db))?; + + println!("start client"); + + let client = client_start(&client_config, &timer_loop, db, &scheme, miner.clone())?; + + { + let routing_table = RoutingTable::new(); + let network_config = NetworkConfig { + address: "127.0.0.1".to_string(), + port: 3485, + bootstrap_addresses: Vec::new(), + min_peers: 1, + max_peers: 1, + whitelist: Vec::new(), + blacklist: Vec::new(), + }; + println!("network start"); + let service = + network_start(NetworkId::from_str("wc").unwrap(), timer_loop, &network_config, Arc::clone(&routing_table))?; + println!("register network extension to service"); + scheme.engine.register_network_extension_to_service(&service); + } + + println!("read block "); + let block = client.client().block(&BlockId::Number(block_num)).expect("Read block from DB"); + + println!("import block "); + let now = Instant::now(); + let debug_read = DebugRead::empty(); + + client.client().import_block_debug(block.into_inner()); + let elapsed = now.elapsed().as_micros(); + + println!("{}", opts.lock().print_statistics()); + println!("elapsed {}us", elapsed); + println!("{:?}", debug_read); + + Ok(()) +} + +pub fn open_db(db_dir: &str) -> Result<(Arc, Arc>), String> { + let base_path = ".".to_owned(); + let db_path = base_path + "/" + db_dir; + let client_path = Path::new(&db_path); + let mut db_config = DatabaseConfig::with_columns(NUM_COLUMNS); + + let client_config = ClientConfig::default(); + + db_config.memory_budget = client_config.db_cache_size; + db_config.compaction = client_config.db_compaction.compaction_profile(client_path); + db_config.wal = client_config.db_wal; + + let db = Arc::new( + Database::open(&db_config, &client_path.to_str().expect("DB path could not be converted to string.")) + .map_err(|_e| "Low level database error. Some issue with disk?".to_string())?, + ); + + let opts = db.opts.clone(); + Ok((db, opts)) +} + +fn new_miner(scheme: &Scheme, ap: Arc, db: Arc) -> Result, String> { + let miner = Miner::new(Default::default(), scheme, Some(ap), db); + Ok(miner) +} + +fn prepare_account_provider(keys_path: &str) -> Result, String> { + let keystore_dir = RootDiskDirectory::create(keys_path).map_err(|_| "Cannot read key path directory")?; + let keystore = KeyStore::open(Box::new(keystore_dir)).map_err(|_| "Cannot open key store")?; + Ok(AccountProvider::new(keystore)) +} + +fn client_start( + client_config: &ClientConfig, + timer_loop: &TimerLoop, + db: Arc, + scheme: &Scheme, + miner: Arc, +) -> Result { + cinfo!(CLIENT, "Starting client"); + let reseal_timer = timer_loop.new_timer_with_name("Client reseal timer"); + let service = ClientService::start(client_config, &scheme, db, miner, reseal_timer.clone()) + .map_err(|e| format!("Client service error: {}", e))?; + reseal_timer.set_handler(Arc::downgrade(&service.client())); + + Ok(service) +} + +fn network_start( + network_id: NetworkId, + timer_loop: TimerLoop, + cfg: &NetworkConfig, + routing_table: Arc, +) -> Result, String> { + let addr = cfg.address.parse().map_err(|_| format!("Invalid NETWORK listen host given: {}", cfg.address))?; + let sockaddress = SocketAddr::new(addr, cfg.port); + let filters = Filters::new(cfg.whitelist.clone(), cfg.blacklist.clone()); + let service = NetworkService::start( + network_id, + timer_loop, + sockaddress, + cfg.bootstrap_addresses.clone(), + cfg.min_peers, + cfg.max_peers, + filters, + routing_table, + ) + .map_err(|e| format!("Network service error: {:?}", e))?; + + Ok(service) +} diff --git a/codechain/subcommand/generate_data_command.rs b/codechain/subcommand/generate_data_command.rs new file mode 100644 index 0000000000..b6af02173e --- /dev/null +++ b/codechain/subcommand/generate_data_command.rs @@ -0,0 +1,77 @@ +use ccore::ClientConfig; +use ccrypto::BLAKE_NULL_RLP; +use ckey::Address; +use clap::ArgMatches; +use clogger::{self, LoggerConfig}; +use cstate::{StateDB, StateWithCache, TopLevelState, TopState}; +use journaldb; +use kvdb::{DBTransaction, KeyValueDB}; +use kvdb_rocksdb::{Database, DatabaseConfig}; +use std::{path::Path, sync::Arc}; + +pub const COL_EXTRA: Option = Some(3); +pub const COL_STATE: Option = Some(0); + +/// Generate large trie to test update speed +pub fn run_generate_data_command(matches: &ArgMatches) -> Result<(), String> { + clogger::init(&LoggerConfig::new(777), None).expect("Logger must be successfully initialized"); + + let num = matches.value_of("number").unwrap(); + let num: u32 = num.parse().unwrap(); + + let db = open_db(num + 3)?; + + let journal_db = journaldb::new(Arc::clone(&db), journaldb::Algorithm::Archive, COL_STATE); + let state_db = StateDB::new(journal_db); + let mut root = BLAKE_NULL_RLP; + + //for i in 0..1_000_000_000 { + for i in 0..10_u64.pow(num) { + let mut toplevel_state = TopLevelState::from_existing(state_db.clone(&root), root).unwrap(); + for j in 0..1000 { + let address = Address::random(); + toplevel_state.add_balance(&address, i * 1000 + j + 1).unwrap(); + } + root = toplevel_state.commit().unwrap(); + println!("write root {:?}", root); + { + let mut batch = DBTransaction::new(); + batch.put(COL_EXTRA, b"perf_data_root", root.as_ref()); + db.write(batch).unwrap(); + } + let mut batch = DBTransaction::new(); + let updated = toplevel_state.journal_under(&mut batch, 0).unwrap(); + println!("write to db"); + //let updated = state_db.journal_under(&mut batch, 0, root).map_err(|err| err.to_string())?; + db.write(batch).map_err(|err| err.to_string())?; + println!("flush the db"); + db.flush().unwrap(); + println!("updated {}", updated); + std::thread::sleep(std::time::Duration::from_millis(50)); + } + + Ok(()) +} + +pub const DEFAULT_DB_PATH: &str = "db_test"; +pub const NUM_COLUMNS: Option = Some(6); + +pub fn open_db(num: u32) -> Result, String> { + let base_path = ".".to_owned(); + let db_path = base_path + "/" + DEFAULT_DB_PATH + &format!("_{}", num); + let client_path = Path::new(&db_path); + let mut db_config = DatabaseConfig::with_columns(NUM_COLUMNS); + + let client_config = ClientConfig::default(); + + db_config.memory_budget = client_config.db_cache_size; + db_config.compaction = client_config.db_compaction.compaction_profile(client_path); + db_config.wal = client_config.db_wal; + + let db = Arc::new( + Database::open(&db_config, &client_path.to_str().expect("DB path could not be converted to string.")) + .map_err(|_e| "Low level database error. Some issue with disk?".to_string())?, + ); + + Ok(db) +} diff --git a/codechain/subcommand/mod.rs b/codechain/subcommand/mod.rs index 51843fbb1b..0577d047ea 100644 --- a/codechain/subcommand/mod.rs +++ b/codechain/subcommand/mod.rs @@ -16,17 +16,32 @@ mod account_command; mod convert_command; +mod execute_block_command; +mod generate_data_command; +mod perf_write_command; +mod read_db_command; +mod read_toplevel_command; use clap::ArgMatches; use self::account_command::run_account_command; use self::convert_command::run_convert_command; +use self::execute_block_command::run_execute_block_command; +use self::generate_data_command::run_generate_data_command; +use self::perf_write_command::run_perf_write_command; +use self::read_db_command::run_read_db_command; +use self::read_toplevel_command::run_read_toplevel_command; pub fn run_subcommand(matches: &ArgMatches) -> Result<(), String> { let subcommand = matches.subcommand.as_ref().unwrap(); match subcommand.name.as_str() { "account" => run_account_command(&subcommand.matches), "convert" => run_convert_command(&subcommand.matches), + "generate-data" => run_generate_data_command(&subcommand.matches), + "perf-write" => run_perf_write_command(&subcommand.matches), + "read-db" => run_read_db_command(&subcommand.matches), + "read-toplevel" => run_read_toplevel_command(&subcommand.matches), + "execute-block" => run_execute_block_command(&subcommand.matches), "commit-hash" => { println!("{}", env!("VERGEN_SHA")); Ok(()) diff --git a/codechain/subcommand/perf_write_command.rs b/codechain/subcommand/perf_write_command.rs new file mode 100644 index 0000000000..5de7b90a72 --- /dev/null +++ b/codechain/subcommand/perf_write_command.rs @@ -0,0 +1,143 @@ +use ccore::ClientConfig; +use ckey::Address; +use clap::ArgMatches; +use clogger::{self, LoggerConfig}; +use cstate::{StateDB, StateWithCache, TopLevelState, TopState}; +use ctypes::DebugInfo; +use kvdb::KeyValueDB; +use kvdb_rocksdb::{Database, DatabaseConfig}; +use parking_lot::Mutex; +use primitives::H256; +use rocksdb::Options; +use std::{path::Path, sync::Arc, time::Instant, u128, u32, u64}; + +pub const COL_STATE: Option = Some(0); +pub const COL_EXTRA: Option = Some(3); + +pub fn run_perf_write_command(matches: &ArgMatches) -> Result<(), String> { + clogger::init(&LoggerConfig::new(777), None).expect("Logger must be successfully initialized"); + + let num = matches.value_of("number").unwrap(); + let num: u32 = num.parse().unwrap(); + + let db_dir = matches.value_of("db-dir").unwrap(); + + println!("start"); + let (db, _) = open_db(db_dir)?; + println!("open db"); + let journal_db = journaldb::new(Arc::clone(&db), journaldb::Algorithm::Archive, COL_STATE); + println!("open journal db"); + let state_db = StateDB::new(journal_db); + println!("open state db"); + + println!("before getting root"); + + let root = match std::env::var("ROOT") { + Ok(val) => val.parse().unwrap(), + Err(_) => { + let bytes = db.get(COL_EXTRA, b"perf_data_root").unwrap().unwrap(); + H256::from(bytes.as_ref()) + } + }; + + let mut addresses = Vec::new(); + + let mut toplevel_state = TopLevelState::from_existing(state_db.clone(&root), root).unwrap(); + + println!("before loop"); + + let mut total_elapsed_micros = 0_u128; + // (index, elapsed, read count) + let mut max_elapsed = (0_u64, 0_u128, DebugInfo::empty()); + // (index, elapsed, read count) + let mut max_height = (0_u64, 0_u128, DebugInfo::empty()); + // (index, elapsed, read count) + let mut min_elapsed = (u64::MAX, u128::MAX, DebugInfo::empty()); + for i in 0..10_u64.pow(num) { + // let address = Address::random(); + let address = Address::from(i as u64); + addresses.push(address); + let now = Instant::now(); + let debug_info = toplevel_state.add_balance_debug(&address, i + 1).unwrap(); + let elapsed = now.elapsed().as_micros(); + total_elapsed_micros += elapsed; + let (_, max_elapsed_micros, _) = max_elapsed; + if elapsed > max_elapsed_micros { + max_elapsed = (i, elapsed, debug_info.clone()); + } + let (_, min_elapsed_micros, _) = min_elapsed; + if elapsed < min_elapsed_micros { + min_elapsed = (i, elapsed, debug_info.clone()); + } + let (_, _, max_height_) = &max_height; + if debug_info.read_count > max_height_.read_count { + max_height = (i, elapsed, debug_info.clone()); + } + println!("debug info {:?}", debug_info); + } + println!("Average {}us from DB", total_elapsed_micros / 10_u128.pow(num)); + println!("Max {}us from DB address: {} debug_info {:?}", max_elapsed.1, max_elapsed.0, max_elapsed.2); + println!("Min {}us from DB address: {} debug_info {:?}", min_elapsed.1, min_elapsed.0, min_elapsed.2); + println!("Max height {}us from DB address: {} debug_info {:?}", max_height.1, max_height.0, max_height.2); + + toplevel_state.commit().unwrap(); + // { + // let mut batch = DBTransaction::new(); + // let updated = toplevel_state.journal_under(&mut batch, 0).unwrap(); + // println!("write to db {}", updated); + // db.write(batch).map_err(|err| err.to_string())?; + // println!("flush the db"); + // db.flush().unwrap(); + // } + + // toplevel_state = TopLevelState::from_existing(state_db.clone(&root), root).unwrap(); + + // let mut total_elapsed_micros = 0_u128; + // let mut max_elapsed_micros = 0_u128; + // for i in 0..10_u64.pow(num) { + // // println!("loop"); + // let now = Instant::now(); + // let address = addresses[i as usize]; + // toplevel_state.add_balance(&address, i).unwrap(); + // let elapsed = now.elapsed().as_micros(); + // if elapsed >= 1 * 1000 * 1000 { + // println!("{}", elapsed); + // } + // total_elapsed_micros += now.elapsed().as_micros(); + // if elapsed > max_elapsed_micros { + // max_elapsed_micros = elapsed; + // } + // } + // println!("Average {}us from cache", total_elapsed_micros / 10_u128.pow(num)); + // println!("Max {}us from cache", max_elapsed_micros); + + // toplevel_state.commit().unwrap(); + + println!("Finished"); + + Ok(()) +} + +// pub const DEFAULT_DB_PATH: &str = "db_test"; +pub const NUM_COLUMNS: Option = Some(6); + +pub fn open_db(db_dir: &str) -> Result<(Arc, Arc>), String> { + let base_path = ".".to_owned(); + let db_path = base_path + "/" + db_dir; + let client_path = Path::new(&db_path); + let mut db_config = DatabaseConfig::with_columns(NUM_COLUMNS); + + let client_config = ClientConfig::default(); + + db_config.memory_budget = client_config.db_cache_size; + db_config.compaction = client_config.db_compaction.compaction_profile(client_path); + db_config.wal = client_config.db_wal; + + let db = Arc::new( + Database::open(&db_config, &client_path.to_str().expect("DB path could not be converted to string.")) + .map_err(|_e| "Low level database error. Some issue with disk?".to_string())?, + ); + + let opts = db.opts.clone(); + Ok((db, opts)) +} diff --git a/codechain/subcommand/read_db_command.rs b/codechain/subcommand/read_db_command.rs new file mode 100644 index 0000000000..8fbffb45fc --- /dev/null +++ b/codechain/subcommand/read_db_command.rs @@ -0,0 +1,58 @@ +use ccore::ClientConfig; +use clap::ArgMatches; +use clogger::{self, LoggerConfig}; +// use cstate::StateDB; +use ctypes::DebugRead; +use kvdb::KeyValueDB; +use kvdb_rocksdb::{Database, DatabaseConfig}; +use parking_lot::Mutex; +use rocksdb::Options; +use rustc_hex::{FromHex, ToHex}; +use std::{path::Path, sync::Arc, time::Instant, u32}; + +// pub const COL_EXTRA: Option = Some(3); +pub const COL_STATE: Option = Some(0); +pub const NUM_COLUMNS: Option = Some(6); + +pub fn run_read_db_command(matches: &ArgMatches) -> Result<(), String> { + clogger::init(&LoggerConfig::new(777), None).expect("Logger must be successfully initialized"); + + let db_dir = matches.value_of("db-dir").unwrap(); + let key = matches.value_of("key").unwrap().to_string().from_hex().unwrap(); + + let (db, opts) = open_db(db_dir)?; + // let journal_db = journaldb::new(Arc::clone(&db), journaldb::Algorithm::Archive, COL_STATE); + // let state_db = StateDB::new(journal_db); + + let now = Instant::now(); + let mut debug_read = DebugRead::empty(); + let value = db.get_debug(COL_STATE, &key, &mut debug_read).unwrap().unwrap(); + let elapsed = now.elapsed().as_micros(); + + println!("{}", opts.lock().print_statistics()); + println!("elapsed {}us value: {}", elapsed, value.to_hex()); + println!("{:?}", debug_read); + + Ok(()) +} + +pub fn open_db(db_dir: &str) -> Result<(Arc, Arc>), String> { + let base_path = ".".to_owned(); + let db_path = base_path + "/" + db_dir; + let client_path = Path::new(&db_path); + let mut db_config = DatabaseConfig::with_columns(NUM_COLUMNS); + + let client_config = ClientConfig::default(); + + db_config.memory_budget = client_config.db_cache_size; + db_config.compaction = client_config.db_compaction.compaction_profile(client_path); + db_config.wal = client_config.db_wal; + + let db = Arc::new( + Database::open(&db_config, &client_path.to_str().expect("DB path could not be converted to string.")) + .map_err(|_e| "Low level database error. Some issue with disk?".to_string())?, + ); + + let opts = db.opts.clone(); + Ok((db, opts)) +} diff --git a/codechain/subcommand/read_toplevel_command.rs b/codechain/subcommand/read_toplevel_command.rs new file mode 100644 index 0000000000..879a2c9e8e --- /dev/null +++ b/codechain/subcommand/read_toplevel_command.rs @@ -0,0 +1,88 @@ +use ccore::ClientConfig; +use clap::ArgMatches; +use clogger::{self, LoggerConfig}; +// use cstate::StateDB; +use ckey::Address; +use cstate::{StateDB, TopLevelState, TopState}; +use kvdb::KeyValueDB; +use kvdb_rocksdb::{Database, DatabaseConfig}; +use parking_lot::Mutex; +use primitives::H256; +use rocksdb::Options; +use std::{path::Path, sync::Arc, time::Instant, u32}; + +pub const COL_EXTRA: Option = Some(3); +pub const COL_STATE: Option = Some(0); +pub const NUM_COLUMNS: Option = Some(6); + +pub fn run_read_toplevel_command(matches: &ArgMatches) -> Result<(), String> { + clogger::init(&LoggerConfig::new(777), None).expect("Logger must be successfully initialized"); + + let db_dir = matches.value_of("db-dir").unwrap(); + let num_address = matches.value_of("key").map(|s| { + let num: u64 = s.parse().unwrap(); + Address::from(num) + }); + let address = num_address.unwrap_or_else(|| { + let str_key = matches.value_of("key-str").unwrap(); + str_key.parse().unwrap() + }); + let prepare: Option = matches.value_of("prepare").map(|s| s.parse().unwrap()); + + let (db, opts) = open_db(db_dir)?; + let journal_db = journaldb::new(Arc::clone(&db), journaldb::Algorithm::Archive, COL_STATE); + let state_db = StateDB::new(journal_db); + + let root = match std::env::var("ROOT") { + Ok(val) => val.parse().unwrap(), + Err(_) => { + let bytes = db.get(COL_EXTRA, b"perf_data_root").unwrap().unwrap(); + H256::from(bytes.as_ref()) + } + }; + + let mut toplevel_state = TopLevelState::from_existing(state_db.clone(&root), root).unwrap(); + + if let Some(prepare_max) = prepare { + for key_ in 0..prepare_max { + // let address = Address::random(); + let address = Address::from(key_); + let now = Instant::now(); + toplevel_state.add_balance_debug(&address, 7).unwrap(); + let elapsed = now.elapsed().as_micros(); + if elapsed > 1000 { + println!("elapsed {}us key: {}", elapsed, key_); + } + } + } + + let now = Instant::now(); + let debug_info = toplevel_state.add_balance_debug(&address, 7).unwrap(); + let elapsed = now.elapsed().as_micros(); + + println!("{}", opts.lock().print_statistics()); + println!("elapsed {}us debug info: {:?}", elapsed, debug_info); + + Ok(()) +} + +pub fn open_db(db_dir: &str) -> Result<(Arc, Arc>), String> { + let base_path = ".".to_owned(); + let db_path = base_path + "/" + db_dir; + let client_path = Path::new(&db_path); + let mut db_config = DatabaseConfig::with_columns(NUM_COLUMNS); + + let client_config = ClientConfig::default(); + + db_config.memory_budget = client_config.db_cache_size; + db_config.compaction = client_config.db_compaction.compaction_profile(client_path); + db_config.wal = client_config.db_wal; + + let db = Arc::new( + Database::open(&db_config, &client_path.to_str().expect("DB path could not be converted to string.")) + .map_err(|_e| "Low level database error. Some issue with disk?".to_string())?, + ); + + let opts = db.opts.clone(); + Ok((db, opts)) +} diff --git a/core/src/block.rs b/core/src/block.rs index 9498ef9337..45134fe9c8 100644 --- a/core/src/block.rs +++ b/core/src/block.rs @@ -133,7 +133,9 @@ impl<'x> OpenBlock<'x> { author: Address, extra_data: Bytes, ) -> Result { + println!("create state from existing"); let state = TopLevelState::from_existing(db, *parent.state_root()).map_err(StateError::from)?; + println!("create open block"); let mut r = OpenBlock { block: ExecutedBlock::new(state, parent), engine, @@ -143,7 +145,10 @@ impl<'x> OpenBlock<'x> { r.block.header.set_extra_data(extra_data); r.block.header.note_dirty(); + println!("engine.machine.populate_from_parent"); engine.machine().populate_from_parent(&mut r.block.header, parent); + println!("engine.populate_from_parent"); + // FIXME: comment the line below not to use tendermint in perf engine.populate_from_parent(&mut r.block.header, parent); Ok(r) @@ -498,11 +503,15 @@ pub fn enact( db: StateDB, parent: &Header, ) -> Result { + println!("open block"); let mut b = OpenBlock::try_new(engine, db, parent, Address::default(), vec![])?; + println!("populate block"); b.populate_from(header); + println!("push transactions"); b.push_transactions(transactions, client, parent.number(), parent.timestamp())?; + println!("common params"); let parent_common_params = client.common_params((*header.parent_hash()).into()).unwrap(); let term_common_params = { let block_number = client @@ -514,6 +523,7 @@ pub fn enact( Some(client.common_params((block_number).into()).expect("Common params should exist")) } }; + println!("close and lock"); b.close_and_lock(parent, &parent_common_params, term_common_params.as_ref()) } diff --git a/core/src/client/client.rs b/core/src/client/client.rs index add839f9fb..4331d3bc3f 100644 --- a/core/src/client/client.rs +++ b/core/src/client/client.rs @@ -654,6 +654,10 @@ impl ImportBlock for Client { Ok(self.importer.block_queue.import(unverified)?) } + fn import_block_debug(&self, bytes: Bytes) { + self.importer.import_block_debug(self, bytes) + } + fn import_header(&self, bytes: Bytes) -> Result { let unverified = ::encoded::Header::new(bytes).decode(); { diff --git a/core/src/client/importer.rs b/core/src/client/importer.rs index b9bf9479a5..a57d458681 100644 --- a/core/src/client/importer.rs +++ b/core/src/client/importer.rs @@ -36,8 +36,12 @@ use crate::service::ClientIoMessage; use crate::types::BlockId; use crate::verification::queue::{BlockQueue, HeaderQueue}; use crate::verification::{self, PreverifiedBlock, Verifier}; -use crate::views::{BlockView, HeaderView}; +use crate::{ + views::{BlockView, HeaderView}, + SignedTransaction, +}; use client::EngineInfo; +use primitives::Bytes; pub struct Importer { /// Lock used during block import @@ -86,6 +90,53 @@ impl Importer { }) } + pub fn import_block_debug(&self, client: &Client, bytes: Bytes) { + println!("import_block_debug"); + let mut transactions = Vec::new(); + { + let v = BlockView::new(&bytes); + for t in v.transactions() { + let signed = SignedTransaction::try_new(t).unwrap(); + transactions.push(signed); + } + } + + let header = BlockView::new(&bytes).header(); + let block = PreverifiedBlock { + header: header.clone(), + transactions, + bytes, + }; + + let chain = client.block_chain(); + + println!("read parent header"); + let parent = chain + .block_header(header.parent_hash()) + .ok_or_else(|| { + cwarn!( + CLIENT, + "Block import failed for #{} ({}): Parent not found ({}) ", + header.number(), + header.hash(), + header.parent_hash() + ); + }) + .unwrap(); + + println!("Read state root"); + + let db = client.state_db().read().clone(&parent.state_root()); + + let engine = &*self.engine; + println!("enact"); + let enact_result = enact(&block.header, &block.transactions, engine, client, db, &parent); + match enact_result { + Ok(_) => cwarn!(CLIENT, "block {} is executed", header.hash()), + Err(err) => cwarn!(CLIENT, "executing block {} has error {}", header.hash(), err), + } + } + /// This is triggered by a message coming from a block queue when the block is ready for insertion pub fn import_verified_blocks(&self, client: &Client) -> usize { let (imported_blocks, import_results, invalid_blocks, imported, duration, is_empty) = { diff --git a/core/src/client/mod.rs b/core/src/client/mod.rs index 6b565e903a..586c0e6f6b 100644 --- a/core/src/client/mod.rs +++ b/core/src/client/mod.rs @@ -196,6 +196,9 @@ pub trait Shard { pub trait ImportBlock { /// Import a block into the blockchain. fn import_block(&self, bytes: Bytes) -> Result; + fn import_block_debug(&self, bytes: Bytes) { + self.import_block(bytes).unwrap(); + } /// Import a header into the blockchain fn import_header(&self, bytes: Bytes) -> Result; diff --git a/core/src/codechain_machine.rs b/core/src/codechain_machine.rs index 9d10dcf0aa..445bfb60a6 100644 --- a/core/src/codechain_machine.rs +++ b/core/src/codechain_machine.rs @@ -228,6 +228,13 @@ impl CodeChainMachine { Ok(()) } + pub fn add_balance_debug(&self, live: &mut ExecutedBlock, address: &Address, amount: u64) -> Result<(), Error> { + let debug_info = live.state_mut().add_balance_debug(address, amount).map_err(StateError::from)?; + println!("add_balance {:?}", debug_info); + Ok(()) + } + + pub fn increase_term_id(&self, live: &mut ExecutedBlock, last_term_finished_block_num: u64) -> Result<(), Error> { live.state_mut().increase_term_id(last_term_finished_block_num)?; Ok(()) diff --git a/core/src/consensus/tendermint/engine.rs b/core/src/consensus/tendermint/engine.rs index 3261e087a5..f86119c6a9 100644 --- a/core/src/consensus/tendermint/engine.rs +++ b/core/src/consensus/tendermint/engine.rs @@ -155,14 +155,14 @@ impl ConsensusEngine for Tendermint { let mut distributor = stake::fee_distribute(total_min_fee, &stakes); for (address, share) in &mut distributor { - self.machine.add_balance(block, &address, share)? + self.machine.add_balance_debug(block, &address, share)? } let block_author_reward = total_reward - total_min_fee + distributor.remaining_fee(); let metadata = block.state().metadata()?.expect("Metadata must exist"); if metadata.current_term_id() == 0 { - self.machine.add_balance(block, &author, block_author_reward)?; + self.machine.add_balance_debug(block, &author, block_author_reward)?; if let Some(block_number) = block_number_if_term_changed(block.header(), parent_header, parent_common_params) @@ -231,7 +231,7 @@ impl ConsensusEngine for Tendermint { )?; for (address, reward) in pending_rewards { - self.machine.add_balance(block, &address, reward)?; + self.machine.add_balance_debug(block, &address, reward)?; } let validators = stake::Validators::load_from_state(block.state())? diff --git a/rust-rocksdb b/rust-rocksdb new file mode 160000 index 0000000000..beb64b7d98 --- /dev/null +++ b/rust-rocksdb @@ -0,0 +1 @@ +Subproject commit beb64b7d981d1e8f29f3b0d3a93e99b99fb6a600 diff --git a/state/src/cache/top_cache.rs b/state/src/cache/top_cache.rs index 91c442078b..210ca123f6 100644 --- a/state/src/cache/top_cache.rs +++ b/state/src/cache/top_cache.rs @@ -24,6 +24,7 @@ use super::WriteBack; use crate::{ Account, ActionData, Metadata, MetadataAddress, RegularAccount, RegularAccountAddress, Shard, ShardAddress, Text, }; +use ctypes::DebugInfo; pub struct TopCache { account: WriteBack, @@ -98,6 +99,10 @@ impl TopCache { self.account.get_mut(a, db) } + pub fn account_mut_debug(&self, a: &Address, db: &TrieDB) -> TrieResult<(RefMut, DebugInfo)> { + self.account.get_mut_debug(a, db) + } + pub fn remove_account(&self, address: &Address) { self.account.remove(address) } diff --git a/state/src/cache/write_back.rs b/state/src/cache/write_back.rs index f2323ec4d1..5c5b74519c 100644 --- a/state/src/cache/write_back.rs +++ b/state/src/cache/write_back.rs @@ -20,11 +20,12 @@ use std::collections::HashMap; use std::convert::AsRef; use std::fmt; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::vec::Vec; +use std::{time::Instant, vec::Vec}; use cmerkle::{self, Result as TrieResult, Trie, TrieDB, TrieMut}; use super::CacheableItem; +use ctypes::DebugInfo; static TOUCHED_COUNT: AtomicUsize = AtomicUsize::new(0); fn touched_count() -> usize { @@ -234,6 +235,43 @@ where })) } + /// Pull item `a` in our cache from the trie DB. + /// If it doesn't exist, make item equal the evaluation of `default`. + pub fn get_mut_debug(&self, a: &Item::Address, db: &TrieDB) -> cmerkle::Result<(RefMut, DebugInfo)> { + let contains_key = self.cache.borrow().contains_key(a); + let mut debug_info = DebugInfo::empty(); + if !contains_key { + let now = Instant::now(); + let (maybe_item, mut debug_info_) = db.get_with_debug(a.as_ref(), ::rlp::decode::)?; + let elapsed = now.elapsed().as_micros(); + debug_info_.db_read_time_us = elapsed; + self.insert(a, Entry::::new_clean(maybe_item)); + debug_info = debug_info_; + } else { + debug_info.read_from_cache = true; + } + self.note(a); + + // at this point the entry is guaranteed to be in the cache. + Ok(( + RefMut::map(self.cache.borrow_mut(), |c| { + let entry = c.get_mut(a).expect("entry known to exist in the cache; qed"); + + match &mut entry.item { + Some(_) => {} + slot @ None => *slot = Some(Item::default()), + } + + // set the dirty flag after changing data. + entry.is_dirty = true; + entry.touched = touched_count(); + entry.item.as_mut().expect("Required item must always exist; qed") + }), + debug_info, + )) + } + + pub fn create Item>(&self, a: &Item::Address, f: F) -> cmerkle::Result { if let Some(cached) = self.cache.borrow().get(a) { assert!(cached.item.is_none()); diff --git a/state/src/impls/top_level.rs b/state/src/impls/top_level.rs index b871efaa58..d61bb705e1 100644 --- a/state/src/impls/top_level.rs +++ b/state/src/impls/top_level.rs @@ -48,7 +48,7 @@ use ctypes::transaction::{ use ctypes::util::unexpected::Mismatch; #[cfg(test)] use ctypes::Tracker; -use ctypes::{BlockNumber, CommonParams, ShardId, TxHash}; +use ctypes::{BlockNumber, CommonParams, DebugInfo, ShardId, TxHash}; use cvm::ChainTimeInfo; use hashdb::AsHashDB; use kvdb::DBTransaction; @@ -639,6 +639,15 @@ impl TopLevelState { self.top_cache.account_mut(&a, &trie) } + fn get_account_mut_debug(&self, a: &Address) -> TrieResult<(RefMut, DebugInfo)> { + debug_assert_eq!(Ok(false), self.regular_account_exists_and_not_null_by_address(a)); + + let db = self.db.borrow(); + let trie = TrieFactory::readonly(db.as_hashdb(), &self.root)?; + self.top_cache.account_mut_debug(&a, &trie) + } + + fn get_regular_account_mut(&self, public: &Public) -> TrieResult> { let regular_account_address = RegularAccountAddress::new(public); let db = self.db.borrow(); @@ -813,6 +822,16 @@ impl TopState for TopLevelState { Ok(()) } + fn add_balance_debug(&mut self, a: &Address, incr: u64) -> TrieResult { + ctrace!(STATE, "add_balance({}, {}): {}", a, incr, self.balance(a)?); + if incr != 0 { + let (mut account, debug_info) = self.get_account_mut_debug(a)?; + account.add_balance(incr); + return Ok(debug_info) + } + Ok(DebugInfo::empty()) + } + fn sub_balance(&mut self, a: &Address, decr: u64) -> StateResult<()> { ctrace!(STATE, "sub_balance({}, {}): {}", a, decr, self.balance(a)?); if decr == 0 { diff --git a/state/src/traits.rs b/state/src/traits.rs index 40d94688f7..9b4cd195d6 100644 --- a/state/src/traits.rs +++ b/state/src/traits.rs @@ -17,7 +17,7 @@ use ckey::{public_to_address, Address, Public, Signature}; use cmerkle::Result as TrieResult; use ctypes::transaction::ShardTransaction; -use ctypes::{BlockNumber, CommonParams, ShardId, Tracker, TxHash}; +use ctypes::{BlockNumber, CommonParams, DebugInfo, ShardId, Tracker, TxHash}; use cvm::ChainTimeInfo; use primitives::{Bytes, H160, H256}; @@ -155,6 +155,8 @@ pub trait TopState { /// Add `incr` to the balance of account `a`. fn add_balance(&mut self, a: &Address, incr: u64) -> TrieResult<()>; + + fn add_balance_debug(&mut self, a: &Address, incr: u64) -> TrieResult; /// Subtract `decr` from the balance of account `a`. fn sub_balance(&mut self, a: &Address, decr: u64) -> StateResult<()>; /// Subtracts `by` from the balance of `from` and adds it to that of `to`. diff --git a/types/Cargo.toml b/types/Cargo.toml index 379558ca39..96fd06f8d1 100644 --- a/types/Cargo.toml +++ b/types/Cargo.toml @@ -12,6 +12,7 @@ rlp = { path = "../util/rlp" } rlp_derive = { path = "../util/rlp_derive" } serde = "1.0" serde_derive = "1.0" +rustc-hex = "1.0" [dev-dependencies] serde_json = "1.0" diff --git a/types/src/lib.rs b/types/src/lib.rs index 05e9fbf1c4..e87354c44a 100644 --- a/types/src/lib.rs +++ b/types/src/lib.rs @@ -24,6 +24,7 @@ extern crate rlp_derive; extern crate serde; #[macro_use] extern crate serde_derive; +extern crate rustc_hex; #[cfg(test)] extern crate serde_json; @@ -43,5 +44,63 @@ pub type ShardId = u16; pub use block_hash::BlockHash; pub use common_params::CommonParams; pub use header::Header; +use rustc_hex::ToHex; pub use tracker::Tracker; pub use tx_hash::TxHash; + +#[derive(Debug, Clone)] +pub struct DebugRead { + pub time_us: u128, + pub key: String, + pub archive_db_overlay_us: u128, + pub kvrocks_overlay_us: u128, + pub kvrocks_flushing_us: u128, + pub kvrocks_get_opt_us: u128, + pub kvrocks_get_cf_opt_us: u128, + pub kvrocks_get_cf_opt_us_vec: Vec, +} + +impl DebugRead { + pub fn empty() -> Self { + Self { + time_us: 0, + key: "".to_string(), + archive_db_overlay_us: 0, + kvrocks_overlay_us: 0, + kvrocks_flushing_us: 0, + kvrocks_get_opt_us: 0, + kvrocks_get_cf_opt_us: 0, + kvrocks_get_cf_opt_us_vec: Vec::new(), + } + } +} + +#[derive(Debug, Clone)] +pub struct DebugInfo { + pub read_count: u32, + pub db_read_time_us: u128, + pub reads: Vec, + pub read_from_cache: bool, +} + +impl DebugInfo { + pub fn empty() -> Self { + Self { + read_count: 0, + db_read_time_us: 0, + reads: Vec::new(), + read_from_cache: false, + } + } + + pub fn inc_read_count(&mut self) { + self.read_count += 1; + } + + // pub fn add_read(&mut self, time_us: u128, key: &[u8]) { + // self.reads.push(DebugRead { + // time_us, + // key: key.to_hex(), + // }) + // } +} diff --git a/util/hashdb/Cargo.toml b/util/hashdb/Cargo.toml index 0694acbc2e..3f638fed43 100644 --- a/util/hashdb/Cargo.toml +++ b/util/hashdb/Cargo.toml @@ -8,3 +8,4 @@ license = "GPL-3.0" [dependencies] elastic-array = "0.10" primitives = { git = "https://github.com/CodeChain-io/rust-codechain-primitives.git", version = "0.4" } +codechain-types = { path = "../../types" } diff --git a/util/hashdb/src/lib.rs b/util/hashdb/src/lib.rs index 3550d0619b..c0552249c1 100644 --- a/util/hashdb/src/lib.rs +++ b/util/hashdb/src/lib.rs @@ -15,9 +15,11 @@ // along with Parity. If not, see . //! Database of byte-slices keyed to their Keccak hash. +extern crate codechain_types as ctypes; extern crate elastic_array; extern crate primitives; +use ctypes::DebugRead; use elastic_array::ElasticArray128; use primitives::H256; use std::collections::HashMap; @@ -34,6 +36,12 @@ pub trait HashDB: AsHashDB + Send + Sync { /// hash is not known. fn get(&self, key: &H256) -> Option; + /// Look up a given hash into the bytes that hash to it, returning None if the + /// hash is not known. + fn get_debug(&self, key: &H256, debug_info: &mut DebugRead) -> Option { + self.get(key) + } + /// Check for the existance of a hash-key. fn contains(&self, key: &H256) -> bool; diff --git a/util/journaldb/Cargo.toml b/util/journaldb/Cargo.toml index 1b4ac31d4f..2947f02578 100644 --- a/util/journaldb/Cargo.toml +++ b/util/journaldb/Cargo.toml @@ -12,6 +12,7 @@ kvdb = { path = "../kvdb" } memorydb = { path = "../memorydb" } rlp = { path = "../rlp" } util-error = { path = "../error" } +codechain-types = { path = "../../types" } [dev-dependencies] codechain-crypto = { git = "https://github.com/CodeChain-io/rust-codechain-crypto.git", version = "0.1" } diff --git a/util/journaldb/src/archivedb.rs b/util/journaldb/src/archivedb.rs index 62e5729780..2d80402bc9 100644 --- a/util/journaldb/src/archivedb.rs +++ b/util/journaldb/src/archivedb.rs @@ -18,6 +18,7 @@ use super::memorydb::*; use super::{DB_PREFIX_LEN, LATEST_ERA_KEY}; +use ctypes::DebugRead; use error::{BaseDataError, UtilError}; use hashdb::*; use kvdb::{DBTransaction, KeyValueDB}; @@ -25,7 +26,7 @@ use primitives::{Bytes, H256}; use rlp::{decode, encode}; use std::collections::hash_map::Entry; use std::collections::HashMap; -use std::sync::Arc; +use std::{sync::Arc, time::Instant}; use traits::JournalDB; /// Implementation of the `HashDB` trait for a disk-backed database with a memory overlay @@ -58,6 +59,12 @@ impl ArchiveDB { fn payload(&self, key: &H256) -> Option { self.backing.get(self.column, key).expect("Low-level database error. Some issue with your hard disk?") } + + fn payload_debug(&self, key: &H256, debug_read: &mut DebugRead) -> Option { + self.backing + .get_debug(self.column, key, debug_read) + .expect("Low-level database error. Some issue with your hard disk?") + } } impl HashDB for ArchiveDB { @@ -87,6 +94,17 @@ impl HashDB for ArchiveDB { self.payload(key) } + fn get_debug(&self, key: &H256, debug_read: &mut DebugRead) -> Option { + let now = Instant::now(); + if let Some((d, rc)) = self.overlay.raw(key) { + debug_read.archive_db_overlay_us = now.elapsed().as_micros(); + if rc > 0 { + return Some(d) + } + } + self.payload_debug(key, debug_read) + } + fn contains(&self, key: &H256) -> bool { self.get(key).is_some() } diff --git a/util/journaldb/src/lib.rs b/util/journaldb/src/lib.rs index 4614077fa0..d90ef021b6 100644 --- a/util/journaldb/src/lib.rs +++ b/util/journaldb/src/lib.rs @@ -16,6 +16,7 @@ //! `JournalDB` interface and implementation. +extern crate codechain_types as ctypes; extern crate hashdb; extern crate kvdb; extern crate memorydb; @@ -28,6 +29,7 @@ extern crate codechain_crypto as crypto; #[cfg(test)] extern crate kvdb_memorydb; +use ctypes::DebugInfo; use std::sync::Arc; use std::{fmt, str}; diff --git a/util/kvdb-rocksdb/Cargo.toml b/util/kvdb-rocksdb/Cargo.toml index 2d443da235..b5f48f8066 100644 --- a/util/kvdb-rocksdb/Cargo.toml +++ b/util/kvdb-rocksdb/Cargo.toml @@ -13,6 +13,7 @@ regex = "0.2" rlp = { path = "../rlp" } rocksdb = { git = "https://github.com/paritytech/rust-rocksdb", rev = "ecf06adf3148ab10f6f7686b724498382ff4f36e" } interleaved-ordered = "0.1.0" +codechain-types = { path = "../../types" } [dev-dependencies] primitives = { git = "https://github.com/CodeChain-io/rust-codechain-primitives.git", version = "0.4" } diff --git a/util/kvdb-rocksdb/src/lib.rs b/util/kvdb-rocksdb/src/lib.rs index 7b1ba8822c..fef0c80765 100644 --- a/util/kvdb-rocksdb/src/lib.rs +++ b/util/kvdb-rocksdb/src/lib.rs @@ -24,13 +24,16 @@ extern crate parking_lot; extern crate regex; extern crate rocksdb; +extern crate codechain_types as ctypes; extern crate kvdb; extern crate rlp; +use ctypes::DebugRead; use std::cmp; use std::collections::HashMap; use std::marker::PhantomData; use std::path::{Path, PathBuf}; +use std::sync::Arc; use std::{fs, io, mem, result}; use interleaved_ordered::{interleave_ordered, InterleaveOrdered}; @@ -49,7 +52,7 @@ use regex::Regex; #[cfg(target_os = "linux")] use std::fs::File; #[cfg(target_os = "linux")] -use std::process::Command; +use std::{process::Command, time::Instant}; const DB_DEFAULT_MEMORY_BUDGET_MB: usize = 128; @@ -244,6 +247,7 @@ fn col_config(config: &DatabaseConfig, block_opts: &BlockBasedOptions) -> Result /// Key-Value database. pub struct Database { db: RwLock>, + pub opts: Arc>, config: DatabaseConfig, write_opts: WriteOptions, read_opts: ReadOptions, @@ -296,12 +300,19 @@ impl Database { opts.set_parsed_options("bytes_per_sync=1048576")?; opts.set_db_write_buffer_size(config.memory_budget_per_col() / 2); opts.increase_parallelism(cmp::max(1, ::num_cpus::get() as i32 / 2)); + opts.enable_statistics(); let mut block_opts = BlockBasedOptions::new(); { block_opts.set_block_size(config.compaction.block_size); - let cache_size = cmp::max(8, config.memory_budget() / 3); + let mut cache_size = cmp::max(8, config.memory_budget() / 3); + println!("Cache size is {}", cache_size); + cache_size = match std::env::var("JH_CACHE") { + Ok(val) => val.parse().unwrap(), + Err(_) => cache_size, + }; + println!("Cache size is {}", cache_size); let cache = Cache::new(cache_size); block_opts.set_cache(cache); } @@ -386,6 +397,7 @@ impl Database { db, cfs, })), + opts: Arc::new(Mutex::new(opts)), config: *config, write_opts, overlay: RwLock::new((0..=num_cols).map(|_| HashMap::new()).collect()), @@ -580,6 +592,73 @@ impl Database { } } + pub fn get_debug(&self, col: Option, key: &[u8], debug_read: &mut DebugRead) -> Result> { + match *self.db.read() { + Some(DBAndColumns { + ref db, + ref cfs, + }) => { + let overlay = &self.overlay.read()[col_to_overlay_column(col)]; + let overlay_get_result = { + let now = Instant::now(); + let ret = overlay.get(key); + debug_read.kvrocks_overlay_us = now.elapsed().as_micros(); + + ret + }; + match overlay_get_result { + Some(&KeyState::Insert(ref value)) | Some(&KeyState::InsertCompressed(ref value)) => { + Ok(Some(value.clone())) + } + Some(&KeyState::Delete) => Ok(None), + None => { + let flushing = &self.flushing.read()[col_to_overlay_column(col)]; + let flushing_get = { + let now = Instant::now(); + let ret = flushing.get(key); + debug_read.kvrocks_flushing_us = now.elapsed().as_micros(); + ret + }; + match flushing_get { + Some(&KeyState::Insert(ref value)) | Some(&KeyState::InsertCompressed(ref value)) => { + Ok(Some(value.clone())) + } + Some(&KeyState::Delete) => Ok(None), + None => match col { + Some(c) => { + let now = Instant::now(); + let ret = db + .get_cf_opt(cfs[c as usize], key, &self.read_opts) + .map(|r| r.map(|v| DBValue::from_slice(&v))); + debug_read.kvrocks_get_cf_opt_us = now.elapsed().as_micros(); + if (now.elapsed().as_micros() > 1000) { + // for i in 0..10 { + // db.get_cf_opt(cfs[c as usize], key, &self.read_opts) + // .map(|r| r.map(|v| DBValue::from_slice(&v))); + // debug_read.kvrocks_get_cf_opt_us_vec.push(now.elapsed().as_micros()); + // } + } + + + ret + } + None => { + let now = Instant::now(); + let ret = + db.get_opt(key, &self.read_opts).map(|r| r.map(|v| DBValue::from_slice(&v))); + + debug_read.kvrocks_get_opt_us = now.elapsed().as_micros(); + ret + } + } + .map_err(Into::into), + } + } + } + } + None => Ok(None), + } + } /// Get value by partial key. Prefix size should match configured prefix size. Only searches flushed values. // TODO: support prefix seek for unflushed data pub fn get_by_prefix(&self, col: Option, prefix: &[u8]) -> Option> { @@ -772,6 +851,10 @@ impl KeyValueDB for Database { Database::get(self, col, key) } + fn get_debug(&self, col: Option, key: &[u8], debug_read: &mut DebugRead) -> Result> { + Database::get_debug(self, col, key, debug_read) + } + fn get_by_prefix(&self, col: Option, prefix: &[u8]) -> Option> { Database::get_by_prefix(self, col, prefix) } diff --git a/util/kvdb/Cargo.toml b/util/kvdb/Cargo.toml index 7b627e8371..945d6920b8 100644 --- a/util/kvdb/Cargo.toml +++ b/util/kvdb/Cargo.toml @@ -7,3 +7,4 @@ authors = ["Parity Technologies "] elastic-array = "0.10" error-chain = { version = "0.12", default-features = false } primitives = { git = "https://github.com/CodeChain-io/rust-codechain-primitives.git", version = "0.4" } +codechain-types = { path = "../../types" } diff --git a/util/kvdb/src/lib.rs b/util/kvdb/src/lib.rs index cdee56678f..9b2c6da850 100644 --- a/util/kvdb/src/lib.rs +++ b/util/kvdb/src/lib.rs @@ -18,9 +18,11 @@ #[macro_use] extern crate error_chain; +extern crate codechain_types as ctypes; extern crate elastic_array; extern crate primitives; +use ctypes::DebugRead; use elastic_array::{ElasticArray128, ElasticArray32}; use primitives::Bytes; @@ -158,6 +160,10 @@ pub trait KeyValueDB: Sync + Send { /// Get a value by key. fn get(&self, col: Option, key: &[u8]) -> Result>; + fn get_debug(&self, col: Option, key: &[u8], debug_read: &mut DebugRead) -> Result> { + self.get(col, key) + } + /// Get a value by partial key. Only works for flushed data. fn get_by_prefix(&self, col: Option, prefix: &[u8]) -> Option>; diff --git a/util/merkle/Cargo.toml b/util/merkle/Cargo.toml index 4cf53e157a..f3e75a7015 100644 --- a/util/merkle/Cargo.toml +++ b/util/merkle/Cargo.toml @@ -6,10 +6,12 @@ authors = ["CodeChain Team "] [dependencies] elastic-array = "0.10" rand = "0.6.1" +rustc-hex = "1.0" hashdb = {path = "../hashdb" } codechain-crypto = { git = "https://github.com/CodeChain-io/rust-codechain-crypto.git", version = "0.1" } primitives = { git = "https://github.com/CodeChain-io/rust-codechain-primitives.git", version = "0.4" } rlp = {path = "../rlp" } +codechain-types= {path = "../../types"} [dev-dependencies] trie-standardmap = { path = "../trie-standardmap" } diff --git a/util/merkle/src/lib.rs b/util/merkle/src/lib.rs index 63f7b708f7..15d36f870e 100644 --- a/util/merkle/src/lib.rs +++ b/util/merkle/src/lib.rs @@ -15,16 +15,19 @@ // along with this program. If not, see . extern crate codechain_crypto as ccrypto; +extern crate codechain_types as ctypes; extern crate elastic_array; extern crate hashdb; #[cfg(test)] extern crate memorydb; extern crate primitives; extern crate rlp; +extern crate rustc_hex; #[cfg(test)] extern crate trie_standardmap as standardmap; +use ctypes::DebugInfo; use std::fmt; use ccrypto::BLAKE_NULL_RLP; @@ -109,6 +112,12 @@ pub trait Trie { /// Search for the key with the given query parameter. See the docs of the `Query` /// trait for more details. fn get_with(&self, key: &[u8], query: Q) -> Result>; + + /// Search for the key with the given query parameter. See the docs of the `Query` + /// trait for more details. + fn get_with_debug(&self, key: &[u8], query: Q) -> Result<(Option, DebugInfo)> { + Ok((self.get_with(key, query)?, DebugInfo::empty())) + } } /// A key-value datastore implemented as a database-backed modified Merkle tree. diff --git a/util/merkle/src/triedb.rs b/util/merkle/src/triedb.rs index c65ce58c6a..46d5521139 100644 --- a/util/merkle/src/triedb.rs +++ b/util/merkle/src/triedb.rs @@ -17,10 +17,13 @@ use ccrypto::blake256; use hashdb::HashDB; use primitives::H256; +use rustc_hex::ToHex; use crate::nibbleslice::NibbleSlice; use crate::node::Node as RlpNode; use crate::{Query, Trie, TrieError}; +use ctypes::{DebugInfo, DebugRead}; +use std::time::Instant; /// A `Trie` implementation using a generic `HashDB` backing database. /// /// Use it as a `Trie` trait object. You can use `db()` to get the backing database object. @@ -105,6 +108,54 @@ impl<'db> TrieDB<'db> { None => Ok(None), } } + + /// Get auxiliary + fn get_aux_debug( + &self, + path: &NibbleSlice, + cur_node_hash: Option, + query: Q, + ) -> crate::Result<(Option, DebugInfo)> { + match cur_node_hash { + Some(hash) => { + let read_start = Instant::now(); + let mut read = DebugRead::empty(); + let node_rlp = + self.db.get_debug(&hash, &mut read).ok_or_else(|| TrieError::IncompleteDatabase(hash))?; + let elapsed = read_start.elapsed().as_micros(); + let mut empty = DebugInfo::empty(); + read.time_us = elapsed; + read.key = (hash.as_ref() as &[u8]).to_hex(); + empty.reads.push(read.clone()); + + match RlpNode::decoded(&node_rlp) { + Some(RlpNode::Leaf(partial, value)) => { + if &partial == path { + Ok((Some(query.decode(value)), empty)) + } else { + Ok((None, empty)) + } + } + Some(RlpNode::Branch(partial, children)) => { + if path.starts_with(&partial) { + let (result, mut debug_info) = self.get_aux_debug( + &path.mid(partial.len() + 1), + children[path.mid(partial.len()).at(0) as usize], + query, + )?; + debug_info.inc_read_count(); + debug_info.reads.push(read); + Ok((result, debug_info)) + } else { + Ok((None, empty)) + } + } + None => Ok((None, empty)), + } + } + None => Ok((None, DebugInfo::empty())), + } + } } impl<'db> Trie for TrieDB<'db> { @@ -118,6 +169,13 @@ impl<'db> Trie for TrieDB<'db> { self.get_aux(&NibbleSlice::new(&path), Some(root), query) } + + fn get_with_debug(&self, key: &[u8], query: Q) -> crate::Result<(Option, DebugInfo)> { + let path = blake256(key); + let root = *self.root; + + self.get_aux_debug(&NibbleSlice::new(&path), Some(root), query) + } } #[cfg(test)]