familia 1.2.3 → 2.0.0.pre.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +68 -0
- data/.github/workflows/docs.yml +64 -0
- data/.gitignore +3 -0
- data/.pre-commit-config.yaml +3 -1
- data/.rubocop.yml +16 -9
- data/.rubocop_todo.yml +177 -31
- data/.yardopts +9 -0
- data/CLAUDE.md +141 -0
- data/Gemfile +15 -2
- data/Gemfile.lock +61 -61
- data/README.md +39 -23
- data/bin/irb +3 -0
- data/docs/connection_pooling.md +317 -0
- data/familia.gemspec +8 -5
- data/lib/familia/base.rb +19 -9
- data/lib/familia/connection.rb +232 -65
- data/lib/familia/core_ext.rb +1 -1
- data/lib/familia/datatype/commands.rb +59 -0
- data/lib/familia/{redistype → datatype}/serialization.rb +9 -13
- data/lib/familia/{redistype → datatype}/types/hashkey.rb +25 -25
- data/lib/familia/{redistype → datatype}/types/list.rb +13 -13
- data/lib/familia/{redistype → datatype}/types/sorted_set.rb +20 -20
- data/lib/familia/{redistype → datatype}/types/string.rb +22 -21
- data/lib/familia/{redistype → datatype}/types/unsorted_set.rb +11 -11
- data/lib/familia/datatype.rb +243 -0
- data/lib/familia/errors.rb +5 -2
- data/lib/familia/features/expiration.rb +33 -34
- data/lib/familia/features/quantization.rb +9 -3
- data/lib/familia/features/safe_dump.rb +2 -3
- data/lib/familia/features.rb +2 -2
- data/lib/familia/horreum/class_methods.rb +97 -130
- data/lib/familia/horreum/commands.rb +46 -51
- data/lib/familia/horreum/connection.rb +82 -0
- data/lib/familia/horreum/{relations_management.rb → related_fields_management.rb} +37 -35
- data/lib/familia/horreum/serialization.rb +61 -198
- data/lib/familia/horreum/settings.rb +6 -17
- data/lib/familia/horreum/utils.rb +11 -10
- data/lib/familia/horreum.rb +69 -60
- data/lib/familia/logging.rb +12 -12
- data/lib/familia/multi_result.rb +72 -0
- data/lib/familia/refinements.rb +7 -44
- data/lib/familia/settings.rb +11 -11
- data/lib/familia/utils.rb +123 -90
- data/lib/familia/version.rb +4 -21
- data/lib/familia.rb +17 -12
- data/lib/middleware/database_middleware.rb +150 -0
- data/try/configuration/scenarios_try.rb +65 -0
- data/try/core/connection_try.rb +58 -0
- data/try/core/errors_try.rb +93 -0
- data/try/core/extensions_try.rb +26 -0
- data/try/{10_familia_try.rb → core/familia_extended_try.rb} +11 -10
- data/try/{00_familia_try.rb → core/familia_try.rb} +5 -3
- data/try/core/middleware_try.rb +68 -0
- data/try/core/refinements_try.rb +39 -0
- data/try/core/settings_try.rb +76 -0
- data/try/core/tools_try.rb +54 -0
- data/try/core/utils_try.rb +189 -0
- data/try/{26_redis_bool_try.rb → datatypes/boolean_try.rb} +4 -2
- data/try/datatypes/datatype_base_try.rb +69 -0
- data/try/{25_redis_type_hash_try.rb → datatypes/hash_try.rb} +5 -3
- data/try/{23_redis_type_list_try.rb → datatypes/list_try.rb} +5 -3
- data/try/{22_redis_type_set_try.rb → datatypes/set_try.rb} +5 -3
- data/try/{21_redis_type_zset_try.rb → datatypes/sorted_set_try.rb} +6 -4
- data/try/{24_redis_type_string_try.rb → datatypes/string_try.rb} +8 -8
- data/try/edge_cases/empty_identifiers_try.rb +48 -0
- data/try/{92_symbolize_try.rb → edge_cases/hash_symbolization_try.rb} +12 -8
- data/try/edge_cases/json_serialization_try.rb +85 -0
- data/try/edge_cases/race_conditions_try.rb +60 -0
- data/try/edge_cases/reserved_keywords_try.rb +59 -0
- data/try/{93_string_coercion_try.rb → edge_cases/string_coercion_try.rb} +63 -60
- data/try/edge_cases/ttl_side_effects_try.rb +51 -0
- data/try/features/expiration_try.rb +86 -0
- data/try/features/quantization_try.rb +90 -0
- data/try/{35_feature_safedump_try.rb → features/safe_dump_advanced_try.rb} +7 -6
- data/try/features/safe_dump_try.rb +137 -0
- data/try/{test_helpers.rb → helpers/test_helpers.rb} +25 -60
- data/try/{27_redis_horreum_try.rb → horreum/base_try.rb} +39 -14
- data/try/horreum/class_methods_try.rb +41 -0
- data/try/horreum/commands_try.rb +49 -0
- data/try/{29_redis_horreum_initialization_try.rb → horreum/initialization_try.rb} +9 -7
- data/try/horreum/relations_try.rb +146 -0
- data/try/{28_redis_horreum_serialization_try.rb → horreum/serialization_try.rb} +13 -11
- data/try/horreum/settings_try.rb +43 -0
- data/try/integration/cross_component_try.rb +46 -0
- data/try/{41_customer_safedump_try.rb → models/customer_safe_dump_try.rb} +9 -7
- data/try/{40_customer_try.rb → models/customer_try.rb} +20 -17
- data/try/models/datatype_base_try.rb +101 -0
- data/try/{30_familia_object_try.rb → models/familia_object_try.rb} +18 -16
- data/try/performance/benchmarks_try.rb +55 -0
- data/try/pooling/README.md +20 -0
- data/try/pooling/configurable_stress_test_try.rb +435 -0
- data/try/pooling/connection_pool_test_try.rb +273 -0
- data/try/pooling/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- data/try/pooling/lib/connection_pool_metrics.rb +372 -0
- data/try/pooling/lib/connection_pool_stress_test.rb +959 -0
- data/try/pooling/lib/connection_pool_threading_models.rb +421 -0
- data/try/pooling/lib/visualize_stress_results.rb +434 -0
- data/try/pooling/pool_siege_try.rb +509 -0
- data/try/pooling/run_stress_tests_try.rb +482 -0
- data/try/prototypes/atomic_saves_v1_context_proxy.rb +121 -0
- data/try/prototypes/atomic_saves_v2_connection_switching.rb +161 -0
- data/try/prototypes/atomic_saves_v3_connection_pool.rb +189 -0
- data/try/prototypes/atomic_saves_v4.rb +105 -0
- data/try/prototypes/lib/atomic_saves_v2_connection_switching_helpers.rb +124 -0
- data/try/prototypes/lib/atomic_saves_v3_connection_pool_helpers.rb +192 -0
- metadata +124 -38
- data/.github/workflows/ruby.yml +0 -71
- data/VERSION.yml +0 -4
- data/lib/familia/redistype/commands.rb +0 -59
- data/lib/familia/redistype.rb +0 -228
- data/lib/familia/tools.rb +0 -68
- data/lib/redis_middleware.rb +0 -109
- data/try/20_redis_type_try.rb +0 -70
- data/try/91_json_bug_try.rb +0 -86
@@ -0,0 +1,20 @@
|
|
1
|
+
# Connection Pool Stress Testing
|
2
|
+
|
3
|
+
## Quick Start
|
4
|
+
```bash
|
5
|
+
# Basic stress test
|
6
|
+
ruby run_stress_tests.rb --config light
|
7
|
+
|
8
|
+
# Full comparison
|
9
|
+
ruby configurable_stress_test.rb --ci
|
10
|
+
```
|
11
|
+
|
12
|
+
## Documentation
|
13
|
+
- [Basic Usage](docs/README_stress_testing.md)
|
14
|
+
- [Advanced Configuration](docs/README_advanced_usage.md)
|
15
|
+
|
16
|
+
## Structure
|
17
|
+
- `run_stress_tests.rb` - Main test orchestrator
|
18
|
+
- `configurable_stress_test.rb` - Configurable test runner
|
19
|
+
- `lib/` - Core testing libraries
|
20
|
+
- `docs/` - Documentation
|
@@ -0,0 +1,435 @@
|
|
1
|
+
# try/pooling/configurable_stress_test.rb
|
2
|
+
#
|
3
|
+
# Configurable Stress Test - Systematic testing using StressTestConfig
|
4
|
+
#
|
5
|
+
# This class provides methodical approaches to testing specific aspects
|
6
|
+
# of the connection pool by using the centralized StressTestConfig.
|
7
|
+
|
8
|
+
require_relative 'lib/connection_pool_stress_test'
|
9
|
+
require_relative 'lib/connection_pool_threading_models'
|
10
|
+
require_relative 'lib/connection_pool_metrics'
|
11
|
+
|
12
|
+
class ConfigurableStressTest
|
13
|
+
attr_reader :results_aggregator
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@results_aggregator = ConnectionPoolMetrics::ResultAggregator.new
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
# Generate test matrices for specific testing goals
|
21
|
+
def generate_test_matrix(scope = :all)
|
22
|
+
case scope
|
23
|
+
when :thread_scaling
|
24
|
+
# Test how performance scales with thread count
|
25
|
+
StressTestConfig::THREAD_COUNTS.map do |thread_count|
|
26
|
+
StressTestConfig.merge_and_validate(
|
27
|
+
StressTestConfig.default,
|
28
|
+
{
|
29
|
+
thread_count: thread_count,
|
30
|
+
pool_size: [thread_count / 2, 5].max, # Keep pool smaller than threads
|
31
|
+
operations_per_thread: 100,
|
32
|
+
scenario: :pool_starvation
|
33
|
+
}
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
when :pool_sizing
|
38
|
+
# Test optimal pool sizes for different workloads
|
39
|
+
StressTestConfig::POOL_SIZES.map do |pool_size|
|
40
|
+
StressTestConfig.merge_and_validate(
|
41
|
+
StressTestConfig.default,
|
42
|
+
{
|
43
|
+
thread_count: pool_size * 2, # Create pressure
|
44
|
+
pool_size: pool_size,
|
45
|
+
operations_per_thread: 50,
|
46
|
+
scenario: :mixed_workload
|
47
|
+
}
|
48
|
+
)
|
49
|
+
end
|
50
|
+
|
51
|
+
when :operation_mixes
|
52
|
+
# Test all operation patterns
|
53
|
+
StressTestConfig::OPERATION_MIXES.keys.map do |mix|
|
54
|
+
StressTestConfig.merge_and_validate(
|
55
|
+
StressTestConfig.default,
|
56
|
+
{
|
57
|
+
thread_count: 20,
|
58
|
+
pool_size: 10,
|
59
|
+
operations_per_thread: 100,
|
60
|
+
operation_mix: mix,
|
61
|
+
scenario: :mixed_workload
|
62
|
+
}
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
when :timeout_behavior
|
67
|
+
# Test different timeout scenarios
|
68
|
+
StressTestConfig::POOL_TIMEOUTS.map do |timeout|
|
69
|
+
StressTestConfig.merge_and_validate(
|
70
|
+
StressTestConfig.default,
|
71
|
+
{
|
72
|
+
thread_count: 50, # High contention
|
73
|
+
pool_size: 5, # Small pool
|
74
|
+
pool_timeout: timeout,
|
75
|
+
operations_per_thread: 20, # Quick to see timeout effects
|
76
|
+
scenario: :pool_starvation
|
77
|
+
}
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
when :scenario_comparison
|
82
|
+
# Test all scenarios with consistent parameters
|
83
|
+
StressTestConfig::SCENARIOS.map do |scenario|
|
84
|
+
StressTestConfig.merge_and_validate(
|
85
|
+
StressTestConfig.default,
|
86
|
+
{
|
87
|
+
scenario: scenario,
|
88
|
+
thread_count: 20,
|
89
|
+
pool_size: 10,
|
90
|
+
operations_per_thread: 50
|
91
|
+
}
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
when :threading_models
|
96
|
+
# Test different threading models
|
97
|
+
[:traditional, :thread_pool, :fiber, :hybrid].map do |model|
|
98
|
+
StressTestConfig.merge_and_validate(
|
99
|
+
StressTestConfig.default,
|
100
|
+
{
|
101
|
+
threading_model: model,
|
102
|
+
thread_count: 20,
|
103
|
+
pool_size: 10,
|
104
|
+
operations_per_thread: 100,
|
105
|
+
scenario: :mixed_workload
|
106
|
+
}
|
107
|
+
)
|
108
|
+
end
|
109
|
+
|
110
|
+
when :high_contention
|
111
|
+
# Test extreme contention scenarios
|
112
|
+
[
|
113
|
+
{ thread_count: 100, pool_size: 5, scenario: :pool_starvation },
|
114
|
+
{ thread_count: 200, pool_size: 10, scenario: :rapid_fire },
|
115
|
+
{ thread_count: 50, pool_size: 5, scenario: :long_transactions }
|
116
|
+
].map do |config|
|
117
|
+
StressTestConfig.merge_and_validate(
|
118
|
+
StressTestConfig.default,
|
119
|
+
config.merge(operations_per_thread: 50)
|
120
|
+
)
|
121
|
+
end
|
122
|
+
|
123
|
+
when :comprehensive
|
124
|
+
# Comprehensive test combining multiple dimensions
|
125
|
+
configs = []
|
126
|
+
StressTestConfig::SCENARIOS.each do |scenario|
|
127
|
+
[10, 50].each do |threads|
|
128
|
+
[5, 20].each do |pool_size|
|
129
|
+
[:balanced, :transaction_heavy].each do |mix|
|
130
|
+
configs << StressTestConfig.merge_and_validate(
|
131
|
+
StressTestConfig.default,
|
132
|
+
{
|
133
|
+
scenario: scenario,
|
134
|
+
thread_count: threads,
|
135
|
+
pool_size: pool_size,
|
136
|
+
operation_mix: mix,
|
137
|
+
operations_per_thread: 50
|
138
|
+
}
|
139
|
+
)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
configs
|
145
|
+
|
146
|
+
else
|
147
|
+
raise ArgumentError, "Unknown test matrix scope: #{scope}. Valid: #{valid_scopes.join(', ')}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def valid_scopes
|
152
|
+
[:thread_scaling, :pool_sizing, :operation_mixes, :timeout_behavior,
|
153
|
+
:scenario_comparison, :threading_models, :high_contention, :comprehensive]
|
154
|
+
end
|
155
|
+
|
156
|
+
# Run targeted tests for a specific scope
|
157
|
+
def run_targeted_tests(scope, options = {})
|
158
|
+
puts "\n" + "=" * 80
|
159
|
+
puts "RUNNING TARGETED TESTS: #{scope.to_s.upcase}"
|
160
|
+
puts "=" * 80
|
161
|
+
|
162
|
+
test_runner = new
|
163
|
+
configs = generate_test_matrix(scope)
|
164
|
+
|
165
|
+
puts "Generated #{configs.size} test configurations"
|
166
|
+
|
167
|
+
results = test_runner.run_test_matrix(configs, options)
|
168
|
+
|
169
|
+
puts "\n" + "=" * 80
|
170
|
+
puts "TARGETED TEST RESULTS SUMMARY"
|
171
|
+
puts "=" * 80
|
172
|
+
test_runner.display_matrix_summary(results, scope)
|
173
|
+
|
174
|
+
results
|
175
|
+
end
|
176
|
+
|
177
|
+
# Quick development tests using StressTestConfig
|
178
|
+
def run_development_tests
|
179
|
+
config_set = StressTestConfig.for_development
|
180
|
+
run_config_set(config_set, "DEVELOPMENT")
|
181
|
+
end
|
182
|
+
|
183
|
+
# CI-appropriate tests
|
184
|
+
def run_ci_tests
|
185
|
+
config_set = StressTestConfig.for_ci
|
186
|
+
run_config_set(config_set, "CI/CD")
|
187
|
+
end
|
188
|
+
|
189
|
+
# Production validation tests
|
190
|
+
def run_production_validation_tests
|
191
|
+
config_set = StressTestConfig.for_production_validation
|
192
|
+
run_config_set(config_set, "PRODUCTION VALIDATION")
|
193
|
+
end
|
194
|
+
|
195
|
+
def run_config_set(config_set, name)
|
196
|
+
puts "\n" + "=" * 80
|
197
|
+
puts "#{name} TEST SUITE"
|
198
|
+
puts "=" * 80
|
199
|
+
|
200
|
+
test_runner = new
|
201
|
+
total_configs = config_set.values.map(&:size).reduce(:*)
|
202
|
+
puts "Total test configurations: #{total_configs}"
|
203
|
+
|
204
|
+
# Generate all combinations
|
205
|
+
configs = []
|
206
|
+
config_set[:scenarios].each do |scenario|
|
207
|
+
config_set[:thread_counts].each do |threads|
|
208
|
+
config_set[:pool_sizes].each do |pool_size|
|
209
|
+
config_set[:pool_timeouts].each do |timeout|
|
210
|
+
config_set[:operation_mixes].each do |mix|
|
211
|
+
configs << StressTestConfig.merge_and_validate(
|
212
|
+
StressTestConfig.default,
|
213
|
+
{
|
214
|
+
scenario: scenario,
|
215
|
+
thread_count: threads,
|
216
|
+
pool_size: pool_size,
|
217
|
+
pool_timeout: timeout,
|
218
|
+
operation_mix: mix,
|
219
|
+
operations_per_thread: config_set[:operations_per_thread].first
|
220
|
+
}
|
221
|
+
)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
results = test_runner.run_test_matrix(configs, { verbose: false })
|
229
|
+
test_runner.display_matrix_summary(results, name.downcase.to_sym)
|
230
|
+
|
231
|
+
results
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
# Instance methods for running test matrices
|
236
|
+
def run_test_matrix(configs, options = {})
|
237
|
+
results = []
|
238
|
+
verbose = options.fetch(:verbose, true)
|
239
|
+
|
240
|
+
configs.each_with_index do |config, index|
|
241
|
+
puts "\n[#{index + 1}/#{configs.size}] #{format_config_summary(config)}" if verbose
|
242
|
+
|
243
|
+
begin
|
244
|
+
# Clean database before each test
|
245
|
+
BankAccount.dbclient.flushdb
|
246
|
+
|
247
|
+
# Run the test
|
248
|
+
result = run_single_config(config)
|
249
|
+
results << { config: config, result: result, success: true }
|
250
|
+
|
251
|
+
# Add to aggregator
|
252
|
+
model_info = { name: config[:threading_model] || 'traditional' }
|
253
|
+
@results_aggregator.add_result(config, result[:summary], model_info)
|
254
|
+
|
255
|
+
# Quick result summary
|
256
|
+
if verbose
|
257
|
+
puts " ✅ Success: #{result[:summary][:success_rate]}%, " \
|
258
|
+
"Duration: #{(result[:summary][:avg_duration] * 1000).round(2)}ms"
|
259
|
+
else
|
260
|
+
print "."
|
261
|
+
end
|
262
|
+
|
263
|
+
rescue => e
|
264
|
+
puts " ❌ Error: #{e.message}" if verbose
|
265
|
+
results << { config: config, error: e, success: false }
|
266
|
+
|
267
|
+
# Record failed test
|
268
|
+
error_summary = { success_rate: 0, failed_operations: 1, total_operations: 0 }
|
269
|
+
@results_aggregator.add_result(config, error_summary, { error: e.message })
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
puts "" unless verbose # newline after dots
|
274
|
+
results
|
275
|
+
end
|
276
|
+
|
277
|
+
def run_single_config(config)
|
278
|
+
if config[:threading_model] && config[:threading_model] != :traditional
|
279
|
+
# Use enhanced test for non-traditional threading
|
280
|
+
test = EnhancedConnectionPoolStressTest.new(config)
|
281
|
+
model_info = test.run_with_model(config[:threading_model])
|
282
|
+
{ summary: test.metrics.summary, model_info: model_info }
|
283
|
+
else
|
284
|
+
# Use standard test
|
285
|
+
test = ConnectionPoolStressTest.new(config)
|
286
|
+
test.run
|
287
|
+
{ summary: test.metrics.summary, model_info: { name: 'traditional' } }
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
def display_matrix_summary(results, scope)
|
292
|
+
successful = results.count { |r| r[:success] }
|
293
|
+
failed = results.count { |r| !r[:success] }
|
294
|
+
|
295
|
+
puts "Results: #{successful}/#{results.size} tests passed (#{failed} failed)"
|
296
|
+
|
297
|
+
if successful > 0
|
298
|
+
# Find best and worst performers
|
299
|
+
successful_results = results.select { |r| r[:success] }
|
300
|
+
|
301
|
+
best = successful_results.max_by { |r| r[:result][:summary][:success_rate] }
|
302
|
+
worst = successful_results.min_by { |r| r[:result][:summary][:success_rate] }
|
303
|
+
|
304
|
+
puts "\nBest performer:"
|
305
|
+
puts " Config: #{format_config_summary(best[:config])}"
|
306
|
+
puts " Results: #{best[:result][:summary][:success_rate]}% success, " \
|
307
|
+
"#{(best[:result][:summary][:avg_duration] * 1000).round(2)}ms avg"
|
308
|
+
|
309
|
+
if best != worst
|
310
|
+
puts "\nWorst performer:"
|
311
|
+
puts " Config: #{format_config_summary(worst[:config])}"
|
312
|
+
puts " Results: #{worst[:result][:summary][:success_rate]}% success, " \
|
313
|
+
"#{(worst[:result][:summary][:avg_duration] * 1000).round(2)}ms avg"
|
314
|
+
end
|
315
|
+
|
316
|
+
# Specific insights based on scope
|
317
|
+
display_scope_specific_insights(successful_results, scope)
|
318
|
+
end
|
319
|
+
|
320
|
+
if failed > 0
|
321
|
+
puts "\nFailed configurations:"
|
322
|
+
results.select { |r| !r[:success] }.each do |failure|
|
323
|
+
puts " #{format_config_summary(failure[:config])} - #{failure[:error].message}"
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
private
|
329
|
+
|
330
|
+
def format_config_summary(config)
|
331
|
+
parts = []
|
332
|
+
parts << "#{config[:scenario]}" if config[:scenario]
|
333
|
+
parts << "T#{config[:thread_count]}" if config[:thread_count]
|
334
|
+
parts << "P#{config[:pool_size]}" if config[:pool_size]
|
335
|
+
parts << "#{config[:operation_mix]}" if config[:operation_mix]
|
336
|
+
parts << "#{config[:threading_model]}" if config[:threading_model] && config[:threading_model] != :traditional
|
337
|
+
parts.join("/")
|
338
|
+
end
|
339
|
+
|
340
|
+
def display_scope_specific_insights(results, scope)
|
341
|
+
case scope
|
342
|
+
when :thread_scaling
|
343
|
+
puts "\nThread Scaling Insights:"
|
344
|
+
thread_performance = results.group_by { |r| r[:config][:thread_count] }
|
345
|
+
thread_performance.each do |threads, group|
|
346
|
+
avg_success = group.map { |r| r[:result][:summary][:success_rate] }.sum / group.size
|
347
|
+
puts " #{threads} threads: #{avg_success.round(1)}% avg success rate"
|
348
|
+
end
|
349
|
+
|
350
|
+
when :pool_sizing
|
351
|
+
puts "\nPool Sizing Insights:"
|
352
|
+
pool_performance = results.group_by { |r| r[:config][:pool_size] }
|
353
|
+
pool_performance.each do |pool_size, group|
|
354
|
+
avg_duration = group.map { |r| r[:result][:summary][:avg_duration] }.sum / group.size
|
355
|
+
puts " Pool size #{pool_size}: #{(avg_duration * 1000).round(2)}ms avg duration"
|
356
|
+
end
|
357
|
+
|
358
|
+
when :operation_mixes
|
359
|
+
puts "\nOperation Mix Insights:"
|
360
|
+
mix_performance = results.group_by { |r| r[:config][:operation_mix] }
|
361
|
+
mix_performance.each do |mix, group|
|
362
|
+
avg_success = group.map { |r| r[:result][:summary][:success_rate] }.sum / group.size
|
363
|
+
puts " #{mix}: #{avg_success.round(1)}% avg success rate"
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
# Example usage and CLI runner
|
370
|
+
if __FILE__ == $0
|
371
|
+
require 'optparse'
|
372
|
+
|
373
|
+
options = { scope: :thread_scaling, verbose: true }
|
374
|
+
|
375
|
+
OptionParser.new do |opts|
|
376
|
+
opts.banner = "Usage: configurable_stress_test.rb [options]"
|
377
|
+
|
378
|
+
opts.on("-s", "--scope SCOPE", "Test scope: #{ConfigurableStressTest.valid_scopes.join(', ')}") do |scope|
|
379
|
+
options[:scope] = scope.to_sym
|
380
|
+
end
|
381
|
+
|
382
|
+
opts.on("-q", "--quiet", "Quiet output") do
|
383
|
+
options[:verbose] = false
|
384
|
+
end
|
385
|
+
|
386
|
+
opts.on("--development", "Run development test suite") do
|
387
|
+
options[:preset] = :development
|
388
|
+
end
|
389
|
+
|
390
|
+
opts.on("--ci", "Run CI test suite") do
|
391
|
+
options[:preset] = :ci
|
392
|
+
end
|
393
|
+
|
394
|
+
opts.on("--production", "Run production validation suite") do
|
395
|
+
options[:preset] = :production_validation
|
396
|
+
end
|
397
|
+
|
398
|
+
opts.on("--list-scopes", "List available test scopes") do
|
399
|
+
puts "Available test scopes:"
|
400
|
+
ConfigurableStressTest.valid_scopes.each do |scope|
|
401
|
+
puts " #{scope}"
|
402
|
+
end
|
403
|
+
exit
|
404
|
+
end
|
405
|
+
|
406
|
+
opts.on("-h", "--help", "Show this help") do
|
407
|
+
puts opts
|
408
|
+
exit
|
409
|
+
end
|
410
|
+
end.parse!
|
411
|
+
|
412
|
+
# Initialize Familia
|
413
|
+
require_relative '../helpers/test_helpers'
|
414
|
+
Familia.debug = false
|
415
|
+
|
416
|
+
if options[:preset]
|
417
|
+
case options[:preset]
|
418
|
+
when :development
|
419
|
+
ConfigurableStressTest.run_development_tests
|
420
|
+
when :ci
|
421
|
+
ConfigurableStressTest.run_ci_tests
|
422
|
+
when :production_validation
|
423
|
+
ConfigurableStressTest.run_production_validation_tests
|
424
|
+
end
|
425
|
+
else
|
426
|
+
# Run targeted tests
|
427
|
+
unless ConfigurableStressTest.valid_scopes.include?(options[:scope])
|
428
|
+
puts "Error: Unknown scope '#{options[:scope]}'"
|
429
|
+
puts "Valid scopes: #{ConfigurableStressTest.valid_scopes.join(', ')}"
|
430
|
+
exit 1
|
431
|
+
end
|
432
|
+
|
433
|
+
ConfigurableStressTest.run_targeted_tests(options[:scope], options)
|
434
|
+
end
|
435
|
+
end
|