From 684b8ac0321d349ac5231ad4b204adb2fa236c5c Mon Sep 17 00:00:00 2001 From: "Brian J. Cardiff" Date: Thu, 30 Nov 2023 11:26:02 -0300 Subject: [PATCH] Add pool_concurrency_test manual spec Add MT connection count without Mutex --- spec/dummy_driver.cr | 41 +++++++++++++++-- spec/manual/pool_concurrency_test.cr | 68 ++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 spec/manual/pool_concurrency_test.cr diff --git a/spec/dummy_driver.cr b/spec/dummy_driver.cr index da6417c..8d0f7e5 100644 --- a/spec/dummy_driver.cr +++ b/spec/dummy_driver.cr @@ -1,4 +1,3 @@ -require "spec" require "../src/db" class DummyDriver < DB::Driver @@ -17,20 +16,37 @@ class DummyDriver < DB::Driver end class DummyConnection < DB::Connection + @@connections = [] of DummyConnection + @@connections_count = Atomic(Int32).new(0) + def initialize(options : DB::Connection::Options) super(options) Fiber.yield + @@connections_count.add(1) @connected = true - @@connections ||= [] of DummyConnection - @@connections.not_nil! << self + {% unless flag?(:preview_mt) %} + # @@connections is only used in single-threaded mode in specs + # for benchmarks we want to avoid the overhead of synchronizing this array + @@connections << self + {% end %} + end + + def self.connections_count + @@connections_count.get end def self.connections - @@connections.not_nil! + {% if flag?(:preview_mt) %} + raise "DummyConnection.connections is only available in single-threaded mode" + {% end %} + @@connections end def self.clear_connections - @@connections.try &.clear + {% if flag?(:preview_mt) %} + raise "DummyConnection.clear_connections is only available in single-threaded mode" + {% end %} + @@connections.clear end def build_prepared_statement(query) : DB::Statement @@ -117,17 +133,30 @@ class DummyDriver < DB::Driver end class DummyStatement < DB::Statement + @@statements_count = Atomic(Int32).new(0) + @@statements_exec_count = Atomic(Int32).new(0) property params def initialize(connection, command : String, @prepared : Bool) @params = Hash(Int32 | String, DB::Any | Array(DB::Any)).new super(connection, command) + @@statements_count.add(1) raise DB::Error.new(command) if command == "syntax error" end + def self.statements_count + @@statements_count.get + end + + def self.statements_exec_count + @@statements_exec_count.get + end + protected def perform_query(args : Enumerable) : DB::ResultSet assert_not_closed! + @@statements_exec_count.add(1) + Fiber.yield @connection.as(DummyConnection).check set_params args @@ -137,6 +166,8 @@ class DummyDriver < DB::Driver protected def perform_exec(args : Enumerable) : DB::ExecResult assert_not_closed! + @@statements_exec_count.add(1) + @connection.as(DummyConnection).check set_params args raise DB::Error.new("forced exception due to query") if command == "raise" diff --git a/spec/manual/pool_concurrency_test.cr b/spec/manual/pool_concurrency_test.cr new file mode 100644 index 0000000..84bdb28 --- /dev/null +++ b/spec/manual/pool_concurrency_test.cr @@ -0,0 +1,68 @@ +# This file is to be executed as: +# +# % crystal run --release [-Dpreview_mt] ./spec/manual/pool_concurrency_test.cr -- --options="max_pool_size=5" --duration=30 --concurrency=4 +# +# + +require "option_parser" +require "../dummy_driver" +require "../../src/db" + +options = "" +duration = 3 +concurrency = 4 + +OptionParser.parse do |parser| + parser.banner = "Usage: pool_concurrency_test [arguments]" + parser.on("-o", "--options=VALUE", "Connection string options") { |v| options = v } + parser.on("-d", "--duration=SECONDS", "Specifies the duration in seconds") { |v| duration = v.to_i } + parser.on("-c", "--concurrency=VALUE", "Specifies the concurrent requests to perform") { |v| concurrency = v.to_i } + parser.on("-h", "--help", "Show this help") do + puts parser + exit + end + parser.invalid_option do |flag| + STDERR.puts "ERROR: #{flag} is not a valid option." + STDERR.puts parser + exit(1) + end +end + +multi_threaded = {% if flag?(:preview_mt) %} ENV["CRYSTAL_WORKERS"]?.try(&.to_i?) || 4 {% else %} false {% end %} +release = {% if flag?(:release) %} true {% else %} false {% end %} + +if !release + puts "WARNING: This should be run in release mode." +end + +db = DB.open "dummy://host?#{options}" + +start_time = Time.monotonic + +puts "Starting test for #{duration} seconds..." + +concurrency.times do + spawn do + loop do + db.scalar "1" + Fiber.yield + end + end +end + +sleep duration.seconds + +end_time = Time.monotonic + +puts " Options : #{options}" +puts " Duration (sec) : #{duration} (actual #{end_time - start_time})" +puts " Concurrency : #{concurrency}" +puts " Multi Threaded : #{multi_threaded ? "Yes (#{multi_threaded})" : "No"}" +puts "Total Connections : #{DummyDriver::DummyConnection.connections_count}" +puts " Total Statements : #{DummyDriver::DummyStatement.statements_count}" +puts " Total Queries : #{DummyDriver::DummyStatement.statements_exec_count}" +puts "Throughtput (q/s) : #{DummyDriver::DummyStatement.statements_exec_count / duration}" + +if !release + puts "WARNING: This should be run in release mode." +end