fractor 0.1.4 → 0.1.7

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 (189) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-main-ci-rubocop-yml +552 -0
  3. data/.rubocop.yml +14 -8
  4. data/.rubocop_todo.yml +284 -43
  5. data/README.adoc +111 -950
  6. data/docs/.lycheeignore +16 -0
  7. data/docs/Gemfile +24 -0
  8. data/docs/README.md +157 -0
  9. data/docs/_config.yml +151 -0
  10. data/docs/_features/error-handling.adoc +1192 -0
  11. data/docs/_features/index.adoc +80 -0
  12. data/docs/_features/monitoring.adoc +589 -0
  13. data/docs/_features/signal-handling.adoc +202 -0
  14. data/docs/_features/workflows.adoc +1235 -0
  15. data/docs/_guides/continuous-mode.adoc +736 -0
  16. data/docs/_guides/cookbook.adoc +1133 -0
  17. data/docs/_guides/index.adoc +55 -0
  18. data/docs/_guides/pipeline-mode.adoc +730 -0
  19. data/docs/_guides/troubleshooting.adoc +358 -0
  20. data/docs/_pages/architecture.adoc +1390 -0
  21. data/docs/_pages/core-concepts.adoc +1392 -0
  22. data/docs/_pages/design-principles.adoc +862 -0
  23. data/docs/_pages/getting-started.adoc +290 -0
  24. data/docs/_pages/installation.adoc +143 -0
  25. data/docs/_reference/api.adoc +1080 -0
  26. data/docs/_reference/error-reporting.adoc +670 -0
  27. data/docs/_reference/examples.adoc +181 -0
  28. data/docs/_reference/index.adoc +96 -0
  29. data/docs/_reference/troubleshooting.adoc +862 -0
  30. data/docs/_tutorials/complex-workflows.adoc +1022 -0
  31. data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
  32. data/docs/_tutorials/first-application.adoc +384 -0
  33. data/docs/_tutorials/index.adoc +48 -0
  34. data/docs/_tutorials/long-running-services.adoc +931 -0
  35. data/docs/assets/images/favicon-16.png +0 -0
  36. data/docs/assets/images/favicon-32.png +0 -0
  37. data/docs/assets/images/favicon-48.png +0 -0
  38. data/docs/assets/images/favicon.ico +0 -0
  39. data/docs/assets/images/favicon.png +0 -0
  40. data/docs/assets/images/favicon.svg +45 -0
  41. data/docs/assets/images/fractor-icon.svg +49 -0
  42. data/docs/assets/images/fractor-logo.svg +61 -0
  43. data/docs/index.adoc +131 -0
  44. data/docs/lychee.toml +39 -0
  45. data/examples/api_aggregator/README.adoc +627 -0
  46. data/examples/api_aggregator/api_aggregator.rb +376 -0
  47. data/examples/auto_detection/README.adoc +407 -29
  48. data/examples/auto_detection/auto_detection.rb +9 -9
  49. data/examples/continuous_chat_common/message_protocol.rb +53 -0
  50. data/examples/continuous_chat_fractor/README.adoc +217 -0
  51. data/examples/continuous_chat_fractor/chat_client.rb +303 -0
  52. data/examples/continuous_chat_fractor/chat_common.rb +83 -0
  53. data/examples/continuous_chat_fractor/chat_server.rb +167 -0
  54. data/examples/continuous_chat_fractor/simulate.rb +345 -0
  55. data/examples/continuous_chat_server/README.adoc +135 -0
  56. data/examples/continuous_chat_server/chat_client.rb +303 -0
  57. data/examples/continuous_chat_server/chat_server.rb +359 -0
  58. data/examples/continuous_chat_server/simulate.rb +343 -0
  59. data/examples/error_reporting.rb +207 -0
  60. data/examples/file_processor/README.adoc +170 -0
  61. data/examples/file_processor/file_processor.rb +615 -0
  62. data/examples/file_processor/sample_files/invalid.csv +1 -0
  63. data/examples/file_processor/sample_files/orders.xml +24 -0
  64. data/examples/file_processor/sample_files/products.json +23 -0
  65. data/examples/file_processor/sample_files/users.csv +6 -0
  66. data/examples/hierarchical_hasher/README.adoc +629 -41
  67. data/examples/hierarchical_hasher/hierarchical_hasher.rb +12 -8
  68. data/examples/image_processor/README.adoc +610 -0
  69. data/examples/image_processor/image_processor.rb +349 -0
  70. data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
  71. data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
  72. data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
  73. data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
  74. data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
  75. data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
  76. data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
  77. data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
  78. data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
  79. data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
  80. data/examples/image_processor/test_images/sample_1.png +1 -0
  81. data/examples/image_processor/test_images/sample_10.png +1 -0
  82. data/examples/image_processor/test_images/sample_2.png +1 -0
  83. data/examples/image_processor/test_images/sample_3.png +1 -0
  84. data/examples/image_processor/test_images/sample_4.png +1 -0
  85. data/examples/image_processor/test_images/sample_5.png +1 -0
  86. data/examples/image_processor/test_images/sample_6.png +1 -0
  87. data/examples/image_processor/test_images/sample_7.png +1 -0
  88. data/examples/image_processor/test_images/sample_8.png +1 -0
  89. data/examples/image_processor/test_images/sample_9.png +1 -0
  90. data/examples/log_analyzer/README.adoc +662 -0
  91. data/examples/log_analyzer/log_analyzer.rb +579 -0
  92. data/examples/log_analyzer/sample_logs/apache.log +20 -0
  93. data/examples/log_analyzer/sample_logs/json.log +15 -0
  94. data/examples/log_analyzer/sample_logs/nginx.log +15 -0
  95. data/examples/log_analyzer/sample_logs/rails.log +29 -0
  96. data/examples/multi_work_type/README.adoc +576 -26
  97. data/examples/multi_work_type/multi_work_type.rb +30 -29
  98. data/examples/performance_monitoring.rb +120 -0
  99. data/examples/pipeline_processing/README.adoc +740 -26
  100. data/examples/pipeline_processing/pipeline_processing.rb +16 -16
  101. data/examples/priority_work_example.rb +155 -0
  102. data/examples/producer_subscriber/README.adoc +889 -46
  103. data/examples/producer_subscriber/producer_subscriber.rb +20 -16
  104. data/examples/scatter_gather/README.adoc +829 -27
  105. data/examples/scatter_gather/scatter_gather.rb +29 -28
  106. data/examples/simple/README.adoc +347 -0
  107. data/examples/simple/sample.rb +5 -5
  108. data/examples/specialized_workers/README.adoc +622 -26
  109. data/examples/specialized_workers/specialized_workers.rb +88 -45
  110. data/examples/stream_processor/README.adoc +206 -0
  111. data/examples/stream_processor/stream_processor.rb +284 -0
  112. data/examples/web_scraper/README.adoc +625 -0
  113. data/examples/web_scraper/web_scraper.rb +285 -0
  114. data/examples/workflow/README.adoc +406 -0
  115. data/examples/workflow/circuit_breaker/README.adoc +360 -0
  116. data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
  117. data/examples/workflow/conditional/README.adoc +483 -0
  118. data/examples/workflow/conditional/conditional_workflow.rb +215 -0
  119. data/examples/workflow/dead_letter_queue/README.adoc +374 -0
  120. data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
  121. data/examples/workflow/fan_out/README.adoc +381 -0
  122. data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
  123. data/examples/workflow/retry/README.adoc +248 -0
  124. data/examples/workflow/retry/retry_workflow.rb +195 -0
  125. data/examples/workflow/simple_linear/README.adoc +267 -0
  126. data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
  127. data/examples/workflow/simplified/README.adoc +329 -0
  128. data/examples/workflow/simplified/simplified_workflow.rb +222 -0
  129. data/exe/fractor +10 -0
  130. data/lib/fractor/cli.rb +288 -0
  131. data/lib/fractor/configuration.rb +307 -0
  132. data/lib/fractor/continuous_server.rb +183 -0
  133. data/lib/fractor/error_formatter.rb +72 -0
  134. data/lib/fractor/error_report_generator.rb +152 -0
  135. data/lib/fractor/error_reporter.rb +244 -0
  136. data/lib/fractor/error_statistics.rb +147 -0
  137. data/lib/fractor/execution_tracer.rb +162 -0
  138. data/lib/fractor/logger.rb +230 -0
  139. data/lib/fractor/main_loop_handler.rb +406 -0
  140. data/lib/fractor/main_loop_handler3.rb +135 -0
  141. data/lib/fractor/main_loop_handler4.rb +299 -0
  142. data/lib/fractor/performance_metrics_collector.rb +181 -0
  143. data/lib/fractor/performance_monitor.rb +215 -0
  144. data/lib/fractor/performance_report_generator.rb +202 -0
  145. data/lib/fractor/priority_work.rb +93 -0
  146. data/lib/fractor/priority_work_queue.rb +189 -0
  147. data/lib/fractor/result_aggregator.rb +33 -1
  148. data/lib/fractor/shutdown_handler.rb +168 -0
  149. data/lib/fractor/signal_handler.rb +80 -0
  150. data/lib/fractor/supervisor.rb +430 -144
  151. data/lib/fractor/supervisor_logger.rb +88 -0
  152. data/lib/fractor/version.rb +1 -1
  153. data/lib/fractor/work.rb +12 -0
  154. data/lib/fractor/work_distribution_manager.rb +151 -0
  155. data/lib/fractor/work_queue.rb +88 -0
  156. data/lib/fractor/work_result.rb +181 -9
  157. data/lib/fractor/worker.rb +75 -1
  158. data/lib/fractor/workflow/builder.rb +210 -0
  159. data/lib/fractor/workflow/chain_builder.rb +169 -0
  160. data/lib/fractor/workflow/circuit_breaker.rb +183 -0
  161. data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
  162. data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
  163. data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
  164. data/lib/fractor/workflow/execution_hooks.rb +39 -0
  165. data/lib/fractor/workflow/execution_strategy.rb +225 -0
  166. data/lib/fractor/workflow/execution_trace.rb +134 -0
  167. data/lib/fractor/workflow/helpers.rb +191 -0
  168. data/lib/fractor/workflow/job.rb +290 -0
  169. data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
  170. data/lib/fractor/workflow/logger.rb +110 -0
  171. data/lib/fractor/workflow/pre_execution_context.rb +193 -0
  172. data/lib/fractor/workflow/retry_config.rb +156 -0
  173. data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
  174. data/lib/fractor/workflow/retry_strategy.rb +93 -0
  175. data/lib/fractor/workflow/structured_logger.rb +30 -0
  176. data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
  177. data/lib/fractor/workflow/visualizer.rb +211 -0
  178. data/lib/fractor/workflow/workflow_context.rb +132 -0
  179. data/lib/fractor/workflow/workflow_executor.rb +669 -0
  180. data/lib/fractor/workflow/workflow_result.rb +55 -0
  181. data/lib/fractor/workflow/workflow_validator.rb +295 -0
  182. data/lib/fractor/workflow.rb +333 -0
  183. data/lib/fractor/wrapped_ractor.rb +66 -91
  184. data/lib/fractor/wrapped_ractor3.rb +161 -0
  185. data/lib/fractor/wrapped_ractor4.rb +242 -0
  186. data/lib/fractor.rb +93 -3
  187. metadata +192 -6
  188. data/tests/sample.rb.bak +0 -309
  189. data/tests/sample_working.rb.bak +0 -209
