synthra 0.1.0

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.
Files changed (302) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +4 -0
  3. data/.rubocop.yml +40 -0
  4. data/.yardopts +18 -0
  5. data/ACCESS_DOCS.md +116 -0
  6. data/ADVANCED_FEATURES_SUMMARY.md +245 -0
  7. data/CHANGELOG.md +498 -0
  8. data/CODE_OF_CONDUCT.md +122 -0
  9. data/CONTRIBUTING.md +307 -0
  10. data/DOCUMENTATION.md +204 -0
  11. data/DOCUMENTATION_GUIDE.md +194 -0
  12. data/LICENSE +22 -0
  13. data/README.md +1458 -0
  14. data/Rakefile +128 -0
  15. data/benchmark/README.md +591 -0
  16. data/benchmark/batch_generation.rb +63 -0
  17. data/benchmark/engine_comparison.rb +295 -0
  18. data/benchmark/single_record.rb +108 -0
  19. data/benchmark/streaming.rb +74 -0
  20. data/docs/ACTIVERECORD_INFERENCE.md +524 -0
  21. data/docs/API_SERVER.md +278 -0
  22. data/docs/CONFIG_FILE.md +315 -0
  23. data/docs/DATA_CONTRACTS.md +312 -0
  24. data/docs/ENHANCED_REPL.md +304 -0
  25. data/docs/FACTORY_BOT.md +271 -0
  26. data/docs/GITHUB_ACTION.md +420 -0
  27. data/docs/GRAPHQL_EXPORT.md +303 -0
  28. data/docs/GRAPHQL_FEDERATION.md +651 -0
  29. data/docs/HOW_TO_GENERATE_DOCS.md +252 -0
  30. data/docs/LSP.md +189 -0
  31. data/docs/MIGRATION_GENERATOR.md +547 -0
  32. data/docs/MOCK_SERVER.md +280 -0
  33. data/docs/NATIVE_ENGINE.md +298 -0
  34. data/docs/OPENAPI_EXPORT.md +252 -0
  35. data/docs/PERFORMANCE_MODE.md +228 -0
  36. data/docs/PERSONAS.md +547 -0
  37. data/docs/PROPERTY_TESTING.md +284 -0
  38. data/docs/PROTOBUF_EXPORT.md +276 -0
  39. data/docs/QUALITY_METRICS.md +555 -0
  40. data/docs/QUICK_REFERENCE.md +88 -0
  41. data/docs/RAILS_ENGINE.md +404 -0
  42. data/docs/RAILS_INTEGRATION.md +454 -0
  43. data/docs/README.md +175 -0
  44. data/docs/REPL_QUICK_REFERENCE.md +149 -0
  45. data/docs/RUST_INTEGRATION_GUIDE.md +1376 -0
  46. data/docs/SCENARIOS.md +576 -0
  47. data/docs/SECURITY_TESTING.md +318 -0
  48. data/docs/SNAPSHOT_TESTING.md +308 -0
  49. data/docs/STRING_TEXT_TYPES_REFERENCE.md +512 -0
  50. data/docs/TERRAFORM_EXPORT.md +447 -0
  51. data/docs/TIME_TRAVEL.md +507 -0
  52. data/docs/USAGE_GUIDE.md +980 -0
  53. data/docs/WEBHOOK_SIMULATOR.md +579 -0
  54. data/docs/advanced_features.md +183 -0
  55. data/docs/api_reference.md +153 -0
  56. data/docs/behaviors.md +138 -0
  57. data/docs/best_practices.md +191 -0
  58. data/docs/dsl_reference.md +172 -0
  59. data/docs/examples.md +162 -0
  60. data/docs/generation_modes.md +125 -0
  61. data/docs/getting_started.md +92 -0
  62. data/docs/index.html +177 -0
  63. data/docs/strategic/CLOUD_REGISTRY_ARCHITECTURE.md +268 -0
  64. data/docs/strategic/SIMD_OPTIMIZATION_STRATEGY.md +153 -0
  65. data/docs/troubleshooting.md +215 -0
  66. data/docs/type_reference.md +224 -0
  67. data/examples/hostile_mode_demo.rb +64 -0
  68. data/exe/synthra +6 -0
  69. data/ext/synthra_native/Cargo.lock +972 -0
  70. data/ext/synthra_native/Cargo.toml +46 -0
  71. data/ext/synthra_native/extconf.rb +41 -0
  72. data/ext/synthra_native/src/generator.rs +397 -0
  73. data/ext/synthra_native/src/lib.rs +229 -0
  74. data/ext/synthra_native/src/types.rs +1485 -0
  75. data/lib/generators/synthra/install_generator.rb +86 -0
  76. data/lib/generators/synthra/templates/api_response.dsl +15 -0
  77. data/lib/generators/synthra/templates/fake_data.rake +116 -0
  78. data/lib/generators/synthra/templates/synthra.yml +31 -0
  79. data/lib/generators/synthra/templates/synthra_support.rb +19 -0
  80. data/lib/generators/synthra/templates/user.dsl +11 -0
  81. data/lib/synthra/activerecord_inference.rb +394 -0
  82. data/lib/synthra/api.rb +381 -0
  83. data/lib/synthra/api_server.rb +718 -0
  84. data/lib/synthra/behaviors/applicator.rb +165 -0
  85. data/lib/synthra/behaviors/base.rb +155 -0
  86. data/lib/synthra/behaviors/close_connection.rb +60 -0
  87. data/lib/synthra/behaviors/deprecated.rb +100 -0
  88. data/lib/synthra/behaviors/failure.rb +69 -0
  89. data/lib/synthra/behaviors/latency.rb +103 -0
  90. data/lib/synthra/behaviors/partial_data.rb +83 -0
  91. data/lib/synthra/behaviors/randomize_order.rb +74 -0
  92. data/lib/synthra/behaviors/registry.rb +238 -0
  93. data/lib/synthra/behaviors/simulate_error.rb +79 -0
  94. data/lib/synthra/cli/commands/base.rb +105 -0
  95. data/lib/synthra/cli/commands/diff.rb +227 -0
  96. data/lib/synthra/cli/commands/docs.rb +67 -0
  97. data/lib/synthra/cli/commands/export.rb +150 -0
  98. data/lib/synthra/cli/commands/generate.rb +72 -0
  99. data/lib/synthra/cli/commands/import.rb +80 -0
  100. data/lib/synthra/cli/commands/lint.rb +53 -0
  101. data/lib/synthra/cli/commands/live.rb +57 -0
  102. data/lib/synthra/cli/commands/seed.rb +99 -0
  103. data/lib/synthra/cli/commands/validate.rb +28 -0
  104. data/lib/synthra/cli.rb +2471 -0
  105. data/lib/synthra/config_file.rb +129 -0
  106. data/lib/synthra/configuration.rb +281 -0
  107. data/lib/synthra/contracts_registry.rb +408 -0
  108. data/lib/synthra/database_seeder.rb +268 -0
  109. data/lib/synthra/deterministic_ids.rb +218 -0
  110. data/lib/synthra/documentation_generator.rb +414 -0
  111. data/lib/synthra/engine.rb +251 -0
  112. data/lib/synthra/errors.rb +1169 -0
  113. data/lib/synthra/export/base.rb +85 -0
  114. data/lib/synthra/export/csv.rb +101 -0
  115. data/lib/synthra/export/graphql.rb +266 -0
  116. data/lib/synthra/export/graphql_federation.rb +377 -0
  117. data/lib/synthra/export/graphviz.rb +258 -0
  118. data/lib/synthra/export/javascript.rb +327 -0
  119. data/lib/synthra/export/json_data.rb +61 -0
  120. data/lib/synthra/export/json_schema.rb +290 -0
  121. data/lib/synthra/export/openapi.rb +514 -0
  122. data/lib/synthra/export/protobuf.rb +483 -0
  123. data/lib/synthra/export/python.rb +560 -0
  124. data/lib/synthra/export/sql.rb +381 -0
  125. data/lib/synthra/export/sql_insert.rb +152 -0
  126. data/lib/synthra/export/terraform.rb +501 -0
  127. data/lib/synthra/export/type_mapping.rb +389 -0
  128. data/lib/synthra/export/typescript.rb +300 -0
  129. data/lib/synthra/export/xml_data.rb +104 -0
  130. data/lib/synthra/export/yaml_data.rb +56 -0
  131. data/lib/synthra/export.rb +404 -0
  132. data/lib/synthra/factory_bot_integration.rb +157 -0
  133. data/lib/synthra/field.rb +440 -0
  134. data/lib/synthra/functions/registry.rb +104 -0
  135. data/lib/synthra/generator/context.rb +372 -0
  136. data/lib/synthra/generator/engine.rb +336 -0
  137. data/lib/synthra/generator/faker_adapter.rb +425 -0
  138. data/lib/synthra/generator/modes.rb +444 -0
  139. data/lib/synthra/generator/resolver.rb +256 -0
  140. data/lib/synthra/generator/rng.rb +273 -0
  141. data/lib/synthra/generator/streamer.rb +63 -0
  142. data/lib/synthra/generator/uniqueness.rb +118 -0
  143. data/lib/synthra/initializer.rb +97 -0
  144. data/lib/synthra/limits.rb +279 -0
  145. data/lib/synthra/live_preview.rb +518 -0
  146. data/lib/synthra/loader_config.rb +78 -0
  147. data/lib/synthra/lsp/server.rb +689 -0
  148. data/lib/synthra/migration_generator.rb +301 -0
  149. data/lib/synthra/mixin.rb +174 -0
  150. data/lib/synthra/mock_server.rb +458 -0
  151. data/lib/synthra/native_engine.rb +304 -0
  152. data/lib/synthra/openapi_importer.rb +228 -0
  153. data/lib/synthra/output/json_formatter.rb +40 -0
  154. data/lib/synthra/output/ndjson_formatter.rb +55 -0
  155. data/lib/synthra/parser/ast.rb +1287 -0
  156. data/lib/synthra/parser/lexer.rb +1152 -0
  157. data/lib/synthra/parser/parser.rb +1664 -0
  158. data/lib/synthra/parser/tokens.rb +461 -0
  159. data/lib/synthra/performance_mode.rb +364 -0
  160. data/lib/synthra/personas.rb +397 -0
  161. data/lib/synthra/property_testing.rb +172 -0
  162. data/lib/synthra/quality_metrics.rb +405 -0
  163. data/lib/synthra/rails_test_helper.rb +248 -0
  164. data/lib/synthra/registry.rb +533 -0
  165. data/lib/synthra/relationships.rb +193 -0
  166. data/lib/synthra/repl/enhanced_repl.rb +607 -0
  167. data/lib/synthra/repl/formatter.rb +191 -0
  168. data/lib/synthra/scenarios.rb +423 -0
  169. data/lib/synthra/schema.rb +605 -0
  170. data/lib/synthra/schema_inheritance.rb +104 -0
  171. data/lib/synthra/schema_versioning.rb +212 -0
  172. data/lib/synthra/snapshot_testing.rb +199 -0
  173. data/lib/synthra/time_travel.rb +338 -0
  174. data/lib/synthra/type_definitions.rb +274 -0
  175. data/lib/synthra/types/address_location/addresses.rb +125 -0
  176. data/lib/synthra/types/address_location/airports.rb +200 -0
  177. data/lib/synthra/types/address_location/banks_hospitals.rb +207 -0
  178. data/lib/synthra/types/address_location/locations.rb +406 -0
  179. data/lib/synthra/types/base.rb +48 -0
  180. data/lib/synthra/types/commerce_products/commerce.rb +119 -0
  181. data/lib/synthra/types/commerce_products/companies.rb +71 -0
  182. data/lib/synthra/types/commerce_products/construction.rb +126 -0
  183. data/lib/synthra/types/commerce_products/products.rb +205 -0
  184. data/lib/synthra/types/core/collections.rb +513 -0
  185. data/lib/synthra/types/core/defaults.rb +76 -0
  186. data/lib/synthra/types/core/enums.rb +102 -0
  187. data/lib/synthra/types/core/identifiers.rb +445 -0
  188. data/lib/synthra/types/core/primitives.rb +586 -0
  189. data/lib/synthra/types/core/references.rb +466 -0
  190. data/lib/synthra/types/core/sequences.rb +304 -0
  191. data/lib/synthra/types/crypto/crypto.rb +162 -0
  192. data/lib/synthra/types/date_time/dates.rb +387 -0
  193. data/lib/synthra/types/finance_banking/banking.rb +424 -0
  194. data/lib/synthra/types/finance_banking/credit_cards.rb +24 -0
  195. data/lib/synthra/types/finance_banking/identifiers.rb +93 -0
  196. data/lib/synthra/types/formula.rb +184 -0
  197. data/lib/synthra/types/health_medical/medical.rb +150 -0
  198. data/lib/synthra/types/hostile_payloads.rb +122 -0
  199. data/lib/synthra/types/json_array.rb +51 -0
  200. data/lib/synthra/types/media_entertainment/media.rb +62 -0
  201. data/lib/synthra/types/naughty_string.rb +30 -0
  202. data/lib/synthra/types/personal_names/chinese.rb +41 -0
  203. data/lib/synthra/types/personal_names/identifiers.rb +187 -0
  204. data/lib/synthra/types/personal_names/names.rb +246 -0
  205. data/lib/synthra/types/personal_names/national_id.rb +89 -0
  206. data/lib/synthra/types/personal_names/titles_suffixes.rb +41 -0
  207. data/lib/synthra/types/regex.rb +248 -0
  208. data/lib/synthra/types/registry.rb +182 -0
  209. data/lib/synthra/types/repeating_element.rb +50 -0
  210. data/lib/synthra/types/scenario.rb +29 -0
  211. data/lib/synthra/types/technology_internet/apps.rb +67 -0
  212. data/lib/synthra/types/technology_internet/communication.rb +162 -0
  213. data/lib/synthra/types/technology_internet/devices.rb +80 -0
  214. data/lib/synthra/types/technology_internet/formats.rb +139 -0
  215. data/lib/synthra/types/technology_internet/networking.rb +143 -0
  216. data/lib/synthra/types/template.rb +92 -0
  217. data/lib/synthra/types/text_content/business.rb +128 -0
  218. data/lib/synthra/types/text_content/colors.rb +70 -0
  219. data/lib/synthra/types/text_content/misc.rb +237 -0
  220. data/lib/synthra/types/text_content/security.rb +130 -0
  221. data/lib/synthra/types/text_content/text_generation.rb +532 -0
  222. data/lib/synthra/types/travel/travel.rb +150 -0
  223. data/lib/synthra/utils/string_distance.rb +133 -0
  224. data/lib/synthra/validator/dsl_validator.rb +339 -0
  225. data/lib/synthra/validator/path_validator.rb +615 -0
  226. data/lib/synthra/version.rb +35 -0
  227. data/lib/synthra/webhook_simulator.rb +341 -0
  228. data/lib/synthra.rb +259 -0
  229. data/schemas/address.dsl +16 -0
  230. data/schemas/api_response.dsl +8 -0
  231. data/schemas/error_payload.dsl +9 -0
  232. data/schemas/order.dsl +10 -0
  233. data/schemas/order_item.dsl +8 -0
  234. data/schemas/payment.dsl +11 -0
  235. data/schemas/social_post.dsl +13 -0
  236. data/schemas/user.dsl +10 -0
  237. data/scripts/batch_fix_all.rb +96 -0
  238. data/scripts/delete_old_files.rb +32 -0
  239. data/scripts/fix_all_grouped_files.rb +137 -0
  240. data/scripts/fix_all_indentation.rb +48 -0
  241. data/scripts/fix_all_syntax.rb +124 -0
  242. data/scripts/fix_grouped_files.rb +184 -0
  243. data/scripts/fix_syntax_errors.rb +108 -0
  244. data/scripts/group_domain_types.rb +112 -0
  245. data/scripts/group_domain_types_fixed.rb +150 -0
  246. data/scripts/merge_domains_to_one_file.rb +68 -0
  247. data/scripts/move_existing_types.rb +130 -0
  248. data/scripts/split_grouped_types.rb +142 -0
  249. data/tech_docs/README.md +134 -0
  250. data/tech_docs/advanced/streaming.md +405 -0
  251. data/tech_docs/advanced/thread_safety.md +253 -0
  252. data/tech_docs/api/overview.md +485 -0
  253. data/tech_docs/appendices/type_chart.md +193 -0
  254. data/tech_docs/basic_concepts.md +383 -0
  255. data/tech_docs/behaviors/overview.md +346 -0
  256. data/tech_docs/dsl/complex_types.md +790 -0
  257. data/tech_docs/dsl/core_types.md +464 -0
  258. data/tech_docs/dsl/datetime_types.md +325 -0
  259. data/tech_docs/dsl/field_modifiers.md +414 -0
  260. data/tech_docs/dsl/grammar.md +431 -0
  261. data/tech_docs/dsl/schema_definition.md +399 -0
  262. data/tech_docs/export/README.md +276 -0
  263. data/tech_docs/installation.md +273 -0
  264. data/tech_docs/integration/ci_cd.md +707 -0
  265. data/tech_docs/integration/ci_cd_guide.md +579 -0
  266. data/tech_docs/integration/factory_bot.md +485 -0
  267. data/tech_docs/integration/rails.md +630 -0
  268. data/tech_docs/integration/rspec.md +449 -0
  269. data/tech_docs/modes/overview.md +350 -0
  270. data/tech_docs/performance/NATIVE_RUST_EXTENSION.md +1270 -0
  271. data/tech_docs/performance/OPTIMIZATION_GUIDE.md +901 -0
  272. data/tech_docs/quick_start.md +256 -0
  273. data/tech_docs/templates/README.md +805 -0
  274. data/tech_docs/tutorials/advanced.md +371 -0
  275. data/tech_docs/tutorials/getting_started.md +189 -0
  276. data/tech_docs/tutorials/intermediate.md +231 -0
  277. data/tech_docs/tutorials/template_gallery.md +569 -0
  278. data/tech_example/01_basic_usage.rb +238 -0
  279. data/tech_example/02_types_demo.rb +336 -0
  280. data/tech_example/04_cli_usage.md +429 -0
  281. data/tech_example/05_database_seeding.rb +283 -0
  282. data/tech_example/07_rspec_integration.rb +359 -0
  283. data/tech_example/10_custom_types.rb +387 -0
  284. data/tech_example/12_behaviors.rb +372 -0
  285. data/tech_example/13_twitter_dm_example.rb +120 -0
  286. data/tech_example/14_exact_json_structure.rb +253 -0
  287. data/tech_example/16_lsp_server.rb +56 -0
  288. data/tech_example/17_property_testing.rb +92 -0
  289. data/tech_example/18_enhanced_repl.rb +120 -0
  290. data/tech_example/19_security_fuzzing.rb +145 -0
  291. data/tech_example/NEW_FEATURES.md +169 -0
  292. data/tech_example/README.md +118 -0
  293. data/tech_example/schemas/api_response.dsl +123 -0
  294. data/tech_example/schemas/ecommerce.dsl +121 -0
  295. data/tech_example/schemas/twitter_dm.dsl +109 -0
  296. data/tech_example/schemas/user.dsl +36 -0
  297. data/vscode-extension/README.md +246 -0
  298. data/vscode-extension/language-configuration.json +31 -0
  299. data/vscode-extension/package.json +55 -0
  300. data/vscode-extension/snippets/fakedatadsl.json +198 -0
  301. data/vscode-extension/syntaxes/fakedatadsl.tmLanguage.json +128 -0
  302. metadata +478 -0
