From 1069b2c9afec43493cf4e9dda976559f59fc52c6 Mon Sep 17 00:00:00 2001 From: Ge Gao Date: Thu, 21 Nov 2024 17:56:36 -0500 Subject: [PATCH] rpc benchmark: init commit of direct reading DB based on schema --- Cargo.lock | 24 ++ Cargo.toml | 1 + crates/sui-rpc-benchmark/Cargo.toml | 23 ++ crates/sui-rpc-benchmark/README.md | 22 ++ crates/sui-rpc-benchmark/src/direct/mod.rs | 5 + .../src/direct/query_executor.rs | 277 ++++++++++++++++ .../src/direct/query_generator.rs | 311 ++++++++++++++++++ crates/sui-rpc-benchmark/src/lib.rs | 68 ++++ crates/sui-rpc-benchmark/src/main.rs | 11 + 9 files changed, 742 insertions(+) create mode 100644 crates/sui-rpc-benchmark/Cargo.toml create mode 100644 crates/sui-rpc-benchmark/README.md create mode 100644 crates/sui-rpc-benchmark/src/direct/mod.rs create mode 100644 crates/sui-rpc-benchmark/src/direct/query_executor.rs create mode 100644 crates/sui-rpc-benchmark/src/direct/query_generator.rs create mode 100644 crates/sui-rpc-benchmark/src/lib.rs create mode 100644 crates/sui-rpc-benchmark/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 3b3f746f7994af..e7f0b945905e30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12638,6 +12638,15 @@ dependencies = [ "der 0.7.9", ] +[[package]] +name = "sqlparser" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a875d8cd437cc8a97e9aeaeea352ec9a19aea99c23e9effb17757291de80b08" +dependencies = [ + "log", +] + [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -15147,6 +15156,21 @@ dependencies = [ "typed-store", ] +[[package]] +name = "sui-rpc-benchmark" +version = "1.39.0" +dependencies = [ + "anyhow", + "async-trait", + "clap", + "futures", + "sqlparser", + "sui-pg-temp-db", + "tokio", + "tokio-postgres", + "tracing", +] + [[package]] name = "sui-rpc-loadgen" version = "1.39.0" diff --git a/Cargo.toml b/Cargo.toml index c33adee27a8a61..201f316630b1a0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -148,6 +148,7 @@ members = [ "crates/sui-replay", "crates/sui-rest-api", "crates/sui-rosetta", + "crates/sui-rpc-benchmark", "crates/sui-rpc-loadgen", "crates/sui-sdk", "crates/sui-security-watchdog", diff --git a/crates/sui-rpc-benchmark/Cargo.toml b/crates/sui-rpc-benchmark/Cargo.toml new file mode 100644 index 00000000000000..0912cddad7abae --- /dev/null +++ b/crates/sui-rpc-benchmark/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "sui-rpc-benchmark" +version.workspace = true +authors = ["Mysten Labs "] +license = "Apache-2.0" +publish = false +edition = "2021" + +[dependencies] +anyhow.workspace = true +async-trait.workspace = true +clap = { workspace = true, features = ["derive"] } +tokio = { workspace = true, features = ["full"] } +tracing.workspace = true +futures.workspace = true +sqlparser = "0.52.0" +tokio-postgres = "0.7.12" + +sui-pg-temp-db = { workspace = true } + +[[bin]] +name = "sui-rpc-benchmark" +path = "src/main.rs" diff --git a/crates/sui-rpc-benchmark/README.md b/crates/sui-rpc-benchmark/README.md new file mode 100644 index 00000000000000..4fe3d3104a44c8 --- /dev/null +++ b/crates/sui-rpc-benchmark/README.md @@ -0,0 +1,22 @@ +# sui-rpc-benchmark: Benchmarking Tool for SUI RPC Performance + +`sui-rpc-benchmark` is a benchmarking utility designed to measure performance across different RPC access methods in Sui: +- Direct database reads +- JSON RPC endpoints +- GraphQL queries + +## Usage Examples +Run benchmarks with: +``` +# Direct database queries: +cargo run --bin sui-rpc-benchmark direct --db-url postgres://postgres:postgres@localhost:5432/sui + +# JSON RPC endpoints: +cargo run --bin sui-rpc-benchmark jsonrpc --endpoint http://127.0.0.1:9000 + +# GraphQL queries: +cargo run --bin sui-rpc-benchmark graphql --endpoint http://127.0.0.1:9000/graphql +``` + + + diff --git a/crates/sui-rpc-benchmark/src/direct/mod.rs b/crates/sui-rpc-benchmark/src/direct/mod.rs new file mode 100644 index 00000000000000..f06667707b6b43 --- /dev/null +++ b/crates/sui-rpc-benchmark/src/direct/mod.rs @@ -0,0 +1,5 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub mod query_executor; +pub mod query_generator; diff --git a/crates/sui-rpc-benchmark/src/direct/query_executor.rs b/crates/sui-rpc-benchmark/src/direct/query_executor.rs new file mode 100644 index 00000000000000..755264c7efa18a --- /dev/null +++ b/crates/sui-rpc-benchmark/src/direct/query_executor.rs @@ -0,0 +1,277 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use tokio_postgres::{types::ToSql, types::Type, Client, Row}; + +use crate::direct::query_generator::BenchmarkQuery; + +#[derive(Debug)] +pub struct EnrichedBenchmarkQuery { + pub query: BenchmarkQuery, + pub rows: Vec, +} + +pub struct QueryExecutor { + db_client: Client, + enriched_benchmark_queries: Vec, +} + +impl QueryExecutor { + pub async fn new( + db_url: &str, + benchmark_queries: Vec, + ) -> Result { + let (client, connection) = tokio_postgres::connect(db_url, tokio_postgres::NoTls).await?; + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + + let mut executor = Self { + db_client: client, + enriched_benchmark_queries: Vec::new(), + }; + let mut enriched_queries = Vec::new(); + for query in benchmark_queries { + let enriched = executor.enrich_query(&query).await?; + enriched_queries.push(enriched); + } + executor.enriched_benchmark_queries = enriched_queries; + + Ok(executor) + } + + pub async fn run(&self) -> Result<(), anyhow::Error> { + println!( + "Starting parallel execution of {} queries", + self.enriched_benchmark_queries.len() + ); + let futures: Vec<_> = self + .enriched_benchmark_queries + .iter() + .map(|enriched| { + println!("Executing query: {}", enriched.query.query_template); + self.execute_query(enriched) + }) + .collect(); + let results = futures::future::join_all(futures).await; + + for (i, result) in results.into_iter().enumerate() { + match result { + Ok(rows) => println!( + "Query \n'{}'\n completed successfully with {} rows", + self.enriched_benchmark_queries[i].query.query_template, + rows.len() + ), + Err(e) => println!( + "Query \n'{}'\n failed with error: {}", + self.enriched_benchmark_queries[i].query.query_template, e + ), + } + } + println!("All benchmark queries completed"); + Ok(()) + } + + async fn enrich_query( + &self, + bq: &BenchmarkQuery, + ) -> Result { + // TODO(gegaowp): only fetch one row for quick execution, will configure and fetch more. + let query = format!( + "SELECT {} FROM {} LIMIT 1", + bq.needed_columns.join(","), + bq.table_name + ); + println!("Enriched query: {}", query); + let rows = self.db_client.query(&query, &[]).await?; + + Ok(EnrichedBenchmarkQuery { + query: bq.clone(), + rows, + }) + } + + async fn execute_query( + &self, + query: &EnrichedBenchmarkQuery, + ) -> Result, tokio_postgres::Error> { + let mut all_results = Vec::new(); + for row in &query.rows { + let params_vec = row_to_params(row); + let value_refs: Vec<&(dyn tokio_postgres::types::ToSql + Sync)> = + params_vec.iter().map(|v| v.as_ref()).collect(); + + let results = self + .db_client + .query(&query.query.query_template, &value_refs) + .await?; + all_results.extend(results); + } + Ok(all_results) + } +} + +fn row_to_params(row: &Row) -> Vec> { + let mut params: Vec> = Vec::new(); + + for i in 0..row.len() { + match row.columns()[i].type_() { + &Type::TEXT | &Type::VARCHAR => { + params.push(Box::new(row.get::<_, Option>(i)) as Box) + } + &Type::INT4 => { + params.push(Box::new(row.get::<_, Option>(i)) as Box) + } + &Type::INT8 => { + params.push(Box::new(row.get::<_, Option>(i)) as Box) + } + &Type::FLOAT8 => { + params.push(Box::new(row.get::<_, Option>(i)) as Box) + } + &Type::BOOL => { + params.push(Box::new(row.get::<_, Option>(i)) as Box) + } + &Type::INT2 => { + params.push(Box::new(row.get::<_, Option>(i)) as Box) + } + &Type::BYTEA => { + params.push(Box::new(row.get::<_, Option>>(i)) as Box) + } + _ => panic!("Unsupported type: {:?}", row.columns()[i].type_()), + } + } + params +} + +#[cfg(test)] +mod tests { + use super::*; + use sui_pg_temp_db::TempDb; + use tokio_postgres::NoTls; + + #[tokio::test] + async fn test_execute_enriched_query() -> Result<(), Box> { + let db = TempDb::new().unwrap(); + let url = db.database().url(); + let (client, connection) = tokio_postgres::connect(url.as_str(), NoTls).await?; + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + + // Create test table and insert test data + client + .execute( + "CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY, name TEXT)", + &[], + ) + .await?; + client + .execute( + "INSERT INTO test_table (id, name) VALUES ($1, $2)", + &[ + &1i32 as &(dyn tokio_postgres::types::ToSql + Sync), + &"test" as &(dyn tokio_postgres::types::ToSql + Sync), + ], + ) + .await?; + // Create benchmark query + let benchmark_query = BenchmarkQuery { + query_template: "SELECT * FROM test_table WHERE id = $1 AND name = $2".to_string(), + table_name: "test_table".to_string(), + needed_columns: vec!["id".to_string(), "name".to_string()], + }; + + // Create executor and enrich query + let executor = QueryExecutor::new(url.as_str(), vec![benchmark_query]).await?; + let enriched_query = &executor.enriched_benchmark_queries[0]; + + // Assert enriched query details match what we expect + assert_eq!( + enriched_query.query.query_template, + "SELECT * FROM test_table WHERE id = $1 AND name = $2" + ); + assert_eq!(enriched_query.query.table_name, "test_table"); + assert_eq!( + enriched_query.query.needed_columns, + vec!["id".to_string(), "name".to_string()] + ); + assert_eq!(enriched_query.rows.len(), 1); + + // Execute enriched query + let result = executor.execute_query(enriched_query).await?; + + // Verify result matches expected values + assert_eq!(result.len(), 1); + assert!(check_rows_consistency(&result[0], &enriched_query.rows[0])?); + + Ok(()) + } + + fn check_rows_consistency(row1: &Row, row2: &Row) -> Result> { + // Get column names for both rows + let cols1: Vec<&str> = row1.columns().iter().map(|c| c.name()).collect(); + let cols2: Vec<&str> = row2.columns().iter().map(|c| c.name()).collect(); + + // Find overlapping columns + let common_cols: Vec<&str> = cols1 + .iter() + .filter(|&col| cols2.contains(col)) + .cloned() + .collect(); + + // Check each common column for value equality + for col in common_cols { + // assert the column types match + let col_type1 = row1 + .columns() + .iter() + .find(|c| c.name() == col) + .map(|c| c.type_()) + .unwrap(); + let col_type2 = row2 + .columns() + .iter() + .find(|c| c.name() == col) + .map(|c| c.type_()) + .unwrap(); + assert_eq!( + col_type1, col_type2, + "Column types should match for column {}", + col + ); + let col_type = col_type1; + + // assert the column values match + if !compare_row_values(row1, row2, col, col_type)? { + println!("Column '{}' has inconsistent values between rows", col); + return Ok(false); + } + } + + Ok(true) + } + + fn compare_row_values( + row1: &Row, + row2: &Row, + col: &str, + col_type: &Type, + ) -> Result> { + Ok(match col_type { + &Type::TEXT | &Type::VARCHAR => { + row1.get::<_, String>(col) == row2.get::<_, String>(col) + } + &Type::INT4 => row1.get::<_, i32>(col) == row2.get::<_, i32>(col), + &Type::INT8 => row1.get::<_, i64>(col) == row2.get::<_, i64>(col), + &Type::FLOAT8 => row1.get::<_, f64>(col) == row2.get::<_, f64>(col), + &Type::BOOL => row1.get::<_, bool>(col) == row2.get::<_, bool>(col), + &Type::INT2 => row1.get::<_, i16>(col) == row2.get::<_, i16>(col), + &Type::BYTEA => row1.get::<_, Vec>(col) == row2.get::<_, Vec>(col), + _ => panic!("Unsupported type: {:?}", col_type), + }) + } +} diff --git a/crates/sui-rpc-benchmark/src/direct/query_generator.rs b/crates/sui-rpc-benchmark/src/direct/query_generator.rs new file mode 100644 index 00000000000000..c534343a2728c4 --- /dev/null +++ b/crates/sui-rpc-benchmark/src/direct/query_generator.rs @@ -0,0 +1,311 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use sqlparser::ast::{ColumnOption, CreateIndex, CreateTable, Statement, TableConstraint}; +use sqlparser::dialect::PostgreSqlDialect; +use sqlparser::parser::Parser; + +pub struct QueryGenerator; + +#[derive(Debug, Clone)] +pub struct BenchmarkQuery { + pub query_template: String, + pub table_name: String, + pub needed_columns: Vec, +} + +impl QueryGenerator { + fn read_sqls() -> Result, anyhow::Error> { + let current_dir = std::env::current_dir()?; + let migration_path = current_dir + .parent() // up to crates/ + .and_then(|p| p.parent()) // up to sui/ + .map(|p| p.join("crates/sui-indexer-alt/migrations")) + .ok_or_else(|| anyhow::anyhow!("Could not find migrations directory"))?; + let migration_path = migration_path.to_str().unwrap(); + let mut sqls = Vec::new(); + + Self::read_sql_impl(std::path::Path::new(migration_path), &mut sqls)?; + println!("Read {} up.sql files from migrations directory", sqls.len()); + Ok(sqls) + } + + fn read_sql_impl(dir: &std::path::Path, sqls: &mut Vec) -> Result<(), anyhow::Error> { + if dir.is_dir() { + for entry in std::fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + Self::read_sql_impl(&path, sqls)?; + } else if path.is_file() + && path + .file_name() + .and_then(|f| f.to_str()) + .map(|s| s.ends_with("up.sql")) + .unwrap_or(false) + { + let sql = std::fs::read_to_string(&path)?; + sqls.push(sql); + } + } + } + Ok(()) + } + + pub fn generate_benchmark_queries() -> Result, anyhow::Error> { + let sqls = Self::read_sqls()?; + let mut benchmark_queries = Vec::new(); + for sql in sqls { + let queries = sql_to_benchmark_queries(&sql)?; + benchmark_queries.extend(queries); + } + Ok(benchmark_queries) + } +} + +fn sql_to_benchmark_queries(sql: &str) -> Result, anyhow::Error> { + let dialect = PostgreSqlDialect {}; + let statements = Parser::parse_sql(&dialect, sql)?; + + let mut tables = Vec::new(); + let mut indexes = Vec::new(); + + for stmt in statements { + match stmt { + Statement::CreateTable(CreateTable { + name, + columns, + constraints, + .. + }) => { + let table_name = name.to_string(); + let mut primary_keys = Vec::new(); + // Extract primary keys from table PRIMARY KEY constraint, for example: + // CREATE TABLE test_table ( + // id BIGINT, + // name VARCHAR(255), + // value INTEGER, + // PRIMARY KEY (id) + // ); + for constraint in &constraints { + if let TableConstraint::PrimaryKey { columns, .. } = constraint { + primary_keys = columns.iter().map(|c| c.to_string()).collect(); + } + } + // If table PRIMARY KEY constraint is not defined, check column constraints for PRIMARY KEY option, for example: + // CREATE TABLE test_table ( + // id BIGINT PRIMARY KEY, + // name VARCHAR(255), + // value INTEGER + // ); + if primary_keys.is_empty() { + for column in &columns { + for option in &column.options { + if let ColumnOption::Unique { is_primary, .. } = &option.option { + if *is_primary { + primary_keys.push(column.name.to_string()); + } + } + } + } + } + + tables.push((table_name, primary_keys)); + } + Statement::CreateIndex(CreateIndex { + name: _, + table_name: idx_table, + columns, + .. + }) => { + indexes.push(( + idx_table.to_string(), + columns.iter().map(|c| c.to_string()).collect::>(), + )); + } + _ => {} + } + } + + // Generate queries + let mut queries = Vec::new(); + + // 1. Primary key lookup queries + for (table_name, primary_keys) in tables { + if !primary_keys.is_empty() { + let pk_conditions = primary_keys + .iter() + .enumerate() + .map(|(i, pk)| format!("{} = ${}", pk, i + 1)) + .collect::>() + .join(" AND "); + + queries.push(BenchmarkQuery { + query_template: format!( + "SELECT * FROM {} WHERE {} LIMIT 1", + table_name, pk_conditions + ), + table_name: table_name.clone(), + needed_columns: primary_keys, + }); + } + } + + // 2. Index-based queries + for (table_name, idx_columns) in indexes { + let conditions = idx_columns + .iter() + .enumerate() + .map(|(i, col)| format!("{} = ${}", col, i + 1)) + .collect::>() + .join(" AND "); + + queries.push(BenchmarkQuery { + query_template: format!("SELECT * FROM {} WHERE {} LIMIT 50", table_name, conditions), + table_name, + needed_columns: idx_columns, + }); + } + + Ok(queries) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generate_queries() -> Result<(), Box> { + let sql = r#" + CREATE TABLE test_table ( + id BIGINT PRIMARY KEY, + name VARCHAR(255), + value INTEGER + ); + + CREATE INDEX idx_name ON test_table(name); + CREATE INDEX idx_value ON test_table(value); + "#; + + let queries = sql_to_benchmark_queries(sql)?; + + // Should generate 3 queries - one for PK lookup and two for indexes + assert_eq!(queries.len(), 3); + + // Verify PK query + assert_eq!( + queries[0].query_template, + "SELECT * FROM test_table WHERE id = $1 LIMIT 1" + ); + assert_eq!(queries[0].table_name, "test_table"); + assert_eq!(queries[0].needed_columns, vec!["id"]); + + // Verify index queries + assert_eq!( + queries[1].query_template, + "SELECT * FROM test_table WHERE name = $1 LIMIT 50" + ); + assert_eq!(queries[1].table_name, "test_table"); + assert_eq!(queries[1].needed_columns, vec!["name"]); + + assert_eq!( + queries[2].query_template, + "SELECT * FROM test_table WHERE value = $1 LIMIT 50" + ); + assert_eq!(queries[2].table_name, "test_table"); + assert_eq!(queries[2].needed_columns, vec!["value"]); + + Ok(()) + } + + #[test] + fn test_generate_queries_with_table_pk() -> Result<(), Box> { + let sql = r#" + CREATE TABLE test_table2 ( + id BIGINT, + name VARCHAR(255), + value INTEGER, + PRIMARY KEY (id) + ); + + CREATE INDEX idx_name ON test_table2(name); + CREATE INDEX idx_value ON test_table2(value); + "#; + + let queries = sql_to_benchmark_queries(sql)?; + + // Should generate 3 queries - one for PK lookup and two for indexes + assert_eq!(queries.len(), 3); + + // Verify PK query + assert_eq!( + queries[0].query_template, + "SELECT * FROM test_table2 WHERE id = $1 LIMIT 1" + ); + assert_eq!(queries[0].table_name, "test_table2"); + assert_eq!(queries[0].needed_columns, vec!["id"]); + + // Verify index queries + assert_eq!( + queries[1].query_template, + "SELECT * FROM test_table2 WHERE name = $1 LIMIT 50" + ); + assert_eq!(queries[1].table_name, "test_table2"); + assert_eq!(queries[1].needed_columns, vec!["name"]); + + assert_eq!( + queries[2].query_template, + "SELECT * FROM test_table2 WHERE value = $1 LIMIT 50" + ); + assert_eq!(queries[2].table_name, "test_table2"); + assert_eq!(queries[2].needed_columns, vec!["value"]); + + Ok(()) + } + + #[test] + fn test_generate_queries_with_multi_column_indexes() -> Result<(), Box> { + let sql = r#" + CREATE TABLE test_table3 ( + id BIGINT, + name VARCHAR(255), + value INTEGER, + category VARCHAR(50), + PRIMARY KEY (id) + ); + + CREATE INDEX idx_name_value ON test_table3(name, value); + CREATE INDEX idx_category_value ON test_table3(category, value); + "#; + + let queries = sql_to_benchmark_queries(sql)?; + + // Should generate 3 queries - one for PK and two for composite indexes + assert_eq!(queries.len(), 3); + + // Verify PK query + assert_eq!( + queries[0].query_template, + "SELECT * FROM test_table3 WHERE id = $1 LIMIT 1" + ); + assert_eq!(queries[0].table_name, "test_table3"); + assert_eq!(queries[0].needed_columns, vec!["id"]); + + // Verify multi-column index queries + assert_eq!( + queries[1].query_template, + "SELECT * FROM test_table3 WHERE name = $1 AND value = $2 LIMIT 50" + ); + assert_eq!(queries[1].table_name, "test_table3"); + assert_eq!(queries[1].needed_columns, vec!["name", "value"]); + + assert_eq!( + queries[2].query_template, + "SELECT * FROM test_table3 WHERE category = $1 AND value = $2 LIMIT 50" + ); + assert_eq!(queries[2].table_name, "test_table3"); + assert_eq!(queries[2].needed_columns, vec!["category", "value"]); + + Ok(()) + } +} diff --git a/crates/sui-rpc-benchmark/src/lib.rs b/crates/sui-rpc-benchmark/src/lib.rs new file mode 100644 index 00000000000000..f2cd6dcd77d28b --- /dev/null +++ b/crates/sui-rpc-benchmark/src/lib.rs @@ -0,0 +1,68 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub mod direct; + +use anyhow::Result; +use clap::{Parser, Subcommand}; + +use crate::direct::query_executor::QueryExecutor; +use crate::direct::query_generator::QueryGenerator; + +#[derive(Parser)] +#[clap( + name = "sui-rpc-benchmark", + about = "Benchmark tool for comparing Sui RPC access methods" +)] +pub struct Opts { + #[clap(subcommand)] + pub command: Command, +} + +#[derive(Subcommand)] +pub enum Command { + /// Benchmark direct database queries + #[clap(name = "direct")] + DirectQuery { + #[clap( + long, + default_value = "postgres://postgres:postgres@localhost:5432/sui" + )] + db_url: String, + }, + /// Benchmark JSON RPC endpoints + #[clap(name = "jsonrpc")] + JsonRpc { + #[clap(long, default_value = "http://127.0.0.1:9000")] + endpoint: String, + }, + /// Benchmark GraphQL queries + #[clap(name = "graphql")] + GraphQL { + #[clap(long, default_value = "http://127.0.0.1:9000/graphql")] + endpoint: String, + }, +} + +pub async fn run_benchmarks() -> Result<(), anyhow::Error> { + let opts: Opts = Opts::parse(); + + match opts.command { + Command::DirectQuery { db_url } => { + println!("Running direct query benchmark against {}", db_url); + let benchmark_queries = QueryGenerator::generate_benchmark_queries()?; + println!("Generated {} benchmark queries", benchmark_queries.len()); + let query_executor = QueryExecutor::new(db_url.as_str(), benchmark_queries).await?; + query_executor.run().await?; + Ok(()) + } + Command::JsonRpc { endpoint } => { + println!("Running JSON RPC benchmark against {}", endpoint); + todo!() + } + Command::GraphQL { endpoint } => { + println!("Running GraphQL benchmark against {}", endpoint); + todo!() + } + } +} diff --git a/crates/sui-rpc-benchmark/src/main.rs b/crates/sui-rpc-benchmark/src/main.rs new file mode 100644 index 00000000000000..fec649451d1608 --- /dev/null +++ b/crates/sui-rpc-benchmark/src/main.rs @@ -0,0 +1,11 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use anyhow::Result; + +use sui_rpc_benchmark::run_benchmarks; + +#[tokio::main] +async fn main() -> Result<()> { + run_benchmarks().await +}