@@ -45,7 +45,7 @@ module MultiWorkType
45
45
  end
46
46
 
47
47
  def to_s
48
- "ImageWork: dimensions=#{dimensions.join("x")}, format=#{format}"
48
+ "ImageWork: dimensions=#{dimensions.join('x')}, format=#{format}"
49
49
  end
50
50
  end
51
51
 
@@ -62,7 +62,7 @@ module MultiWorkType
62
62
  error = TypeError.new("Unsupported work type: #{work.class}")
63
63
  Fractor::WorkResult.new(
64
64
  error: error,
65
- work: work
65
+ work: work,
66
66
  )
67
67
  end
68
68
  end
@@ -74,7 +74,8 @@ module MultiWorkType
74
74
  sleep(rand(0.01..0.05)) # Simulate processing time
75
75
 
76
76
  processed_text = case work.format
77
- when :markdown then process_markdown(work.data, work.options)
77
+ when :markdown then process_markdown(work.data,
78
+ work.options)
78
79
  when :html then process_html(work.data, work.options)
79
80
  when :json then process_json(work.data, work.options)
80
81
  else work.data.upcase # Simple transformation for plain text
@@ -87,10 +88,10 @@ module MultiWorkType
87
88
  transformed_data: processed_text,
88
89
  metadata: {
89
90
  word_count: processed_text.split(/\s+/).size,
90
- char_count: processed_text.length
91
- }
91
+ char_count: processed_text.length,
92
+ },
92
93
  },