@@ -0,0 +1,364 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "etc"
4
+
5
+ module Synthra
6
+ # Performance Mode
7
+ #
8
+ # High-performance data generation for millions of records.
9
+ # Uses parallel processing, memory pooling, and native acceleration.
10
+ #
11
+ # @example Generate millions of records
12
+ # Synthra::PerformanceMode.generate(schema, count: 1_000_000)
13
+ #
14
+ # @example With progress callback
15
+ # Synthra::PerformanceMode.generate(schema, count: 10_000_000) do |progress|
16
+ # puts "Generated #{progress[:current]} / #{progress[:total]} (#{progress[:percent]}%)"
17
+ # end
18
+ #
19
+ module PerformanceMode
20
+ # Default configuration
21
+ DEFAULT_CONFIG = {
22
+ batch_size: 10_000, # Records per batch
23
+ threads: nil, # nil = auto (CPU cores)
24
+ memory_limit_mb: 512, # Max memory per batch
25
+ use_native: true, # Use Rust engine if available
26
+ progress_interval: 100_000, # Report progress every N records
27
+ gc_interval: 500_000 # Force GC every N records
28
+ }.freeze
29
+
30
+ class << self
31
+ # Generate records in high-performance mode
32
+ #
33
+ # @param schema [Schema] schema to generate
34
+ # @param count [Integer] number of records
35
+ # @param options [Hash] generation options
36
+ # @yield [Hash] progress updates
37
+ # @return [Array, Enumerator] generated records or enumerator
38
+ #
39
+ def generate(schema, count:, **options, &progress_callback)
40
+ config = DEFAULT_CONFIG.merge(options)
41
+ config[:threads] ||= optimal_thread_count
42
+
43
+ if config[:use_native] && native_available?
44
+ generate_native(schema, count, config, &progress_callback)
45
+ else
46
+ generate_parallel(schema, count, config, &progress_callback)
47
+ end
48
+ end
49
+
50
+ # Stream records without loading all into memory
51
+ #
52
+ # @param schema [Schema] schema to generate
53
+ # @param count [Integer] number of records
54
+ # @param options [Hash] generation options
55
+ # @return [Enumerator] record enumerator
56
+ #
57
+ def stream(schema, count:, **options)
58
+ config = DEFAULT_CONFIG.merge(options)
59
+
60
+ Enumerator.new do |yielder|
61
+ generated = 0
62
+ last_gc = 0
63
+ seed = options[:seed] || Random.new_seed
64
+
65
+ while generated < count
66
+ batch_size = [config[:batch_size], count - generated].min
67
+ batch_seed = seed + generated
68
+
69
+ batch = schema.generate_many(
70
+ batch_size,
71
+ seed: batch_seed,
72
+ mode: options[:mode] || :random
73
+ )
74
+
75
+ batch.each { |record| yielder << record }
76
+ generated += batch_size
77
+
78
+ # Memory management — trigger on the gc_interval *delta*, not exact divisibility, so a
79
+ # batch_size that doesn't evenly divide gc_interval can't skip every GC.
80
+ if generated - last_gc >= config[:gc_interval]
81
+ GC.start
82
+ last_gc = generated
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ # Write records directly to file (streaming)
89
+ #
90
+ # @param schema [Schema] schema to generate
91
+ # @param count [Integer] number of records
92
+ # @param output [String, IO] file path or IO object
93
+ # @param format [Symbol] output format (:json, :ndjson, :csv)
94
+ # @param options [Hash] generation options
95
+ #
96
+ def to_file(schema, count:, output:, format: :ndjson, **options, &progress_callback)
97
+ config = DEFAULT_CONFIG.merge(options)
98
+
99
+ io = output.is_a?(String) ? File.open(output, "w") : output
100
+ generated = 0
101
+ start_time = Time.now
102
+
103
+ begin
104
+ # Write header for CSV
105
+ if format == :csv
106
+ io.puts schema.fields.map(&:name).join(",")
107
+ end
108
+
109
+ # JSON array start
110
+ if format == :json
111
+ io.puts "["
112
+ end
113
+
114
+ stream(schema, count: count, **options).each_with_index do |record, index|
115
+ case format
116
+ when :json
117
+ io.print " "
118
+ io.print JSON.generate(record)
119
+ io.puts(index < count - 1 ? "," : "")
120
+ when :ndjson
121
+ io.puts JSON.generate(record)
122
+ when :csv
123
+ io.puts schema.fields.map { |f| format_csv_value(record[f.name]) }.join(",")
124
+ end
125
+
126
+ generated += 1
127
+
128
+ if progress_callback && generated % config[:progress_interval] == 0
129
+ elapsed = Time.now - start_time
130
+ rate = safe_rate(generated, elapsed)
131
+ progress_callback.call({
132
+ current: generated,
133
+ total: count,
134
+ percent: (generated * 100.0 / count).round(1),
135
+ rate: rate,
136
+ elapsed: elapsed.round(1),
137
+ eta: safe_eta(count - generated, rate)
138
+ })
139
+ end
140
+ end
141
+
142
+ # JSON array end
143
+ if format == :json
144
+ io.puts "]"
145
+ end
146
+ ensure
147
+ io.close if output.is_a?(String)
148
+ end
149
+
150
+ final_elapsed = Time.now - start_time
151
+ {
152
+ records: generated,
153
+ elapsed: final_elapsed,
154
+ rate: safe_rate(generated, final_elapsed)
155
+ }
156
+ end
157
+
158
+ # Benchmark generation performance
159
+ #
160
+ # @param schema [Schema] schema to benchmark
161
+ # @param counts [Array<Integer>] record counts to test
162
+ # @return [Hash] benchmark results
163
+ #
164
+ def benchmark(schema, counts: [1000, 10_000, 100_000])
165
+ results = {}
166
+
167
+ counts.each do |count|
168
+ start_time = Time.now
169
+ records = schema.generate_many(count, mode: :random)
170
+ elapsed = Time.now - start_time
171
+
172
+ results[count] = {
173
+ elapsed: elapsed.round(3),
174
+ rate: safe_rate(count, elapsed),
175
+ memory_mb: estimate_memory_usage
176
+ }
177
+
178
+ # Force GC between benchmarks
179
+ records = nil
180
+ GC.start
181
+ end
182
+
183
+ results
184
+ end
185
+
186
+ # Get system capabilities
187
+ #
188
+ # @return [Hash] system info
189
+ #
190
+ def system_info
191
+ {
192
+ cpu_cores: Etc.nprocessors,
193
+ ruby_version: RUBY_VERSION,
194
+ native_available: native_available?,
195
+ optimal_threads: optimal_thread_count,
196
+ recommended_batch_size: recommended_batch_size
197
+ }
198
+ end
199
+
200
+ private
201
+
202
+ def native_available?
203
+ defined?(Synthra::Native) && Synthra::Native.respond_to?(:generate_batch)
204
+ rescue StandardError
205
+ false
206
+ end
207
+
208
+ def optimal_thread_count
209
+ # Leave one core for the main thread
210
+ [Etc.nprocessors - 1, 1].max
211
+ end
212
+
213
+ def recommended_batch_size
214
+ # Based on available memory
215
+ available_mb = begin
216
+ # Try to get actual available memory
217
+ if RUBY_PLATFORM.include?("darwin")
218
+ `sysctl -n hw.memsize`.to_i / 1024 / 1024 / 4
219
+ else
220
+ 1024
221
+ end
222
+ rescue StandardError
223
+ 1024
224
+ end
225
+
226
+ [available_mb * 100, 100_000].min
227
+ end
228
+
229
+ def generate_native(schema, count, config, &progress_callback)
230
+ require_relative "native" if defined?(Synthra::Native)
231
+
232
+ results = []
233
+ generated = 0
234
+ start_time = Time.now
235
+ seed = config[:seed] || Random.new_seed
236
+
237
+ batches = (count.to_f / config[:batch_size]).ceil
238
+
239
+ batches.times do |batch_num|
240
+ batch_count = [config[:batch_size], count - generated].min
241
+ batch_seed = seed + (batch_num * config[:batch_size])
242
+
243
+ batch = Synthra::Native.generate_batch(
244
+ schema.to_native_spec,
245
+ batch_count,
246
+ batch_seed,
247
+ config[:threads]
248
+ )
249
+
250
+ results.concat(batch)
251
+ generated += batch_count
252
+
253
+ if progress_callback && generated % config[:progress_interval] == 0
254
+ report_progress(progress_callback, generated, count, start_time)
255
+ end
256
+ end
257
+
258
+ results
259
+ rescue StandardError
260
+ # Fallback to parallel mode
261
+ generate_parallel(schema, count, config, &progress_callback)
262
+ end
263
+
264
+ def generate_parallel(schema, count, config, &progress_callback)
265
+ results = []
266
+ generated = 0
267
+ start_time = Time.now
268
+ seed = config[:seed] || Random.new_seed
269
+ mutex = Mutex.new
270
+
271
+ threads = config[:threads]
272
+ records_per_thread = (count.to_f / threads).ceil
273
+
274
+ worker_threads = threads.times.map do |thread_num|
275
+ Thread.new do
276
+ thread_start = thread_num * records_per_thread
277
+ thread_count = [records_per_thread, count - thread_start].min
278
+ next [] if thread_count <= 0
279
+
280
+ thread_seed = seed + thread_start
281
+
282
+ schema.generate_many(
283
+ thread_count,
284
+ seed: thread_seed,
285
+ mode: config[:mode] || :random
286
+ )
287
+ end
288
+ end
289
+
290
+ worker_threads.each do |thread|
291
+ batch = thread.value
292
+ mutex.synchronize do
293
+ results.concat(batch)
294
+ generated = results.length
295
+
296
+ if progress_callback && generated % config[:progress_interval] < batch.length
297
+ report_progress(progress_callback, generated, count, start_time)
298
+ end
299
+ end
300
+ end
301
+
302
+ results
303
+ end
304
+
305
+ # Records/sec, guarded: sub-millisecond runs on fast hardware give elapsed == 0.0, and
306
+ # records / 0.0 is Float::INFINITY whose #round raises FloatDomainError. Return 0 instead.
307
+ def safe_rate(records, elapsed)
308
+ elapsed.positive? ? (records / elapsed).round(0) : 0
309
+ end
310
+
311
+ # Seconds remaining, guarded against a 0 rate (which would otherwise divide by zero).
312
+ def safe_eta(remaining, rate)
313
+ rate.positive? ? (remaining / rate.to_f).round(1) : 0.0
314
+ end
315
+
316
+ def report_progress(callback, current, total, start_time)
317
+ elapsed = Time.now - start_time
318
+ rate = safe_rate(current, elapsed)
319
+
320
+ callback.call({
321
+ current: current,
322
+ total: total,
323
+ percent: (current * 100.0 / total).round(1),
324
+ rate: rate,
325
+ elapsed: elapsed.round(1),
326
+ eta: safe_eta(total - current, rate)
327
+ })
328
+ end
329
+
330
+ def format_csv_value(value)
331
+ case value
332
+ when nil
333
+ ""
334
+ when String
335
+ value.include?(",") || value.include?('"') ? "\"#{value.gsub('"', '""')}\"" : value
336
+ when Array
337
+ "\"#{value.to_json}\""
338
+ when Hash
339
+ "\"#{value.to_json.gsub('"', '""')}\""
340
+ else
341
+ value.to_s
342
+ end
343
+ end
344
+
345
+ def estimate_memory_usage
346
+ # Try to get memory usage, fallback to 0 if not available
347
+ if defined?(ObjectSpace) && ObjectSpace.respond_to?(:memsize_of_all)
348
+ (ObjectSpace.memsize_of_all / 1024.0 / 1024.0).round(2)
349
+ elsif RUBY_PLATFORM.include?("darwin") || RUBY_PLATFORM.include?("linux")
350
+ begin
351
+ # Use ps command as fallback
352
+ pid = Process.pid
353
+ mem = `ps -o rss= -p #{pid}`.strip.to_i
354
+ (mem / 1024.0).round(2)
355
+ rescue StandardError
356
+ 0.0
357
+ end
358
+ else
359
+ 0.0
360
+ end
361
+ end
362
+ end
363
+ end
364
+ end