diff --git a/Cargo.lock b/Cargo.lock index fc80de1c..76c93daf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -278,6 +278,12 @@ version = "1.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c76a5792e44e4abe34d3abf15636779261d45a7450612059293d1d2cfc63422" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -912,6 +918,16 @@ version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + [[package]] name = "heck" version = "0.5.0" @@ -2220,9 +2236,9 @@ version = "0.1.1" dependencies = [ "chrono", "clap", + "hdrhistogram", "indicatif", "kite_sql", - "ordered-float", "pprof", "rand", "rust_decimal", diff --git a/README.md b/README.md index a612aa6b..1e269227 100755 --- a/README.md +++ b/README.md @@ -203,14 +203,14 @@ Run `make tpcc-dual` to mirror every TPCC statement to an in-memory SQLite datab Recent stable-run 720-second local comparison on the machine above: -| Backend | TpmC | New-Order p90 | Payment p90 | Order-Status p90 | Delivery p90 | Stock-Level p90 | +| Backend | TpmC | New-Order p90 (µs) | Payment p90 (µs) | Order-Status p90 (µs) | Delivery p90 (µs) | Stock-Level p90 (µs) | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| KiteSQL LMDB | 82871 | 0.001s | 0.001s | 0.001s | 0.002s | 0.001s | -| KiteSQL RocksDB | 40960 | 0.001s | 0.001s | 0.001s | 0.011s | 0.001s | -| SQLite balanced | 51637 | 0.001s | 0.001s | 0.001s | 0.001s | 0.001s | -| SQLite practical | 61424 | 0.001s | 0.001s | 0.001s | 0.001s | 0.001s | +| KiteSQL LMDB | 70315 | 561 | 196 | 208 | 1089 | 1693 | +| KiteSQL RocksDB | 35556 | 747 | 394 | 365 | 10175 | 2125 | +| SQLite balanced | 54797 | 303 | 72 | 52 | 363 | 508 | +| SQLite practical | 44847 | 527 | 146 | 61 | 1248 | 486 | -These rows are from the stable runs on `2026-07-11`; the detailed raw outputs are recorded in [tpcc/README.md](tpcc/README.md). +These rows are from the local run on `2026-09-06–2026-09-07`; latencies are in microseconds and include transaction commit. #### 👉[check more](tpcc/README.md) diff --git a/scripts/run_tpcc_stable.py b/scripts/run_tpcc_stable.py index 61187f67..09cf2f0b 100755 --- a/scripts/run_tpcc_stable.py +++ b/scripts/run_tpcc_stable.py @@ -311,15 +311,14 @@ def extract_tpmc(log_text: str) -> str: def extract_p90(log_text: str, label: str) -> str: - marker = "<90th Percentile RT (MaxRT)>" + marker = "" if marker not in log_text: return "-" block = log_text.split(marker, 1)[1] for line in block.splitlines(): - if label not in line: - continue - parts = line.split() - return parts[2] if len(parts) >= 3 else "-" + columns = [column.strip() for column in line.strip().strip("|").split("|")] + if len(columns) == 3 and columns[0] == label: + return columns[1] return "-" @@ -443,7 +442,7 @@ def write_summary_header(args: argparse.Namespace, summary_file: Path) -> None: f"sample_interval={args.sample_interval_sec}s" ), "", - "| Variant | Status | Attempts | Measure Time | TpmC | New-Order p90 | Payment p90 | Order-Status p90 | Delivery p90 | Stock-Level p90 | Notes | Raw Log |", + "| Variant | Status | Attempts | Measure Time | TpmC | New-Order p90 (us) | Payment p90 (us) | Order-Status p90 (us) | Delivery p90 (us) | Stock-Level p90 (us) | Notes | Raw Log |", "| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- | --- |", ] ) diff --git a/tpcc/Cargo.toml b/tpcc/Cargo.toml index 9ea476eb..82cc9969 100644 --- a/tpcc/Cargo.toml +++ b/tpcc/Cargo.toml @@ -9,9 +9,9 @@ pprof = ["dep:pprof"] [dependencies] clap = { version = "4", features = ["derive"] } chrono = { version = "0.4" } +hdrhistogram = { version = "7", default-features = false } kite_sql = { path = "..", package = "kite_sql", features = ["rocksdb", "lmdb", "decimal"] } indicatif = { version = "0.17" } -ordered-float = { version = "4" } rand = { version = "0.8" } rust_decimal = { version = "1" } sqlite = { version = "0.34" } diff --git a/tpcc/README.md b/tpcc/README.md index e933815e..1181f0fc 100644 --- a/tpcc/README.md +++ b/tpcc/README.md @@ -42,20 +42,21 @@ The benchmark stores `history.h_date` as `timestamp(6)`, so high-throughput `Pay - Tips: TPC-C currently runs as a single worker. ## 720s comparison -Local stable-run 720-second comparison on the machine above: +Local 720-second comparison on the machine above: -| Backend | TpmC | New-Order p90 | Payment p90 | Order-Status p90 | Delivery p90 | Stock-Level p90 | +| Backend | TpmC | New-Order p90 (µs) | Payment p90 (µs) | Order-Status p90 (µs) | Delivery p90 (µs) | Stock-Level p90 (µs) | | --- | ---: | ---: | ---: | ---: | ---: | ---: | -| KiteSQL LMDB | 82871 | 0.001s | 0.001s | 0.001s | 0.002s | 0.001s | -| KiteSQL RocksDB | 40960 | 0.001s | 0.001s | 0.001s | 0.011s | 0.001s | -| SQLite balanced | 51637 | 0.001s | 0.001s | 0.001s | 0.001s | 0.001s | -| SQLite practical | 61424 | 0.001s | 0.001s | 0.001s | 0.001s | 0.001s | +| KiteSQL LMDB | 70315 | 561 | 196 | 208 | 1089 | 1693 | +| KiteSQL RocksDB | 35556 | 747 | 394 | 365 | 10175 | 2125 | +| SQLite balanced | 54797 | 303 | 72 | 52 | 363 | 508 | +| SQLite practical | 44847 | 527 | 146 | 61 | 1248 | 486 | -- The KiteSQL rows are from `2026-07-11_17-20-24`; the SQLite rows are from `2026-07-11_20-25-01`. -- The stable-run gates were `temp<=65.0C`, `cpu<=20.0%`, `min_cooldown=300s`, `stable_samples=3`, and `sample_interval=10.0s`. +- Run dates: `2026-09-06–2026-09-07`; results: `2026-09-06_19-04-32`. Latency is measured in microseconds and includes commit. - All rows use `--num-ware 1`, `--max-retry 5`, and TPCC's default 720-second measure time. - SQLite rows use the `balanced` and `practical` profiles respectively. +## Historical raw outputs — 2026-07-11 + ### KiteSQL LMDB ```shell Transaction Summary (elapsed 720.0s) diff --git a/tpcc/src/backend/dual.rs b/tpcc/src/backend/dual.rs index 1a2793b9..a197233f 100644 --- a/tpcc/src/backend/dual.rs +++ b/tpcc/src/backend/dual.rs @@ -18,11 +18,9 @@ use super::{ BackendControl, BackendTransaction, DbParam, KiteSqlPreparedStatement, PreparedStatement, SimpleExecutor, StatementSpec, }; -use crate::{TpccError, STOCK_LEVEL_DISTINCT_SQL, STOCK_LEVEL_DISTINCT_SQLITE}; +use crate::TpccError; use kite_sql::types::tuple::Tuple; -use kite_sql::types::value::DataValue; use std::borrow::Cow; -use std::collections::HashMap; pub struct DualBackend { kitesql: KiteSqlRocksDbBackend, @@ -65,12 +63,8 @@ impl BackendControl for DualBackend { &self, specs: &[Vec], ) -> Result>>, TpccError> { - let sqlite_specs: Vec> = specs - .iter() - .map(|group| group.iter().map(sqlite_statement_spec).collect()) - .collect(); let kitesql_groups = self.kitesql.prepare_statements(specs)?; - let sqlite_groups = self.sqlite.prepare_statements(&sqlite_specs)?; + let sqlite_groups = self.sqlite.prepare_statements(specs)?; let mut groups = Vec::with_capacity(kitesql_groups.len()); for (kitesql_group, sqlite_group) in kitesql_groups.into_iter().zip(sqlite_groups) { @@ -130,13 +124,7 @@ impl<'a> BackendTransaction for DualTransaction<'a> { let sqlite_iter = self.sqlite.execute_raw(&mut statement.sqlite, params)?; if is_select_sql(&spec) { - if spec.sql == STOCK_LEVEL_DISTINCT_SQL { - let (kitesql_counts, kitesql_len) = collect_kitesql_value_counts(kitesql_iter)?; - let sqlite_rows = collect_sqlite_rows(sqlite_iter)?; - compare_unordered_rows(kitesql_counts, kitesql_len, &sqlite_rows, spec.sql) - } else { - drain_and_compare_ordered(kitesql_iter, sqlite_iter, spec.sql) - } + drain_and_compare_ordered(kitesql_iter, sqlite_iter, spec.sql) } else { drain_sqlite_iter(sqlite_iter)?; drain_kitesql_iter(kitesql_iter) @@ -152,6 +140,46 @@ impl<'a> BackendTransaction for DualTransaction<'a> { self.with_query_nth(statement, params, 0, visitor) } + fn with_query_all( + &mut self, + statement: &mut Self::PreparedStatement, + params: &[DbParam], + visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>, + ) -> Result<(), TpccError> { + let mut rows = Vec::new(); + self.kitesql + .with_query_all(&mut statement.kitesql, params, &mut |tuple| { + rows.push(tuple.clone()); + Ok(()) + })?; + let mut sqlite_rows = Vec::new(); + self.sqlite + .with_query_all(&mut statement.sqlite, params, &mut |tuple| { + sqlite_rows.push(tuple.values.clone()); + Ok(()) + })?; + // SQL without ORDER BY may return the same rows in different orders. + for row in &rows { + let Some(index) = sqlite_rows.iter().position(|values| *values == row.values) else { + return Err(TpccError::BackendMismatch(format!( + "Result mismatch for SQL: {}", + statement.spec.sql + ))); + }; + sqlite_rows.swap_remove(index); + } + if !sqlite_rows.is_empty() { + return Err(TpccError::BackendMismatch(format!( + "SQLite returned extra rows for SQL: {}", + statement.spec.sql + ))); + } + for row in &rows { + visitor(row)?; + } + Ok(()) + } + fn with_query_nth( &mut self, statement: &mut Self::PreparedStatement, @@ -164,14 +192,6 @@ impl<'a> BackendTransaction for DualTransaction<'a> { let kitesql_iter = self.kitesql.execute_raw(&mut statement.kitesql, params)?; let sqlite_iter = self.sqlite.execute_raw(&mut statement.sqlite, params)?; - if spec.sql == STOCK_LEVEL_DISTINCT_SQL { - let (kitesql_counts, kitesql_len) = collect_kitesql_value_counts(kitesql_iter)?; - let sqlite_rows = collect_sqlite_rows(sqlite_iter)?; - compare_unordered_rows(kitesql_counts, kitesql_len, &sqlite_rows, spec.sql)?; - let tuple = sqlite_rows.get(n).ok_or(TpccError::EmptyTuples)?; - return visitor(tuple); - } - if !is_select_sql(&spec) { drain_sqlite_iter(sqlite_iter)?; return with_kitesql_nth(kitesql_iter, n, visitor); @@ -210,27 +230,6 @@ fn drain_kitesql_iter( Ok(()) } -fn collect_sqlite_rows(mut iter: SqliteResult<'_, '_>) -> Result, TpccError> { - let mut rows = Vec::new(); - while let Some(row) = iter.next() { - rows.push(row?); - } - Ok(rows) -} - -fn collect_kitesql_value_counts( - mut iter: KiteSqlTxnResult<'_, T>, -) -> Result<(HashMap, usize>, usize), TpccError> { - let mut counts = HashMap::new(); - let mut len = 0; - while let Some(()) = iter.with_next_tuple(|tuple| { - *counts.entry(tuple.values.clone()).or_insert(0) += 1; - len += 1; - Ok(()) - })? {} - Ok((counts, len)) -} - fn with_kitesql_nth( mut iter: KiteSqlTxnResult<'_, T>, n: usize, @@ -343,58 +342,6 @@ fn drain_and_compare_ordered( } } -fn compare_unordered_rows( - mut counts: HashMap, usize>, - kitesql_len: usize, - sqlite_rows: &[Tuple], - sql: &'static str, -) -> Result<(), TpccError> { - if kitesql_len != sqlite_rows.len() { - return Err(TpccError::BackendMismatch(format!( - "SQLite returned different row count for SQL: {}", - sql - ))); - } - - for row in sqlite_rows { - match counts.get_mut(&row.values) { - Some(count) => { - if *count == 1 { - counts.remove(&row.values); - } else { - *count -= 1; - } - } - None => { - return Err(TpccError::BackendMismatch(format!( - "SQLite returned different distinct set for SQL: {}", - sql - ))); - } - } - } - - if counts.is_empty() { - Ok(()) - } else { - Err(TpccError::BackendMismatch(format!( - "SQLite returned different distinct set for SQL: {}", - sql - ))) - } -} - -fn sqlite_statement_spec(spec: &StatementSpec) -> StatementSpec { - if spec.sql == STOCK_LEVEL_DISTINCT_SQL { - StatementSpec { - sql: STOCK_LEVEL_DISTINCT_SQLITE, - result_types: spec.result_types, - } - } else { - spec.clone() - } -} - fn is_select_sql(spec: &StatementSpec) -> bool { spec.sql .trim_start() diff --git a/tpcc/src/backend/kitesql_lmdb.rs b/tpcc/src/backend/kitesql_lmdb.rs index 92026ad9..bed4101a 100644 --- a/tpcc/src/backend/kitesql_lmdb.rs +++ b/tpcc/src/backend/kitesql_lmdb.rs @@ -130,6 +130,17 @@ impl<'a> BackendTransaction for KiteSqlLmdbTransactionWrapper<'a> { .ok_or(TpccError::EmptyTuples) } + fn with_query_all( + &mut self, + statement: &mut Self::PreparedStatement, + params: &[DbParam], + visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>, + ) -> Result<(), TpccError> { + let mut iter = self.execute_raw(statement, params)?; + while iter.with_next_tuple(|tuple| visitor(tuple))?.is_some() {} + Ok(()) + } + fn with_query_nth( &mut self, statement: &mut Self::PreparedStatement, diff --git a/tpcc/src/backend/kitesql_rocksdb.rs b/tpcc/src/backend/kitesql_rocksdb.rs index 6a6e14da..9a9cd82e 100644 --- a/tpcc/src/backend/kitesql_rocksdb.rs +++ b/tpcc/src/backend/kitesql_rocksdb.rs @@ -203,6 +203,17 @@ impl<'a, S: Storage> BackendTransaction for KiteSqlRocksTransaction<'a, S> { .ok_or(TpccError::EmptyTuples) } + fn with_query_all( + &mut self, + statement: &mut Self::PreparedStatement, + params: &[DbParam], + visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>, + ) -> Result<(), TpccError> { + let mut iter = self.execute_raw(statement, params)?; + while iter.with_next_tuple(|tuple| visitor(tuple))?.is_some() {} + Ok(()) + } + fn with_query_nth( &mut self, statement: &mut Self::PreparedStatement, diff --git a/tpcc/src/backend/mod.rs b/tpcc/src/backend/mod.rs index 1ee25287..35df2c5d 100644 --- a/tpcc/src/backend/mod.rs +++ b/tpcc/src/backend/mod.rs @@ -64,6 +64,13 @@ pub trait BackendTransaction { visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>, ) -> Result<(), TpccError>; + fn with_query_all( + &mut self, + statement: &mut Self::PreparedStatement, + params: &[DbParam], + visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>, + ) -> Result<(), TpccError>; + fn with_query_nth( &mut self, statement: &mut Self::PreparedStatement, diff --git a/tpcc/src/backend/sqlite.rs b/tpcc/src/backend/sqlite.rs index f690a606..c4c5b8dc 100644 --- a/tpcc/src/backend/sqlite.rs +++ b/tpcc/src/backend/sqlite.rs @@ -171,6 +171,19 @@ impl<'a> BackendTransaction for SqliteTransaction<'a> { visitor(&tuple) } + fn with_query_all( + &mut self, + statement: &mut Self::PreparedStatement, + params: &[DbParam], + visitor: &mut dyn FnMut(&Tuple) -> Result<(), TpccError>, + ) -> Result<(), TpccError> { + let iter = self.execute_raw(statement, params)?; + for row in iter { + visitor(&row?)?; + } + Ok(()) + } + fn with_query_nth( &mut self, statement: &mut Self::PreparedStatement, diff --git a/tpcc/src/main.rs b/tpcc/src/main.rs index 4a0d9925..2cf3b73a 100644 --- a/tpcc/src/main.rs +++ b/tpcc/src/main.rs @@ -66,8 +66,6 @@ const TX_NAMES: [&str; 5] = [ "Delivery", "Stock-Level", ]; -pub(crate) const STOCK_LEVEL_DISTINCT_SQL: &str = "SELECT DISTINCT ol_i_id FROM order_line WHERE ol_w_id = $1 AND ol_d_id = $2 AND ol_o_id < $3 AND ol_o_id >= ($4 - 20)"; -pub(crate) const STOCK_LEVEL_DISTINCT_SQLITE: &str = "SELECT DISTINCT ol_i_id FROM (SELECT ol_i_id FROM order_line WHERE ol_w_id = $1 AND ol_d_id = $2 AND ol_o_id < $3 AND ol_o_id >= ($4 - 20) ORDER BY ol_w_id, ol_d_id, ol_o_id)"; pub(crate) trait TpccTransaction { type Args; @@ -231,6 +229,7 @@ fn run_tpcc( failure[i] += 1; last_error = Some(err); } else { + tx.commit()?; let rt = transaction_start.elapsed(); rt_hist.hist_inc(i, rt); is_succeed = true; @@ -240,7 +239,6 @@ fn run_tpcc( } else { late[i] += 1; } - tx.commit()?; break; } } @@ -294,7 +292,6 @@ fn run_tpcc( print_constraint_checks(&success, &late); print_response_checks(&success, &late); println!(); - rt_hist.finalize(); rt_hist.hist_report(); println!(""); let tpmc = ((success[0] + late[0]) as f64 / (actual_tpcc_time.as_secs_f64() / 60.0)).round(); @@ -588,8 +585,10 @@ fn statement_specs() -> Vec> { "SELECT d_next_o_id FROM district WHERE d_id = $1 AND d_w_id = $2", &[ColumnType::Int32], ), - stmt(STOCK_LEVEL_DISTINCT_SQL, &[ColumnType::Int32]), - // "SELECT count(*) FROM stock WHERE s_w_id = $1 AND s_i_id = $2 AND s_quantity < $3" + stmt( + "SELECT DISTINCT ol_i_id FROM order_line WHERE ol_w_id = $1 AND ol_d_id = $2 AND ol_o_id < $3 AND ol_o_id >= ($4 - 20)", + &[ColumnType::Int32], + ), stmt( "SELECT count(*) FROM stock WHERE s_w_id = $1 AND s_i_id = $2 AND s_quantity < $3", &[ColumnType::Int32], @@ -705,7 +704,7 @@ fn print_checkpoint( checkpoint_idx: usize, round: usize, test_name: &str, - p90: f64, + p90_us: u64, success: &[usize], late: &[usize], failure: &[usize], @@ -721,7 +720,7 @@ fn print_checkpoint( }; progress.println(format!( - "[CP {checkpoint_idx:>3} | round {round:>6} | {test_name} p90={p90:.3}s | \ + "[CP {checkpoint_idx:>3} | round {round:>6} | {test_name} p90={p90_us}us | \ est TpmC {:>6.0} | total fail {:>6}]", est_tpmc, total_failure )); diff --git a/tpcc/src/rt_hist.rs b/tpcc/src/rt_hist.rs index 8ed62166..44cab344 100644 --- a/tpcc/src/rt_hist.rs +++ b/tpcc/src/rt_hist.rs @@ -12,151 +12,85 @@ // See the License for the specific language governing permissions and // limitations under the License. -use ordered_float::OrderedFloat; +use hdrhistogram::Histogram; use std::time::Duration; -pub(crate) const MAX_REC: usize = 20; -pub(crate) const REC_PER_SEC: usize = 1000; pub(crate) const NUM_TRANSACTIONS: usize = 5; +const SIGNIFICANT_FIGURES: u8 = 3; + +const TX_NAMES: [&str; NUM_TRANSACTIONS] = [ + "New-Order", + "Payment", + "Order-Status", + "Delivery", + "Stock-Level", +]; -#[derive(Clone)] pub(crate) struct RtHist { - total_hist: Vec>, - cur_hist: Vec>, - max_rt: [f64; 5], - cur_max_rt: [f64; 5], + total: Vec>, + current_total: Vec>, } impl RtHist { pub(crate) fn new() -> Self { - let total_hist = vec![vec![0; MAX_REC * REC_PER_SEC]; 5]; - let cur_hist = vec![vec![0; MAX_REC * REC_PER_SEC]; 5]; - - let max_rt = [0.0; 5]; - let cur_max_rt = [0.0; 5]; - Self { - total_hist, - cur_hist, - max_rt, - cur_max_rt, + total: new_histograms(), + current_total: new_histograms(), } } - // Increment matched one - pub fn hist_inc(&mut self, transaction: usize, rtclk: Duration) { - let i = (rtclk.as_secs_f64() * REC_PER_SEC as f64) as usize; - let i = if i >= (MAX_REC * REC_PER_SEC) { - (MAX_REC * REC_PER_SEC) - 1 - } else { - i - }; - - if rtclk.as_secs_f64() > self.cur_max_rt[transaction] { - self.cur_max_rt[transaction] = rtclk.as_secs_f64(); - } - - self.cur_hist[transaction][i] += 1; + pub fn hist_inc(&mut self, transaction: usize, total: Duration) { + record(&mut self.total[transaction], total); + record(&mut self.current_total[transaction], total); } - // Checkpoint and add to the total histogram - pub fn hist_ckp(&mut self, transaction: usize) -> f64 { - let mut total = 0; - let mut tmp = 0; - let mut line = MAX_REC * REC_PER_SEC; - let mut line_set = false; - - for i in 0..(MAX_REC * REC_PER_SEC) { - total += self.cur_hist[transaction][i]; - } - - for i in 0..(MAX_REC * REC_PER_SEC) { - tmp += self.cur_hist[transaction][i]; - self.total_hist[transaction][i] += self.cur_hist[transaction][i]; - self.cur_hist[transaction][i] = 0; - - if tmp >= total * 99 / 100 && !line_set { - line = i; - line_set = true; - } - } - for i in 0..5 { - self.max_rt[i] = *OrderedFloat(self.cur_max_rt[i]).max(OrderedFloat(self.max_rt[i])); - self.cur_max_rt[i] = 0.0; - } - - line as f64 / REC_PER_SEC as f64 + pub fn hist_ckp(&mut self, transaction: usize) -> u64 { + let histogram = &mut self.current_total[transaction]; + let p90 = percentile(histogram, 0.90); + histogram.clear(); + p90 } - // Report histograms - pub fn finalize(&mut self) { - for transaction in 0..NUM_TRANSACTIONS { - for i in 0..(MAX_REC * REC_PER_SEC) { - self.total_hist[transaction][i] += self.cur_hist[transaction][i]; - self.cur_hist[transaction][i] = 0; - } - self.max_rt[transaction] = *OrderedFloat(self.cur_max_rt[transaction]) - .max(OrderedFloat(self.max_rt[transaction])); - self.cur_max_rt[transaction] = 0.0; - } - } - - // Report histograms pub fn hist_report(&self) { - let mut total = [0; NUM_TRANSACTIONS]; - let mut tmp = [0; NUM_TRANSACTIONS]; - let mut line = [MAX_REC * REC_PER_SEC; NUM_TRANSACTIONS]; + println!("\n"); + println!("| Transaction | p90 | Max |"); + println!("| --- | ---: | ---: |"); - for j in 0..NUM_TRANSACTIONS { - for i in 0..(MAX_REC * REC_PER_SEC) { - total[j] += self.total_hist[j][i]; - } - - for i in (0..(MAX_REC * REC_PER_SEC)).rev() { - tmp[j] += self.total_hist[j][i]; - if tmp[j] * 10 <= total[j] { - line[j] = i; - } - } + for (transaction, name) in TX_NAMES.iter().enumerate() { + print_percentiles(name, &self.total[transaction]); } + } +} - println!("\n"); - for j in 0..NUM_TRANSACTIONS { - match j { - 0 => println!("\n1.New-Order\n"), - 1 => println!("\n2.Payment\n"), - 2 => println!("\n3.Order-Status\n"), - 3 => println!("\n4.Delivery\n"), - 4 => println!("\n5.Stock-Level\n"), - _ => (), - } +fn new_histograms() -> Vec> { + (0..NUM_TRANSACTIONS) + .map(|_| Histogram::new(SIGNIFICANT_FIGURES).expect("valid histogram precision")) + .collect() +} - for i in 0..(MAX_REC * REC_PER_SEC) { - if i <= line[j] * 4 && self.total_hist[j][i] > 0 { - println!( - "{:.3}, {:6}", - (i + 1) as f64 / REC_PER_SEC as f64, - self.total_hist[j][i] - ); - } - } - } +fn duration_micros(duration: Duration) -> u64 { + let micros = duration.as_nanos().div_ceil(1_000).max(1); + micros.min(u64::MAX as u128) as u64 +} - println!("\n<90th Percentile RT (MaxRT)>"); - for j in 0..NUM_TRANSACTIONS { - match j { - 0 => print!(" New-Order : "), - 1 => print!(" Payment : "), - 2 => print!("Order-Status : "), - 3 => print!(" Delivery : "), - 4 => print!(" Stock-Level : "), - _ => (), - } - println!( - "{:.3} ({:.3})", - line[j] as f64 / REC_PER_SEC as f64, - self.max_rt[j] - ); - } +fn record(histogram: &mut Histogram, duration: Duration) { + histogram + .record(duration_micros(duration)) + .expect("duration fits in histogram"); +} + +fn percentile(histogram: &Histogram, quantile: f64) -> u64 { + if histogram.is_empty() { + 0 + } else { + histogram.value_at_quantile(quantile) } } + +fn print_percentiles(name: &str, histogram: &Histogram) { + println!( + "| {name} | {} | {} |", + percentile(histogram, 0.90), + histogram.max(), + ); +} diff --git a/tpcc/src/slev.rs b/tpcc/src/slev.rs index 6b24718f..b3942b17 100644 --- a/tpcc/src/slev.rs +++ b/tpcc/src/slev.rs @@ -57,8 +57,8 @@ impl TpccTransaction for Slev { }, )?; // "SELECT DISTINCT ol_i_id FROM order_line WHERE ol_w_id = ? AND ol_d_id = ? AND ol_o_id < ? AND ol_o_id >= (? - 20)" - let mut ol_i_id = 0; - tx.with_query_one( + let mut item_ids = Vec::new(); + tx.with_query_all( &mut statements[1], &[ ("$1", DataValue::Int16(args.w_id as i16)), @@ -67,22 +67,26 @@ impl TpccTransaction for Slev { ("$4", DataValue::Int32(d_next_o_id)), ], &mut |tuple| { - ol_i_id = tuple.values[0].i32().unwrap(); + item_ids.push(tuple.values[0].i32().unwrap()); Ok(()) }, )?; // "SELECT count(*) FROM stock WHERE s_w_id = ? AND s_i_id = ? AND s_quantity < ?" - tx.with_query_one( - &mut statements[2], - &[ - ("$1", DataValue::Int16(args.w_id as i16)), - ("$2", DataValue::Int8(ol_i_id as i8)), - ("$3", DataValue::Int16(args.level as i16)), - ], - &mut |_| Ok(()), - )?; - // let i_count = tuple.values[0].i32().unwrap(); - + let mut _low_stock = 0; + for item_id in item_ids { + tx.with_query_one( + &mut statements[2], + &[ + ("$1", DataValue::Int16(args.w_id as i16)), + ("$2", DataValue::Int32(item_id)), + ("$3", DataValue::Int16(args.level as i16)), + ], + &mut |tuple| { + _low_stock += tuple.values[0].i32().unwrap(); + Ok(()) + }, + )?; + } Ok(()) } } @@ -97,7 +101,7 @@ impl TpccTest for SlevTest { statements: &mut [T::PreparedStatement], ) -> Result<(), TpccError> { let w_id = rng.gen_range(0..num_ware) + 1; - let d_id = rng.gen_range(1..DIST_PER_WARE); + let d_id = rng.gen_range(1..=DIST_PER_WARE); let level = rng.gen_range(10..20); let args = SlevArgs::new(w_id, d_id, level);