93
- work: work
94
+ work: work,
94
95
  )
95
96
  end
96
97
 
@@ -110,13 +111,13 @@ module MultiWorkType
110
111
  applied_filters: %i[sharpen contrast],
111
112
  processing_metadata: {
112
113
  original_size: input_size,
113
- processed_size: (input_size * 0.8).to_i # Simulate compression
114
- }
114
+ processed_size: (input_size * 0.8).to_i, # Simulate compression
115
+ },
115
116
  }
116
117
 
117
118
  Fractor::WorkResult.new(
118
119
  result: simulated_result,
119
- work: work
120
+ work: work,
120
121
  )
121
122
  end
122
123
 
@@ -127,7 +128,7 @@ module MultiWorkType
127
128
  links = text.scan(/\[(.+?)\]\((.+?)\)/)
128
129
 
129
130
  "Processed Markdown: #{text.length} chars, #{headers.size} headers, #{links.size} links\n" \
130
- "Headers: #{headers.join(", ")}\n" \
131
+ "Headers: #{headers.join(', ')}\n" \
131
132
  "#{text.gsub(/^#+\s+(.+)$/, '💫 \1 💫')}"
132
133
  end
133
134
 
@@ -136,7 +137,7 @@ module MultiWorkType
136
137
  tags = text.scan(/<(\w+)[^>]*>/).flatten
