├── .gitignore ├── rust-toolchain ├── ycsb-rs ├── src │ ├── workload.rs │ ├── generator │ │ ├── constant_generator.rs │ │ ├── counter_generator.rs │ │ ├── uniform_long_generator.rs │ │ ├── discrete_generator.rs │ │ ├── acknowledged_counter_generator.rs │ │ └── zipfian_generator.rs │ ├── db.rs │ ├── runtime.rs │ ├── generator.rs │ ├── sqlite.rs │ ├── properties.rs │ └── main.rs ├── Cargo.toml ├── Makefile ├── workloads │ ├── workload_obkv.toml │ ├── workloada.toml │ ├── workloadc.toml │ ├── workloadb.toml │ ├── workloadf.toml │ ├── workloadd.toml │ ├── workloade.toml │ └── workload_template.toml └── README.md ├── docs ├── ycsb.sql └── simple_demo │ └── simple_operation │ ├── aggregationDemo.md │ └── demo.md ├── Makefile ├── rustfmt.toml ├── etc └── license.template ├── tests ├── utils │ ├── mod.rs │ └── common.rs ├── test_table_client_sql.rs └── test_table_client.rs ├── src ├── rpc │ ├── util │ │ ├── mod.rs │ │ ├── checksum │ │ │ └── mod.rs │ │ └── hash │ │ │ └── mod.rs │ ├── protocol │ │ ├── partition │ │ │ ├── mod.rs │ │ │ ├── ob_partition_key.rs │ │ │ └── ob_column.rs │ │ └── query_and_mutate.rs │ └── proxy.rs ├── constant.rs ├── monitors │ ├── mod.rs │ ├── prometheus.rs │ ├── proxy_metrics.rs │ ├── rpc_metrics.rs │ ├── runtime_metrics.rs │ └── client_metrics.rs ├── serde_obkv │ ├── mod.rs │ ├── error.rs │ └── value │ │ └── from.rs ├── macros.rs ├── lib.rs ├── location │ ├── part_func_type.rs │ └── ob_part_constants.rs ├── util │ ├── mod.rs │ ├── security.rs │ ├── permit.rs │ └── obversion.rs ├── client │ └── mod.rs ├── error.rs └── runtime │ └── mod.rs ├── README.md ├── Cargo.toml ├── CONTRIBUTING.md ├── .github └── workflows │ └── ci.yaml ├── benches └── concurrent_insert │ └── mod.rs └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | .idea/ 4 | .vscode 5 | ycsb-rs/opt/ 6 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = [ "rustfmt", "clippy" ] 4 | -------------------------------------------------------------------------------- /ycsb-rs/src/workload.rs: -------------------------------------------------------------------------------- 1 | mod core_workload; 2 | 3 | use std::{cell::RefCell, rc::Rc}; 4 | 5 | pub use core_workload::CoreWorkload; 6 | use rand::rngs::SmallRng; 7 | 8 | use crate::db::DB; 9 | 10 | pub trait Workload { 11 | fn do_insert(&self, db: Rc); 12 | fn do_transaction(&self, rng: Rc>, db: Rc); 13 | } 14 | -------------------------------------------------------------------------------- /docs/ycsb.sql: -------------------------------------------------------------------------------- 1 | create table usertable ( 2 | ycsb_key varchar(255) primary key, 3 | field0 varchar(255), 4 | field1 varchar(255), 5 | field2 varchar(255), 6 | field3 varchar(255), 7 | field4 varchar(255), 8 | field5 varchar(255), 9 | field6 varchar(255), 10 | field7 varchar(255), 11 | field8 varchar(255), 12 | field9 varchar(255)) 13 | partition by key(ycsb_key) partitions 3; 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL = /bin/bash 2 | 3 | DIR=$(shell pwd) 4 | 5 | check-license: 6 | cd $(DIR); sh scripts/check-license.sh 7 | 8 | check-cargo-toml: 9 | cd $(DIR); cargo sort --workspace --check 10 | 11 | clippy: 12 | cd $(DIR); cargo clippy --all-targets --all-features --workspace -- -D warnings 13 | 14 | test-ut: 15 | cd $(DIR); cargo test --lib --all-features --workspace 16 | 17 | test-all: 18 | cd $(DIR); cargo test --all --all-features --workspace 19 | 20 | fmt: 21 | cd $(DIR); cargo fmt -- --check 22 | -------------------------------------------------------------------------------- /ycsb-rs/src/generator/constant_generator.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::SmallRng; 2 | 3 | use super::Generator; 4 | 5 | pub struct ConstantGenerator { 6 | value: T, 7 | } 8 | 9 | impl ConstantGenerator { 10 | pub fn new(value: T) -> Self { 11 | Self { value } 12 | } 13 | } 14 | 15 | impl Generator for ConstantGenerator { 16 | fn next_value(&self, _rng: &mut SmallRng) -> T { 17 | self.value.clone() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ycsb-rs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "obkv-ycsb" 3 | version = "0.1.0" 4 | authors = ["OceanBase OBKV Developers"] 5 | 6 | [package.edition] 7 | workspace = true 8 | 9 | [dependencies] 10 | anyhow = { workspace = true } 11 | log = { workspace = true } 12 | obkv-table-client-rs = { path = ".." } 13 | rand = { version = "0.8", features = ["small_rng"] } 14 | serde = { version = "1.0.130", features = ["derive"] } 15 | sql-builder = "3.1" 16 | sqlite = "0.26.0" 17 | structopt = "0.3.23" 18 | tokio = { workspace = true } 19 | toml = { workspace = true } 20 | -------------------------------------------------------------------------------- /ycsb-rs/src/generator/counter_generator.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::AtomicU64; 2 | 3 | use rand::prelude::*; 4 | 5 | use super::Generator; 6 | 7 | pub struct CounterGenerator { 8 | counter: AtomicU64, 9 | } 10 | 11 | impl CounterGenerator { 12 | pub fn new(count_start: u64) -> Self { 13 | Self { 14 | counter: AtomicU64::new(count_start), 15 | } 16 | } 17 | } 18 | 19 | impl Generator for CounterGenerator { 20 | fn next_value(&self, _rng: &mut SmallRng) -> u64 { 21 | self.counter 22 | .fetch_add(1, std::sync::atomic::Ordering::SeqCst) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md 2 | 3 | edition = "2021" 4 | 5 | # Break comments to fit on the line 6 | # wrap_comments = true 7 | 8 | # Merge multiple imports into a single nested import. 9 | # imports_granularity = "Crate" 10 | 11 | # Format code snippet included in doc comments. 12 | # format_code_in_doc_comments = true 13 | 14 | # Reorder impl items. type and const are put first, then macros and methods. 15 | # reorder_impl_items = true 16 | 17 | # Discard existing import groups, and create three groups for std, external crates, crates 18 | # group_imports = "StdExternalCrate" 19 | -------------------------------------------------------------------------------- /etc/license.template: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the Mulan PSL v2. 9 | * You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 12 | * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 13 | * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | -------------------------------------------------------------------------------- /tests/utils/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | pub mod common; 19 | -------------------------------------------------------------------------------- /src/rpc/util/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | pub mod checksum; 19 | pub mod hash; 20 | -------------------------------------------------------------------------------- /src/rpc/util/checksum/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | #[rustfmt::skip] 19 | pub mod ob_crc64; 20 | -------------------------------------------------------------------------------- /src/rpc/util/hash/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | #[rustfmt::skip] 19 | pub mod ob_hash_sort_utf8mb4; 20 | -------------------------------------------------------------------------------- /src/rpc/protocol/partition/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | pub mod ob_column; 19 | pub mod ob_partition_key; 20 | -------------------------------------------------------------------------------- /src/constant.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | pub const OCEANBASE_DATABASE: &str = "oceanbase"; 19 | pub const ALL_DUMMY_TABLE: &str = "__all_dummy"; 20 | -------------------------------------------------------------------------------- /ycsb-rs/src/generator/uniform_long_generator.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::*; 2 | 3 | use super::{Generator, NumberGenerator}; 4 | 5 | pub struct UniformLongGenerator { 6 | lower_bound: u64, 7 | upper_bound: u64, 8 | } 9 | 10 | impl UniformLongGenerator { 11 | pub fn new(lower_bound: u64, upper_bound: u64) -> Self { 12 | Self { 13 | lower_bound, 14 | upper_bound, 15 | } 16 | } 17 | } 18 | 19 | impl Generator for UniformLongGenerator { 20 | fn next_value(&self, rng: &mut SmallRng) -> u64 { 21 | rng.gen_range(self.lower_bound..=self.upper_bound) 22 | } 23 | } 24 | 25 | impl NumberGenerator for UniformLongGenerator { 26 | fn mean(&self) -> u64 { 27 | (self.lower_bound + self.upper_bound) / 2 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/monitors/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | pub mod client_metrics; 19 | pub mod prometheus; 20 | pub mod proxy_metrics; 21 | pub mod rpc_metrics; 22 | pub mod runtime_metrics; 23 | -------------------------------------------------------------------------------- /src/serde_obkv/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | pub mod de; 19 | pub mod error; 20 | pub mod ser; 21 | pub mod util; 22 | pub mod value; 23 | 24 | pub use self::{ 25 | de::{from_bytes_mut, Deserializer}, 26 | error::{Error, Result}, 27 | ser::{to_bytes_mut, Serializer}, 28 | }; 29 | -------------------------------------------------------------------------------- /ycsb-rs/src/db.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, rc::Rc, sync::Arc}; 2 | 3 | use anyhow::{anyhow, Result}; 4 | 5 | use crate::{ 6 | obkv_client::{OBKVClient, OBKVClientInitStruct}, 7 | sqlite::SQLite, 8 | }; 9 | 10 | pub trait DB { 11 | fn init(&self) -> Result<()>; 12 | fn insert(&self, table: &str, key: &str, values: &HashMap<&str, String>) -> Result<()>; 13 | fn read(&self, table: &str, key: &str, result: &mut HashMap) -> Result<()>; 14 | fn update(&self, table: &str, key: &str, values: &HashMap<&str, String>) -> Result<()>; 15 | fn scan( 16 | &self, 17 | table: &str, 18 | startkey: &str, 19 | endkey: &str, 20 | values: &mut HashMap, 21 | ) -> Result<()>; 22 | } 23 | 24 | pub fn create_db(db: &str, _config: Arc) -> Result> { 25 | match db { 26 | "sqlite" => Ok(Rc::new(SQLite::new()?)), 27 | db => Err(anyhow!("{} is an invalid database name", db)), 28 | } 29 | } 30 | 31 | pub fn create_ob(db: &str, config: Arc) -> Result> { 32 | match db { 33 | "obkv" => Ok(Arc::new(OBKVClient::build_normal_client(config)?)), 34 | db => Err(anyhow!("{} is an invalid database name", db)), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ycsb-rs/Makefile: -------------------------------------------------------------------------------- 1 | MODE ?= release 2 | ROOT = $(shell pwd) 3 | 4 | export OBKV_YCSB_BINARY ?= $(ROOT)/../target/$(MODE)/obkv-ycsb 5 | 6 | database ?= obkv 7 | workload ?= $(ROOT)/workloads/workload_obkv.toml 8 | threads ?= 400 9 | 10 | build-test: 11 | cargo build --$(MODE) 12 | 13 | build:build-test 14 | 15 | load: build 16 | $(OBKV_YCSB_BINARY) load --database $(database) --threads $(threads) --workload $(workload) 17 | 18 | run: build 19 | $(OBKV_YCSB_BINARY) run --database $(database) --threads $(threads) --workload $(workload) 20 | 21 | clean: 22 | rm -rf $(ROOT)/../target/ 23 | 24 | install-flamegraph: 25 | wget https://github.com/brendangregg/FlameGraph/archive/master.zip && unzip master.zip && sudo mkdir $(ROOT)/opt && sudo mv FlameGraph-master/ $(ROOT)/opt/FlameGraph && rm master.zip 26 | 27 | record-flamegraph: 28 | sudo perf record --call-graph=dwarf -F 99 -p $$(ps -ef | grep obkv-ycsb | grep -v grep | awk '{print $$2}') -g -- sleep 30 29 | 30 | out-flamegraph: 31 | sudo perf script -i perf.data > out.perf 32 | 33 | folded-flamegraph: 34 | $(ROOT)/opt/FlameGraph/stackcollapse-perf.pl out.perf > out.folded 35 | 36 | gengerate-flamegraph: 37 | $(ROOT)/opt/FlameGraph/flamegraph.pl out.folded > obkv.svg 38 | 39 | flamegraph: 40 | $(MAKE) out-flamegraph && $(MAKE) folded-flamegraph && $(MAKE) gengerate-flamegraph 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # obkv-table-client-rs 2 | 3 | OBKV Table Client is Rust Library that can be used to access table data from [OceanBase](https://github.com/oceanbase/oceanbase) storage layer. Its access method is different from JDBC, it skips the SQL parsing layer, so it has significant performance advantage. 4 | 5 | ## 🚧 Experimental – Not for Production Use 6 | ⚠️ **Warning: This project is currently in an experimental stage and is not recommended for production environments.** 7 | 8 | ## Examples 9 | A simple example could be found in [Demo](https://github.com/oceanbase/obkv-table-client-rs/blob/main/docs/simple_demo/simple_operation/demo.md). 10 | 11 | ## Acknowledgment 12 | The CeresDB team implemented this rust client from scratch. Thanks to the [CeresDB](https://github.com/CeresDB/ceresdb) team (CeresDB is a high-performance, distributed, cloud native time-series database). 13 | 14 | ## Contributing 15 | Contributions are warmly welcomed and greatly appreciated. Here are a few ways you can contribute: 16 | 17 | - Raise us an [Issue](https://github.com/oceanbase/obkv-table-client-rs/issues) 18 | - Submit Pull Requests. For details, see [How to contribute](CONTRIBUTING.md). 19 | 20 | ## Licencing 21 | obkv-table-client-rs is under [MulanPSL - 2.0](http://license.coscl.org.cn/MulanPSL2) licence. You can freely copy and use the source code. When you modify or distribute the source code, please obey the MulanPSL - 2.0 licence. 22 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | #[macro_export] 19 | macro_rules! box_future_try { 20 | ($expr:expr) => {{ 21 | match $expr { 22 | Ok(r) => r, 23 | Err(e) => return Box::new(::futures::future::err(e)), 24 | } 25 | }}; 26 | } 27 | 28 | #[macro_export] 29 | macro_rules! box_stream_try { 30 | ($expr:expr) => {{ 31 | match $expr { 32 | Ok(r) => r, 33 | Err(e) => { 34 | use futures::Future; 35 | return Box::new(::futures::future::err(e).into_stream()); 36 | } 37 | } 38 | }}; 39 | } 40 | 41 | #[macro_export] 42 | macro_rules! fatal { 43 | ($msg:expr, $($arg:tt)+) => ({ 44 | error!($msg, $($arg)+); 45 | panic!($msg, $($arg)+); 46 | }) 47 | } 48 | -------------------------------------------------------------------------------- /src/monitors/prometheus.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::sync::Mutex; 19 | 20 | use prometheus_client::{encoding::text::encode, registry::Registry}; 21 | 22 | lazy_static! { 23 | pub static ref OBKV_CLIENT_REGISTRY: Mutex = 24 | Mutex::new(ObClientRegistry::default()); 25 | } 26 | 27 | pub fn dump_metrics() -> Result { 28 | let registry = OBKV_CLIENT_REGISTRY.lock().unwrap(); 29 | registry.dump_metrics() 30 | } 31 | 32 | #[derive(Default)] 33 | pub struct ObClientRegistry { 34 | pub registry: Registry, 35 | } 36 | 37 | impl ObClientRegistry { 38 | pub fn dump_metrics(&self) -> Result { 39 | let mut buffer = String::new(); 40 | encode(&mut buffer, &self.registry).unwrap(); 41 | Ok(buffer) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /ycsb-rs/src/runtime.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2023 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::sync::Arc; 19 | 20 | use obkv::runtime; 21 | 22 | use crate::properties::Properties; 23 | 24 | /// OBKV Table Runtime 25 | #[derive(Clone, Debug)] 26 | pub struct ObYCSBRuntimes { 27 | /// Default runtime tasks 28 | pub default_runtime: runtime::RuntimeRef, 29 | /// Runtime for block_on 30 | pub block_runtime: runtime::RuntimeRef, 31 | } 32 | 33 | fn build_runtime(name: &str, threads_num: usize) -> runtime::Runtime { 34 | runtime::Builder::default() 35 | .worker_threads(threads_num) 36 | .thread_name(name) 37 | .enable_all() 38 | .build() 39 | .expect("Failed to create runtime") 40 | } 41 | 42 | pub fn build_ycsb_runtimes(props: Arc) -> ObYCSBRuntimes { 43 | ObYCSBRuntimes { 44 | default_runtime: Arc::new(build_runtime("ycsb-default", props.ycsb_thread_num)), 45 | block_runtime: Arc::new(build_runtime("ycsb-block", 1)), 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ycsb-rs/src/generator.rs: -------------------------------------------------------------------------------- 1 | mod acknowledged_counter_generator; 2 | mod constant_generator; 3 | mod counter_generator; 4 | mod discrete_generator; 5 | mod uniform_long_generator; 6 | mod zipfian_generator; 7 | 8 | use std::string::ToString; 9 | 10 | pub use acknowledged_counter_generator::AcknowledgedCounterGenerator; 11 | pub use constant_generator::ConstantGenerator; 12 | pub use counter_generator::CounterGenerator; 13 | pub use discrete_generator::{DiscreteGenerator, WeightPair}; 14 | use rand::prelude::SmallRng; 15 | pub use uniform_long_generator::UniformLongGenerator; 16 | pub use zipfian_generator::ZipfianGenerator; 17 | 18 | pub trait Generator { 19 | fn next_value(&self, rng: &mut SmallRng) -> T; 20 | } 21 | 22 | pub trait NumberGenerator: Generator { 23 | fn mean(&self) -> T; 24 | } 25 | 26 | pub struct GeneratorImpl> { 27 | last_value: Option, 28 | generator: G, 29 | } 30 | 31 | impl GeneratorImpl 32 | where 33 | G: Generator, 34 | T: ToString + Clone + Send, 35 | { 36 | pub fn new(generator: G) -> Self { 37 | Self { 38 | generator, 39 | last_value: None, 40 | } 41 | } 42 | 43 | pub fn next_value(&mut self, rng: &mut SmallRng) -> T { 44 | let v = self.generator.next_value(rng); 45 | self.last_value = Some(v.clone()); 46 | v 47 | } 48 | 49 | pub fn last_value(&self) -> T { 50 | self.last_value.clone().unwrap() 51 | } 52 | 53 | pub fn next_string(&mut self, rng: &mut SmallRng) -> String { 54 | self.next_value(rng).to_string() 55 | } 56 | 57 | pub fn last_string(&self) -> String { 58 | self.last_value().to_string() 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ycsb-rs/src/generator/discrete_generator.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::*; 2 | 3 | use super::Generator; 4 | 5 | pub struct WeightPair { 6 | weight: f64, 7 | value: T, 8 | } 9 | 10 | impl WeightPair { 11 | pub fn new(weight: f64, value: impl Into) -> Self { 12 | Self { 13 | weight, 14 | value: value.into(), 15 | } 16 | } 17 | } 18 | 19 | pub struct DiscreteGenerator { 20 | values: Vec>, 21 | sum: f64, 22 | } 23 | 24 | impl DiscreteGenerator { 25 | pub fn new(values: Vec>) -> Self { 26 | let sum: f64 = values.iter().map(|v| v.weight).sum(); 27 | Self { values, sum } 28 | } 29 | } 30 | 31 | impl Generator for DiscreteGenerator { 32 | fn next_value(&self, rng: &mut SmallRng) -> T { 33 | let mut val = rng.gen::(); 34 | for WeightPair { weight, value } in &self.values { 35 | let pw = *weight / self.sum; 36 | if val < pw { 37 | return value.clone(); 38 | } 39 | val -= pw; 40 | } 41 | unreachable!(); 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::*; 48 | 49 | #[test] 50 | fn test_discrete_generator() { 51 | let weight_pairs = vec![WeightPair::new(0.3, "test"), WeightPair::new(0.7, "b")]; 52 | let generator = DiscreteGenerator::::new(weight_pairs); 53 | let mut result = std::collections::HashMap::new(); 54 | let mut rng = SmallRng::from_entropy(); 55 | for _i in 0..10000 { 56 | let val = generator.next_value(&mut rng); 57 | result.entry(val).and_modify(|x| *x += 1).or_insert(1); 58 | } 59 | println!("{result:?}"); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | extern crate bytes; 19 | extern crate chrono; 20 | extern crate crossbeam; 21 | extern crate futures; 22 | extern crate mysql; 23 | #[macro_use] 24 | extern crate serde; 25 | extern crate serde_bytes; 26 | extern crate tokio_util; 27 | #[macro_use] 28 | extern crate serde_derive; 29 | #[macro_use] 30 | extern crate quick_error; 31 | extern crate rand; 32 | extern crate reqwest; 33 | extern crate serde_json; 34 | #[macro_use] 35 | extern crate log; 36 | extern crate futures_cpupool; 37 | #[macro_use] 38 | extern crate lazy_static; 39 | extern crate r2d2; 40 | extern crate scheduled_thread_pool; 41 | extern crate spin; 42 | extern crate uuid; 43 | extern crate zstd; 44 | 45 | #[macro_use] 46 | mod macros; 47 | pub mod client; 48 | mod constant; 49 | pub mod error; 50 | mod location; 51 | pub mod monitors; 52 | mod rpc; 53 | pub mod runtime; 54 | pub mod serde_obkv; 55 | mod util; 56 | pub use self::{ 57 | client::{ 58 | filter, 59 | query::QueryResultSet, 60 | table::ObTable, 61 | table_client::{Builder, ObTableClient, RunningMode}, 62 | ClientConfig, TableOpResult, 63 | }, 64 | monitors::prometheus::dump_metrics, 65 | rpc::{ 66 | protocol::{codes::ResultCodes, payloads, query}, 67 | proxy, 68 | }, 69 | serde_obkv::value::{ObjType, Value}, 70 | }; 71 | -------------------------------------------------------------------------------- /ycsb-rs/workloads/workload_obkv.toml: -------------------------------------------------------------------------------- 1 | # OBKV Table Client Framework 2 | # %% 3 | # Copyright (C) 2023 OceanBase 4 | # %% 5 | # OBKV Table Client Framework is licensed under Mulan PSL v2. 6 | # You can use this software according to the terms and conditions of the 7 | # Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 8 | # http://license.coscl.org.cn/MulanPSL2 9 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 10 | # KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 11 | # NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 12 | # See the Mulan PSL v2 for more details. 13 | 14 | # insertcount should be equal to n * threadcount 15 | operationcount = 5000000 16 | insert_start = 0 17 | recordcount = 1000000 18 | workload = "core" 19 | 20 | measurementtype = "histogram" 21 | 22 | # insertcount or recordcount - insert_start 23 | # insertcount = 10000 24 | 25 | readallfields = true 26 | 27 | # The number of operation in one batch 28 | batchcount = 100 29 | 30 | # Show params from prometheus or not 31 | showprometheus = false 32 | 33 | # Duration of showing progress 34 | show_progress_duration_sec = 30 35 | 36 | insertproportion = 0 37 | readproportion = 1.0 38 | scanproportion = 0 39 | updateproportion = 0 40 | batchreadproportion = 0 41 | batchinsertupproportion = 0 42 | 43 | requestdistribution = "uniform" 44 | 45 | # For OBKV Client 46 | full_user_name = "" 47 | param_url = "" 48 | test_password = "" 49 | test_sys_user_name = "" 50 | test_sys_password = "" 51 | 52 | # How may YCSB Client will use a OBKV Client 53 | obkv_client_reuse = 200 54 | 55 | rpc_connect_timeout = 1000 56 | rpc_read_timeout = 3000 57 | rpc_login_timeout = 3000 58 | rpc_operation_timeout = 3000 59 | rpc_retry_limit = 3 60 | rpc_retry_interval = 0 61 | refresh_workers_num = 1 62 | max_conns_per_server = 10 63 | min_idle_conns_per_server = 10 64 | 65 | ycsb_thread_num = 10 66 | 67 | bg_thread_num = 2 68 | tcp_recv_thread_num = 6 69 | tcp_send_thread_num = 4 70 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "obkv-table-client-rs" 3 | version = "0.2.0" 4 | authors = ["OceanBase OBKV Developers", "CeresDB Authors "] 5 | edition = "2021" 6 | 7 | [workspace] 8 | members = ["ycsb-rs"] 9 | 10 | [workspace.package] 11 | version = "0.2.0" 12 | authors = ["OceanBase OBKV Developers", "CeresDB Authors "] 13 | edition = "2021" 14 | 15 | [workspace.dependencies] 16 | anyhow = "1.0" 17 | log = "0.4" 18 | prometheus-client = "0.21" 19 | tokio = { version = "1", features = ["full"] } 20 | toml = "0.7.3" 21 | 22 | [lib] 23 | name = "obkv" 24 | 25 | [dependencies] 26 | anyhow = { workspace = true } 27 | backtrace = "0.3" 28 | byteorder = "1.2" 29 | bytes = "1.4" 30 | chrono = "0.4" 31 | crossbeam = "0.8.2" 32 | futures = "0.1" 33 | futures-cpupool = "0.1" 34 | lazy_static = "1.3" 35 | linked-hash-map = "0.5" 36 | log = { workspace = true } 37 | murmur2 = "0.1" 38 | mysql = { version = "24.0.0", default-features = false, features = ["default-rustls"] } 39 | net2 = "0.2" 40 | pin-project-lite = "0.2" 41 | prometheus-client = { workspace = true } 42 | quick-error = "1.2" 43 | r2d2 = "0.8.3" 44 | rand = "0.8" 45 | regex = "1.7" 46 | reqwest = { version = "0.11.13", default-features = false, features = ["rustls-tls", "blocking"] } 47 | scheduled-thread-pool = "0.2" 48 | serde = "1.0" 49 | serde_bytes = "0.11" 50 | serde_derive = "1.0" 51 | serde_json = "1.0" 52 | sha1 = "0.10.5" 53 | socket2 = "0.5" 54 | spin = "0.9" 55 | tokio = { workspace = true } 56 | tokio-util = "0.7" 57 | uuid = { version = "1.3.0", default-features = false, features = ["v4", "fast-rng", "macro-diagnostics"] } 58 | zstd = "0.12" 59 | 60 | [dev-dependencies] 61 | env_logger = "0.10" 62 | scoped_threadpool = "0.1" 63 | serial_test = "2.0" 64 | serial_test_derive = "2.0" 65 | tempfile = "3.0" 66 | test-log = "0.2" 67 | time = "0.3.36" 68 | tokio-test = "0.4" 69 | 70 | [[bench]] 71 | name = "concurrent_insert" 72 | harness = false 73 | path = "benches/concurrent_insert/mod.rs" 74 | 75 | [profile.release] 76 | debug = true 77 | -------------------------------------------------------------------------------- /ycsb-rs/README.md: -------------------------------------------------------------------------------- 1 | # YCSB for OBKV Client 2 | ## How to Build 3 | ### Build OBKV Client 4 | Make sure you are in `/obkv-table-client-rs/ycsb-rs` directory, and then just execute the command: 5 | ```shell 6 | make build 7 | ``` 8 | 9 | ## How to Run Test 10 | 11 | ### Load Data 12 | ```shell 13 | make load 14 | ``` 15 | There are 3 option parameters for load: 16 | - `database` to specify the database to use. Default is `obkv` 17 | - `threads` to specify the number of threads(clients) to use. Default is `200` 18 | - `workload` to specify the workload to use. Default is `workloads/workload_obkv.toml` 19 | ```shell 20 | make load database=obkv threads=200 workload=workloads/workload_obkv.toml 21 | ``` 22 | 23 | ### Run Test 24 | ```shell 25 | make run 26 | ``` 27 | There are 3 option parameters for load: 28 | - `database` to specify the database to use. Default is `obkv` 29 | - `threads` to specify the number of threads(clients) to use. Default is `200` 30 | - `workload` to specify the workload to use. Default is `workloads/workload_obkv.toml` 31 | ```shell 32 | make run database=obkv threads=200 workload=workloads/workload_obkv.toml 33 | ``` 34 | To change the operation of test, you should modify the `workload` file. 35 | You could learn more from `workloads/workload_template.toml`. 36 | 37 | To change the configuration of OBKV Client, you should modify the `workload` file. 38 | You could learn more from `workloads/workload_obkv.toml`. 39 | 40 | ## Flame Graph 41 | ### Manual 42 | #### Record Data 43 | Record data when running test: 44 | ```shell 45 | sudo perf record -F 99 -p $(ps -ef | grep obkv-ycsb | grep -v grep | awk '{print $2}') -g -- sleep 20 46 | ``` 47 | #### Generate Flame Graph Data 48 | ```shell 49 | sudo perf script -i perf.data > out.perf 50 | ``` 51 | To generate frame graph, see [FlameGraph](https://github.com/brendangregg/FlameGraph.git). 52 | 53 | ### Auto 54 | #### Download Dependency 55 | ```shell 56 | make install-flamegraph 57 | ``` 58 | 59 | #### Record Data 60 | Record data when running test: 61 | ```shell 62 | make record-flamegraph 63 | ``` 64 | 65 | #### Generate Flame Graph 66 | ```shell 67 | make flamegraph 68 | ``` 69 | The generated `obkv.svg` is what you want. 70 | 71 | ## Acknowledgement 72 | YCSB for OBKV Client is based on [YCSB](https://github.com/penberg/ycsb-rs). 73 | -------------------------------------------------------------------------------- /src/serde_obkv/error.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::{ 19 | borrow::Cow, 20 | error, 21 | fmt::{self, Display}, 22 | str, 23 | }; 24 | 25 | use serde::{de, ser}; 26 | 27 | pub type Result = std::result::Result; 28 | 29 | //Referer https://github.com/nox/serde_urlencoded/blob/master/src/ser/mod.rs 30 | 31 | #[derive(Clone, Debug, PartialEq, Eq)] 32 | pub enum Error { 33 | Custom(Cow<'static, str>), 34 | Utf8(str::Utf8Error), 35 | } 36 | 37 | impl fmt::Display for Error { 38 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 39 | match *self { 40 | Error::Custom(ref msg) => msg.fmt(f), 41 | Error::Utf8(ref err) => write!(f, "invalid UTF-8: {err}"), 42 | } 43 | } 44 | } 45 | 46 | impl error::Error for Error { 47 | fn source(&self) -> Option<&(dyn error::Error + 'static)> { 48 | match self { 49 | Error::Custom(_) => None, 50 | Error::Utf8(err) => Some(err), 51 | } 52 | } 53 | 54 | /// The lower-level cause of this error, in the case of a `Utf8` error. 55 | fn cause(&self) -> Option<&dyn error::Error> { 56 | match *self { 57 | Error::Custom(_) => None, 58 | Error::Utf8(ref err) => Some(err), 59 | } 60 | } 61 | } 62 | 63 | impl ser::Error for Error { 64 | fn custom(msg: T) -> Self { 65 | Error::Custom(format!("{msg}").into()) 66 | } 67 | } 68 | 69 | impl de::Error for Error { 70 | fn custom(msg: T) -> Self { 71 | Error::Custom(format!("{msg}").into()) 72 | } 73 | } 74 | 75 | use std::io; 76 | 77 | impl From for io::Error { 78 | fn from(e: Error) -> io::Error { 79 | io::Error::other(format!("cause: {e}")) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/location/part_func_type.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | #[derive(Clone, Debug, PartialEq)] 19 | pub enum PartFuncType { 20 | Unknown = -1, 21 | Hash = 0, 22 | Key = 1, 23 | KeyImplicit = 2, 24 | Range = 3, 25 | RangeColumns = 4, 26 | List = 5, 27 | KeyV2 = 6, 28 | ListColumns = 7, 29 | HashV2 = 8, 30 | KeyV3 = 9, 31 | KeyImplicitV2 = 10, 32 | } 33 | 34 | impl PartFuncType { 35 | pub fn from_i32(index: i32) -> PartFuncType { 36 | match index { 37 | 0 => PartFuncType::Hash, 38 | 1 => PartFuncType::Key, 39 | 2 => PartFuncType::KeyImplicit, 40 | 3 => PartFuncType::Range, 41 | 4 => PartFuncType::RangeColumns, 42 | 5 => PartFuncType::List, 43 | 6 => PartFuncType::KeyV2, 44 | 7 => PartFuncType::ListColumns, 45 | 8 => PartFuncType::HashV2, 46 | 9 => PartFuncType::KeyV3, 47 | 10 => PartFuncType::KeyImplicitV2, 48 | _ => PartFuncType::Unknown, 49 | } 50 | } 51 | 52 | pub fn is_list_part(&self) -> bool { 53 | matches!(self, PartFuncType::List | PartFuncType::ListColumns) 54 | } 55 | 56 | pub fn is_key_part(&self) -> bool { 57 | matches!( 58 | self, 59 | PartFuncType::Key 60 | | PartFuncType::KeyImplicit 61 | | PartFuncType::KeyV2 62 | | PartFuncType::KeyV3 63 | | PartFuncType::KeyImplicitV2 64 | ) 65 | } 66 | 67 | pub fn is_range_part(&self) -> bool { 68 | matches!(self, PartFuncType::Range | PartFuncType::RangeColumns) 69 | } 70 | 71 | pub fn is_hash_part(&self) -> bool { 72 | matches!(self, PartFuncType::Hash | PartFuncType::HashV2) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /tests/utils/common.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | // TODO(xikai): it seems a bug of rust procedural macro that the linter cannot 19 | // see the expanded statements. check the referenced issue: https://github.com/rust-lang/rust/issues/73556 20 | use std::sync::Arc; 21 | 22 | #[allow(unused)] 23 | use obkv::error::CommonErrCode; 24 | use obkv::{Builder, ObTableClient, RunningMode}; 25 | use tokio::task; 26 | 27 | // TODO: use test conf to control which environments to test. 28 | const TEST_FULL_USER_NAME: &str = "test"; 29 | const TEST_URL: &str = "127.0.0.1"; 30 | const TEST_PASSWORD: &str = "test"; 31 | const TEST_SYS_USER_NAME: &str = ""; 32 | const TEST_SYS_PASSWORD: &str = ""; 33 | 34 | pub fn build_client(mode: RunningMode) -> ObTableClient { 35 | let builder = Builder::new() 36 | .full_user_name(TEST_FULL_USER_NAME) 37 | .param_url(TEST_URL) 38 | .running_mode(mode) 39 | .password(TEST_PASSWORD) 40 | .sys_user_name(TEST_SYS_USER_NAME) 41 | .sys_password(TEST_SYS_PASSWORD); 42 | 43 | let client = builder.build(); 44 | 45 | assert!(client.is_ok()); 46 | 47 | let client = client.unwrap(); 48 | client.init().expect("Fail to create obkv client."); 49 | client 50 | } 51 | 52 | pub fn build_hbase_client() -> ObTableClient { 53 | build_client(RunningMode::HBase) 54 | } 55 | 56 | pub fn build_normal_client() -> ObTableClient { 57 | build_client(RunningMode::Normal) 58 | } 59 | 60 | // copy to the test file if needed 61 | pub async fn execute_sql(client: Arc, sql: String) -> obkv::error::Result<()> { 62 | let sql_handle = task::spawn_blocking(move || sync_execute_sql(client, sql)); 63 | sql_handle.await.unwrap() 64 | } 65 | 66 | // copy to the test file if needed 67 | pub fn sync_execute_sql(client: Arc, sql: String) -> obkv::error::Result<()> { 68 | client.execute_sql(&sql) 69 | } 70 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | OBKV Table Client is a community-driven open source project and we welcome all the contributors. Contributions to the ODP project are expected to adhere to our [Code of Conduct](CODE_OF_CONDUCT.md). 4 | 5 | ## Before you contribute 6 | 7 | Before you contribute, please click the **Sign in with Gitub to agree button** to sign the CLA. You can find an example [here](link TODO). 8 | 9 | What is [CLA](https://en.wikipedia.org/wiki/Contributor_License_Agreement)? 10 | 11 | ## Contribution guide 12 | 13 | Please follow these steps to create your Pull Request to this repository. 14 | 15 | > **Note:** 16 | > 17 | > This section takes creating a PR to the `master` branch as an example. The steps of creating PRs for other branches are similar. 18 | 19 | ### Step 1: Fork the repository 20 | 21 | 1. Visit the project (link TODO) 22 | 2. Click the **Fork** button to establish an online fork. 23 | 24 | ### Step 2: Clone your fork to local 25 | 26 | ```bash 27 | # Define your working directory 28 | working_dir=$HOME/Workspace 29 | 30 | # Configure GitHub 31 | user={your Github profile name} 32 | 33 | # Create your clone 34 | mkdir -p $working_dir 35 | cd $working_dir 36 | git clone link TODO 37 | 38 | # Add upstream 39 | git remote add upstream link TODO 40 | 41 | # Set no push for the upstream master 42 | git remote set-url --push upstream no_push 43 | 44 | # Confirm your remote setting 45 | git remote -v 46 | ``` 47 | 48 | ### Step 3: Create a new branch 49 | 50 | 1. Get your local master up-to-date with the upstream/master. 51 | 52 | ```bash 53 | cd $working_dir/docs 54 | git fetch upstream 55 | git checkout master 56 | git rebase upstream/master 57 | ``` 58 | 59 | 2. Create a new branch based on the master branch. 60 | 61 | ```bash 62 | git checkout -b new-branch-name 63 | ``` 64 | 65 | ### Step 4: Develop 66 | 67 | Edit some file(s) on the `new-branch-name` branch and save your changes. 68 | 69 | ### Step 5: Commit your changes 70 | 71 | ```bash 72 | git status # Checks the local status 73 | git add ... # Adds the file(s) you want to commit. If you want to commit all changes, you can directly use `git add.` 74 | git commit -m "commit-message: update the xx" 75 | ``` 76 | 77 | ### Step 6: Keep your branch in sync with upstream/master 78 | 79 | ```bash 80 | # While on your new branch 81 | git fetch upstream 82 | git rebase upstream/master 83 | ``` 84 | 85 | ### Step 7: Push your changes to the remote 86 | 87 | ```bash 88 | git push -u origin new-branch-name # "-u" is used to track the remote branch from the origin 89 | ``` 90 | 91 | ### Step 8: Create a pull request 92 | 93 | 1. Visit your fork at (replace `$user` with your GitHub account). 94 | 2. Click the `Compare & pull request` button next to your `new-branch-name` branch to create your PR. 95 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::{ 19 | sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}, 20 | time::Duration, 21 | }; 22 | 23 | use bytes::BytesMut; 24 | use chrono::Utc; 25 | 26 | use crate::serde_obkv::value::{ObjType, TableObjType, Value}; 27 | 28 | pub mod obversion; 29 | pub mod permit; 30 | pub mod security; 31 | 32 | pub(crate) enum RefreshTunnelMessage { 33 | Data(String), 34 | Exit, 35 | } 36 | 37 | #[inline] 38 | pub fn current_time_millis() -> i64 { 39 | Utc::now().timestamp_millis() 40 | } 41 | 42 | #[inline] 43 | pub fn assert_not_empty(s: &str, msg: &'static str) { 44 | assert!(!s.is_empty(), "{}", msg); 45 | } 46 | 47 | #[inline] 48 | pub fn duration_to_millis(duration: &Duration) -> i64 { 49 | duration.as_secs() as i64 * 1000 50 | } 51 | 52 | #[inline] 53 | pub fn millis_to_secs(t: i64) -> i64 { 54 | t / 1000 55 | } 56 | 57 | // A handy shortcut to replace `RwLock` write/read().unwrap() pattern to 58 | // shortcut wl and rl 59 | // From: https://github.com/tikv/tikv/blob/master/src/util/mod.rs 60 | pub trait HandyRwLock { 61 | fn wl(&self) -> RwLockWriteGuard<'_, T>; 62 | 63 | fn rl(&self) -> RwLockReadGuard<'_, T>; 64 | } 65 | 66 | impl HandyRwLock for RwLock { 67 | #[inline] 68 | fn wl(&self) -> RwLockWriteGuard<'_, T> { 69 | self.write().unwrap() 70 | } 71 | 72 | #[inline] 73 | fn rl(&self) -> RwLockReadGuard<'_, T> { 74 | self.read().unwrap() 75 | } 76 | } 77 | 78 | pub fn string_from_bytes(bs: &[u8]) -> String { 79 | if bs.is_empty() { 80 | return "".to_owned(); 81 | } 82 | match String::from_utf8(bs[0..bs.len() - 1].to_vec()) { 83 | Ok(s) => s, 84 | Err(_e) => "FromUtf8 Error".to_owned(), 85 | } 86 | } 87 | 88 | #[inline] 89 | pub fn decode_value(src: &mut BytesMut) -> std::result::Result { 90 | let obj_type = ObjType::from_u8(*src.first().unwrap())?; 91 | Ok(Value::decode(src, obj_type)?) 92 | } 93 | 94 | #[inline] 95 | pub fn decode_table_value(src: &mut BytesMut) -> std::result::Result { 96 | let obj_type = TableObjType::from_u8(*src.first().unwrap())?; 97 | Ok(Value::table_obj_decode(src, obj_type)?) 98 | } 99 | -------------------------------------------------------------------------------- /ycsb-rs/src/sqlite.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use anyhow::Result; 4 | use sql_builder::SqlBuilder; 5 | use sqlite::{Connection, OpenFlags, State}; 6 | 7 | use crate::db::DB; 8 | 9 | const PRIMARY_KEY: &str = "ycsb_key"; 10 | 11 | pub struct SQLite { 12 | conn: Connection, 13 | } 14 | 15 | impl SQLite { 16 | pub fn new() -> Result { 17 | let flags = OpenFlags::new().set_read_write().set_no_mutex(); 18 | let mut conn = Connection::open_with_flags("test.db", flags)?; 19 | conn.set_busy_timeout(5000)?; 20 | Ok(SQLite { conn }) 21 | } 22 | } 23 | 24 | impl DB for SQLite { 25 | fn init(&self) -> Result<()> { 26 | Ok(()) 27 | } 28 | 29 | fn insert(&self, table: &str, key: &str, values: &HashMap<&str, String>) -> Result<()> { 30 | // TODO: cache prepared statement 31 | let mut sql = SqlBuilder::insert_into(table); 32 | let mut vals: Vec = Vec::new(); 33 | sql.field(PRIMARY_KEY); 34 | vals.push(format!(":{PRIMARY_KEY}")); 35 | for key in values.keys() { 36 | sql.field(key); 37 | let marker = format!(":{key}"); 38 | vals.push(marker); 39 | } 40 | sql.values(&vals); 41 | let sql = sql.sql()?; 42 | let mut stmt = self.conn.prepare(sql)?; 43 | let marker = format!(":{PRIMARY_KEY}"); 44 | stmt.bind_by_name(&marker, key)?; 45 | for (key, value) in values { 46 | let marker = format!(":{key}"); 47 | stmt.bind_by_name(&marker, &value[..])?; 48 | } 49 | let state = stmt.next()?; 50 | assert!(state == State::Done); 51 | Ok(()) 52 | } 53 | 54 | fn read(&self, table: &str, key: &str, result: &mut HashMap) -> Result<()> { 55 | // TODO: cache prepared statement 56 | let mut sql = SqlBuilder::select_from(table); 57 | sql.field("*"); 58 | // TODO: fields 59 | sql.and_where(format!("{PRIMARY_KEY} = :{PRIMARY_KEY}")); 60 | let sql = sql.sql()?; 61 | let mut stmt = self.conn.prepare(sql)?; 62 | let marker = format!(":{PRIMARY_KEY}"); 63 | stmt.bind_by_name(&marker, key)?; 64 | while let State::Row = stmt.next().unwrap() { 65 | for idx in 0..stmt.column_count() { 66 | let key = stmt.column_name(idx); 67 | let value = stmt.read::(idx).unwrap(); 68 | result.insert(key.to_string(), value); 69 | } 70 | } 71 | // TODO: results 72 | Ok(()) 73 | } 74 | 75 | #[allow(unused_variables)] 76 | fn update(&self, table: &str, key: &str, values: &HashMap<&str, String>) -> Result<()> { 77 | todo!() 78 | } 79 | 80 | #[allow(unused_variables)] 81 | fn scan( 82 | &self, 83 | table: &str, 84 | startkey: &str, 85 | endkey: &str, 86 | values: &mut HashMap, 87 | ) -> Result<()> { 88 | todo!() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ycsb-rs/workloads/workloada.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Yahoo! Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you 4 | # may not use this file except in compliance with the License. You 5 | # may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. See the License for the specific language governing 13 | # permissions and limitations under the License. See accompanying 14 | # LICENSE file. 15 | 16 | # Yahoo! Cloud System Benchmark 17 | # Workload A: Update heavy workload 18 | # Application example: Session store recording recent actions 19 | # 20 | # Read/update ratio: 50/50 21 | # Default data size: 1 KB records (10 fields, 100 bytes each, plus key) 22 | # Request distribution: zipfian 23 | 24 | operationcount = 1000 25 | recordcount = 1000 26 | workload = "core" 27 | 28 | readallfields = true 29 | 30 | insertproportion = 0 31 | readproportion = 0.5 32 | scanproportion = 0 33 | updateproportion = 0.5 34 | 35 | requestdistribution = "uniform" 36 | -------------------------------------------------------------------------------- /ycsb-rs/workloads/workloadc.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Yahoo! Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you 4 | # may not use this file except in compliance with the License. You 5 | # may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. See the License for the specific language governing 13 | # permissions and limitations under the License. See accompanying 14 | # LICENSE file. 15 | 16 | # Yahoo! Cloud System Benchmark 17 | # Workload C: Read only 18 | # Application example: user profile cache, where profiles are constructed elsewhere (e.g., Hadoop) 19 | # 20 | # Read/update ratio: 100/0 21 | # Default data size: 1 KB records (10 fields, 100 bytes each, plus key) 22 | # Request distribution: zipfian 23 | 24 | operationcount = 1000 25 | recordcount = 1000 26 | workload = "core" 27 | 28 | readallfields = true 29 | 30 | insertproportion = 0 31 | readproportion = 1 32 | scanproportion = 0 33 | updateproportion = 0 34 | 35 | requestdistribution = "uniform" 36 | -------------------------------------------------------------------------------- /ycsb-rs/src/generator/acknowledged_counter_generator.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{ 2 | atomic::{AtomicBool, AtomicU64, Ordering}, 3 | Mutex, 4 | }; 5 | 6 | use rand::prelude::*; 7 | 8 | use super::{CounterGenerator, Generator}; 9 | 10 | const WINDOW_SIZE: u64 = 1 << 20; 11 | const WINDOW_MASK: u64 = WINDOW_SIZE - 1; 12 | 13 | pub struct AcknowledgedCounterGenerator { 14 | counter: CounterGenerator, 15 | window: Vec, 16 | limit: AtomicU64, 17 | core: Mutex<()>, 18 | } 19 | 20 | impl AcknowledgedCounterGenerator { 21 | pub fn new(count_start: u64) -> Self { 22 | let counter = CounterGenerator::new(count_start); 23 | let mut window = Vec::with_capacity(WINDOW_SIZE as usize); 24 | for _i in 0..WINDOW_SIZE { 25 | window.push(AtomicBool::new(false)); 26 | } 27 | Self { 28 | counter, 29 | window, 30 | limit: AtomicU64::new(count_start - 1), 31 | core: Mutex::new(()), 32 | } 33 | } 34 | 35 | pub fn acknowledge(&self, value: u64) { 36 | let current_slot = value & WINDOW_MASK; 37 | let slot = &self.window[current_slot as usize]; 38 | if slot.swap(true, Ordering::SeqCst) { 39 | panic!("too many unacknowledged requests"); 40 | } 41 | if let Ok(_lock) = self.core.try_lock() { 42 | let limit = self.limit.load(Ordering::SeqCst); 43 | let before_first_slot = limit & WINDOW_MASK; 44 | let mut index = limit + 1; 45 | let new_index = loop { 46 | if index != before_first_slot { 47 | let slot = (index & WINDOW_MASK) as usize; 48 | if !self.window[slot].load(Ordering::SeqCst) { 49 | break index; 50 | } 51 | self.window[slot].store(false, Ordering::SeqCst); 52 | } else { 53 | break index; 54 | } 55 | index += 1; 56 | }; 57 | self.limit.store(new_index - 1, Ordering::SeqCst); 58 | } 59 | } 60 | 61 | pub fn last_value(&self) -> u64 { 62 | self.limit.load(Ordering::SeqCst) 63 | } 64 | } 65 | 66 | impl Generator for AcknowledgedCounterGenerator { 67 | fn next_value(&self, rng: &mut SmallRng) -> u64 { 68 | self.counter.next_value(rng) 69 | } 70 | } 71 | 72 | #[cfg(test)] 73 | mod tests { 74 | use super::*; 75 | 76 | #[test] 77 | fn test_counter() { 78 | let generator = AcknowledgedCounterGenerator::new(1); 79 | let mut rng = SmallRng::from_entropy(); 80 | assert_eq!(generator.next_value(&mut rng), 1); 81 | assert_eq!(generator.last_value(), 0); 82 | assert_eq!(generator.next_value(&mut rng), 2); 83 | assert_eq!(generator.last_value(), 0); 84 | generator.acknowledge(1); 85 | assert_eq!(generator.last_value(), 1); 86 | generator.acknowledge(2); 87 | assert_eq!(generator.last_value(), 2); 88 | generator.acknowledge(1); 89 | assert_eq!(generator.last_value(), 2); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ycsb-rs/workloads/workloadb.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Yahoo! Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you 4 | # may not use this file except in compliance with the License. You 5 | # may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. See the License for the specific language governing 13 | # permissions and limitations under the License. See accompanying 14 | # LICENSE file. 15 | 16 | # Yahoo! Cloud System Benchmark 17 | # Workload B: Read mostly workload 18 | # Application example: photo tagging; add a tag is an update, but most operations are to read tags 19 | # 20 | # Read/update ratio: 95/5 21 | # Default data size: 1 KB records (10 fields, 100 bytes each, plus key) 22 | # Request distribution: zipfian 23 | 24 | operationcount = 1000 25 | recordcount = 1000 26 | workload = "core" 27 | 28 | readallfields = true 29 | 30 | insertproportion = 0 31 | readproportion = 0.95 32 | scanproportion = 0 33 | updateproportion = 0.05 34 | 35 | requestdistribution = "uniform" 36 | -------------------------------------------------------------------------------- /src/monitors/proxy_metrics.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::time::Duration; 19 | 20 | use prometheus_client::{ 21 | encoding::EncodeLabelSet, 22 | metrics::{family::Family, histogram}, 23 | registry::Registry, 24 | }; 25 | 26 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 27 | pub struct ProxyMiscLabels { 28 | pub misc_type: String, 29 | } 30 | 31 | pub struct ProxyMetrics { 32 | proxy_misc: Family, 33 | conn_pool: Family, 34 | } 35 | 36 | impl Default for ProxyMetrics { 37 | fn default() -> Self { 38 | ProxyMetrics { 39 | proxy_misc: Family::::new_with_constructor( 40 | || histogram::Histogram::new(histogram::linear_buckets(5.0, 10.0, 5)), 41 | ), 42 | conn_pool: Family::::new_with_constructor( 43 | || histogram::Histogram::new(histogram::exponential_buckets(0.0001, 2.0, 5)), 44 | ), 45 | } 46 | } 47 | } 48 | 49 | impl ProxyMetrics { 50 | pub fn register(&self, registry: &mut Registry) { 51 | let sub_registry = registry.sub_registry_with_prefix("proxy"); 52 | sub_registry.register( 53 | "system miscellaneous counter histogram ", 54 | "Proxy system miscellaneous counter histogram.", 55 | self.proxy_misc.clone(), 56 | ); 57 | sub_registry.register( 58 | "system connection pool histogram ", 59 | "Proxy system connection pool histogram.", 60 | self.conn_pool.clone(), 61 | ); 62 | } 63 | 64 | pub fn observe_proxy_misc(&self, misc_type: &str, times: f64) { 65 | self.proxy_misc 66 | .get_or_create(&ProxyMiscLabels { 67 | misc_type: misc_type.to_string(), 68 | }) 69 | .observe(times); 70 | } 71 | 72 | pub fn get_proxy_misc(&self) -> &Family { 73 | &self.proxy_misc 74 | } 75 | 76 | pub fn observe_conn_pool_duration(&self, misc_type: &str, duration: Duration) { 77 | self.conn_pool 78 | .get_or_create(&ProxyMiscLabels { 79 | misc_type: misc_type.to_string(), 80 | }) 81 | .observe(duration.as_secs_f64()); 82 | } 83 | 84 | pub fn get_conn_pool(&self) -> &Family { 85 | &self.conn_pool 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/monitors/rpc_metrics.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::time::Duration; 19 | 20 | use prometheus_client::{ 21 | encoding::EncodeLabelSet, 22 | metrics::{family::Family, histogram}, 23 | registry::Registry, 24 | }; 25 | 26 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 27 | pub struct RpcMiscLabels { 28 | pub misc_type: String, 29 | } 30 | 31 | pub struct RpcMetrics { 32 | rpc_operation_duration: Family, 33 | rpc_misc: Family, 34 | } 35 | 36 | impl Default for RpcMetrics { 37 | fn default() -> Self { 38 | RpcMetrics { 39 | rpc_operation_duration: 40 | Family::::new_with_constructor(|| { 41 | histogram::Histogram::new(histogram::exponential_buckets(0.0005, 2.0, 8)) 42 | }), 43 | rpc_misc: Family::::new_with_constructor(|| { 44 | histogram::Histogram::new(histogram::linear_buckets(5.0, 5.0, 10)) 45 | }), 46 | } 47 | } 48 | } 49 | 50 | impl RpcMetrics { 51 | pub fn register(&self, registry: &mut Registry) { 52 | let sub_registry = registry.sub_registry_with_prefix("rpc"); 53 | sub_registry.register( 54 | "system operation duration ", 55 | "RPC system operation latency (duration time) in seconds.", 56 | self.rpc_operation_duration.clone(), 57 | ); 58 | sub_registry.register( 59 | "system miscellaneous counter histogram ", 60 | "RPC system miscellaneous counter histogram.", 61 | self.rpc_misc.clone(), 62 | ); 63 | } 64 | 65 | pub fn observe_rpc_duration(&self, misc_type: &str, duration: Duration) { 66 | self.rpc_operation_duration 67 | .get_or_create(&RpcMiscLabels { 68 | misc_type: misc_type.to_string(), 69 | }) 70 | .observe(duration.as_secs_f64()); 71 | } 72 | 73 | pub fn get_rpc_operation_duration(&self) -> &Family { 74 | &self.rpc_operation_duration 75 | } 76 | 77 | pub fn observe_rpc_misc(&self, misc_type: &str, times: f64) { 78 | self.rpc_misc 79 | .get_or_create(&RpcMiscLabels { 80 | misc_type: misc_type.to_string(), 81 | }) 82 | .observe(times); 83 | } 84 | 85 | pub fn get_rpc_misc(&self) -> &Family { 86 | &self.rpc_misc 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | #- 2 | # #%L 3 | # OBKV Table Client Framework 4 | # %% 5 | # Copyright (C) 2021 OceanBase 6 | # %% 7 | # OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | # You can use this software according to the terms and conditions of the Mulan PSL v2. 9 | # You may obtain a copy of Mulan PSL v2 at: 10 | # http://license.coscl.org.cn/MulanPSL2 11 | # THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, 12 | # EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, 13 | # MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | # See the Mulan PSL v2 for more details. 15 | # #L% 16 | 17 | name: CI 18 | 19 | on: 20 | workflow_dispatch: 21 | push: 22 | branches: 23 | - main 24 | paths-ignore: 25 | - 'docs/**' 26 | - 'etc/**' 27 | - '**.md' 28 | pull_request: 29 | branches: 30 | - main 31 | paths-ignore: 32 | - 'docs/**' 33 | - 'etc/**' 34 | - '**.md' 35 | 36 | # Common environment variables 37 | env: 38 | RUSTFLAGS: "-C debuginfo=1" 39 | CARGO_TERM_COLOR: always 40 | RUST_BACKTRACE: "1" 41 | LOCK_FILE: Cargo.lock 42 | RUST_VERSION: stable 43 | 44 | jobs: 45 | style-check: 46 | name: style-check 47 | runs-on: ubuntu-latest 48 | timeout-minutes: 60 49 | steps: 50 | - uses: actions/checkout@v3 51 | with: 52 | submodules: true 53 | - name: Cache Rust Dependencies 54 | uses: actions/cache@v3 55 | with: 56 | path: | 57 | ~/.cargo 58 | ./target 59 | key: debug-${{ runner.os }}-${{ hashFiles('rust-toolchain') }}-${{ hashFiles('Cargo.lock') }} 60 | restore-keys: | 61 | debug-${{ runner.os }}-${{ hashFiles('rust-toolchain') }}- 62 | debug-${{ runner.os }}- 63 | debug- 64 | - name: Install cargo-sort 65 | run: | 66 | cargo install cargo-sort 67 | - name: Run Style Check 68 | run: | 69 | make clippy 70 | make fmt 71 | make check-cargo-toml 72 | 73 | unit-test: 74 | name: unit-test 75 | runs-on: ubuntu-latest 76 | timeout-minutes: 60 77 | strategy: 78 | matrix: 79 | rust: [ stable ] 80 | steps: 81 | - uses: actions/checkout@v3 82 | with: 83 | submodules: true 84 | - name: Cache Rust Dependencies 85 | uses: actions/cache@v3 86 | with: 87 | path: | 88 | ~/.cargo 89 | ./target 90 | key: debug-${{ runner.os }}-${{ hashFiles('rust-toolchain') }}-${{ hashFiles('Cargo.lock') }} 91 | restore-keys: | 92 | debug-${{ runner.os }}-${{ hashFiles('rust-toolchain') }}- 93 | debug-${{ runner.os }}- 94 | debug- 95 | - name: Backup Lock File 96 | run: | 97 | cp ${LOCK_FILE} ${LOCK_FILE}.bak 98 | - name: Run Unit Tests 99 | run: | 100 | make test-ut 101 | echo "Checking if ${LOCK_FILE} has changed..." 102 | - name: Check Lock File 103 | run: | 104 | diff ${LOCK_FILE} ${LOCK_FILE}.bak 105 | -------------------------------------------------------------------------------- /src/location/ob_part_constants.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | pub const OB_PART_IDS_BITNUM: i32 = 28; 19 | pub const OB_PART_ID_SHIFT: i32 = 32; 20 | pub const OB_TWOPART_BEGIN_MASK: i64 = 21 | (1i64 << OB_PART_IDS_BITNUM) | (1i64 << (OB_PART_IDS_BITNUM + OB_PART_ID_SHIFT)); 22 | 23 | #[inline] 24 | pub fn generate_phy_part_id(part_idx: i64, sub_part_idx: i64) -> i64 { 25 | if part_idx < 0 || sub_part_idx < 0 { 26 | -1 27 | } else { 28 | ((part_idx << OB_PART_ID_SHIFT) | sub_part_idx) | OB_TWOPART_BEGIN_MASK 29 | } 30 | } 31 | 32 | // get subpart_id with PARTITION_LEVEL_TWO_MASK 33 | #[inline] 34 | pub fn extract_subpart_id(id: i64) -> i64 { 35 | id & (!(u64::MAX << OB_PART_ID_SHIFT)) as i64 36 | } 37 | 38 | // get part_id with PARTITION_LEVEL_TWO_MASK 39 | #[inline] 40 | pub fn extract_part_id(id: i64) -> i64 { 41 | id >> OB_PART_ID_SHIFT 42 | } 43 | 44 | // get part idx from one level partid 45 | #[inline] 46 | pub fn extract_idx_from_partid(id: i64) -> i64 { 47 | id & (!(u64::MAX << OB_PART_IDS_BITNUM)) as i64 48 | } 49 | 50 | // get part space from one level partid 51 | #[allow(dead_code)] 52 | #[inline] 53 | pub fn extract_space_from_partid(id: i64) -> i64 { 54 | id >> OB_PART_IDS_BITNUM 55 | } 56 | 57 | // get part_idx 58 | #[inline] 59 | pub fn extract_part_idx(id: i64) -> i64 { 60 | extract_idx_from_partid(extract_part_id(id)) 61 | } 62 | 63 | // get sub_part_idx 64 | #[inline] 65 | pub fn extract_subpart_idx(id: i64) -> i64 { 66 | extract_idx_from_partid(extract_subpart_id(id)) 67 | } 68 | 69 | #[cfg(test)] 70 | mod test { 71 | use crate::location::ob_part_constants::{ 72 | extract_part_idx, extract_space_from_partid, extract_subpart_idx, generate_phy_part_id, 73 | }; 74 | 75 | #[test] 76 | pub fn generate_phy_part_id_test() { 77 | let mut n = generate_phy_part_id(23, 0); 78 | assert_eq!(n, 1152921603659530240); 79 | n = generate_phy_part_id(23, -1); 80 | assert_eq!(n, -1); 81 | } 82 | 83 | #[test] 84 | pub fn extract_space_from_partid_test() { 85 | let n = extract_space_from_partid(1152921603659530240); 86 | assert_eq!(n, 4294967665); 87 | } 88 | 89 | #[test] 90 | pub fn extract_subpart_idx_test() { 91 | let n = extract_subpart_idx(1152921603659530240); 92 | assert_eq!(n, 0); 93 | } 94 | 95 | #[test] 96 | pub fn extract_part_idx_test() { 97 | let n = extract_part_idx(1152921603659530240); 98 | assert_eq!(n, 23); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ycsb-rs/workloads/workloadf.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Yahoo! Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you 4 | # may not use this file except in compliance with the License. You 5 | # may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. See the License for the specific language governing 13 | # permissions and limitations under the License. See accompanying 14 | # LICENSE file. 15 | 16 | # Yahoo! Cloud System Benchmark 17 | # Workload F: Read-modify-write workload 18 | # Application example: user database, where user records are read and modified by the user or to record user activity. 19 | # 20 | # Read/read-modify-write ratio: 50/50 21 | # Default data size: 1 KB records (10 fields, 100 bytes each, plus key) 22 | # Request distribution: zipfian 23 | 24 | operationcount = 1000 25 | recordcount = 1000 26 | workload = "core" 27 | 28 | readallfields = true 29 | 30 | insertproportion = 0 31 | readmodifywriteproportion = 0.5 32 | readproportion = 0.5 33 | scanproportion = 0 34 | updateproportion = 0 35 | 36 | requestdistribution = "uniform" 37 | -------------------------------------------------------------------------------- /docs/simple_demo/simple_operation/aggregationDemo.md: -------------------------------------------------------------------------------- 1 | # Demo for obkv-table-client-rs 2 | Edited by OBKV developers on July 11, 2023. 3 | 4 | ## Introduction 5 | obkv-table-client-rs is Rust Library that can access table data from OceanBase storage layer. 6 | Now we provide an interface to access data from OceanBase, which we will introduce in this document. 7 | 8 | ***Notice that we will also provide another interface to access data from OceanBase in the future(Like [Mutation](https://github.com/oceanbase/obkv-table-client-java/tree/master/example/simple-mutation)).*** 9 | 10 | ## Notice 11 | **We only support aggregate on a single partition now. Aggregation across partitions may lead to consistency problems. You can limit your aggregation in one partition by restricting your scan range. 12 | 13 | Also, result of aggregate on a empty table may have different result between different observer. We will fix this problem later. For your convenience, you'd better not to aggregate empty table before we fix this problem.** 14 | 15 | ## demo 16 | ### Aggregate 17 | Aggregate allows the user to get a range of data for ```max()```、```min()```、```count()```、```sum()```、```avg()```. 18 | 19 | An **Aggregate** could get from **ObTableClient** by calling ```aggregate()``` method, then you could customize your aggregate by calling methods in **ObTableAggregation**. 20 | ```rust ObTableAggregation 21 | impl ObTableAggregation { 22 | pub fn max(mut self, column_name: String) -> Self {} 23 | // ... 24 | pub fn avg(mut self, column_name: String) -> Self {} 25 | } 26 | ``` 27 | The **column_name** is the name of the column you want to aggregate. 28 | 29 | For example, you can use `max(c1)` to get the aggregation result of the maxinum of the column `c1` 30 | 31 | The aggregation result is a **hash map**, you can get the single aggregation result by calling the method `get()` of the hash map with **operation_name** 32 | 33 | For example, you can use `get(max(c1))` to get the aggregation result, the string `"max(c1)"` is the **operation_name** 34 | 35 | You could also get the aggregation result by local index. For example, you can use `index_name(idx1)` to use the idx1 to get the aggregation result. 36 | 37 | A simple aggregate example is shown below: 38 | ```rust aggregate exampleObTableAggregation 39 | async fn aggregate() { 40 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 41 | let client = client_handle.await.unwrap(); 42 | 43 | 44 | let aggregation = client 45 | .aggregate("your_table_name") 46 | .min("c2".to_owned()) 47 | .add_scan_range(vec![Value::from(200i32)], true, vec![Value::from(200i32)], true) 48 | .index_name("your_index_name"); 49 | 50 | // get result 51 | let result_set = aggregation.execute().await; 52 | 53 | assert!(result_set.is_ok()); 54 | let result_set = result_set.unwrap(); 55 | 56 | let single_result = result_set.get("min(c2)"); 57 | match single_result { 58 | Some(singel_value) => { 59 | assert_eq!(150, singel_value.as_i64()); 60 | } 61 | _ => assert!(false) 62 | } 63 | } 64 | ``` 65 | More demos can be found in [test](https://github.com/oceanbase/obkv-table-client-rs/blob/main/tests/test_table_client_aggregation.rs). 66 | -------------------------------------------------------------------------------- /src/rpc/proxy.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::sync::Arc; 19 | 20 | use super::{conn_pool::ConnPool, protocol::ObPayload}; 21 | use crate::{ 22 | error::Result, 23 | monitors::{prometheus::OBKV_CLIENT_REGISTRY, proxy_metrics::ProxyMetrics}, 24 | }; 25 | 26 | lazy_static! { 27 | pub static ref OBKV_PROXY_METRICS: ProxyMetrics = { 28 | let proxy_metrics = ProxyMetrics::default(); 29 | proxy_metrics.register(&mut OBKV_CLIENT_REGISTRY.lock().unwrap().registry); 30 | proxy_metrics 31 | }; 32 | } 33 | 34 | #[derive(Clone)] 35 | pub struct Proxy(Arc); 36 | 37 | impl Proxy { 38 | pub fn new(conn_pool: Arc) -> Self { 39 | Proxy(conn_pool) 40 | } 41 | 42 | pub async fn execute( 43 | &self, 44 | payload: &mut T, 45 | response: &mut R, 46 | ) -> Result<()> { 47 | // the connection is ensured to be active now by checking conn.is_active 48 | // but it may be actually broken already. 49 | let conn = self.0.get()?; 50 | 51 | OBKV_PROXY_METRICS.observe_proxy_misc("conn_load", conn.load() as f64); 52 | 53 | let res = conn.execute(payload, response).await; 54 | if res.is_ok() || conn.is_active() { 55 | return res; 56 | } 57 | 58 | let mut retry_cnt = 0; 59 | // retry until all the idle connections are consumed and then a brand new 60 | // connection is built or an intact connection is taken because all the 61 | // connections may be broken together 62 | let retry_limit = self.0.idle_conn_num() + 1; 63 | 64 | OBKV_PROXY_METRICS.observe_proxy_misc("retry_idle_conns", (retry_limit - 1) as f64); 65 | 66 | let mut err = res.err().unwrap(); 67 | 68 | loop { 69 | retry_cnt += 1; 70 | if retry_cnt > retry_limit { 71 | OBKV_PROXY_METRICS.observe_proxy_misc("retry_times", retry_cnt as f64); 72 | error!( 73 | "Proxy::execute reach the retry limit:{}, err:{}", 74 | retry_limit, err 75 | ); 76 | return Err(err); 77 | } 78 | debug!( 79 | "Proxy::execute retry {} because connection broken, err:{}", 80 | retry_cnt, err 81 | ); 82 | 83 | let conn = self.0.get()?; 84 | let res = conn.execute(payload, response).await; 85 | if res.is_ok() || conn.is_active() { 86 | OBKV_PROXY_METRICS.observe_proxy_misc("retry_times", retry_cnt as f64); 87 | return res; 88 | } 89 | err = res.err().unwrap(); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ycsb-rs/workloads/workloadd.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Yahoo! Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you 4 | # may not use this file except in compliance with the License. You 5 | # may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. See the License for the specific language governing 13 | # permissions and limitations under the License. See accompanying 14 | # LICENSE file. 15 | 16 | # Yahoo! Cloud System Benchmark 17 | # Workload D: Read latest workload 18 | # Application example: user status updates; people want to read the latest 19 | # 20 | # Read/update/insert ratio: 95/0/5 21 | # Default data size: 1 KB records (10 fields, 100 bytes each, plus key) 22 | # Request distribution: latest 23 | 24 | # The insert order for this is hashed, not ordered. The "latest" items may be 25 | # scattered around the keyspace if they are keyed by userid.timestamp. A workload 26 | # which orders items purely by time, and demands the latest, is very different than 27 | # workload here (which we believe is more typical of how people build systems.) 28 | 29 | operationcount = 1000 30 | recordcount = 1000 31 | workload = "core" 32 | 33 | readallfields = true 34 | 35 | insertproportion = 0.05 36 | readproportion = 0.95 37 | scanproportion = 0 38 | updateproportion = 0 39 | 40 | requestdistribution = "latest" 41 | -------------------------------------------------------------------------------- /src/util/security.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | /// Password security module 19 | use std::cell::RefCell; 20 | use std::num::Wrapping; 21 | 22 | use sha1::{Digest, Sha1}; 23 | 24 | use super::current_time_millis; 25 | 26 | const BYTES: &[char] = &[ 27 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 28 | 'p', 'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', 'z', 'x', 'c', 'v', 'b', 'n', 'm', 'Q', 'W', 29 | 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', 'Z', 'X', 30 | 'C', 'V', 'B', 'N', 'M', 31 | ]; 32 | const MULTIPLIER: i64 = 0x0005_DEEC_E66D; 33 | const ADDEND: i64 = 0xB; 34 | const MASK: i64 = (1 << 48) - 1; 35 | const INTEGER_MASK: i64 = (1 << 33) - 1; 36 | const SEED_UNIQUIFIER: i64 = 8_682_522_807_148_012; 37 | 38 | thread_local!( 39 | static SEED: RefCell = RefCell::new(((current_time_millis() + SEED_UNIQUIFIER) ^ MULTIPLIER) & MASK) 40 | ); 41 | 42 | pub fn get_password_scramble(capacity: usize) -> String { 43 | let mut ret = String::with_capacity(capacity); 44 | 45 | for _ in 0..capacity { 46 | ret.push(rand_char()); 47 | } 48 | 49 | ret 50 | } 51 | 52 | fn rand_char() -> char { 53 | let ran = ((random() & INTEGER_MASK) >> 16) as usize; 54 | BYTES[ran % BYTES.len()].to_owned() 55 | } 56 | 57 | fn random() -> i64 { 58 | SEED.with(|seed| { 59 | let old_seed = Wrapping(*seed.borrow()); 60 | let mut next_seed; 61 | loop { 62 | next_seed = (old_seed * Wrapping(MULTIPLIER) + Wrapping(ADDEND)) & Wrapping(MASK); 63 | if old_seed != next_seed { 64 | break; 65 | } 66 | } 67 | 68 | let ret = next_seed.0; 69 | *seed.borrow_mut() = ret; 70 | ret 71 | }) 72 | } 73 | 74 | pub fn scramble_password(password: &str, seed: &str) -> Vec { 75 | if password.is_empty() { 76 | return vec![]; 77 | } 78 | 79 | let mut hasher = Sha1::new(); 80 | hasher.update(password); 81 | let pass1 = hasher.finalize_reset(); 82 | hasher.update(pass1); 83 | let pass2 = hasher.finalize_reset(); 84 | hasher.update(seed); 85 | hasher.update(pass2); 86 | let mut pass3 = hasher.finalize_reset(); 87 | 88 | for i in 0..pass3.len() { 89 | pass3[i] ^= pass1[i]; 90 | } 91 | pass3.to_vec() 92 | } 93 | 94 | #[cfg(test)] 95 | mod test { 96 | use super::*; 97 | 98 | #[test] 99 | fn test_get_password_scramble() { 100 | for i in 1..20 { 101 | let s = get_password_scramble(i); 102 | assert_eq!(i, s.len()); 103 | } 104 | } 105 | 106 | #[test] 107 | fn test_scramble_password() { 108 | let password = "hello"; 109 | let seed = "qXA4YhW5PwaWsARvj3KC"; 110 | 111 | let s_pass = scramble_password(password, seed); 112 | 113 | assert_eq!(20, s_pass.len()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /ycsb-rs/workloads/workloade.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2010 Yahoo! Inc. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you 4 | # may not use this file except in compliance with the License. You 5 | # may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. See the License for the specific language governing 13 | # permissions and limitations under the License. See accompanying 14 | # LICENSE file. 15 | 16 | # Yahoo! Cloud System Benchmark 17 | # Workload E: Short ranges 18 | # Application example: threaded conversations, where each scan is for the posts in a given thread (assumed to be clustered by thread id) 19 | # 20 | # Scan/insert ratio: 95/5 21 | # Default data size: 1 KB records (10 fields, 100 bytes each, plus key) 22 | # Request distribution: zipfian 23 | 24 | # The insert order is hashed, not ordered. Although the scans are ordered, it does not necessarily 25 | # follow that the data is inserted in order. For example, posts for thread 342 may not be inserted contiguously, but 26 | # instead interspersed with posts from lots of other threads. The way the YCSB client works is that it will pick a start 27 | # key, and then request a number of records; this works fine even for hashed insertion. 28 | 29 | operationcount = 1000 30 | recordcount = 1000 31 | workload = "core" 32 | 33 | readallfields = true 34 | 35 | insertproportion = 0.05 36 | readproportion = 0 37 | scanproportion = 0.95 38 | updateproportion = 0 39 | 40 | requestdistribution = "uniform" 41 | 42 | maxscanlength = 1 43 | 44 | scanlengthdistribution = "uniform" 45 | -------------------------------------------------------------------------------- /benches/concurrent_insert/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | extern crate obkv; 19 | 20 | use std::{sync::Arc, time}; 21 | 22 | use obkv::{serde_obkv::value::Value, Builder, ObTableClient, RunningMode}; 23 | use tokio::task; 24 | 25 | // TODO: use test conf to control which environments to test. 26 | const TEST_FULL_USER_NAME: &str = "test"; 27 | const TEST_URL: &str = "127.0.0.1"; 28 | const TEST_PASSWORD: &str = ""; 29 | const TEST_SYS_USER_NAME: &str = ""; 30 | const TEST_SYS_PASSWORD: &str = ""; 31 | 32 | fn build_client(mode: RunningMode) -> ObTableClient { 33 | let builder = Builder::new() 34 | .full_user_name(TEST_FULL_USER_NAME) 35 | .param_url(TEST_URL) 36 | .running_mode(mode) 37 | .password(TEST_PASSWORD) 38 | .sys_user_name(TEST_SYS_USER_NAME) 39 | .sys_password(TEST_SYS_PASSWORD); 40 | 41 | let client = builder.build(); 42 | 43 | assert!(client.is_ok()); 44 | 45 | let client = client.unwrap(); 46 | client.init().expect("Fail to create obkv client."); 47 | client 48 | } 49 | 50 | const TABLE_NAME: &str = "series_key_to_id_0"; 51 | // read and write the table: 52 | // create table series_key_to_id_0 ( 53 | // series_key VARBINARY(8096) NOT NULL, 54 | // series_id BIGINT NOT NULL, 55 | // PRIMARY KEY(series_key), 56 | // KEY index_id(series_id) 57 | // ); 58 | async fn concurrent_insert(client: Arc) { 59 | let mut thds = Vec::with_capacity(20); 60 | for i in 0..50 { 61 | let client = client.clone(); 62 | let thd = task::spawn(async move { 63 | for j in i * 100..(i * 100 + 50) { 64 | let series_key = format!("series_key_test_padding_padding_{j}"); 65 | let series_id = j * j; 66 | client 67 | .insert( 68 | TABLE_NAME, 69 | vec![Value::from(series_key.clone())], 70 | vec!["series_id".to_owned()], 71 | vec![Value::from(series_id as i64)], 72 | ) 73 | .await 74 | .unwrap_or_else(|err| { 75 | panic!("fail to insert row:{series_key} {series_id}, err:{err}") 76 | }); 77 | } 78 | }); 79 | thds.push(thd); 80 | } 81 | 82 | for (i, thd) in thds.into_iter().enumerate() { 83 | thd.await 84 | .unwrap_or_else(|_| panic!("thread#{i} fail to join")); 85 | } 86 | } 87 | 88 | #[tokio::main] 89 | async fn main() { 90 | let client_handle = task::spawn_blocking(|| build_client(RunningMode::Normal)); 91 | let client = client_handle.await.unwrap(); 92 | client 93 | .truncate_table(TABLE_NAME) 94 | .expect("fail to truncate the table"); 95 | let start = time::Instant::now(); 96 | concurrent_insert(Arc::new(client)).await; 97 | let elapsed = time::Instant::now() - start; 98 | println!("Benches::concurrent_insert cost time:{elapsed:?}"); 99 | } 100 | -------------------------------------------------------------------------------- /src/util/permit.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::sync::Arc; 19 | 20 | use spin::Mutex; 21 | 22 | use crate::error::{CommonErrCode, Error::Common as CommonErr, Result}; 23 | 24 | pub struct Permits { 25 | permits: Arc>, 26 | max: usize, 27 | } 28 | 29 | pub struct PermitGuard(Arc>, usize); 30 | 31 | impl Permits { 32 | pub fn new(max: usize) -> Permits { 33 | Permits { 34 | permits: Arc::new(Mutex::new(0)), 35 | max, 36 | } 37 | } 38 | 39 | pub fn acquire(&self) -> Result { 40 | let mut p = self.permits.lock(); 41 | 42 | if *p >= self.max { 43 | return Err(CommonErr( 44 | CommonErrCode::PermitDenied, 45 | format!("Fail to acquire a permit, max: {}", self.max), 46 | )); 47 | } 48 | 49 | *p += 1; 50 | 51 | Ok(PermitGuard(self.permits.clone(), *p)) 52 | } 53 | } 54 | 55 | impl PermitGuard { 56 | #[inline] 57 | pub fn permit(&self) -> usize { 58 | self.1 59 | } 60 | } 61 | 62 | impl Drop for PermitGuard { 63 | #[inline] 64 | fn drop(&mut self) { 65 | *self.0.lock() -= 1; 66 | } 67 | } 68 | 69 | #[cfg(test)] 70 | mod test { 71 | use std::thread; 72 | 73 | use super::*; 74 | 75 | #[test] 76 | fn test_permits() { 77 | let permits = Permits::new(3); 78 | 79 | let guard1 = permits.acquire().expect("Fail to acquire a permit."); 80 | assert_eq!(1, guard1.permit()); 81 | let guard2 = permits.acquire().expect("Fail to acquire a permit."); 82 | assert_eq!(2, guard2.permit()); 83 | let guard3 = permits.acquire().expect("Fail to acquire a permit."); 84 | assert_eq!(3, guard3.permit()); 85 | 86 | assert!(permits.acquire().is_err()); 87 | 88 | drop(guard1); 89 | let guard4 = permits.acquire(); 90 | assert!(guard4.is_ok()); 91 | let guard4 = guard4.unwrap(); 92 | assert_eq!(3, guard4.permit()); 93 | 94 | let guard5 = permits.acquire(); 95 | assert!(guard5.is_err()); 96 | assert_eq!(3, *permits.permits.lock()); 97 | 98 | drop(guard2); 99 | drop(guard3); 100 | drop(guard4); 101 | assert_eq!(0, *permits.permits.lock()); 102 | } 103 | 104 | #[test] 105 | fn test_acquire_concurrently() { 106 | let permits = Arc::new(Permits::new(3)); 107 | 108 | let mut handles = Vec::with_capacity(10); 109 | for _ in 0..10 { 110 | let permits = permits.clone(); 111 | handles.push(thread::spawn(move || { 112 | for _ in 0..10 { 113 | let g = permits.acquire(); 114 | if let Ok(g) = g { 115 | assert!(g.permit() > 0); 116 | } 117 | } 118 | })); 119 | } 120 | 121 | for h in handles { 122 | assert!(h.join().is_ok()); 123 | } 124 | 125 | assert_eq!(0, *permits.permits.lock()); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /tests/test_table_client_sql.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | #[allow(unused_imports)] 19 | #[allow(unused)] 20 | mod utils; 21 | 22 | use obkv::{ObTableClient, Value}; 23 | use tokio::task; 24 | 25 | #[tokio::test] 26 | async fn test_execute_sql() { 27 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 28 | let client = client_handle.await.unwrap(); 29 | let test_table_name = "test_execute_sql"; 30 | let create_table = 31 | format!("create table IF NOT EXISTS {test_table_name}(id int, PRIMARY KEY(id));"); 32 | client 33 | .execute_sql(&create_table) 34 | .expect("fail to create table"); 35 | } 36 | 37 | #[tokio::test] 38 | async fn test_check_table_exists() { 39 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 40 | let client = client_handle.await.unwrap(); 41 | let test_table_name = "test_check_table_exists"; 42 | let drop_table = format!("drop table IF EXISTS {test_table_name};"); 43 | 44 | client 45 | .execute_sql(&drop_table) 46 | .expect("fail to create table"); 47 | 48 | let exists = client 49 | .check_table_exists(test_table_name) 50 | .expect("fail to check table exists"); 51 | assert!(!exists, "should not exists"); 52 | 53 | let create_table = 54 | format!("create table IF NOT EXISTS {test_table_name}(id int, PRIMARY KEY(id));"); 55 | 56 | client 57 | .execute_sql(&create_table) 58 | .expect("fail to create table"); 59 | 60 | let exists = client 61 | .check_table_exists(test_table_name) 62 | .expect("fail to check table exists"); 63 | assert!(exists, "should exists"); 64 | } 65 | 66 | async fn truncate_table(client: &ObTableClient, test_table_name: &str) { 67 | client 68 | .truncate_table(test_table_name) 69 | .expect("Fail to truncate first test table"); 70 | 71 | let result = client 72 | .get( 73 | test_table_name, 74 | vec![Value::from("foo")], 75 | vec!["c2".to_owned()], 76 | ) 77 | .await 78 | .expect("Fail to get row"); 79 | assert!(result.is_empty()); 80 | 81 | let result = client 82 | .insert( 83 | test_table_name, 84 | vec![Value::from("foo")], 85 | vec!["c2".to_owned()], 86 | vec![Value::from("bar")], 87 | ) 88 | .await 89 | .expect("Fail to insert row"); 90 | assert_eq!(result, 1); 91 | 92 | client 93 | .truncate_table(test_table_name) 94 | .expect("Fail to truncate first test table"); 95 | 96 | let result = client 97 | .get( 98 | test_table_name, 99 | vec![Value::from("foo")], 100 | vec!["c2".to_owned()], 101 | ) 102 | .await 103 | .expect("Fail to get row"); 104 | assert!(result.is_empty()); 105 | } 106 | 107 | #[tokio::test] 108 | async fn test_truncate_table() { 109 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 110 | let client = client_handle.await.unwrap(); 111 | let test_table_name = "test_varchar_table"; 112 | 113 | for _ in 0..1 { 114 | truncate_table(&client, test_table_name).await; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/rpc/protocol/partition/ob_partition_key.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::cmp::Ordering; 19 | 20 | use crate::serde_obkv::value::Value; 21 | 22 | #[derive(Debug, Clone)] 23 | pub enum Comparable { 24 | MaxValue, 25 | MinValue, 26 | Value(Value), 27 | } 28 | 29 | impl PartialOrd for Comparable { 30 | fn partial_cmp(&self, other: &Comparable) -> Option { 31 | match &self { 32 | Comparable::MaxValue => Some(Ordering::Greater), 33 | Comparable::MinValue => Some(Ordering::Less), 34 | Comparable::Value(v) => match other { 35 | Comparable::MaxValue => Some(Ordering::Less), 36 | Comparable::MinValue => Some(Ordering::Greater), 37 | Comparable::Value(o) => v.partial_cmp(o), 38 | }, 39 | } 40 | } 41 | } 42 | 43 | impl PartialEq for Comparable { 44 | fn eq(&self, other: &Comparable) -> bool { 45 | match &self { 46 | Comparable::MaxValue => false, 47 | Comparable::MinValue => false, 48 | Comparable::Value(v) => match other { 49 | Comparable::MaxValue => false, 50 | Comparable::MinValue => false, 51 | Comparable::Value(o) => v == o, 52 | }, 53 | } 54 | } 55 | } 56 | 57 | #[derive(Debug, Clone)] 58 | pub struct ObPartitionKey { 59 | partition_elements: Vec, 60 | } 61 | 62 | impl ObPartitionKey { 63 | pub fn new(partition_elements: Vec) -> Self { 64 | Self { partition_elements } 65 | } 66 | } 67 | 68 | impl PartialOrd for ObPartitionKey { 69 | fn partial_cmp(&self, that: &ObPartitionKey) -> Option { 70 | if self.partition_elements.len() != that.partition_elements.len() { 71 | return None; 72 | } 73 | for i in 0..self.partition_elements.len() { 74 | if self.partition_elements[i] == that.partition_elements[i] { 75 | continue; 76 | } 77 | if self.partition_elements[i] == Comparable::MaxValue 78 | || that.partition_elements[i] == Comparable::MinValue 79 | { 80 | return Some(Ordering::Greater); 81 | } 82 | 83 | if that.partition_elements[i] == Comparable::MaxValue 84 | || self.partition_elements[i] == Comparable::MinValue 85 | { 86 | return Some(Ordering::Less); 87 | } 88 | // TODO: ObCollationType 89 | // if self.order_part_columns[i].get_ob_collation_type() == 90 | // CollationType::UTF8MB4GeneralCi { let tmp_ret = 91 | // self.partition_elements[i].partial_cmp(&that.partition_elements[i]); 92 | // } else { 93 | let tmp_ret = self.partition_elements[i].partial_cmp(&that.partition_elements[i]); 94 | // } 95 | if tmp_ret != Some(Ordering::Equal) { 96 | return tmp_ret; 97 | } 98 | } 99 | Some(Ordering::Equal) 100 | } 101 | } 102 | 103 | impl PartialEq for ObPartitionKey { 104 | fn eq(&self, that: &ObPartitionKey) -> bool { 105 | if self.partition_elements.len() != that.partition_elements.len() { 106 | return false; 107 | } 108 | for i in 0..self.partition_elements.len() { 109 | if self.partition_elements[i] == that.partition_elements[i] { 110 | continue; 111 | } 112 | if self.partition_elements[i] == Comparable::MaxValue 113 | || that.partition_elements[i] == Comparable::MinValue 114 | { 115 | return false; 116 | } 117 | 118 | if that.partition_elements[i] == Comparable::MaxValue 119 | || self.partition_elements[i] == Comparable::MinValue 120 | { 121 | return false; 122 | } 123 | // TODO: ObCollationType 124 | let tmp_ret = self.partition_elements[i].partial_cmp(&that.partition_elements[i]); 125 | if tmp_ret != Some(Ordering::Equal) { 126 | return false; 127 | } 128 | } 129 | true 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/client/mod.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::{collections::HashMap, time::Duration}; 19 | 20 | use crate::{rpc::protocol::DEFAULT_FLAG, serde_obkv::value::Value}; 21 | 22 | pub mod filter; 23 | mod ocp; 24 | pub mod query; 25 | pub mod table; 26 | pub mod table_client; 27 | 28 | use self::table::ObTable; 29 | 30 | #[derive(Clone, Debug)] 31 | pub enum TableOpResult { 32 | AffectedRows(i64), 33 | RetrieveRows(HashMap), 34 | } 35 | 36 | /// ObTable client config 37 | #[derive(Clone, Debug, Eq, PartialEq)] 38 | pub struct ClientConfig { 39 | pub sys_user_name: String, 40 | pub sys_password: String, 41 | 42 | pub metadata_mysql_conn_pool_min_size: usize, 43 | pub metadata_mysql_conn_pool_max_size: usize, 44 | pub metadata_refresh_interval: Duration, 45 | pub ocp_model_cache_file: String, 46 | 47 | pub rslist_acquire_timeout: Duration, 48 | pub rslist_acquire_try_times: usize, 49 | pub rslist_acquire_retry_interval: Duration, 50 | 51 | pub table_entry_acquire_connect_timeout: Duration, 52 | pub table_entry_acquire_read_timeout: Duration, 53 | 54 | pub table_entry_refresh_interval_base: Duration, 55 | pub table_entry_refresh_interval_ceiling: Duration, 56 | pub table_entry_refresh_try_times: usize, 57 | pub table_entry_refresh_try_interval: Duration, 58 | pub table_entry_refresh_continuous_failure_ceiling: usize, 59 | 60 | pub server_address_priority_timeout: Duration, 61 | pub runtime_continuous_failure_ceiling: usize, 62 | 63 | pub rpc_connect_timeout: Duration, 64 | pub rpc_read_timeout: Duration, 65 | pub rpc_operation_timeout: Duration, 66 | pub rpc_login_timeout: Duration, 67 | pub rpc_retry_limit: usize, 68 | pub rpc_retry_interval: Duration, 69 | 70 | pub refresh_workers_num: usize, 71 | 72 | pub max_conns_per_server: usize, 73 | pub min_idle_conns_per_server: usize, 74 | pub query_concurrency_limit: Option, 75 | 76 | pub tcp_recv_thread_num: usize, 77 | pub tcp_send_thread_num: usize, 78 | pub bg_thread_num: usize, 79 | 80 | pub max_inflight_reqs_per_conn: usize, 81 | 82 | pub log_level_flag: u16, 83 | } 84 | 85 | impl Default for ClientConfig { 86 | fn default() -> ClientConfig { 87 | ClientConfig { 88 | sys_user_name: "".to_owned(), 89 | sys_password: "".to_owned(), 90 | metadata_mysql_conn_pool_min_size: 1, 91 | metadata_mysql_conn_pool_max_size: 3, 92 | metadata_refresh_interval: Duration::from_secs(3), 93 | ocp_model_cache_file: "/tmp/ocp_model_cache.json".to_owned(), 94 | 95 | rslist_acquire_timeout: Duration::from_secs(10), 96 | rslist_acquire_try_times: 3, 97 | rslist_acquire_retry_interval: Duration::from_millis(100), 98 | 99 | table_entry_acquire_connect_timeout: Duration::from_secs(3), 100 | table_entry_acquire_read_timeout: Duration::from_secs(3), 101 | table_entry_refresh_interval_base: Duration::from_secs(60), 102 | table_entry_refresh_interval_ceiling: Duration::from_secs(120), 103 | table_entry_refresh_try_times: 3, 104 | table_entry_refresh_try_interval: Duration::from_millis(20), 105 | table_entry_refresh_continuous_failure_ceiling: 10, 106 | 107 | server_address_priority_timeout: Duration::from_secs(1800), 108 | runtime_continuous_failure_ceiling: 10, 109 | 110 | rpc_connect_timeout: Duration::from_secs(3), 111 | rpc_read_timeout: Duration::from_secs(3), 112 | rpc_login_timeout: Duration::from_secs(3), 113 | rpc_operation_timeout: Duration::from_secs(3), 114 | rpc_retry_limit: 3, 115 | rpc_retry_interval: Duration::from_millis(500), 116 | 117 | refresh_workers_num: 5, 118 | 119 | max_conns_per_server: 10, 120 | min_idle_conns_per_server: 5, 121 | query_concurrency_limit: None, 122 | 123 | tcp_recv_thread_num: 4, 124 | tcp_send_thread_num: 2, 125 | bg_thread_num: 2, 126 | 127 | max_inflight_reqs_per_conn: 100, 128 | 129 | log_level_flag: DEFAULT_FLAG, 130 | } 131 | } 132 | } 133 | 134 | impl ClientConfig { 135 | pub fn new() -> Self { 136 | Self::default() 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /ycsb-rs/src/generator/zipfian_generator.rs: -------------------------------------------------------------------------------- 1 | use rand::prelude::*; 2 | 3 | use super::{Generator, NumberGenerator}; 4 | 5 | pub const ZIPFIAN_CONSTANT: f64 = 0.99; 6 | 7 | #[allow(dead_code)] 8 | struct ZipfianParameters { 9 | alpha: f64, 10 | zetan: f64, 11 | eta: f64, 12 | theta: f64, 13 | zeta2theta: f64, 14 | } 15 | 16 | #[allow(dead_code)] 17 | pub struct ZipfianGenerator { 18 | items: u64, 19 | base: u64, 20 | zipfian_constant: f64, 21 | zipfian_parameters: ZipfianParameters, 22 | count_for_zeta: u64, 23 | allow_item_count_decrease: bool, 24 | } 25 | 26 | fn zeta_4(st: u64, n: u64, theta: f64, initial_sum: f64) -> f64 { 27 | let mut sum = initial_sum; 28 | for i in st..n { 29 | sum += 1.0 / (i as f64 + 1.0).powf(theta); 30 | } 31 | sum 32 | } 33 | 34 | fn zeta_2(n: u64, theta: f64) -> f64 { 35 | zeta_4(0, n, theta, 0.0) 36 | } 37 | 38 | impl ZipfianGenerator { 39 | pub fn from_items(items: u64) -> Self { 40 | Self::from_range(0, items - 1) 41 | } 42 | 43 | pub fn from_range(min: u64, max: u64) -> Self { 44 | Self::from_range_const(min, max, ZIPFIAN_CONSTANT) 45 | } 46 | 47 | pub fn from_range_const(min: u64, max: u64, zipfian_constant: f64) -> Self { 48 | Self::new( 49 | min, 50 | max, 51 | zipfian_constant, 52 | zeta_2(max - min + 1, zipfian_constant), 53 | ) 54 | } 55 | 56 | pub fn new(min: u64, max: u64, zipfian_constant: f64, zetan: f64) -> Self { 57 | let theta = zipfian_constant; 58 | let zeta2theta = zeta_2(2, theta); 59 | let items = max - min + 1; 60 | let zipfian_parameters = ZipfianParameters { 61 | alpha: 1.0 / (1.0 - theta), 62 | zetan, 63 | eta: (1.0 - (2.0 / items as f64).powf(1.0 - theta)) / (1.0 - zeta2theta / zetan), 64 | theta, 65 | zeta2theta, 66 | }; 67 | Self { 68 | items, 69 | base: min, 70 | zipfian_constant, 71 | zipfian_parameters, 72 | count_for_zeta: items, 73 | allow_item_count_decrease: false, 74 | } 75 | } 76 | 77 | fn next_long(&self, item_count: u64, rng: &mut SmallRng) -> u64 { 78 | if item_count != self.count_for_zeta { 79 | /* 80 | if item_count > self.count_for_zeta { 81 | warn!("incrementally recomputing Zipfian distribtion (increase)"); 82 | self.zipfian_parameters.zetan = zeta_4( 83 | self.count_for_zeta, 84 | item_count, 85 | self.zipfian_parameters.theta, 86 | self.zipfian_parameters.zetan, 87 | ); 88 | } 89 | if item_count < self.count_for_zeta && self.allow_item_count_decrease { 90 | warn!("incrementally recomputing Zipfian distribtion (decrease). This is slow and should be avoided."); 91 | self.zipfian_parameters.zetan = zeta_2(item_count, self.zipfian_parameters.theta); 92 | } 93 | 94 | self.count_for_zeta = item_count; 95 | self.zipfian_parameters.eta = (1.0 96 | - (2.0 / self.items as f64).powf(1.0 - self.zipfian_parameters.theta)) 97 | / (1.0 - self.zipfian_parameters.zeta2theta / self.zipfian_parameters.zetan); 98 | */ 99 | todo!("change item count after creating zipfian is not yet supported"); 100 | } 101 | 102 | let u = rng.gen::(); 103 | let uz = u * self.zipfian_parameters.zetan; 104 | 105 | if uz < 1.0 { 106 | return self.base; 107 | } 108 | 109 | if uz < 1.0 + (0.5_f64).powf(self.zipfian_parameters.theta) { 110 | return self.base + 1; 111 | } 112 | 113 | self.base 114 | + (item_count as f64 115 | * (self.zipfian_parameters.eta * u - self.zipfian_parameters.eta + 1.0) 116 | .powf(self.zipfian_parameters.alpha)) as u64 117 | } 118 | } 119 | 120 | impl Generator for ZipfianGenerator { 121 | fn next_value(&self, rng: &mut SmallRng) -> u64 { 122 | self.next_long(self.items, rng) 123 | } 124 | } 125 | 126 | impl NumberGenerator for ZipfianGenerator { 127 | fn mean(&self) -> u64 { 128 | todo!("implement ZipfianGenerator::mean") 129 | } 130 | } 131 | 132 | #[cfg(test)] 133 | mod tests { 134 | use super::*; 135 | 136 | #[test] 137 | fn test_min_and_max_parameter() { 138 | let min = 5; 139 | let max = 10; 140 | let zipfian = ZipfianGenerator::from_range(min, max); 141 | let mut result = std::collections::HashMap::new(); 142 | let mut rng = SmallRng::from_entropy(); 143 | for _i in 0..100000 { 144 | let val = zipfian.next_value(&mut rng); 145 | assert!(val >= min); 146 | assert!(val <= max); 147 | result.entry(val).and_modify(|x| *x += 1).or_insert(1); 148 | } 149 | println!("{result:?}"); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 木兰宽松许可证, 第2版 2 | 3 | 2020年1月 http://license.coscl.org.cn/MulanPSL2 4 | 5 | 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 6 | 7 | 0. 定义 8 | 9 | “软件” 是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 10 | 11 | “贡献” 是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 12 | 13 | “贡献者” 是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 14 | 15 | “法人实体” 是指提交贡献的机构及其“关联实体”。 16 | 17 | “关联实体” 是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 18 | 19 | 1. 授予版权许可 20 | 21 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 22 | 23 | 2. 授予专利许可 24 | 25 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 26 | 27 | 3. 无商标许可 28 | 29 | “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 30 | 31 | 4. 分发限制 32 | 33 | 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 34 | 35 | 5. 免责声明与责任限制 36 | 37 | “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 38 | 39 | 6. 语言 40 | 41 | “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 42 | 43 | 条款结束 44 | 45 | Mulan Permissive Software License,Version 2 (Mulan PSL v2) 46 | 47 | January 2020 http://license.coscl.org.cn/MulanPSL2 48 | 49 | Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 50 | 51 | 0. Definition 52 | 53 | Software means the program and related documents which are licensed under this License and comprise all Contribution(s). 54 | 55 | Contribution means the copyrightable work licensed by a particular Contributor under this License. 56 | 57 | Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. 58 | 59 | Legal Entity means the entity making a Contribution and all its Affiliates. 60 | 61 | Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 62 | 63 | 1. Grant of Copyright License 64 | 65 | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 66 | 67 | 2. Grant of Patent License 68 | 69 | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 70 | 71 | 3. No Trademark License 72 | 73 | No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in section 4. 74 | 75 | 4. Distribution Restriction 76 | 77 | You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 78 | 79 | 5. Disclaimer of Warranty and Limitation of Liability 80 | 81 | THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 82 | 83 | 6. Language 84 | 85 | THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. 86 | 87 | END OF THE TERMS AND CONDITIONS 88 | -------------------------------------------------------------------------------- /src/monitors/runtime_metrics.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use prometheus_client::{ 19 | encoding::EncodeLabelSet, 20 | metrics::{family::Family, gauge}, 21 | registry::Registry, 22 | }; 23 | 24 | use crate::monitors::prometheus::OBKV_CLIENT_REGISTRY; 25 | 26 | lazy_static! { 27 | pub static ref OBKV_RUNTIME_GAUGE_METRICS: RuntimeGaugeMetrics = { 28 | let runtime_metrics = RuntimeGaugeMetrics::default(); 29 | runtime_metrics.register(&mut OBKV_CLIENT_REGISTRY.lock().unwrap().registry); 30 | runtime_metrics 31 | }; 32 | } 33 | 34 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 35 | pub struct RuntimeNameLabels { 36 | pub string_type: String, 37 | } 38 | 39 | #[derive(Default)] 40 | pub struct RuntimeGaugeMetrics { 41 | runtime_thread_alive_gauges: Family, 42 | runtime_thread_idle_gauges: Family, 43 | } 44 | 45 | impl RuntimeGaugeMetrics { 46 | pub fn register(&self, registry: &mut Registry) { 47 | let sub_registry = registry.sub_registry_with_prefix("runtime"); 48 | sub_registry.register( 49 | "Runtime Alive Threads Gauges ", 50 | "Client alive threads num.", 51 | self.runtime_thread_alive_gauges.clone(), 52 | ); 53 | sub_registry.register( 54 | "Runtime Idle Threads Gauges ", 55 | "Client idle threads num.", 56 | self.runtime_thread_idle_gauges.clone(), 57 | ); 58 | } 59 | 60 | pub fn on_thread_start(&self, rt_name: &str) { 61 | self.runtime_thread_alive_gauges 62 | .get_or_create(&RuntimeNameLabels { 63 | string_type: rt_name.to_string(), 64 | }) 65 | .inc(); 66 | } 67 | 68 | pub fn on_thread_stop(&self, rt_name: &str) { 69 | self.runtime_thread_alive_gauges 70 | .get_or_create(&RuntimeNameLabels { 71 | string_type: rt_name.to_string(), 72 | }) 73 | .dec(); 74 | } 75 | 76 | pub fn on_thread_park(&self, rt_name: &str) { 77 | self.runtime_thread_idle_gauges 78 | .get_or_create(&RuntimeNameLabels { 79 | string_type: rt_name.to_string(), 80 | }) 81 | .inc(); 82 | } 83 | 84 | pub fn on_thread_unpark(&self, rt_name: &str) { 85 | self.runtime_thread_idle_gauges 86 | .get_or_create(&RuntimeNameLabels { 87 | string_type: rt_name.to_string(), 88 | }) 89 | .dec(); 90 | } 91 | 92 | pub fn get_runtime_thread_alive_gauges(&self) -> &Family { 93 | &self.runtime_thread_alive_gauges 94 | } 95 | 96 | pub fn get_runtime_thread_idle_gauges(&self) -> &Family { 97 | &self.runtime_thread_idle_gauges 98 | } 99 | } 100 | 101 | /// Runtime metrics. 102 | #[derive(Debug)] 103 | pub struct RuntimeMetrics { 104 | pub runtime_name: String, 105 | } 106 | 107 | impl RuntimeMetrics { 108 | pub fn new(runtime_name: &str) -> Self { 109 | Self { 110 | runtime_name: runtime_name.to_owned(), 111 | } 112 | } 113 | 114 | pub fn set_runtime_name(&mut self, runtime_name: String) { 115 | self.runtime_name = runtime_name; 116 | } 117 | 118 | pub fn on_thread_start(&self) { 119 | OBKV_RUNTIME_GAUGE_METRICS.on_thread_start(&self.runtime_name); 120 | } 121 | 122 | pub fn on_thread_stop(&self) { 123 | OBKV_RUNTIME_GAUGE_METRICS.on_thread_stop(&self.runtime_name); 124 | } 125 | 126 | pub fn on_thread_park(&self) { 127 | OBKV_RUNTIME_GAUGE_METRICS.on_thread_park(&self.runtime_name); 128 | } 129 | 130 | pub fn on_thread_unpark(&self) { 131 | OBKV_RUNTIME_GAUGE_METRICS.on_thread_unpark(&self.runtime_name); 132 | } 133 | 134 | pub fn get_runtime_thread_alive_gauges(&self) -> i64 { 135 | OBKV_RUNTIME_GAUGE_METRICS 136 | .get_runtime_thread_alive_gauges() 137 | .get_or_create(&RuntimeNameLabels { 138 | string_type: self.runtime_name.clone(), 139 | }) 140 | .get() 141 | } 142 | 143 | pub fn get_runtime_thread_idle_gauges(&self) -> i64 { 144 | OBKV_RUNTIME_GAUGE_METRICS 145 | .get_runtime_thread_idle_gauges() 146 | .get_or_create(&RuntimeNameLabels { 147 | string_type: self.runtime_name.clone(), 148 | }) 149 | .get() 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /docs/simple_demo/simple_operation/demo.md: -------------------------------------------------------------------------------- 1 | # Demo for obkv-table-client-rs 2 | Edited by OBKV developers on June 6, 2023. 3 | 4 | ## Introduction 5 | obkv-table-client-rs is Rust Library that can access table data from OceanBase storage layer. 6 | Now we provide an interface to access data from OceanBase, which we will introduce in this document. 7 | 8 | ***Notice that we will also provide another interface to access data from OceanBase in the future(Like [Mutation](https://github.com/oceanbase/obkv-table-client-java/tree/master/example/simple-mutation)).*** 9 | 10 | ## demo 11 | 12 | ### Simple Operation 13 | obkv-table-client-rs support several simple operations, such as get, insert, update, insert_or_update, replace, append, increment, delete. 14 | 15 | ```rust Table and ObTableClient 16 | impl ObTableClient { 17 | // implement operation 18 | #[inline] 19 | pub async fn insert( 20 | &self, 21 | table_name: &str, 22 | row_keys: Vec, 23 | columns: Vec, 24 | properties: Vec, 25 | ) -> Result {} 26 | 27 | #[inline] 28 | pub async fn update( 29 | &self, 30 | table_name: &str, 31 | row_keys: Vec, 32 | columns: Vec, 33 | properties: Vec, 34 | ) -> Result {} 35 | // ... 36 | } 37 | ``` 38 | 39 | A simple operation example is shown below: 40 | ```rust simple operation example 41 | async fn simple_operation() { 42 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 43 | let client = client_handle.await.unwrap(); 44 | 45 | let result = client.insert( 46 | "your_table_name", 47 | vec![Value::from("foo")], 48 | vec!["c2".to_owned()], 49 | vec![Value::from("baz")], 50 | ).await; 51 | 52 | assert!(result.is_ok()); 53 | } 54 | ``` 55 | More demos can be found in [test](https://github.com/oceanbase/obkv-table-client-rs/blob/main/tests/test_table_client_base.rs). 56 | 57 | ### Batch Operation 58 | All operations supported by **BatchOperation** could be found in **ObTableBatchOperation**. 59 | ```rust ObTableBatchOperation 60 | impl ObTableBatchOperation { 61 | pub fn get(&mut self, row_keys: Vec, columns: Vec) {} 62 | pub fn insert(&mut self, row_keys: Vec, columns: Vec, properties: Vec) {} 63 | pub fn delete(&mut self, row_keys: Vec) {} 64 | pub fn update(&mut self, row_keys: Vec, columns: Vec, properties: Vec) {} 65 | pub fn insert_or_update(&mut self, row_keys: Vec, columns: Vec, properties: Vec) {} 66 | pub fn replace(&mut self, row_keys: Vec, columns: Vec, properties: Vec) {} 67 | pub fn increment(&mut self, row_keys: Vec, columns: Vec, properties: Vec) {} 68 | pub fn append(&mut self, row_keys: Vec, columns: Vec, properties: Vec) {} 69 | // ... 70 | } 71 | ``` 72 | A simple batch operation example is shown below: 73 | ```rust batch operation example 74 | async fn batch_operation() { 75 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 76 | let client = client_handle.await.unwrap(); 77 | 78 | // set number of operations in batch_op 79 | let mut batch_op = client.batch_operation(2); 80 | 81 | // add operation into batch_op 82 | batch_op.delete(vec![Value::from("Key_0"), Value::from("subKey_0")]); 83 | batch_op.insert( 84 | vec![Value::from(Value::from("Key_0")), Value::from("subKey_0")], 85 | vec!["c2".to_owned()], 86 | vec![Value::from("batchValue_0")], 87 | ); 88 | 89 | // execute 90 | let result = client.execute_batch("your_table_name", batch_op).await; 91 | assert!(result.is_ok()); 92 | } 93 | ``` 94 | More [demos](https://github.com/oceanbase/obkv-table-client-rs/blob/main/tests/test_table_client_key.rs) can be found in related test cases. 95 | 96 | ### Query 97 | Query is different from get, it allows the user to get a range of data. 98 | A **Query** could get from **ObTableClient** by calling ```query()``` method, then you could customize your query by calling methods in **ObTableClientQueryImpl** and **TableQuery**. 99 | ```rust ObTableClientQueryImpll 100 | impl ObTableClientQueryImpl { 101 | pub async fn execute(&self) -> Result {} 102 | pub fn select(self, columns: Vec) -> Self {} 103 | // ... 104 | pub fn clear(&mut self) {} 105 | } 106 | ``` 107 | A simple query example is shown below: 108 | ```rust query example 109 | async fn query() { 110 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 111 | let client = client_handle.await.unwrap(); 112 | 113 | let query = client 114 | .query("your_table_name") 115 | .select(vec!["c1".to_owned()]) 116 | .scan_order(false) 117 | .add_scan_range(vec![Value::from("123")], true, vec![Value::from("567")], true); 118 | 119 | let result = query.execute().await; 120 | assert!(result.is_ok()); 121 | } 122 | ``` 123 | More demos can be found in [test](https://github.com/oceanbase/obkv-table-client-rs/blob/main/tests/test_table_client_base.rs). 124 | -------------------------------------------------------------------------------- /src/serde_obkv/value/from.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc}; 19 | use std::borrow::Cow; 20 | 21 | use super::{ObjMeta, ObjType, Value}; 22 | 23 | macro_rules! from_i32 { 24 | ($($tx:ident,$ty:ident)*) => { 25 | $( 26 | impl From<$tx> for Value { 27 | fn from(n: $tx) -> Self { 28 | Value::Int32(n.into(),ObjMeta::default_obj_meta(ObjType::$ty)) 29 | } 30 | } 31 | )* 32 | }; 33 | } 34 | 35 | macro_rules! from_u32 { 36 | ($($tx:ident,$ty:ident)*) => { 37 | $( 38 | impl From<$tx> for Value { 39 | fn from(n: $tx) -> Self { 40 | Value::UInt32(n.into(), ObjMeta::default_obj_meta(ObjType::$ty)) 41 | } 42 | } 43 | )* 44 | }; 45 | } 46 | 47 | from_i32! { 48 | i16,SmallInt 49 | i32,Int32 50 | } 51 | from_u32! { 52 | u16,USmallInt 53 | u32,UInt32 54 | } 55 | 56 | impl From> for Value 57 | where 58 | T: Into, 59 | { 60 | fn from(option: Option) -> Self { 61 | match option { 62 | None => Value::Null(ObjMeta::default_obj_meta(ObjType::Null)), 63 | Some(inner) => inner.into(), 64 | } 65 | } 66 | } 67 | 68 | impl From<()> for Value { 69 | fn from(_: ()) -> Self { 70 | Value::Null(ObjMeta::default_obj_meta(ObjType::Null)) 71 | } 72 | } 73 | 74 | impl From for Value { 75 | fn from(i: i8) -> Self { 76 | Value::Int8(i, ObjMeta::default_obj_meta(ObjType::TinyInt)) 77 | } 78 | } 79 | 80 | impl From for Value { 81 | fn from(i: u8) -> Self { 82 | Value::UInt8(i, ObjMeta::default_obj_meta(ObjType::UTinyInt)) 83 | } 84 | } 85 | 86 | impl From for Value { 87 | fn from(f: i64) -> Self { 88 | Value::Int64(f, ObjMeta::default_obj_meta(ObjType::Int64)) 89 | } 90 | } 91 | 92 | impl From for Value { 93 | fn from(f: u64) -> Self { 94 | Value::UInt64(f, ObjMeta::default_obj_meta(ObjType::UInt64)) 95 | } 96 | } 97 | 98 | impl From for Value { 99 | fn from(f: f32) -> Self { 100 | Value::Float(f, ObjMeta::default_obj_meta(ObjType::Float)) 101 | } 102 | } 103 | 104 | impl From for Value { 105 | fn from(f: f64) -> Self { 106 | Value::Double(f, ObjMeta::default_obj_meta(ObjType::Double)) 107 | } 108 | } 109 | 110 | impl From for Value { 111 | fn from(f: bool) -> Self { 112 | Value::Bool(f, ObjMeta::default_obj_meta(ObjType::TinyInt)) 113 | } 114 | } 115 | 116 | impl From for Value { 117 | fn from(f: String) -> Self { 118 | Value::String(f, ObjMeta::default_obj_meta(ObjType::Varchar)) 119 | } 120 | } 121 | 122 | impl From<&str> for Value { 123 | fn from(f: &str) -> Self { 124 | Value::String(f.to_string(), ObjMeta::default_obj_meta(ObjType::Varchar)) 125 | } 126 | } 127 | 128 | impl<'a> From> for Value { 129 | fn from(f: Cow<'a, str>) -> Self { 130 | Value::String(f.to_string(), ObjMeta::default_obj_meta(ObjType::Varchar)) 131 | } 132 | } 133 | 134 | impl From> for Value { 135 | fn from(f: Vec) -> Self { 136 | Value::Bytes(f, ObjMeta::default_obj_meta(ObjType::Varchar)) 137 | } 138 | } 139 | 140 | impl<'a> From<&'a Vec> for Value { 141 | fn from(f: &'a Vec) -> Self { 142 | Value::Bytes(f.to_vec(), ObjMeta::default_obj_meta(ObjType::Varchar)) 143 | } 144 | } 145 | 146 | impl<'a> From<&'a [u8]> for Value { 147 | fn from(f: &'a [u8]) -> Self { 148 | Value::Bytes(f.to_vec(), ObjMeta::default_obj_meta(ObjType::Varchar)) 149 | } 150 | } 151 | 152 | impl From> for Value { 153 | fn from(dt: DateTime) -> Self { 154 | Value::Time( 155 | dt.timestamp_micros(), 156 | ObjMeta::default_obj_meta(ObjType::Timestamp), 157 | ) 158 | } 159 | } 160 | 161 | impl From for Value { 162 | fn from(f: NaiveDateTime) -> Self { 163 | Value::Time( 164 | f.timestamp_micros(), 165 | ObjMeta::default_obj_meta(ObjType::DateTime), 166 | ) 167 | } 168 | } 169 | 170 | impl From for Value { 171 | fn from(f: NaiveDate) -> Self { 172 | let naive_date_time = f.and_hms_opt(0, 0, 0).unwrap(); 173 | let seconds_since_epoch = naive_date_time.timestamp(); 174 | Value::Date( 175 | seconds_since_epoch as i32, 176 | ObjMeta::default_obj_meta(ObjType::Date), 177 | ) 178 | } 179 | } 180 | 181 | #[cfg(test)] 182 | mod test { 183 | use super::*; 184 | 185 | #[test] 186 | fn test_from_into() { 187 | let v = Value::from(32); 188 | assert_eq!(32, v.as_i32()); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/rpc/protocol/query_and_mutate.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | #![allow(dead_code)] 19 | use bytes::{BufMut, BytesMut}; 20 | 21 | use crate::{ 22 | payloads::ObTableBatchOperation, 23 | query::ObTableQuery, 24 | rpc::protocol::{BasePayLoad, ObPayload, ProtoDecoder, ProtoEncoder}, 25 | serde_obkv::util, 26 | }; 27 | 28 | #[derive(Default, Debug, Clone, PartialEq)] 29 | pub struct ObTableQueryAndMutateFlag { 30 | flags: i64, 31 | } 32 | 33 | impl ObTableQueryAndMutateFlag { 34 | const FLAG_IS_CHECK_AND_EXECUTE: i64 = 1 << 0; 35 | const FLAG_IS_CHECK_NOT_EXISTS: i64 = 1 << 1; 36 | 37 | pub fn new() -> Self { 38 | ObTableQueryAndMutateFlag { flags: 0 } 39 | } 40 | 41 | pub fn value(&self) -> i64 { 42 | self.flags 43 | } 44 | 45 | pub fn set_is_check_and_execute(&mut self, is_check_and_execute: bool) { 46 | if is_check_and_execute { 47 | self.flags |= Self::FLAG_IS_CHECK_AND_EXECUTE; 48 | } else { 49 | self.flags &= !Self::FLAG_IS_CHECK_AND_EXECUTE; 50 | } 51 | } 52 | 53 | pub fn set_check_not_exists(&mut self, check_not_exists: bool) { 54 | if check_not_exists { 55 | self.flags |= Self::FLAG_IS_CHECK_NOT_EXISTS; 56 | } else { 57 | self.flags &= !Self::FLAG_IS_CHECK_NOT_EXISTS; 58 | } 59 | } 60 | 61 | pub fn is_check_not_exists(&self) -> bool { 62 | (self.flags & Self::FLAG_IS_CHECK_NOT_EXISTS) != 0 63 | } 64 | 65 | pub fn is_check_and_execute(&self) -> bool { 66 | (self.flags & Self::FLAG_IS_CHECK_AND_EXECUTE) != 0 67 | } 68 | } 69 | 70 | #[derive(Debug, Clone)] 71 | pub struct ObTableQueryAndMutate { 72 | base: BasePayLoad, 73 | query: Option, 74 | mutations: Option, 75 | return_affected_entity: bool, 76 | option_flag: ObTableQueryAndMutateFlag, 77 | } 78 | 79 | impl Default for ObTableQueryAndMutate { 80 | fn default() -> ObTableQueryAndMutate { 81 | ObTableQueryAndMutate::dummy() 82 | } 83 | } 84 | 85 | impl ObTableQueryAndMutate { 86 | fn internal_new(query: Option, mutations: Option) -> Self { 87 | Self { 88 | base: BasePayLoad::dummy(), 89 | query, 90 | mutations, 91 | return_affected_entity: false, 92 | option_flag: ObTableQueryAndMutateFlag::new(), 93 | } 94 | } 95 | 96 | pub fn new(query: ObTableQuery, mutations: ObTableBatchOperation) -> Self { 97 | ObTableQueryAndMutate::internal_new(Some(query), Some(mutations)) 98 | } 99 | 100 | pub fn dummy() -> Self { 101 | ObTableQueryAndMutate::internal_new(None, None) 102 | } 103 | 104 | pub fn mutations(&self) -> &Option { 105 | &self.mutations 106 | } 107 | 108 | pub fn set_mutations(&mut self, mutations: ObTableBatchOperation) { 109 | self.mutations = Some(mutations) 110 | } 111 | 112 | pub fn query(&self) -> &Option { 113 | &self.query 114 | } 115 | 116 | pub fn set_query(&mut self, query: ObTableQuery) { 117 | self.query = Some(query) 118 | } 119 | 120 | pub fn set_return_affected_entity(&mut self, return_affected_entity: bool) { 121 | self.return_affected_entity = return_affected_entity; 122 | } 123 | 124 | pub fn set_is_check_and_execute(&mut self, is_check_and_execute: bool) { 125 | self.option_flag 126 | .set_is_check_and_execute(is_check_and_execute) 127 | } 128 | 129 | pub fn is_check_and_execute(&self) -> bool { 130 | self.option_flag.is_check_and_execute() 131 | } 132 | 133 | pub fn set_check_not_exists(&mut self, check_not_exists: bool) { 134 | self.option_flag.set_check_not_exists(check_not_exists) 135 | } 136 | 137 | pub fn is_check_not_exists(&self) -> bool { 138 | self.option_flag.is_check_not_exists() 139 | } 140 | } 141 | 142 | impl ObPayload for ObTableQueryAndMutate { 143 | fn base(&self) -> &BasePayLoad { 144 | &self.base 145 | } 146 | 147 | fn base_mut(&mut self) -> &mut BasePayLoad { 148 | &mut self.base 149 | } 150 | 151 | // payload size, without header bytes 152 | fn content_len(&self) -> crate::rpc::protocol::Result { 153 | let mut len: usize = 0; 154 | 155 | len += self 156 | .query 157 | .as_ref() 158 | .expect("query should not be empty") 159 | .len()?; 160 | len += self 161 | .mutations 162 | .as_ref() 163 | .expect("mutations should not be empty") 164 | .len()?; 165 | 166 | len += util::encoded_length_vi64(self.option_flag.value()); 167 | 168 | // 1 for bool 169 | Ok(len + 1) 170 | } 171 | } 172 | 173 | impl ProtoEncoder for ObTableQueryAndMutate { 174 | fn encode(&self, buf: &mut BytesMut) -> crate::rpc::protocol::Result<()> { 175 | self.encode_header(buf)?; 176 | 177 | // query and mutations need to be not empty now 178 | // this is guarantee by developers 179 | self.query 180 | .as_ref() 181 | .expect("query should not be empty") 182 | .encode(buf)?; 183 | self.mutations 184 | .as_ref() 185 | .expect("mutations should not be empty") 186 | .encode(buf)?; 187 | 188 | buf.put_i8(self.return_affected_entity as i8); 189 | util::encode_vi64(self.option_flag.value(), buf)?; 190 | 191 | Ok(()) 192 | } 193 | } 194 | 195 | impl ProtoDecoder for ObTableQueryAndMutate { 196 | fn decode(&mut self, _src: &mut BytesMut) -> crate::rpc::protocol::Result<()> { 197 | unimplemented!(); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /src/util/obversion.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2023 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::sync::atomic::{AtomicU64, Ordering::Relaxed}; 19 | 20 | lazy_static! { 21 | pub static ref OB_VERSION: AtomicU64 = AtomicU64::new(0); 22 | } 23 | 24 | #[allow(dead_code)] 25 | const OB_VSN_MAJOR_SHIFT: u64 = 32; 26 | #[allow(dead_code)] 27 | const OB_VSN_MINOR_SHIFT: u64 = 16; 28 | #[allow(dead_code)] 29 | const OB_VSN_MAJOR_PATCH_SHIFT: u64 = 8; 30 | #[allow(dead_code)] 31 | const OB_VSN_MINOR_PATCH_SHIFT: u64 = 0; 32 | #[allow(dead_code)] 33 | const OB_VSN_MAJOR_MASK: u64 = 0xffffffff; 34 | #[allow(dead_code)] 35 | const OB_VSN_MINOR_MASK: u64 = 0xffff; 36 | #[allow(dead_code)] 37 | const OB_VSN_MAJOR_PATCH_MASK: u64 = 0xff; 38 | #[allow(dead_code)] 39 | const OB_VSN_MINOR_PATCH_MASK: u64 = 0xff; 40 | 41 | #[allow(dead_code)] 42 | pub fn calc_version(major: i32, minor: i16, major_patch: i8, minor_patch: i8) -> u64 { 43 | ((major as u64) << OB_VSN_MAJOR_SHIFT) 44 | | ((minor as u64) << OB_VSN_MINOR_SHIFT) 45 | | ((major_patch as u64) << OB_VSN_MAJOR_PATCH_SHIFT) 46 | | ((minor_patch as u64) << OB_VSN_MINOR_PATCH_SHIFT) 47 | } 48 | 49 | #[allow(dead_code)] 50 | pub fn ob_vsn_major() -> i32 { 51 | get_ob_vsn_major(OB_VERSION.load(Relaxed)) 52 | } 53 | 54 | #[allow(dead_code)] 55 | pub fn get_ob_vsn_major(version: u64) -> i32 { 56 | ((version >> OB_VSN_MAJOR_SHIFT) & OB_VSN_MAJOR_MASK) as i32 57 | } 58 | 59 | #[allow(dead_code)] 60 | pub fn ob_vsn_minor() -> i16 { 61 | get_ob_vsn_minor(OB_VERSION.load(Relaxed)) 62 | } 63 | 64 | #[allow(dead_code)] 65 | pub fn get_ob_vsn_minor(version: u64) -> i16 { 66 | ((version >> OB_VSN_MINOR_SHIFT) & OB_VSN_MINOR_MASK) as i16 67 | } 68 | 69 | #[allow(dead_code)] 70 | pub fn ob_vsn_major_patch() -> i8 { 71 | get_ob_vsn_major_patch(OB_VERSION.load(Relaxed)) 72 | } 73 | 74 | #[allow(dead_code)] 75 | pub fn get_ob_vsn_major_patch(version: u64) -> i8 { 76 | ((version >> OB_VSN_MAJOR_PATCH_SHIFT) & OB_VSN_MAJOR_PATCH_MASK) as i8 77 | } 78 | 79 | #[allow(dead_code)] 80 | pub fn ob_vsn_minor_patch() -> i8 { 81 | get_ob_vsn_minor_patch(OB_VERSION.load(Relaxed)) 82 | } 83 | 84 | #[allow(dead_code)] 85 | pub fn get_ob_vsn_minor_patch(version: u64) -> i8 { 86 | ((version >> OB_VSN_MINOR_PATCH_SHIFT) & OB_VSN_MINOR_PATCH_MASK) as i8 87 | } 88 | 89 | #[allow(dead_code)] 90 | pub fn ob_vsn_string() -> String { 91 | format!( 92 | "{}.{}.{}.{}", 93 | ob_vsn_major(), 94 | ob_vsn_minor(), 95 | ob_vsn_major_patch(), 96 | ob_vsn_minor_patch() 97 | ) 98 | } 99 | 100 | #[allow(dead_code)] 101 | pub fn get_ob_vsn_string(version: u64) -> String { 102 | format!( 103 | "{}.{}.{}.{}", 104 | get_ob_vsn_major(version), 105 | get_ob_vsn_minor(version), 106 | get_ob_vsn_major_patch(version), 107 | get_ob_vsn_minor_patch(version) 108 | ) 109 | } 110 | 111 | pub fn parse_ob_vsn_from_sql(vsn: String) { 112 | // server_version is like "4.2.1.0" 113 | if let Ok(re) = regex::Regex::new(r"(\d+)\.(\d+)\.(\d+)\.(\d+)") { 114 | if let Some(captures) = re.captures(&vsn) { 115 | if let (Some(major), Some(minor), Some(major_patch), Some(minor_patch)) = ( 116 | captures.get(1), 117 | captures.get(2), 118 | captures.get(3), 119 | captures.get(4), 120 | ) { 121 | OB_VERSION.store( 122 | calc_version( 123 | major.as_str().parse().unwrap(), 124 | minor.as_str().parse().unwrap(), 125 | major_patch.as_str().parse().unwrap(), 126 | minor_patch.as_str().parse().unwrap(), 127 | ), 128 | Relaxed, 129 | ); 130 | } 131 | } 132 | } 133 | } 134 | 135 | pub fn parse_ob_vsn_from_login(vsn: &str) { 136 | // server_version is like "OceanBase 4.2.1.0" 137 | if let Ok(re) = regex::Regex::new(r"OceanBase\s+(\d+)\.(\d+)\.(\d+)\.(\d+)") { 138 | if let Some(captures) = re.captures(vsn) { 139 | if let (Some(major), Some(minor), Some(major_patch), Some(minor_patch)) = ( 140 | captures.get(1), 141 | captures.get(2), 142 | captures.get(3), 143 | captures.get(4), 144 | ) { 145 | OB_VERSION.store( 146 | calc_version( 147 | major.as_str().parse().unwrap(), 148 | minor.as_str().parse().unwrap(), 149 | major_patch.as_str().parse().unwrap(), 150 | minor_patch.as_str().parse().unwrap(), 151 | ), 152 | Relaxed, 153 | ); 154 | } 155 | } 156 | } 157 | } 158 | 159 | #[cfg(test)] 160 | mod test { 161 | use super::*; 162 | 163 | #[test] 164 | fn test_ob_version() { 165 | assert_eq!(ob_vsn_major(), 0); 166 | assert_eq!(ob_vsn_minor(), 0); 167 | assert_eq!(ob_vsn_major_patch(), 0); 168 | assert_eq!(ob_vsn_minor_patch(), 0); 169 | 170 | let my_version = calc_version(4, 2, 1, 4); 171 | assert_eq!(get_ob_vsn_major(my_version), 4); 172 | assert_eq!(get_ob_vsn_minor(my_version), 2); 173 | assert_eq!(get_ob_vsn_major_patch(my_version), 1); 174 | assert_eq!(get_ob_vsn_minor_patch(my_version), 4); 175 | 176 | assert_eq!(ob_vsn_string(), "0.0.0.0"); 177 | assert_eq!(get_ob_vsn_string(my_version), "4.2.1.4"); 178 | } 179 | 180 | #[test] 181 | fn test_parse_ob_version() { 182 | parse_ob_vsn_from_sql("4.2.1.4".to_string()); 183 | assert_eq!(get_ob_vsn_major(OB_VERSION.load(Relaxed)), 4); 184 | assert_eq!(get_ob_vsn_minor(OB_VERSION.load(Relaxed)), 2); 185 | assert_eq!(get_ob_vsn_major_patch(OB_VERSION.load(Relaxed)), 1); 186 | assert_eq!(get_ob_vsn_minor_patch(OB_VERSION.load(Relaxed)), 4); 187 | 188 | parse_ob_vsn_from_login("OceanBase 3.21.11.4"); 189 | assert_eq!(get_ob_vsn_major(OB_VERSION.load(Relaxed)), 3); 190 | assert_eq!(get_ob_vsn_minor(OB_VERSION.load(Relaxed)), 21); 191 | assert_eq!(get_ob_vsn_major_patch(OB_VERSION.load(Relaxed)), 11); 192 | assert_eq!(get_ob_vsn_minor_patch(OB_VERSION.load(Relaxed)), 4); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::{ 19 | io, 20 | num::ParseIntError, 21 | str::Utf8Error, 22 | string::{FromUtf8Error, ParseError}, 23 | }; 24 | 25 | use futures::Canceled as FutureCanceled; 26 | 27 | use crate::{rpc::protocol::codes::ResultCodes, serde_obkv}; 28 | 29 | quick_error! { 30 | #[derive(Debug)] 31 | pub enum Error { 32 | IO(e: io::Error) { 33 | from() 34 | description("IO error") 35 | display("IO error, err:{}", e) 36 | cause(e) 37 | } 38 | ParseInt(e: ParseIntError) { 39 | from() 40 | description("ParseIntError error") 41 | display("Fail to parse integer, err:{}", e) 42 | cause(e) 43 | } 44 | FromUtf8(e: FromUtf8Error) { 45 | from() 46 | description("FromUtf8 error") 47 | display("FromUtf8 error, err:{}", e) 48 | cause(e) 49 | } 50 | Common(code: CommonErrCode, descr: String) { 51 | description(descr) 52 | display("Common error, code:{:?}, err:{}", code, descr) 53 | } 54 | FieldType { 55 | description("Field type error") 56 | display("Field type error") 57 | } 58 | Canceled(e: FutureCanceled) { 59 | from() 60 | description("Future canceled error") 61 | display("Future canceled error, err:{}", e) 62 | cause(e) 63 | } 64 | Utf8Error(e: Utf8Error) { 65 | from() 66 | description("UTF-8 encoding error") 67 | display("UTF-8 encoding error, err:{}", e) 68 | cause(e) 69 | } 70 | ParseError(e: ParseError) { 71 | from() 72 | description("Parsing string error") 73 | display("Parsing string error, err:{}", e) 74 | cause(e) 75 | } 76 | SerdeObkv(e: serde_obkv::Error) { 77 | from() 78 | description("Serialize/Deserialize OBKV protocol error") 79 | display("Serialize/Deserialize error, err:{}", e) 80 | cause(e) 81 | } 82 | Json(e: serde_json::Error) { 83 | from() 84 | description("Serialize/Deserialize json error") 85 | display("Fail to encode/decode json error, err:{}", e) 86 | cause(e) 87 | } 88 | Http(e: reqwest::Error) { 89 | from() 90 | description("Http request error") 91 | display("Http request error, err:{}", e) 92 | cause(e) 93 | } 94 | MySql(e: mysql::Error) { 95 | from() 96 | description("Mysql error") 97 | display("Query database error, err:{}", e) 98 | cause(e) 99 | } 100 | JoinTask(e: tokio::task::JoinError) { 101 | from() 102 | description("Tokio join error") 103 | display("Tokio join error, err:{}", e) 104 | cause(e) 105 | } 106 | } 107 | } 108 | 109 | #[derive(Clone, Copy, Debug, PartialEq)] 110 | pub enum CommonErrCode { 111 | InvalidParam, 112 | NotFound, 113 | Rpc, 114 | ConnPool, 115 | MPSC, 116 | BrokenPipe, 117 | InvalidServerAddr, 118 | OcpError, 119 | NotInitialized, 120 | AlreadyClosed, 121 | PartitionError, 122 | ObException(ResultCodes), 123 | Lock, 124 | PermitDenied, 125 | ConvertFailed, 126 | } 127 | 128 | impl Error { 129 | /// Returns true when the error is an ob exception. 130 | pub fn is_ob_exception(&self) -> bool { 131 | matches!(self, Error::Common(CommonErrCode::ObException(_), _)) 132 | } 133 | 134 | // Returns true when the error is common error 135 | pub fn is_common_err(&self) -> bool { 136 | matches!(self, Error::Common(_, _)) 137 | } 138 | 139 | // Return the common error code if it's a common error, otherwise return None. 140 | pub fn common_err_code(&self) -> Option { 141 | if let Error::Common(code, _) = self { 142 | Some(*code) 143 | } else { 144 | None 145 | } 146 | } 147 | 148 | /// Returns the result code of ob exception, return none if it's not an ob 149 | /// exception. 150 | pub fn ob_result_code(&self) -> Option { 151 | if let Error::Common(CommonErrCode::ObException(code), _desc) = self { 152 | Some(*code) 153 | } else { 154 | None 155 | } 156 | } 157 | 158 | /// Returns the result msg of ob exception, return none if it's not an ob 159 | /// exception. 160 | pub fn ob_result_msg(&self) -> Option<&String> { 161 | if let Error::Common(CommonErrCode::ObException(_code), desc) = self { 162 | Some(desc) 163 | } else { 164 | None 165 | } 166 | } 167 | 168 | pub fn need_retry(&self) -> bool { 169 | if let Error::Common(CommonErrCode::ObException(code), _) = self { 170 | code.need_retry() 171 | } else { 172 | false 173 | } 174 | } 175 | 176 | pub fn need_refresh_table(&self) -> bool { 177 | if let Error::Common(CommonErrCode::ObException(code), _) = self { 178 | code.need_refresh_table() 179 | } else if let Error::Common(CommonErrCode::ConnPool, message) = self { 180 | // conn_pool will produced this error if all connection to a server is shutdown 181 | // which means we need refresh 182 | message.ends_with("are all removed") 183 | } else { 184 | false 185 | } 186 | } 187 | 188 | pub fn need_invalidate_table(&self) -> bool { 189 | if let Error::Common(CommonErrCode::PartitionError, message) = self { 190 | // Location::get_table_location_from_remote will produce this error if the table 191 | // is dropped 192 | message.starts_with("Location::get_table_location_from_remote: Table maybe dropped.") 193 | } else { 194 | false 195 | } 196 | } 197 | } 198 | 199 | pub type Result = ::std::result::Result; 200 | 201 | #[cfg(test)] 202 | mod test { 203 | use super::*; 204 | 205 | #[test] 206 | fn need_refresh() { 207 | let err = Error::Common( 208 | CommonErrCode::ObException(ResultCodes::OB_NOT_MASTER), 209 | "test_err".to_owned(), 210 | ); 211 | assert!(err.need_refresh_table()); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/rpc/protocol/partition/ob_column.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::{clone::Clone, fmt::Debug}; 19 | 20 | use crate::{ 21 | error::{CommonErrCode, Error::Common as CommonErr, Result}, 22 | serde_obkv::value::{CollationLevel, CollationType, ObjMeta, ObjType, Value}, 23 | }; 24 | 25 | pub trait ObColumn: ObColumnClone + Debug + Send + Sync { 26 | fn get_column_name(&self) -> String; 27 | fn get_index(&self) -> i32; 28 | fn get_ob_obj_type(&self) -> ObjType; 29 | fn get_ref_column_names(&self) -> Vec; 30 | fn get_ob_collation_type(&self) -> &CollationType; 31 | fn eval_value(&self, refs: &[Value]) -> Result; 32 | } 33 | 34 | pub trait ObColumnClone { 35 | fn clone_box(&self) -> Box; 36 | } 37 | 38 | impl ObColumnClone for T 39 | where 40 | T: 'static + ObColumn + Clone, 41 | { 42 | fn clone_box(&self) -> Box { 43 | Box::new(self.clone()) 44 | } 45 | } 46 | 47 | impl Clone for Box { 48 | fn clone(&self) -> Box { 49 | self.clone_box() 50 | } 51 | } 52 | 53 | #[derive(Clone, Debug, PartialEq)] 54 | pub struct ObGeneratedColumn { 55 | column_name: String, 56 | index: i32, 57 | ob_obj_type: ObjType, 58 | ob_collation_type: CollationType, 59 | ref_column_names: Vec, 60 | // TODO:columnExpress 61 | column_express: String, 62 | } 63 | 64 | impl ObGeneratedColumn { 65 | pub fn new( 66 | column_name: String, 67 | index: i32, 68 | ob_obj_type: ObjType, 69 | ob_collation_type: CollationType, 70 | ) -> Self { 71 | Self { 72 | column_name, 73 | index, 74 | ob_obj_type, 75 | ob_collation_type, 76 | ref_column_names: Vec::new(), 77 | column_express: String::new(), 78 | } 79 | } 80 | } 81 | 82 | impl ObColumn for ObGeneratedColumn { 83 | fn get_column_name(&self) -> String { 84 | self.column_name.clone() 85 | } 86 | 87 | fn get_index(&self) -> i32 { 88 | self.index 89 | } 90 | 91 | fn get_ob_obj_type(&self) -> ObjType { 92 | self.ob_obj_type.clone() 93 | } 94 | 95 | fn get_ref_column_names(&self) -> Vec { 96 | self.ref_column_names.clone() 97 | } 98 | 99 | fn get_ob_collation_type(&self) -> &CollationType { 100 | &self.ob_collation_type 101 | } 102 | 103 | // TODO: impl express, ObGeneratedColumn in java sdk 104 | fn eval_value(&self, refs: &[Value]) -> Result { 105 | Ok(refs[0].clone()) 106 | } 107 | } 108 | 109 | #[derive(Clone, Debug, PartialEq)] 110 | pub struct ObSimpleColumn { 111 | column_name: String, 112 | index: i32, 113 | ob_obj_type: ObjType, 114 | ob_collation_type: CollationType, 115 | ref_column_names: Vec, 116 | } 117 | 118 | impl ObSimpleColumn { 119 | pub fn new( 120 | column_name: String, 121 | index: i32, 122 | ob_obj_type: ObjType, 123 | ob_collation_type: CollationType, 124 | ) -> Self { 125 | let ref_column_names = vec![column_name.clone()]; 126 | Self { 127 | column_name, 128 | index, 129 | ob_obj_type, 130 | ob_collation_type, 131 | ref_column_names, 132 | } 133 | } 134 | } 135 | 136 | impl ObColumn for ObSimpleColumn { 137 | fn get_column_name(&self) -> String { 138 | self.column_name.clone() 139 | } 140 | 141 | fn get_index(&self) -> i32 { 142 | self.index 143 | } 144 | 145 | fn get_ob_obj_type(&self) -> ObjType { 146 | self.ob_obj_type.clone() 147 | } 148 | 149 | fn get_ref_column_names(&self) -> Vec { 150 | self.ref_column_names.clone() 151 | } 152 | 153 | fn get_ob_collation_type(&self) -> &CollationType { 154 | &self.ob_collation_type 155 | } 156 | 157 | fn eval_value(&self, refs: &[Value]) -> Result { 158 | if refs.len() != 1 { 159 | error!("ObSimpleColumn::eval_value ObSimpleColumn is refer to itself so that the length of the refs must be 1. refs:{refs:?}"); 160 | return Err(CommonErr( 161 | CommonErrCode::InvalidParam, 162 | format!("ObSimpleColumn::eval_value ObSimpleColumn is refer to itself so that the length of the refs must be 1. refs:{refs:?}"), 163 | )); 164 | } 165 | match self.ob_obj_type { 166 | ObjType::Varchar => match &refs[0] { 167 | Value::String(_v, _meta) => Ok(refs[0].clone()), 168 | _ => unimplemented!(), 169 | }, 170 | ObjType::UInt64 | ObjType::Int64 | ObjType::UInt32 | ObjType::Int32 => { 171 | if refs[0].is_min() || refs[0].is_max() { 172 | return Ok(refs[0].clone()); 173 | } 174 | match refs[0].to_owned() { 175 | Value::String(v, _meta) => Ok(Value::Int64( 176 | v.parse::()?, 177 | ObjMeta::new( 178 | ObjType::Int64, 179 | CollationLevel::Numeric, 180 | CollationType::Binary, 181 | 10, 182 | ), 183 | )), 184 | Value::Int32(v, _meta) => Ok(Value::Int64( 185 | v as i64, 186 | ObjMeta::new( 187 | ObjType::Int64, 188 | CollationLevel::Numeric, 189 | CollationType::Binary, 190 | 10, 191 | ), 192 | )), 193 | Value::UInt32(v, _meta) => Ok(Value::Int64( 194 | v as i64, 195 | ObjMeta::new( 196 | ObjType::Int64, 197 | CollationLevel::Numeric, 198 | CollationType::Binary, 199 | 10, 200 | ), 201 | )), 202 | Value::Int64(v, _meta) => Ok(Value::Int64( 203 | v, 204 | ObjMeta::new( 205 | ObjType::Int64, 206 | CollationLevel::Numeric, 207 | CollationType::Binary, 208 | 10, 209 | ), 210 | )), 211 | Value::UInt64(v, _meta) => Ok(Value::Int64( 212 | v as i64, 213 | ObjMeta::new( 214 | ObjType::Int64, 215 | CollationLevel::Numeric, 216 | CollationType::Binary, 217 | 10, 218 | ), 219 | )), 220 | _ => unimplemented!(), 221 | } 222 | } 223 | _ => unimplemented!(), 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /tests/test_table_client.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | pub mod test_table_client_base; 18 | 19 | #[allow(unused_imports)] 20 | #[allow(unused)] 21 | mod utils; 22 | 23 | use obkv::{ResultCodes, Value}; 24 | use tokio::task; 25 | 26 | #[tokio::test] 27 | async fn test_obtable_client_curd() { 28 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 29 | let client = client_handle.await.unwrap(); 30 | const TEST_TABLE_NAME: &str = "test_varchar_table"; 31 | 32 | let result = client 33 | .delete(TEST_TABLE_NAME, vec![Value::from("foo")]) 34 | .await; 35 | assert!(result.is_ok()); 36 | 37 | let result = client 38 | .insert( 39 | TEST_TABLE_NAME, 40 | vec![Value::from("foo")], 41 | vec!["c2".to_owned()], 42 | vec![Value::from("bar")], 43 | ) 44 | .await; 45 | assert!(result.is_ok()); 46 | let result = result.unwrap(); 47 | assert_eq!(1, result); 48 | 49 | let result = client 50 | .get( 51 | TEST_TABLE_NAME, 52 | vec![Value::from("foo")], 53 | vec!["c2".to_owned()], 54 | ) 55 | .await; 56 | assert!(result.is_ok()); 57 | let mut result = result.unwrap(); 58 | assert_eq!(1, result.len()); 59 | let value = result.remove("c2").unwrap(); 60 | assert!(value.is_string()); 61 | assert_eq!("bar", value.as_string()); 62 | 63 | let result = client 64 | .update( 65 | TEST_TABLE_NAME, 66 | vec![Value::from("foo")], 67 | vec!["c2".to_owned()], 68 | vec![Value::from("car")], 69 | ) 70 | .await; 71 | 72 | let query = client 73 | .query(TEST_TABLE_NAME) 74 | .select(vec!["c1".to_owned(), "c2".to_owned()]) 75 | .add_scan_range(vec![Value::get_min()], true, vec![Value::get_max()], true); 76 | 77 | let result_set = query.execute().await; 78 | 79 | assert!(result_set.is_ok()); 80 | 81 | let mut result_set = result_set.unwrap(); 82 | 83 | assert!(result_set.cache_size() > 0); 84 | 85 | assert!(result.is_ok()); 86 | 87 | let result = result.unwrap(); 88 | 89 | assert_eq!(1, result); 90 | 91 | if let Err(e) = result_set.close().await { 92 | println!("Error: {e}"); 93 | } 94 | 95 | drop(result_set); 96 | 97 | let result = client 98 | .get( 99 | TEST_TABLE_NAME, 100 | vec![Value::from("foo")], 101 | vec!["c2".to_owned()], 102 | ) 103 | .await; 104 | assert!(result.is_ok()); 105 | let mut result = result.unwrap(); 106 | assert_eq!(1, result.len()); 107 | let value = result.remove("c2").unwrap(); 108 | assert!(value.is_string()); 109 | assert_eq!("car", value.as_string()); 110 | 111 | let result = client 112 | .delete(TEST_TABLE_NAME, vec![Value::from("foo")]) 113 | .await; 114 | assert!(result.is_ok()); 115 | } 116 | 117 | #[tokio::test] 118 | async fn test_obtable_client_batch_op() { 119 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 120 | let client = client_handle.await.unwrap(); 121 | const TEST_TABLE_NAME: &str = "test_varchar_table"; 122 | 123 | let test_key1 = "batchop-row-key-1"; 124 | let test_key2 = "batchop-row-key-2"; 125 | 126 | let mut batch_op = client.batch_operation(4); 127 | batch_op.delete(vec![Value::from(test_key1)]); 128 | batch_op.delete(vec![Value::from(test_key2)]); 129 | batch_op.insert( 130 | vec![Value::from(test_key1)], 131 | vec!["c2".to_owned()], 132 | vec![Value::from("p2")], 133 | ); 134 | batch_op.insert( 135 | vec![Value::from(test_key2)], 136 | vec!["c2".to_owned()], 137 | vec![Value::from("p2")], 138 | ); 139 | let result = client.execute_batch(TEST_TABLE_NAME, batch_op).await; 140 | assert!(result.is_ok()); 141 | 142 | let result = client 143 | .get( 144 | TEST_TABLE_NAME, 145 | vec![Value::from(test_key1)], 146 | vec!["c2".to_owned()], 147 | ) 148 | .await; 149 | assert!(result.is_ok()); 150 | let mut result = result.unwrap(); 151 | assert_eq!(1, result.len()); 152 | let value = result.remove("c2").unwrap(); 153 | assert!(value.is_string()); 154 | assert_eq!("p2", value.as_string()); 155 | 156 | let mut batch_op = client.batch_operation(3); 157 | 158 | batch_op.update( 159 | vec![Value::from(test_key1)], 160 | vec!["c2".to_owned()], 161 | vec![Value::from("p3")], 162 | ); 163 | batch_op.update( 164 | vec![Value::from(test_key2)], 165 | vec!["c2".to_owned()], 166 | vec![Value::from("p3")], 167 | ); 168 | batch_op.update( 169 | vec![Value::from(test_key2)], 170 | vec!["c2".to_owned()], 171 | vec![Value::from("p4")], 172 | ); 173 | 174 | let result = client.execute_batch(TEST_TABLE_NAME, batch_op).await; 175 | assert!(result.is_ok()); 176 | 177 | let result = client 178 | .get( 179 | TEST_TABLE_NAME, 180 | vec![Value::from(test_key2)], 181 | vec!["c2".to_owned()], 182 | ) 183 | .await; 184 | assert!(result.is_ok()); 185 | let mut result = result.unwrap(); 186 | assert_eq!(1, result.len()); 187 | let value = result.remove("c2").unwrap(); 188 | assert!(value.is_string()); 189 | assert_eq!("p4", value.as_string()); 190 | 191 | // test atomic batch operation 192 | let mut batch_op = client.batch_operation(3); 193 | batch_op.set_atomic_op(true); 194 | batch_op.update( 195 | vec![Value::from(test_key2)], 196 | vec!["c2".to_owned()], 197 | vec![Value::from("p5")], 198 | ); 199 | batch_op.update( 200 | vec![Value::from(test_key2)], 201 | vec!["c2".to_owned()], 202 | vec![Value::from("p6")], 203 | ); 204 | batch_op.insert( 205 | vec![Value::from(test_key2)], 206 | vec!["c2".to_owned()], 207 | vec![Value::from("p0")], 208 | ); 209 | 210 | let result = client.execute_batch(TEST_TABLE_NAME, batch_op).await; 211 | assert!(result.is_err()); 212 | let code = result.err().unwrap().ob_result_code().unwrap(); 213 | assert_eq!(code, ResultCodes::OB_ERR_PRIMARY_KEY_DUPLICATE); 214 | 215 | let result = client 216 | .get( 217 | TEST_TABLE_NAME, 218 | vec![Value::from(test_key2)], 219 | vec!["c2".to_owned()], 220 | ) 221 | .await; 222 | assert!(result.is_ok()); 223 | let mut result = result.unwrap(); 224 | assert_eq!(1, result.len()); 225 | let value = result.remove("c2").unwrap(); 226 | assert!(value.is_string()); 227 | assert_eq!("p4", value.as_string()); 228 | } 229 | 230 | #[tokio::test] 231 | async fn test_obtable_time() { 232 | let client_handle = task::spawn_blocking(utils::common::build_normal_client); 233 | let client = client_handle.await.unwrap(); 234 | const TEST_TABLE_NAME: &str = "test_time"; 235 | let test = test_table_client_base::BaseTest::new(client); 236 | test.test_time(TEST_TABLE_NAME).await; 237 | } 238 | -------------------------------------------------------------------------------- /src/runtime/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022 CeresDB Project Authors. Licensed under Apache-2.0. 2 | // Modifications copyright (C) 2023 OceanBase 3 | 4 | #![allow(dead_code)] 5 | 6 | use std::{ 7 | future::Future, 8 | pin::Pin, 9 | sync::Arc, 10 | task::{Context, Poll}, 11 | }; 12 | 13 | use pin_project_lite::pin_project; 14 | use tokio::{ 15 | runtime::{Builder as RuntimeBuilder, Runtime as TokioRuntime}, 16 | task::JoinHandle as TokioJoinHandle, 17 | }; 18 | 19 | use crate::{ 20 | error::{Error, Result}, 21 | monitors::runtime_metrics::RuntimeMetrics, 22 | }; 23 | 24 | pub type RuntimeRef = Arc; 25 | 26 | /// A runtime to run future tasks 27 | #[derive(Debug)] 28 | pub struct Runtime { 29 | rt: TokioRuntime, 30 | metrics: Arc, 31 | } 32 | 33 | impl Runtime { 34 | /// Spawn a future and execute it in this thread pool 35 | /// 36 | /// Similar to tokio::runtime::Runtime::spawn() 37 | pub fn spawn(&self, future: F) -> JoinHandle 38 | where 39 | F: Future + Send + 'static, 40 | F::Output: Send + 'static, 41 | { 42 | JoinHandle { 43 | inner: self.rt.spawn(future), 44 | } 45 | } 46 | 47 | /// Run the provided function on an executor dedicated to blocking 48 | /// operations. 49 | pub fn spawn_blocking(&self, func: F) -> JoinHandle 50 | where 51 | F: FnOnce() -> R + Send + 'static, 52 | R: Send + 'static, 53 | { 54 | JoinHandle { 55 | inner: self.rt.spawn_blocking(func), 56 | } 57 | } 58 | 59 | /// Run a future to complete, this is the runtime's entry point 60 | pub fn block_on(&self, future: F) -> F::Output { 61 | self.rt.block_on(future) 62 | } 63 | 64 | /// Returns the runtime stats 65 | pub fn stats(&self) -> RuntimeStats { 66 | RuntimeStats { 67 | alive_thread_num: self.metrics.get_runtime_thread_alive_gauges(), 68 | idle_thread_num: self.metrics.get_runtime_thread_idle_gauges(), 69 | } 70 | } 71 | } 72 | 73 | pin_project! { 74 | #[derive(Debug)] 75 | pub struct JoinHandle { 76 | #[pin] 77 | inner: TokioJoinHandle, 78 | } 79 | } 80 | 81 | impl JoinHandle { 82 | pub fn abort(&self) { 83 | self.inner.abort(); 84 | } 85 | } 86 | 87 | impl Future for JoinHandle { 88 | type Output = Result; 89 | 90 | fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll { 91 | let this = self.project(); 92 | this.inner.poll(ctx).map_err(Error::JoinTask) 93 | } 94 | } 95 | 96 | /// Helper that aborts the given join handles on drop. 97 | /// 98 | /// Useful to kill background tasks when the consumer is dropped. 99 | #[derive(Debug)] 100 | pub struct AbortOnDropMany(pub Vec>); 101 | 102 | impl Drop for AbortOnDropMany { 103 | fn drop(&mut self) { 104 | for join_handle in &self.0 { 105 | join_handle.inner.abort(); 106 | } 107 | } 108 | } 109 | 110 | /// Runtime statistics 111 | pub struct RuntimeStats { 112 | pub alive_thread_num: i64, 113 | pub idle_thread_num: i64, 114 | } 115 | 116 | pub struct Builder { 117 | thread_name: String, 118 | builder: RuntimeBuilder, 119 | } 120 | 121 | impl Default for Builder { 122 | fn default() -> Self { 123 | Self { 124 | thread_name: "runtime-default".to_string(), 125 | builder: RuntimeBuilder::new_multi_thread(), 126 | } 127 | } 128 | } 129 | 130 | fn with_metrics(metrics: &Arc, f: F) -> impl Fn() 131 | where 132 | F: Fn(&Arc) + 'static, 133 | { 134 | let m = metrics.clone(); 135 | move || { 136 | f(&m); 137 | } 138 | } 139 | 140 | impl Builder { 141 | /// Sets the number of worker threads the Runtime will use. 142 | /// 143 | /// This can be any number above 0 144 | pub fn worker_threads(&mut self, val: usize) -> &mut Self { 145 | self.builder.worker_threads(val); 146 | self 147 | } 148 | 149 | /// Sets name of threads spawned by the Runtime thread pool 150 | pub fn thread_name(&mut self, val: impl Into) -> &mut Self { 151 | self.thread_name = val.into(); 152 | self 153 | } 154 | 155 | /// Enable all feature of the underlying runtime 156 | pub fn enable_all(&mut self) -> &mut Self { 157 | self.builder.enable_all(); 158 | self 159 | } 160 | 161 | pub fn build(&mut self) -> Result { 162 | let metrics = Arc::new(RuntimeMetrics::new(&self.thread_name)); 163 | 164 | let rt = self 165 | .builder 166 | .thread_name(self.thread_name.clone()) 167 | .on_thread_start(with_metrics(&metrics, |m| { 168 | m.on_thread_start(); 169 | })) 170 | .on_thread_stop(with_metrics(&metrics, |m| { 171 | m.on_thread_stop(); 172 | })) 173 | .on_thread_park(with_metrics(&metrics, |m| { 174 | m.on_thread_park(); 175 | })) 176 | .on_thread_unpark(with_metrics(&metrics, |m| { 177 | m.on_thread_unpark(); 178 | })) 179 | .build()?; 180 | 181 | Ok(Runtime { rt, metrics }) 182 | } 183 | } 184 | 185 | #[cfg(test)] 186 | mod tests { 187 | use std::{sync::Arc, thread, time::Duration}; 188 | 189 | use tokio::sync::oneshot; 190 | use tokio_test::assert_ok; 191 | 192 | use super::*; 193 | 194 | fn rt() -> Arc { 195 | let rt = Builder::default() 196 | .worker_threads(2) 197 | .thread_name("test_spawn_join") 198 | .enable_all() 199 | .build(); 200 | assert!(rt.is_ok()); 201 | Arc::new(rt.unwrap()) 202 | } 203 | 204 | #[test] 205 | fn test_stats() { 206 | let rt = Builder::default() 207 | .worker_threads(5) 208 | .thread_name("test_stats") 209 | .enable_all() 210 | .build(); 211 | assert!(rt.is_ok()); 212 | let rt: RuntimeRef = Arc::new(rt.unwrap()); 213 | // wait threads created 214 | thread::sleep(Duration::from_millis(50)); 215 | 216 | let s = rt.stats(); 217 | assert_eq!(5, s.alive_thread_num); 218 | assert_eq!(5, s.idle_thread_num); 219 | 220 | rt.spawn(async { 221 | thread::sleep(Duration::from_millis(50)); 222 | }); 223 | 224 | thread::sleep(Duration::from_millis(10)); 225 | let s = rt.stats(); 226 | assert_eq!(5, s.alive_thread_num); 227 | assert_eq!(4, s.idle_thread_num); 228 | } 229 | 230 | #[test] 231 | fn block_on_async() { 232 | let rt = rt(); 233 | 234 | let out = rt.block_on(async { 235 | let (tx, rx) = oneshot::channel(); 236 | 237 | thread::spawn(move || { 238 | thread::sleep(Duration::from_millis(50)); 239 | tx.send("ZOMG").unwrap(); 240 | }); 241 | 242 | assert_ok!(rx.await) 243 | }); 244 | 245 | assert_eq!(out, "ZOMG"); 246 | } 247 | 248 | #[test] 249 | fn spawn_from_blocking() { 250 | let rt = rt(); 251 | let rt1 = rt.clone(); 252 | let out = rt.block_on(async move { 253 | let rt2 = rt1.clone(); 254 | let inner = assert_ok!( 255 | rt1.spawn_blocking(move || { rt2.spawn(async move { "hello" }) }) 256 | .await 257 | ); 258 | 259 | assert_ok!(inner.await) 260 | }); 261 | 262 | assert_eq!(out, "hello") 263 | } 264 | 265 | #[test] 266 | fn test_spawn_join() { 267 | let rt = rt(); 268 | let handle = rt.spawn(async { 1 + 1 }); 269 | 270 | assert_eq!(2, rt.block_on(handle).unwrap()); 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /ycsb-rs/src/properties.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | fn zero_u64() -> u64 { 4 | 0 5 | } 6 | 7 | fn show_prometheus_default() -> bool { 8 | false 9 | } 10 | 11 | fn show_progress_duration_default() -> u64 { 12 | 60 13 | } 14 | 15 | fn thread_count_default() -> u64 { 16 | 200 17 | } 18 | 19 | fn field_length_distribution_default() -> String { 20 | "constant".to_string() 21 | } 22 | 23 | fn request_distribution_default() -> String { 24 | "uniform".to_string() 25 | } 26 | 27 | fn field_length_default() -> u64 { 28 | 100 29 | } 30 | 31 | fn batch_count_default() -> u64 { 32 | 100 33 | } 34 | 35 | fn read_proportion_default() -> f64 { 36 | 0.95 37 | } 38 | 39 | fn update_proportion_default() -> f64 { 40 | 0.95 41 | } 42 | 43 | fn insert_proportion_default() -> f64 { 44 | 0.0 45 | } 46 | 47 | fn scan_proportion_default() -> f64 { 48 | 0.0 49 | } 50 | 51 | fn read_modify_write_proportion_default() -> f64 { 52 | 0.0 53 | } 54 | 55 | fn batch_read_proportion_default() -> f64 { 56 | 0.0 57 | } 58 | 59 | fn batch_insertup_proportion_default() -> f64 { 60 | 0.0 61 | } 62 | 63 | fn full_user_name_default() -> String { 64 | "FULL_USER_NAME".to_string() 65 | } 66 | 67 | fn param_url_default() -> String { 68 | "PARAM_URL".to_string() 69 | } 70 | 71 | fn test_password_default() -> String { 72 | "TEST_PASSWORD".to_string() 73 | } 74 | 75 | fn test_sys_user_name_default() -> String { 76 | "TEST_SYS_USER_NAME".to_string() 77 | } 78 | 79 | fn test_sys_password_default() -> String { 80 | "TEST_SYS_PASSWORD".to_string() 81 | } 82 | 83 | fn obkv_client_reuse_default() -> usize { 84 | 10 85 | } 86 | 87 | fn rpc_connect_timeout_default() -> u64 { 88 | 5000 89 | } 90 | 91 | fn rpc_read_timeout_default() -> u64 { 92 | 3000 93 | } 94 | 95 | fn rpc_login_timeout_default() -> u64 { 96 | 3000 97 | } 98 | 99 | fn rpc_operation_timeout_default() -> u64 { 100 | 3000 101 | } 102 | 103 | fn rpc_retry_limit_default() -> usize { 104 | 3 105 | } 106 | 107 | fn rpc_retry_interval_default() -> u64 { 108 | 0 109 | } 110 | 111 | fn refresh_workers_num_default() -> usize { 112 | 3 113 | } 114 | 115 | fn max_conns_per_server_default() -> usize { 116 | 1 117 | } 118 | 119 | fn min_idle_conns_per_server_default() -> usize { 120 | 1 121 | } 122 | 123 | fn bg_thread_num_default() -> usize { 124 | 2 125 | } 126 | 127 | fn tcp_recv_thread_num_default() -> usize { 128 | 6 129 | } 130 | 131 | fn tcp_send_thread_num_default() -> usize { 132 | 4 133 | } 134 | 135 | fn ycsb_thread_num_default() -> usize { 136 | 16 137 | } 138 | 139 | #[derive(Deserialize, Debug)] 140 | pub struct Properties { 141 | #[serde(default = "zero_u64", rename = "insertstart")] 142 | pub insert_start: u64, 143 | #[serde(default = "zero_u64", rename = "insertcount")] 144 | pub insert_count: u64, 145 | #[serde(rename = "operationcount")] 146 | pub operation_count: u64, 147 | #[serde(default = "zero_u64", rename = "recordcount")] 148 | pub record_count: u64, 149 | #[serde(default = "thread_count_default", rename = "threacount")] 150 | pub thread_count: u64, 151 | #[serde(rename = "maxexecutiontime")] 152 | pub max_execution_time: Option, 153 | #[serde(rename = "warmuptime")] 154 | pub warmup_time: Option, 155 | // field length 156 | #[serde( 157 | default = "field_length_distribution_default", 158 | rename = "fieldlengthdistribution" 159 | )] 160 | pub field_length_distribution: String, 161 | #[serde( 162 | default = "request_distribution_default", 163 | rename = "requestdistribution" 164 | )] 165 | pub request_distribution: String, 166 | #[serde(default = "field_length_default", rename = "fieldlength")] 167 | pub field_length: u64, 168 | #[serde(default = "batch_count_default", rename = "batchcount")] 169 | pub batch_count: u64, 170 | #[serde(default = "show_prometheus_default", rename = "showprometheus")] 171 | pub show_prometheus: bool, 172 | #[serde( 173 | default = "show_progress_duration_default", 174 | rename = "show_progress_duration_sec" 175 | )] 176 | pub show_progress_duration: u64, 177 | 178 | // read, update, insert, scan, read-modify-write 179 | #[serde(default = "read_proportion_default", rename = "readproportion")] 180 | pub read_proportion: f64, 181 | #[serde(default = "update_proportion_default", rename = "updateproportion")] 182 | pub update_proportion: f64, 183 | #[serde(default = "insert_proportion_default", rename = "insertproportion")] 184 | pub insert_proportion: f64, 185 | #[serde(default = "scan_proportion_default", rename = "scanproportion")] 186 | pub scan_proportion: f64, 187 | #[serde( 188 | default = "read_modify_write_proportion_default", 189 | rename = "readmodifywriteproportion" 190 | )] 191 | pub read_modify_write_proportion: f64, 192 | #[serde( 193 | default = "batch_read_proportion_default", 194 | rename = "batchreadproportion" 195 | )] 196 | pub batch_read_proportion: f64, 197 | #[serde( 198 | default = "batch_insertup_proportion_default", 199 | rename = "batchinsertupproportion" 200 | )] 201 | pub batch_insertup_proportion: f64, 202 | 203 | #[serde(default = "full_user_name_default", rename = "full_user_name")] 204 | pub full_user_name: String, 205 | #[serde(default = "param_url_default", rename = "param_url")] 206 | pub param_url: String, 207 | #[serde(default = "test_password_default", rename = "test_password")] 208 | pub test_password: String, 209 | #[serde(default = "test_sys_user_name_default", rename = "test_sys_user_name")] 210 | pub test_sys_user_name: String, 211 | #[serde(default = "test_sys_password_default", rename = "test_sys_password")] 212 | pub test_sys_password: String, 213 | 214 | // OBKV Client Config 215 | #[serde(default = "obkv_client_reuse_default", rename = "obkv_client_reuse")] 216 | pub obkv_client_reuse: usize, 217 | #[serde( 218 | default = "rpc_connect_timeout_default", 219 | rename = "rpc_connect_timeout" 220 | )] 221 | pub rpc_connect_timeout: u64, 222 | #[serde(default = "rpc_read_timeout_default", rename = "rpc_read_timeout")] 223 | pub rpc_read_timeout: u64, 224 | #[serde(default = "rpc_login_timeout_default", rename = "rpc_login_timeout")] 225 | pub rpc_login_timeout: u64, 226 | #[serde( 227 | default = "rpc_operation_timeout_default", 228 | rename = "rpc_operation_timeout" 229 | )] 230 | pub rpc_operation_timeout: u64, 231 | #[serde(default = "rpc_retry_limit_default", rename = "rpc_retry_limit")] 232 | pub rpc_retry_limit: usize, 233 | #[serde(default = "rpc_retry_interval_default", rename = "rpc_retry_interval")] 234 | pub rpc_retry_interval: u64, 235 | #[serde( 236 | default = "refresh_workers_num_default", 237 | rename = "refresh_workers_num" 238 | )] 239 | pub refresh_workers_num: usize, 240 | #[serde( 241 | default = "max_conns_per_server_default", 242 | rename = "max_conns_per_server" 243 | )] 244 | pub max_conns_per_server: usize, 245 | #[serde( 246 | default = "min_idle_conns_per_server_default", 247 | rename = "min_idle_conns_per_server" 248 | )] 249 | pub min_idle_conns_per_server: usize, 250 | #[serde(default = "bg_thread_num_default", rename = "bg_thread_num")] 251 | pub bg_thread_num: usize, 252 | #[serde( 253 | default = "tcp_recv_thread_num_default", 254 | rename = "tcp_recv_thread_num" 255 | )] 256 | pub tcp_recv_thread_num: usize, 257 | #[serde( 258 | default = "tcp_send_thread_num_default", 259 | rename = "tcp_send_thread_num" 260 | )] 261 | pub tcp_send_thread_num: usize, 262 | #[serde(default = "ycsb_thread_num_default", rename = "ycsb_thread_num")] 263 | pub ycsb_thread_num: usize, 264 | } 265 | -------------------------------------------------------------------------------- /ycsb-rs/workloads/workload_template.toml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2012-2016 YCSB contributors. All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you 4 | # may not use this file except in compliance with the License. You 5 | # may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. See the License for the specific language governing 13 | # permissions and limitations under the License. See accompanying 14 | # LICENSE file. 15 | 16 | # Yahoo! Cloud System Benchmark 17 | # Workload Template: Default Values 18 | # 19 | # File contains all properties that can be set to define a 20 | # YCSB session. All properties are set to their default 21 | # value if one exists. If not, the property is commented 22 | # out. When a property has a finite number of settings, 23 | # the default is enabled and the alternates are shown in 24 | # comments below it. 25 | # 26 | # Use of most explained through comments in Client.java or 27 | # CoreWorkload.java or on the YCSB wiki page: 28 | # https://github.com/brianfrankcooper/YCSB/wiki/Core-Properties 29 | 30 | # The name of the workload class to use 31 | workload = "core" 32 | 33 | # There is no default setting for recordcount but it is 34 | # required to be set. 35 | # The number of records in the table to be inserted in 36 | # the load phase or the number of records already in the 37 | # table before the run phase. 38 | e = 1000000 39 | 40 | # There is no default setting for operationcount but it is 41 | # required to be set. 42 | # The number of operations to use during the run phase. 43 | operationcount = 3000000 44 | 45 | # The number of thread. 46 | threadcount = 500 47 | 48 | # The number of insertions to do, if different from recordcount. 49 | # Used with insertstart to grow an existing table. 50 | #insertcount= 51 | 52 | # The offset of the first insertion 53 | insertstart = 0 54 | 55 | # The number of fields in a record 56 | fieldcount = 10 57 | 58 | # The size of each field (in bytes) 59 | fieldlength = 100 60 | 61 | # Should read all fields 62 | readallfields = true 63 | 64 | # Should write all fields on update 65 | writeallfields = false 66 | 67 | # The distribution used to choose the length of a field 68 | fieldlengthdistribution = "constant" 69 | #fieldlengthdistribution = "uniform" 70 | #fieldlengthdistribution = "zipfian" 71 | 72 | # What proportion of operations are reads 73 | readproportion = 0.95 74 | 75 | # What proportion of operations are updates 76 | updateproportion = 0.05 77 | 78 | # What proportion of operations are inserts 79 | insertproportion = 0 80 | 81 | # What proportion of operations read then modify a record 82 | readmodifywriteproportion = 0 83 | 84 | # What proportion of operations are scans 85 | scanproportion = 0 86 | 87 | # On a single scan, the maximum number of records to access 88 | maxscanlength = 1000 89 | 90 | # The distribution used to choose the number of records to access on a scan 91 | scanlengthdistribution = "uniform" 92 | #scanlengthdistribution = "zipfian" 93 | 94 | # Should records be inserted in order or pseudo-randomly 95 | insertorder = "hashed" 96 | #insertorder = "ordered" 97 | 98 | # The distribution of requests across the keyspace 99 | requestdistribution = "zipfian" 100 | #requestdistribution = "uniform" 101 | #requestdistribution = "latest" 102 | 103 | # Percentage of data items that constitute the hot set 104 | hotspotdatafraction = 0.2 105 | 106 | # Percentage of operations that access the hot set 107 | hotspotopnfraction = 0.8 108 | 109 | # Maximum execution time in seconds 110 | #maxexecutiontime= 111 | 112 | # The name of the database table to run queries against 113 | table = "usertable" 114 | 115 | # The column family of fields (required by some databases) 116 | #columnfamily= 117 | 118 | # How the latency measurements are presented 119 | measurementtype = "histogram" 120 | #measurementtype = "timeseries" 121 | #measurementtype = "raw" 122 | # When measurementtype is set to raw, measurements will be output 123 | # as RAW datapoints in the following csv format: 124 | # "operation, timestamp of the measurement, latency in us" 125 | # 126 | # Raw datapoints are collected in-memory while the test is running. Each 127 | # data point consumes about 50 bytes (including java object overhead). 128 | # For a typical run of 1 million to 10 million operations, this should 129 | # fit into memory most of the time. If you plan to do 100s of millions of 130 | # operations per run, consider provisioning a machine with larger RAM when using 131 | # the RAW measurement type, or split the run into multiple runs. 132 | # 133 | # Optionally, you can specify an output file to save raw datapoints. 134 | # Otherwise, raw datapoints will be written to stdout. 135 | # The output file will be appended to if it already exists, otherwise 136 | # a new output file will be created. 137 | #measurement.raw.output_file = /tmp/your_output_file_for_this_run 138 | 139 | # JVM Reporting. 140 | # 141 | # Measure JVM information over time including GC counts, max and min memory 142 | # used, max and min thread counts, max and min system load and others. This 143 | # setting must be enabled in conjunction with the "-s" flag to run the status 144 | # thread. Every "status.interval", the status thread will capture JVM 145 | # statistics and record the results. At the end of the run, max and mins will 146 | # be recorded. 147 | # measurement.trackjvm = false 148 | 149 | [histogram] 150 | # The range of latencies to track in the histogram (milliseconds) 151 | buckets = 1000 152 | 153 | [timeseries] 154 | # Granularity for time series (in milliseconds) 155 | granularity = 1000 156 | 157 | # Latency reporting. 158 | # 159 | # YCSB records latency of failed operations separately from successful ones. 160 | # Latency of all OK operations will be reported under their operation name, 161 | # such as [READ], [UPDATE], etc. 162 | # 163 | # For failed operations: 164 | # By default we don't track latency numbers of specific error status. 165 | # We just report latency of all failed operation under one measurement name 166 | # such as [READ-FAILED]. But optionally, user can configure to have either: 167 | # 1. Record and report latency for each and every error status code by 168 | # setting reportLatencyForEachError to true, or 169 | # 2. Record and report latency for a select set of error status codes by 170 | # providing a CSV list of Status codes via the "latencytrackederrors" 171 | # property. 172 | # reportlatencyforeacherror=false 173 | # latencytrackederrors="" 174 | 175 | # Insertion error retry for the core workload. 176 | # 177 | # By default, the YCSB core workload does not retry any operations. 178 | # However, during the load process, if any insertion fails, the entire 179 | # load process is terminated. 180 | # If a user desires to have more robust behavior during this phase, they can 181 | # enable retry for insertion by setting the following property to a positive 182 | # number. 183 | # core_workload_insertion_retry_limit = 0 184 | # 185 | # the following number controls the interval between retries (in seconds): 186 | # core_workload_insertion_retry_interval = 3 187 | 188 | # Distributed Tracing via Apache HTrace (http://htrace.incubator.apache.org/) 189 | # 190 | # Defaults to blank / no tracing 191 | # Below sends to a local file, sampling at 0.1% 192 | # 193 | # htrace.sampler.classes=ProbabilitySampler 194 | # htrace.sampler.fraction=0.001 195 | # htrace.span.receiver.classes=org.apache.htrace.core.LocalFileSpanReceiver 196 | # htrace.local.file.span.receiver.path=/some/path/to/local/file 197 | # 198 | # To capture all spans, use the AlwaysSampler 199 | # 200 | # htrace.sampler.classes=AlwaysSampler 201 | # 202 | # To send spans to an HTraced receiver, use the below and ensure 203 | # your classpath contains the htrace-htraced jar (i.e. when invoking the ycsb 204 | # command add -cp /path/to/htrace-htraced.jar) 205 | # 206 | # htrace.span.receiver.classes=org.apache.htrace.impl.HTracedSpanReceiver 207 | # htrace.htraced.receiver.address=example.com:9075 208 | # htrace.htraced.error.log.period.ms=10000 209 | -------------------------------------------------------------------------------- /ycsb-rs/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::sync::atomic::{AtomicUsize, Ordering}; 2 | use std::time::Duration; 3 | use std::{ 4 | cell::RefCell, 5 | fs, 6 | rc::Rc, 7 | sync::{Arc, Mutex}, 8 | thread, 9 | time::Instant, 10 | }; 11 | 12 | use anyhow::{bail, Result}; 13 | use obkv::dump_metrics; 14 | use properties::Properties; 15 | use rand::{rngs::SmallRng, SeedableRng}; 16 | use structopt::StructOpt; 17 | use tokio::time as TokioTime; 18 | use workload::CoreWorkload; 19 | 20 | use crate::{ 21 | db::DB, 22 | obkv_client::{OBKVClient, OBKVClientInitStruct}, 23 | workload::Workload, 24 | }; 25 | 26 | pub mod db; 27 | pub mod generator; 28 | pub mod obkv_client; 29 | pub mod properties; 30 | mod runtime; 31 | pub mod sqlite; 32 | pub mod workload; 33 | 34 | #[derive(StructOpt, Debug)] 35 | #[structopt(name = "ycsb")] 36 | struct Opt { 37 | #[structopt(name = "COMMANDS")] 38 | commands: Vec, 39 | #[structopt(short, long)] 40 | database: String, 41 | #[structopt(short, long)] 42 | workload: String, 43 | #[structopt(short, long, default_value = "1")] 44 | threads: usize, 45 | } 46 | 47 | fn load(wl: Arc, db: Rc, operation_count: usize) { 48 | for _ in 0..operation_count { 49 | wl.do_insert(db.clone()); 50 | } 51 | } 52 | 53 | fn run(wl: Arc, db: Rc, rng: Rc>, operation_count: usize) { 54 | for _ in 0..operation_count { 55 | wl.do_transaction(rng.clone(), db.clone()); 56 | } 57 | } 58 | 59 | async fn load_ob( 60 | wl: Arc, 61 | db: Arc, 62 | operation_count: usize, 63 | counter: Arc, 64 | ) { 65 | for _ in 0..operation_count { 66 | wl.ob_insert(db.clone()).await; 67 | counter.fetch_add(1, Ordering::Relaxed); 68 | } 69 | } 70 | 71 | async fn run_ob( 72 | wl: Arc, 73 | db: Arc, 74 | rng: Arc>, 75 | operation_count: usize, 76 | counter: Arc, 77 | ) { 78 | for _ in 0..operation_count { 79 | wl.ob_transaction(rng.clone(), db.clone()).await; 80 | counter.fetch_add(1, Ordering::Relaxed); 81 | } 82 | } 83 | 84 | fn main() -> Result<()> { 85 | let opt = Opt::from_args(); 86 | 87 | let raw_props = fs::read_to_string(&opt.workload)?; 88 | 89 | let props: Properties = toml::from_str(&raw_props)?; 90 | 91 | let props = Arc::new(props); 92 | 93 | let wl = Arc::new(CoreWorkload::new(&props)); 94 | 95 | let config = Arc::new(OBKVClientInitStruct::new(&props)); 96 | 97 | if opt.commands.is_empty() { 98 | bail!("no command specified"); 99 | } 100 | 101 | let database = opt.database.clone(); 102 | let total_ops_count = props.operation_count; 103 | let thread_operation_count = props.operation_count as usize / opt.threads; 104 | let actual_client_count = opt.threads / props.obkv_client_reuse; 105 | 106 | // verify count of operations 107 | assert_eq!( 108 | props.operation_count, 109 | (actual_client_count * thread_operation_count * props.obkv_client_reuse) as u64, 110 | " 'operationcount' should be an exact multiple of the 'threads', and 'threads' should be an exact multiple of the 'obkv_client_reuse'" 111 | ); 112 | 113 | for cmd in opt.commands { 114 | let start = Instant::now(); 115 | let mut tasks = vec![]; 116 | let mut threads = vec![]; 117 | let mut db_counters = vec![]; 118 | println!( 119 | "Database: {database}, Command: {cmd}, Counts Per Threads: {thread_operation_count}" 120 | ); 121 | println!( 122 | "Actual Client Count: {actual_client_count}, Client Reuse Count: {}", 123 | props.obkv_client_reuse 124 | ); 125 | if database.eq_ignore_ascii_case("obkv") { 126 | let runtimes = runtime::build_ycsb_runtimes(props.clone()); 127 | for _ in 0..actual_client_count { 128 | let database = database.clone(); 129 | let db = db::create_ob(&database, config.clone()).unwrap(); 130 | // count the ops per client 131 | let counter = Arc::new(AtomicUsize::new(0)); 132 | db_counters.push(counter.clone()); 133 | for _ in 0..props.obkv_client_reuse { 134 | let db = db.clone(); 135 | let wl = wl.clone(); 136 | let cmd = cmd.clone(); 137 | let runtime = runtimes.default_runtime.clone(); 138 | let counter_clone = counter.clone(); 139 | tasks.push(runtime.spawn(async move { 140 | let rng = Arc::new(Mutex::new(SmallRng::from_entropy())); 141 | db.init().unwrap(); 142 | match cmd.as_str() { 143 | "load" => { 144 | load_ob(wl.clone(), db, thread_operation_count, counter_clone).await 145 | } 146 | "run" => { 147 | run_ob(wl.clone(), db, rng, thread_operation_count, counter_clone) 148 | .await 149 | } 150 | _ => panic!("invalid command: {cmd}"), 151 | }; 152 | })); 153 | } 154 | } 155 | // show progress 156 | let stat_duration_sec = props.show_progress_duration; 157 | tasks.push(runtimes.default_runtime.spawn(async move { 158 | let mut interval = TokioTime::interval(Duration::from_secs(stat_duration_sec)); 159 | let mut prev_count = 0; 160 | loop { 161 | interval.tick().await; 162 | let completed_operations: usize = db_counters 163 | .iter() 164 | .map(|arc| arc.load(Ordering::Relaxed)) 165 | .sum(); 166 | let ops_per_second = 167 | (completed_operations - prev_count) as f64 / stat_duration_sec as f64; 168 | prev_count = completed_operations; 169 | // estimate remaining time 170 | let remaining_operations = total_ops_count - completed_operations as u64; 171 | let estimated_remaining_time = if ops_per_second > 0.0 { 172 | Duration::from_secs_f64(remaining_operations as f64 / ops_per_second) 173 | } else { 174 | Duration::from_secs(0) 175 | }; 176 | println!("\n-------------------------------"); 177 | println!( 178 | "Throughput(ops/sec) in previous period: {:.2}", 179 | ops_per_second 180 | ); 181 | println!("Runtime: {:?}", start.elapsed()); 182 | println!("Estimate remaining time: {:?}", estimated_remaining_time); 183 | 184 | if completed_operations >= total_ops_count as usize { 185 | println!("All is done"); 186 | break; 187 | } 188 | } 189 | })); 190 | runtimes.block_runtime.block_on(async move { 191 | for task in tasks { 192 | task.await.expect("task failed"); 193 | } 194 | }); 195 | } else { 196 | for _ in 0..opt.threads { 197 | let database = database.clone(); 198 | let wl = wl.clone(); 199 | let config = config.clone(); 200 | let cmd = cmd.clone(); 201 | threads.push(thread::spawn(move || { 202 | let db = db::create_db(&database, config.clone()).unwrap(); 203 | let rng = Rc::new(RefCell::new(SmallRng::from_entropy())); 204 | 205 | db.init().unwrap(); 206 | 207 | match &cmd[..] { 208 | "load" => load(wl.clone(), db, thread_operation_count), 209 | "run" => run(wl.clone(), db, rng, thread_operation_count), 210 | _ => panic!("invalid command: {cmd}"), 211 | }; 212 | })); 213 | } 214 | for t in threads { 215 | let _ = t.join(); 216 | } 217 | } 218 | let runtime = start.elapsed().as_millis(); 219 | println!("****************************"); 220 | println!("[OVERALL], ThreadCount, {}", opt.threads); 221 | println!("[OVERALL], RunTime(ms), {runtime}"); 222 | let throughput = props.operation_count as f64 / (runtime as f64 / 1000.0); 223 | println!("[OVERALL], Throughput(ops/sec), {throughput}\n"); 224 | println!("****************************"); 225 | } 226 | 227 | if props.show_prometheus { 228 | println!("{}", dump_metrics().expect("dump metrics failed")); 229 | } 230 | 231 | Ok(()) 232 | } 233 | -------------------------------------------------------------------------------- /src/monitors/client_metrics.rs: -------------------------------------------------------------------------------- 1 | /*- 2 | * #%L 3 | * OBKV Table Client Framework 4 | * %% 5 | * Copyright (C) 2021 OceanBase 6 | * %% 7 | * OBKV Table Client Framework is licensed under Mulan PSL v2. 8 | * You can use this software according to the terms and conditions of the 9 | * Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at: 10 | * http://license.coscl.org.cn/MulanPSL2 11 | * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY 12 | * KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO 13 | * NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 14 | * See the Mulan PSL v2 for more details. 15 | * #L% 16 | */ 17 | 18 | use std::time::Duration; 19 | 20 | use prometheus_client::{ 21 | encoding::{EncodeLabelSet, EncodeLabelValue}, 22 | metrics::{counter, family::Family, histogram}, 23 | registry::Registry, 24 | }; 25 | 26 | use crate::payloads::ObTableOperationType; 27 | 28 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] 29 | pub enum ObClientOpRecordType { 30 | Get = 0, 31 | Insert = 1, 32 | Del = 2, 33 | Update = 3, 34 | InsertOrUpdate = 4, 35 | Replace = 5, 36 | Increment = 6, 37 | Append = 7, 38 | Batch = 8, 39 | Query = 9, 40 | StreamQuery = 10, 41 | CheckAndDo = 11, 42 | Invalid = 12, 43 | } 44 | 45 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelValue)] 46 | pub enum ObClientOpRetryType { 47 | Execute = 0, 48 | ExecuteBatch = 1, 49 | } 50 | 51 | impl From for ObClientOpRecordType { 52 | fn from(op: ObTableOperationType) -> Self { 53 | match op { 54 | ObTableOperationType::Get => ObClientOpRecordType::Get, 55 | ObTableOperationType::Insert => ObClientOpRecordType::Insert, 56 | ObTableOperationType::Del => ObClientOpRecordType::Del, 57 | ObTableOperationType::Update => ObClientOpRecordType::Update, 58 | ObTableOperationType::InsertOrUpdate => ObClientOpRecordType::InsertOrUpdate, 59 | ObTableOperationType::Replace => ObClientOpRecordType::Replace, 60 | ObTableOperationType::Increment => ObClientOpRecordType::Increment, 61 | ObTableOperationType::Append => ObClientOpRecordType::Append, 62 | ObTableOperationType::Scan => unreachable!(), 63 | ObTableOperationType::TTL => unreachable!(), 64 | ObTableOperationType::CheckAndInsertUp => ObClientOpRecordType::CheckAndDo, 65 | ObTableOperationType::Invalid => ObClientOpRecordType::Invalid, 66 | } 67 | } 68 | } 69 | 70 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 71 | pub struct OperationLabels { 72 | pub operation_type: ObClientOpRecordType, 73 | } 74 | 75 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 76 | pub struct OperationRetryLabels { 77 | pub operation_retry_type: ObClientOpRetryType, 78 | } 79 | 80 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 81 | pub struct ClientStringLabels { 82 | pub string_type: String, 83 | } 84 | 85 | #[derive(Clone, Debug, Hash, PartialEq, Eq, EncodeLabelSet)] 86 | pub struct ClientStreamQueryLabels { 87 | pub string_type: String, 88 | pub string_tag: String, 89 | } 90 | 91 | pub struct ClientMetrics { 92 | client_operation_rt: Family, 93 | client_sys_op_rt: Family, 94 | client_retry: Family, 95 | client_misc: Family, 96 | client_stream_query_counter: Family, 97 | } 98 | 99 | impl Default for ClientMetrics { 100 | fn default() -> Self { 101 | ClientMetrics { 102 | client_operation_rt: 103 | Family::::new_with_constructor(|| { 104 | histogram::Histogram::new(histogram::exponential_buckets(0.0005, 2.0, 8)) 105 | }), 106 | client_sys_op_rt: 107 | Family::::new_with_constructor(|| { 108 | histogram::Histogram::new(histogram::exponential_buckets(0.001, 2.0, 10)) 109 | }), 110 | client_retry: Default::default(), 111 | client_misc: Family::::new_with_constructor( 112 | || histogram::Histogram::new(histogram::exponential_buckets(5.0, 2.0, 8)), 113 | ), 114 | client_stream_query_counter: Default::default(), 115 | } 116 | } 117 | } 118 | 119 | impl ClientMetrics { 120 | pub fn register(&self, registry: &mut Registry) { 121 | let sub_registry = registry.sub_registry_with_prefix("client"); 122 | sub_registry.register( 123 | "operation response time ", 124 | "Client operation latency (response time) in seconds.", 125 | self.client_operation_rt.clone(), 126 | ); 127 | sub_registry.register( 128 | "system operation response time ", 129 | "Client system operation latency (response time) in seconds.", 130 | self.client_sys_op_rt.clone(), 131 | ); 132 | sub_registry.register( 133 | "operation retry times ", 134 | "Client operation retry times.", 135 | self.client_retry.clone(), 136 | ); 137 | sub_registry.register( 138 | "system miscellaneous counter histogram ", 139 | "Client system miscellaneous counter histogram.", 140 | self.client_misc.clone(), 141 | ); 142 | sub_registry.register( 143 | "counter for common use ", 144 | "Client counter for common use.", 145 | self.client_stream_query_counter.clone(), 146 | ); 147 | } 148 | 149 | pub fn observe_operation_opt_rt( 150 | &self, 151 | operation_type: ObTableOperationType, 152 | duration: Duration, 153 | ) { 154 | self.client_operation_rt 155 | .get_or_create(&OperationLabels { 156 | operation_type: operation_type.into(), 157 | }) 158 | .observe(duration.as_secs_f64()); 159 | } 160 | 161 | pub fn observe_operation_ort_rt( 162 | &self, 163 | operation_type: ObClientOpRecordType, 164 | duration: Duration, 165 | ) { 166 | self.client_operation_rt 167 | .get_or_create(&OperationLabels { operation_type }) 168 | .observe(duration.as_secs_f64()); 169 | } 170 | 171 | pub fn get_client_operation_rt(&self) -> &Family { 172 | &self.client_operation_rt 173 | } 174 | 175 | pub fn observe_sys_operation_rt(&self, sys_operation: &str, duration: Duration) { 176 | self.client_sys_op_rt 177 | .get_or_create(&ClientStringLabels { 178 | string_type: sys_operation.to_string(), 179 | }) 180 | .observe(duration.as_secs_f64()); 181 | } 182 | 183 | pub fn get_client_sys_op_rt(&self) -> &Family { 184 | &self.client_sys_op_rt 185 | } 186 | 187 | pub fn inc_retry_times(&self, retry_type: ObClientOpRetryType) { 188 | self.client_retry 189 | .get_or_create(&OperationRetryLabels { 190 | operation_retry_type: retry_type, 191 | }) 192 | .inc(); 193 | } 194 | 195 | pub fn inc_by_retry_times(&self, retry_type: ObClientOpRetryType, times: u64) { 196 | self.client_retry 197 | .get_or_create(&OperationRetryLabels { 198 | operation_retry_type: retry_type, 199 | }) 200 | .inc_by(times); 201 | } 202 | 203 | pub fn get_client_retry(&self) -> &Family { 204 | &self.client_retry 205 | } 206 | 207 | pub fn observe_misc(&self, misc_type: &str, times: f64) { 208 | self.client_misc 209 | .get_or_create(&ClientStringLabels { 210 | string_type: misc_type.to_string(), 211 | }) 212 | .observe(times); 213 | } 214 | 215 | pub fn get_client_misc(&self) -> &Family { 216 | &self.client_misc 217 | } 218 | 219 | pub fn inc_stream_query_counter(&self, string_type: &str, string_tag: &str) { 220 | self.client_stream_query_counter 221 | .get_or_create(&ClientStreamQueryLabels { 222 | string_type: string_type.to_string(), 223 | string_tag: string_tag.to_string(), 224 | }) 225 | .inc(); 226 | } 227 | 228 | pub fn inc_by_stream_query_counter(&self, string_type: &str, string_tag: &str, times: u64) { 229 | self.client_stream_query_counter 230 | .get_or_create(&ClientStreamQueryLabels { 231 | string_type: string_type.to_string(), 232 | string_tag: string_tag.to_string(), 233 | }) 234 | .inc_by(times); 235 | } 236 | 237 | pub fn get_common_counter(&self) -> &Family { 238 | &self.client_stream_query_counter 239 | } 240 | } 241 | --------------------------------------------------------------------------------