137
138
 
138
139
  "Processed HTML: #{text.length} chars, #{tags.size} tags\n" \
139
- "Tags: #{tags.uniq.join(", ")}\n" \
140
+ "Tags: #{tags.uniq.join(', ')}\n" \
140
141
  "#{text.gsub(%r{<(\w+)[^>]*>(.+?)</\1>}, '✨\2✨')}"
141
142
  end
142
143
 
@@ -147,7 +148,7 @@ module MultiWorkType
147
148
  keys = data.keys
148
149
 
149
150
  "Processed JSON: #{keys.size} top-level keys\n" \
150
- "Keys: #{keys.join(", ")}\n" \
151
+ "Keys: #{keys.join(', ')}\n" \
151
152
  "Pretty-printed: #{data}"
152
153
  rescue StandardError => e
153
154
  "Invalid JSON: #{e.message}"
@@ -162,14 +163,14 @@ module MultiWorkType
162
163
  # Create supervisor with a MultiFormatWorker pool
163
164
  @supervisor = Fractor::Supervisor.new(
164
165
  worker_pools: [
165
- { worker_class: MultiFormatWorker, num_workers: worker_count }
166
- ]
166
+ { worker_class: MultiFormatWorker, num_workers: worker_count },
167
+ ],
167
168
  )
168
169
 
169
170
  @results = {
170
171
  text: [],
171
172
  image: [],
172
- errors: []
173
+ errors: [],
173
174
  }
174
175
  end
175
176
 
@@ -197,10 +198,10 @@ module MultiWorkType
197
198
  total_items: text_items.size + image_items.size,
198
199
  processed: {
199
200
  text: @results[:text].size,
200
- image: @results[:image].size
201
+ image: @results[:image].size,
201
202
  },
202
203
  errors: @results[:errors].size,
203
- results: @results
204
+ results: @results,
204
205
  }
205
206
  end
206
207
 
@@ -220,7 +221,7 @@ module MultiWorkType
220
221
  results_aggregator.errors.each do |error_result|
221
222
  @results[:errors] << {
222
223
  error: error_result.error,
223
- work_type: error_result.work.class.name
224
+ work_type: error_result.work.class.name,
224
225
  }
225
226
  end
226
227
 
@@ -244,21 +245,21 @@ if __FILE__ == $PROGRAM_NAME
244
245
  text_items = [
245
246
  {
246
247
  data: "This is a plain text document. It has no special formatting.",
247
- format: :plain
248
+ format: :plain,
248
249
  },
249
250
  {
250
251
  data: "# Markdown Document\n\nThis is a **bold** statement. Here's a [link](https://example.com).",
251
- format: :markdown
252
+ format: :markdown,
252
253
  },
253
254
  {
254
255
  data: "<html><body><h1>HTML Document</h1><p>This is a paragraph.</p></body></html>",
255
- format: :html
256
+ format: :html,
256
257
  },
257
258
  {
258
259
  data: "{name: 'Product', price: 29.99, tags: ['electronics', 'gadget']}",
259
260
  format: :json,
260
- options: { pretty: true }
261
- }
261
+ options: { pretty: true },
262
+ },
262
263
  ]
263
264
 
264
265
  # Sample image items (simulated)
@@ -266,18 +267,18 @@ if __FILE__ == $PROGRAM_NAME
266
267
  {
267
268
  data: "simulated_jpeg_data_1",
268
269
  dimensions: [800, 600],
269
- format: :jpeg
270
+ format: :jpeg,
270
271
  },
271
272
  {
272
273
  data: "simulated_png_data_1",
273
274
  dimensions: [1024, 768],
274
- format: :png
275
+ format: :png,
275
276
  },
276
277
  {
277
278
  data: "simulated_gif_data_1",
278
279
  dimensions: [320, 240],
279
- format: :gif
280
- }
280
+ format: :gif,
281
+ },
281
282
  ]
282
283
 
283
284
  worker_count = 4
@@ -309,8 +310,8 @@ if __FILE__ == $PROGRAM_NAME
309
310
  puts "Image Processing Results:"
310
311
  result[:results][:image].each_with_index do |image_result, index|
311
312
  puts "Image Item #{index + 1} (#{image_result[:format]}):"
312
- puts " Dimensions: #{image_result[:dimensions].join("x")}"
313
- puts " Applied filters: #{image_result[:applied_filters].join(", ")}"
313
+ puts " Dimensions: #{image_result[:dimensions].join('x')}"
314
+ puts " Applied filters: #{image_result[:applied_filters].join(', ')}"
314
315
  puts " Compression: #{(1 - image_result[:processing_metadata][:processed_size].to_f / image_result[:processing_metadata][:original_size]).round(2) * 100}%"
315
316
  puts
316
317
  end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/fractor"
4
+ require_relative "../lib/fractor/performance_monitor"
5
+
6
+ # Example worker that simulates varying processing times
7
+ class VariableLatencyWorker
8
+ def self.perform(work)
9
+ # Simulate variable work latency
10
+ sleep(rand * 0.1) # 0-100ms
11
+
12
+ result = work.payload[:value] * 2
13
+ Fractor::WorkResult.new(result: result, work: work)
14
+ end
15
+ end
16
+
17
+ # Example: Basic Performance Monitoring
18
+ puts "=" * 80
19
+ puts "Performance Monitoring Example"
20
+ puts "=" * 80
21
+
22
+ # Create supervisor with multiple workers
23
+ supervisor = Fractor::Supervisor.new(
24
+ worker_pools: [
25
+ { worker_class: VariableLatencyWorker, num_workers: 4 },
26
+ ],
27
+ )
28
+
29
+ # Create and start performance monitor
30
+ monitor = Fractor::PerformanceMonitor.new(supervisor, sample_interval: 0.5)
31
+ monitor.start
32
+
33
+ puts "\nProcessing 100 work items..."
34
+ puts "Monitor started, sampling every 0.5 seconds"
35
+
36
+ # Add work items
37
+ 100.times do |i|
38
+ supervisor.add_work_item(Fractor::Work.new({ value: i }))
39
+ end
40
+
41
+ # Run the supervisor
42
+ supervisor_thread = Thread.new { supervisor.run }
43
+
44
+ # Wait for completion
45
+ sleep 0.5 while supervisor.work_queue.size > 0 || supervisor.results.results.size < 100
46
+
47
+ # Stop monitoring
48
+ monitor.stop
49
+ supervisor.stop
50
+ supervisor_thread.join
51
+
52
+ puts "\n" + "=" * 80
53
+ puts "Human-Readable Report"
54
+ puts "=" * 80
55
+ puts monitor.report
56
+
57
+ puts "\n" + "=" * 80
58
+ puts "JSON Export"
59
+ puts "=" * 80
60
+ puts JSON.pretty_generate(JSON.parse(monitor.to_json))
61
+
62
+ puts "\n" + "=" * 80
63
+ puts "Prometheus Metrics"
64
+ puts "=" * 80
65
+ puts monitor.to_prometheus
66
+
67
+ puts "\n" + "=" * 80
68
+ puts "Example: Real-time Monitoring with Manual Recording"
69
+ puts "=" * 80
70
+
71
+ # Create a new monitor
72
+ supervisor2 = Fractor::Supervisor.new(
73
+ worker_pools: [
74
+ { worker_class: VariableLatencyWorker, num_workers: 2 },
75
+ ],
76
+ )
77
+
78
+ monitor2 = Fractor::PerformanceMonitor.new(supervisor2)
79
+ monitor2.start
80
+
81
+ puts "\nManually recording job completions..."
82
+
83
+ # Simulate job execution and manually record metrics
84
+ 10.times do |i|
85
+ start_time = Time.now
86
+
87
+ # Simulate work
88
+ sleep(rand * 0.05)
89
+
90
+ # Record the job
91
+ latency = Time.now - start_time
92
+ success = rand > 0.1 # 90% success rate
93
+
94
+ monitor2.record_job(latency, success: success)
95
+
96
+ print "." if i % 10 == 0
97
+ end
98
+
99
+ monitor2.stop
100
+
101
+ puts "\n\nFinal Metrics:"
102
+ snapshot = monitor2.snapshot
103
+ puts " Jobs Processed: #{snapshot[:jobs_processed]}"
104
+ puts " Success Rate: #{(snapshot[:jobs_succeeded].to_f / snapshot[:jobs_processed] * 100).round(2)}%"
105
+ puts " Average Latency: #{(snapshot[:average_latency] * 1000).round(2)}ms"
106
+ puts " P95 Latency: #{(snapshot[:p95_latency] * 1000).round(2)}ms"
107
+ puts " Throughput: #{snapshot[:throughput].round(2)} jobs/sec"
108
+
109
+ puts "\n" + "=" * 80
110
+ puts "Example Complete"
111
+ puts "=" * 80
112
+ puts "\nPerformance monitoring provides:"
113
+ puts " ✓ Jobs processed counter"
114
+ puts " ✓ Latency tracking (average, p50, p95, p99)"
115
+ puts " ✓ Throughput calculation (jobs/second)"
116
+ puts " ✓ Worker utilization tracking"
117
+ puts " ✓ Queue depth monitoring"
118
+ puts " ✓ Memory usage tracking"
119
+ puts " ✓ JSON export"
120
+ puts " ✓ Prometheus metrics export"