fractor 0.1.6 → 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 (172) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +227 -102
  3. data/README.adoc +113 -1940
  4. data/docs/.lycheeignore +16 -0
  5. data/docs/Gemfile +24 -0
  6. data/docs/README.md +157 -0
  7. data/docs/_config.yml +151 -0
  8. data/docs/_features/error-handling.adoc +1192 -0
  9. data/docs/_features/index.adoc +80 -0
  10. data/docs/_features/monitoring.adoc +589 -0
  11. data/docs/_features/signal-handling.adoc +202 -0
  12. data/docs/_features/workflows.adoc +1235 -0
  13. data/docs/_guides/continuous-mode.adoc +736 -0
  14. data/docs/_guides/cookbook.adoc +1133 -0
  15. data/docs/_guides/index.adoc +55 -0
  16. data/docs/_guides/pipeline-mode.adoc +730 -0
  17. data/docs/_guides/troubleshooting.adoc +358 -0
  18. data/docs/_pages/architecture.adoc +1390 -0
  19. data/docs/_pages/core-concepts.adoc +1392 -0
  20. data/docs/_pages/design-principles.adoc +862 -0
  21. data/docs/_pages/getting-started.adoc +290 -0
  22. data/docs/_pages/installation.adoc +143 -0
  23. data/docs/_reference/api.adoc +1080 -0
  24. data/docs/_reference/error-reporting.adoc +670 -0
  25. data/docs/_reference/examples.adoc +181 -0
  26. data/docs/_reference/index.adoc +96 -0
  27. data/docs/_reference/troubleshooting.adoc +862 -0
  28. data/docs/_tutorials/complex-workflows.adoc +1022 -0
  29. data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
  30. data/docs/_tutorials/first-application.adoc +384 -0
  31. data/docs/_tutorials/index.adoc +48 -0
  32. data/docs/_tutorials/long-running-services.adoc +931 -0
  33. data/docs/assets/images/favicon-16.png +0 -0
  34. data/docs/assets/images/favicon-32.png +0 -0
  35. data/docs/assets/images/favicon-48.png +0 -0
  36. data/docs/assets/images/favicon.ico +0 -0
  37. data/docs/assets/images/favicon.png +0 -0
  38. data/docs/assets/images/favicon.svg +45 -0
  39. data/docs/assets/images/fractor-icon.svg +49 -0
  40. data/docs/assets/images/fractor-logo.svg +61 -0
  41. data/docs/index.adoc +131 -0
  42. data/docs/lychee.toml +39 -0
  43. data/examples/api_aggregator/README.adoc +627 -0
  44. data/examples/api_aggregator/api_aggregator.rb +376 -0
  45. data/examples/auto_detection/README.adoc +407 -29
  46. data/examples/continuous_chat_common/message_protocol.rb +1 -1
  47. data/examples/error_reporting.rb +207 -0
  48. data/examples/file_processor/README.adoc +170 -0
  49. data/examples/file_processor/file_processor.rb +615 -0
  50. data/examples/file_processor/sample_files/invalid.csv +1 -0
  51. data/examples/file_processor/sample_files/orders.xml +24 -0
  52. data/examples/file_processor/sample_files/products.json +23 -0
  53. data/examples/file_processor/sample_files/users.csv +6 -0
  54. data/examples/hierarchical_hasher/README.adoc +629 -41
  55. data/examples/image_processor/README.adoc +610 -0
  56. data/examples/image_processor/image_processor.rb +349 -0
  57. data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
  58. data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
  59. data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
  60. data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
  61. data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
  62. data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
  63. data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
  64. data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
  65. data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
  66. data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
  67. data/examples/image_processor/test_images/sample_1.png +1 -0
  68. data/examples/image_processor/test_images/sample_10.png +1 -0
  69. data/examples/image_processor/test_images/sample_2.png +1 -0
  70. data/examples/image_processor/test_images/sample_3.png +1 -0
  71. data/examples/image_processor/test_images/sample_4.png +1 -0
  72. data/examples/image_processor/test_images/sample_5.png +1 -0
  73. data/examples/image_processor/test_images/sample_6.png +1 -0
  74. data/examples/image_processor/test_images/sample_7.png +1 -0
  75. data/examples/image_processor/test_images/sample_8.png +1 -0
  76. data/examples/image_processor/test_images/sample_9.png +1 -0
  77. data/examples/log_analyzer/README.adoc +662 -0
  78. data/examples/log_analyzer/log_analyzer.rb +579 -0
  79. data/examples/log_analyzer/sample_logs/apache.log +20 -0
  80. data/examples/log_analyzer/sample_logs/json.log +15 -0
  81. data/examples/log_analyzer/sample_logs/nginx.log +15 -0
  82. data/examples/log_analyzer/sample_logs/rails.log +29 -0
  83. data/examples/multi_work_type/README.adoc +576 -26
  84. data/examples/performance_monitoring.rb +120 -0
  85. data/examples/pipeline_processing/README.adoc +740 -26
  86. data/examples/pipeline_processing/pipeline_processing.rb +2 -2
  87. data/examples/priority_work_example.rb +155 -0
  88. data/examples/producer_subscriber/README.adoc +889 -46
  89. data/examples/scatter_gather/README.adoc +829 -27
  90. data/examples/simple/README.adoc +347 -0
  91. data/examples/specialized_workers/README.adoc +622 -26
  92. data/examples/specialized_workers/specialized_workers.rb +44 -8
  93. data/examples/stream_processor/README.adoc +206 -0
  94. data/examples/stream_processor/stream_processor.rb +284 -0
  95. data/examples/web_scraper/README.adoc +625 -0
  96. data/examples/web_scraper/web_scraper.rb +285 -0
  97. data/examples/workflow/README.adoc +406 -0
  98. data/examples/workflow/circuit_breaker/README.adoc +360 -0
  99. data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
  100. data/examples/workflow/conditional/README.adoc +483 -0
  101. data/examples/workflow/conditional/conditional_workflow.rb +215 -0
  102. data/examples/workflow/dead_letter_queue/README.adoc +374 -0
  103. data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
  104. data/examples/workflow/fan_out/README.adoc +381 -0
  105. data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
  106. data/examples/workflow/retry/README.adoc +248 -0
  107. data/examples/workflow/retry/retry_workflow.rb +195 -0
  108. data/examples/workflow/simple_linear/README.adoc +267 -0
  109. data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
  110. data/examples/workflow/simplified/README.adoc +329 -0
  111. data/examples/workflow/simplified/simplified_workflow.rb +222 -0
  112. data/exe/fractor +10 -0
  113. data/lib/fractor/cli.rb +288 -0
  114. data/lib/fractor/configuration.rb +307 -0
  115. data/lib/fractor/continuous_server.rb +60 -65
  116. data/lib/fractor/error_formatter.rb +72 -0
  117. data/lib/fractor/error_report_generator.rb +152 -0
  118. data/lib/fractor/error_reporter.rb +244 -0
  119. data/lib/fractor/error_statistics.rb +147 -0
  120. data/lib/fractor/execution_tracer.rb +162 -0
  121. data/lib/fractor/logger.rb +230 -0
  122. data/lib/fractor/main_loop_handler.rb +406 -0
  123. data/lib/fractor/main_loop_handler3.rb +135 -0
  124. data/lib/fractor/main_loop_handler4.rb +299 -0
  125. data/lib/fractor/performance_metrics_collector.rb +181 -0
  126. data/lib/fractor/performance_monitor.rb +215 -0
  127. data/lib/fractor/performance_report_generator.rb +202 -0
  128. data/lib/fractor/priority_work.rb +93 -0
  129. data/lib/fractor/priority_work_queue.rb +189 -0
  130. data/lib/fractor/result_aggregator.rb +32 -0
  131. data/lib/fractor/shutdown_handler.rb +168 -0
  132. data/lib/fractor/signal_handler.rb +80 -0
  133. data/lib/fractor/supervisor.rb +382 -269
  134. data/lib/fractor/supervisor_logger.rb +88 -0
  135. data/lib/fractor/version.rb +1 -1
  136. data/lib/fractor/work.rb +12 -0
  137. data/lib/fractor/work_distribution_manager.rb +151 -0
  138. data/lib/fractor/work_queue.rb +20 -0
  139. data/lib/fractor/work_result.rb +181 -9
  140. data/lib/fractor/worker.rb +73 -0
  141. data/lib/fractor/workflow/builder.rb +210 -0
  142. data/lib/fractor/workflow/chain_builder.rb +169 -0
  143. data/lib/fractor/workflow/circuit_breaker.rb +183 -0
  144. data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
  145. data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
  146. data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
  147. data/lib/fractor/workflow/execution_hooks.rb +39 -0
  148. data/lib/fractor/workflow/execution_strategy.rb +225 -0
  149. data/lib/fractor/workflow/execution_trace.rb +134 -0
  150. data/lib/fractor/workflow/helpers.rb +191 -0
  151. data/lib/fractor/workflow/job.rb +290 -0
  152. data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
  153. data/lib/fractor/workflow/logger.rb +110 -0
  154. data/lib/fractor/workflow/pre_execution_context.rb +193 -0
  155. data/lib/fractor/workflow/retry_config.rb +156 -0
  156. data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
  157. data/lib/fractor/workflow/retry_strategy.rb +93 -0
  158. data/lib/fractor/workflow/structured_logger.rb +30 -0
  159. data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
  160. data/lib/fractor/workflow/visualizer.rb +211 -0
  161. data/lib/fractor/workflow/workflow_context.rb +132 -0
  162. data/lib/fractor/workflow/workflow_executor.rb +669 -0
  163. data/lib/fractor/workflow/workflow_result.rb +55 -0
  164. data/lib/fractor/workflow/workflow_validator.rb +295 -0
  165. data/lib/fractor/workflow.rb +333 -0
  166. data/lib/fractor/wrapped_ractor.rb +66 -101
  167. data/lib/fractor/wrapped_ractor3.rb +161 -0
  168. data/lib/fractor/wrapped_ractor4.rb +242 -0
  169. data/lib/fractor.rb +92 -4
  170. metadata +179 -6
  171. data/tests/sample.rb.bak +0 -309
  172. data/tests/sample_working.rb.bak +0 -209
@@ -1,31 +1,158 @@
1
1
  = Auto-Detection Example
2
2
 
3
- This example demonstrates Fractor's automatic worker detection feature.
4
-
5
3
  == Purpose
6
4
 
7
- Shows how Fractor can automatically detect the number of available processors on your system and create the optimal number of workers without manual configuration.
5
+ Demonstrates Fractor's automatic worker detection feature, showing how the framework adapts to different hardware environments by automatically detecting and utilizing available CPU cores.
6
+
7
+ == Focus
8
8
 
9
- == What This Demonstrates
9
+ This example focuses on demonstrating:
10
10
 
11
- * How Fractor automatically detects the number of available processors
11
+ * Automatic detection of available processors using `Etc.nprocessors`
12
12
  * Comparison between auto-detection and explicit worker configuration
13
- * Mixed configuration (some pools with auto-detection, some explicit)
14
- * How to verify the number of workers being used
13
+ * Mixed configuration strategies (combining both approaches)
14
+ * Resource utilization optimization without manual tuning
15
+ * Portable code that adapts to different environments
16
+
17
+ == Architecture
18
+
19
+ .Auto-Detection vs Explicit Configuration
20
+ [source]
21
+ ----
22
+ System Hardware
23
+ │
24
+ ├─→ Etc.nprocessors
25
+ │ │
26
+ │ └─→ Returns: 8 (example)
27
+ │
28
+ â–ŧ
29
+ ┌─────────────────────────────────────────────┐
30
+ │ Fractor Supervisor │
31
+ └─────────────────────────────────────────────┘
32
+ │
33
+ ├─→ Auto-Detection Mode
34
+ │ │
35
+ │ └─→ Worker Pool { worker_class: ComputeWorker }
36
+ │ │
37
+ │ └─→ Creates 8 Ractors (auto-detected)
38
+ │ │
39
+ │ ├─→ Ractor 1 (ComputeWorker)
40
+ │ ├─→ Ractor 2 (ComputeWorker)
41
+ │ ├─→ Ractor 3 (ComputeWorker)
42
+ │ ├─→ ...
43
+ │ └─→ Ractor 8 (ComputeWorker)
44
+ │
45
+ └─→ Explicit Configuration Mode
46
+ │
47
+ └─→ Worker Pool { worker_class: ComputeWorker, num_workers: 4 }
48
+ │
49
+ └─→ Creates 4 Ractors (explicit)
50
+ │
51
+ ├─→ Ractor 1 (ComputeWorker)
52
+ ├─→ Ractor 2 (ComputeWorker)
53
+ ├─→ Ractor 3 (ComputeWorker)
54
+ └─→ Ractor 4 (ComputeWorker)
55
+ ----
56
+
57
+ .Resource Utilization Comparison
58
+ [source]
59
+ ----
60
+ Machine A (4 cores) Machine B (16 cores)
61
+ ────────────────────────────────────────────────────────
15
62
 
16
- == When to Use Auto-Detection
63
+ Auto-Detection:
64
+ 4 workers created 16 workers created
65
+ 100% CPU utilization 100% CPU utilization
66
+ ✓ Optimal ✓ Optimal
17
67
 
18
- * For portable code that adapts to different environments
19
- * When you want optimal resource utilization without manual tuning
20
- * For development where the number of cores varies across machines
68
+ Explicit (num_workers: 4):
69
+ 4 workers created 4 workers created
70
+ 100% CPU utilization 25% CPU utilization
71
+ ✓ Optimal ✗ Underutilized
21
72
 
22
- == When to Set Explicit Values
73
+ Explicit (num_workers: 8):
74
+ 8 workers created 8 workers created
75
+ 200% oversubscribed 50% CPU utilization
76
+ ✗ Oversubscribed ✗ Underutilized
77
+ ----
78
+
79
+ == Key Components
80
+
81
+ === Auto-Detection Configuration
82
+
83
+ Create supervisor without specifying `num_workers`:
84
+
85
+ [source,ruby]
86
+ ----
87
+ supervisor = Fractor::Supervisor.new(
88
+ worker_pools: [
89
+ { worker_class: ComputeWorker } # <1>
90
+ ]
91
+ )
92
+ ----
93
+ <1> Omitting `num_workers` triggers auto-detection
23
94
 
24
- * When you need precise control over resource usage
25
- * For production environments with specific requirements
26
- * When limiting workers due to memory or other constraints
95
+ Fractor internally uses:
96
+
97
+ [source,ruby]
98
+ ----
99
+ require 'etc'
100
+
101
+ num_workers = Etc.nprocessors # <1>
102
+ # Creates num_workers Ractor instances
103
+ ----
104
+ <1> Returns the number of available processors
105
+
106
+ === Explicit Configuration
107
+
108
+ Specify exact number of workers:
109
+
110
+ [source,ruby]
111
+ ----
112
+ supervisor = Fractor::Supervisor.new(
113
+ worker_pools: [
114
+ { worker_class: ComputeWorker, num_workers: 4 } # <1>
115
+ ]
116
+ )
117
+ ----
118
+ <1> Always creates exactly 4 workers regardless of hardware
119
+
120
+ === Mixed Configuration
121
+
122
+ Combine both approaches in different worker pools:
123
+
124
+ [source,ruby]
125
+ ----
126
+ supervisor = Fractor::Supervisor.new(
127
+ worker_pools: [
128
+ { worker_class: FastWorker }, # <1>
129
+ { worker_class: SlowWorker, num_workers: 2 } # <2>
130
+ ]
131
+ )
132
+ ----
133
+ <1> Auto-detects optimal workers for FastWorker
134
+ <2> Limits SlowWorker to 2 workers (e.g., memory intensive)
27
135
 
28
- == Running the Example
136
+ === Worker Implementation
137
+
138
+ Simple worker for demonstration:
139
+
140
+ [source,ruby]
141
+ ----
142
+ class ComputeWorker < Fractor::Worker
143
+ def process(work)
144
+ result = work.value * work.value # <1>
145
+ Fractor::WorkResult.new(result: result, work: work)
146
+ rescue StandardError => e
147
+ Fractor::WorkResult.new(error: e, work: work)
148
+ end
149
+ end
150
+ ----
151
+ <1> Simple computation (squares the input value)
152
+
153
+ == Usage
154
+
155
+ Run the example from the project root:
29
156
 
30
157
  [source,shell]
31
158
  ----
@@ -34,19 +161,270 @@ ruby examples/auto_detection/auto_detection.rb
34
161
 
35
162
  == Expected Output
36
163
 
37
- The script will:
164
+ [example]
165
+ ====
166
+ [source]
167
+ ----
168
+ ================================================================================
169
+ Fractor Auto-Detection Example
170
+ ================================================================================
171
+
172
+ System Information:
173
+ Available processors: 8
174
+
175
+ --------------------------------------------------------------------------------
176
+ Example 1: Auto-Detection
177
+ --------------------------------------------------------------------------------
178
+ Creating supervisor WITHOUT specifying num_workers...
179
+ Fractor will automatically detect and use 8 workers
180
+
181
+ Processing 10 work items with auto-detected workers...
182
+ Results: 1, 4, 9, 16, 25, 36, 49, 64, 81, 100
183
+ ✓ Auto-detection successful!
184
+
185
+ --------------------------------------------------------------------------------
186
+ Example 2: Explicit Configuration
187
+ --------------------------------------------------------------------------------
188
+ Creating supervisor WITH explicit num_workers=4...
189
+
190
+ Processing 10 work items with 4 explicitly configured workers...
191
+ Results: 121, 144, 169, 196, 225, 256, 289, 324, 361, 400
192
+ ✓ Explicit configuration successful!
193
+
194
+ --------------------------------------------------------------------------------
195
+ Example 3: Mixed Auto-Detection and Explicit Configuration
196
+ --------------------------------------------------------------------------------
197
+ Creating supervisor with multiple worker pools:
198
+ - Pool 1: Auto-detected workers
199
+ - Pool 2: 2 explicitly configured workers
200
+
201
+ Processing 10 work items with mixed configuration...
202
+ Results: 441, 484, 529, 576, 625, 676, 729, 784, 841, 900
203
+ ✓ Mixed configuration successful!
204
+
205
+ ================================================================================
206
+ Summary
207
+ ================================================================================
208
+
209
+ Auto-detection provides:
210
+ ✓ Automatic adaptation to different environments
211
+ ✓ Optimal resource utilization by default
212
+ ✓ Less configuration needed
213
+ ✓ Portability across machines with different CPU counts
214
+
215
+ Explicit configuration provides:
216
+ ✓ Precise control over worker count
217
+ ✓ Ability to limit resource usage
218
+ ✓ Predictable behavior in production
219
+
220
+ Best practice: Use auto-detection for development and testing,
221
+ then tune explicitly for production if needed.
222
+
223
+ ================================================================================
224
+ ----
225
+ ====
226
+
227
+ == Learning Points
228
+
229
+ === When to Use Auto-Detection
230
+
231
+ Auto-detection is ideal for:
232
+
233
+ * **Development environments**: Different developers may have different hardware
234
+ * **Portable code**: Applications that run on various machines
235
+ * **Default optimization**: When you want optimal performance without tuning
236
+ * **CI/CD pipelines**: Runners may have varying CPU counts
237
+ * **Container deployments**: CPU allocation may vary by environment
238
+
239
+ Example use cases:
240
+ [source,ruby]
241
+ ----
242
+ # Development script - works well on any machine
243
+ supervisor = Fractor::Supervisor.new(
244
+ worker_pools: [{ worker_class: DataProcessor }]
245
+ )
246
+
247
+ # CI pipeline - adapts to runner hardware
248
+ supervisor = Fractor::Supervisor.new(
249
+ worker_pools: [{ worker_class: TestRunner }]
250
+ )
251
+ ----
252
+
253
+ === When to Use Explicit Configuration
254
+
255
+ Explicit configuration is better for:
256
+
257
+ * **Production systems**: Predictable resource usage for capacity planning
258
+ * **Resource-constrained environments**: Limit workers to avoid overwhelming system
259
+ * **Memory-intensive tasks**: Fewer workers to prevent memory exhaustion
260
+ * **I/O-bound tasks**: More workers than CPUs for better throughput
261
+ * **Mixed workloads**: Different worker counts for different task types
262
+
263
+ Example use cases:
264
+ [source,ruby]
265
+ ----
266
+ # Memory-intensive processing - limit workers
267
+ supervisor = Fractor::Supervisor.new(
268
+ worker_pools: [
269
+ { worker_class: MemoryHeavyWorker, num_workers: 2 }
270
+ ]
271
+ )
272
+
273
+ # I/O-bound tasks - more workers than CPUs
274
+ num_io_workers = Etc.nprocessors * 2 # <1>
275
+ supervisor = Fractor::Supervisor.new(
276
+ worker_pools: [
277
+ { worker_class: ApiWorker, num_workers: num_io_workers }
278
+ ]
279
+ )
280
+ ----
281
+ <1> I/O-bound tasks benefit from more workers
282
+
283
+ === Auto-Detection Mechanism
284
+
285
+ Fractor uses Ruby's `Etc.nprocessors`:
286
+
287
+ [source,ruby]
288
+ ----
289
+ require 'etc'
290
+
291
+ # Returns number of available processors
292
+ # - Physical cores on bare metal
293
+ # - Virtual CPUs in VMs
294
+ # - CPU quota in containers (if set)
295
+ num_processors = Etc.nprocessors
296
+ ----
297
+
298
+ Behavior in different environments:
299
+
300
+ * **Bare metal**: Returns physical CPU cores
301
+ * **Virtual machines**: Returns allocated vCPUs
302
+ * **Docker containers**: Returns container CPU quota (if set) or host CPUs
303
+ * **Kubernetes pods**: Returns CPU limit (if set) or node CPUs
304
+
305
+ === Performance Considerations
306
+
307
+ ==== CPU-Bound Tasks
308
+
309
+ For CPU-intensive work, auto-detection provides optimal parallelism:
310
+
311
+ [source,ruby]
312
+ ----
313
+ # Good: One worker per CPU core
314
+ supervisor = Fractor::Supervisor.new(
315
+ worker_pools: [{ worker_class: CpuIntensiveWorker }]
316
+ )
317
+ ----
318
+
319
+ ==== I/O-Bound Tasks
320
+
321
+ For I/O-heavy work, consider explicit over-subscription:
322
+
323
+ [source,ruby]
324
+ ----
325
+ # Better: More workers than CPUs for I/O tasks
326
+ io_workers = Etc.nprocessors * 2
327
+ supervisor = Fractor::Supervisor.new(
328
+ worker_pools: [
329
+ { worker_class: ApiClient, num_workers: io_workers }
330
+ ]
331
+ )
332
+ ----
333
+
334
+ ==== Memory-Intensive Tasks
335
+
336
+ For memory-heavy work, limit workers to prevent exhaustion:
337
+
338
+ [source,ruby]
339
+ ----
340
+ # Better: Fewer workers for memory-intensive tasks
341
+ max_workers = [Etc.nprocessors / 2, 2].max # <1>
342
+ supervisor = Fractor::Supervisor.new(
343
+ worker_pools: [
344
+ { worker_class: LargeDataProcessor, num_workers: max_workers }
345
+ ]
346
+ )
347
+ ----
348
+ <1> Use half the CPUs, minimum of 2
349
+
350
+ === Mixed Configuration Strategy
351
+
352
+ Combine auto-detection with explicit limits:
353
+
354
+ [source,ruby]
355
+ ----
356
+ supervisor = Fractor::Supervisor.new(
357
+ worker_pools: [
358
+ # Light CPU work - auto-detect
359
+ { worker_class: FastWorker },
360
+
361
+ # Heavy memory work - limit to 2
362
+ { worker_class: MemoryHeavyWorker, num_workers: 2 },
363
+
364
+ # I/O work - 2x CPUs
365
+ { worker_class: IoWorker, num_workers: Etc.nprocessors * 2 }
366
+ ]
367
+ )
368
+ ----
369
+
370
+ == Best Practices
371
+
372
+ === Development
373
+
374
+ Use auto-detection for development:
375
+
376
+ [source,ruby]
377
+ ----
378
+ # Good for development - adapts to developer's machine
379
+ supervisor = Fractor::Supervisor.new(
380
+ worker_pools: [{ worker_class: MyWorker }]
381
+ )
382
+ ----
383
+
384
+ === Production
385
+
386
+ Start with auto-detection, then tune based on monitoring:
387
+
388
+ [source,ruby]
389
+ ----
390
+ # Production approach
391
+ num_workers = ENV.fetch('NUM_WORKERS', Etc.nprocessors).to_i
392
+
393
+ supervisor = Fractor::Supervisor.new(
394
+ worker_pools: [
395
+ { worker_class: MyWorker, num_workers: num_workers }
396
+ ]
397
+ )
398
+ ----
399
+
400
+ This allows:
401
+ - Auto-detection by default
402
+ - Environment-based override for production tuning
403
+ - Easy testing of different worker counts
404
+
405
+ === Container Environments
406
+
407
+ In containers, consider CPU quotas:
408
+
409
+ [source,ruby]
410
+ ----
411
+ # Respect container CPU limits
412
+ def optimal_workers
413
+ # Auto-detection respects container quotas
414
+ Etc.nprocessors
415
+ end
416
+
417
+ supervisor = Fractor::Supervisor.new(
418
+ worker_pools: [
419
+ { worker_class: MyWorker, num_workers: optimal_workers }
420
+ ]
421
+ )
422
+ ----
38
423
 
39
- 1. Display the number of processors detected on your system
40
- 2. Run three examples:
41
- * Example 1: Auto-detection (uses all available processors)
42
- * Example 2: Explicit configuration (uses exactly 4 workers)
43
- * Example 3: Mixed configuration (combines both approaches)
44
- 3. Show results from processing work items in parallel
45
- 4. Provide a summary of benefits for each approach
424
+ == Next Steps
46
425
 
47
- == Key Takeaways
426
+ After understanding auto-detection, explore:
48
427
 
49
- * Auto-detection provides automatic adaptation to different environments
50
- * Explicit configuration provides precise control when needed
51
- * You can mix both approaches in the same supervisor
52
- * Best practice: use auto-detection for development, tune for production if needed
428
+ * link:../simple/README.adoc[Simple Example] - Basic Fractor concepts
429
+ * link:../specialized_workers/README.adoc[Specialized Workers] - Multiple worker pools with different configurations
430
+ * link:../multi_work_type/README.adoc[Multi Work Type] - Handling different work types efficiently
@@ -41,7 +41,7 @@ module ContinuousChat
41
41
  def self.parse_packet(json_string)
42
42
  data = JSON.parse(json_string)
43
43
  type = data["type"]&.to_sym
44
- content = data["data"]
44
+ content = data["data"] || {}
45
45
  timestamp = data["timestamp"] || Time.now.to_i
46
46
 
47
47
  MessagePacket.new(type, content, timestamp)
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../lib/fractor"
4
+ require_relative "../lib/fractor/error_reporter"
5
+
6
+ # Example demonstrating comprehensive error reporting and analytics
7
+ #
8
+ # This example shows how to:
9
+ # 1. Set up an ErrorReporter to track errors
10
+ # 2. Record successes and failures
11
+ # 3. Generate comprehensive error reports
12
+ # 4. Export metrics to Prometheus format
13
+ # 5. Set up error handlers for real-time notifications
14
+
15
+ # Simulate various types of workers
16
+ class NetworkWorker < Fractor::Worker
17
+ def process(work)
18
+ # Simulate network errors
19
+ if work.input[:fail]
20
+ Fractor::WorkResult.new(
21
+ error: SocketError.new("Connection refused"),
22
+ error_code: :connection_refused,
23
+ error_context: { endpoint: work.input[:endpoint], attempt: 1 },
24
+ work: work,
25
+ )
26
+ else
27
+ Fractor::WorkResult.new(result: "Data fetched", work: work)
28
+ end
29
+ end
30
+ end
31
+
32
+ class ValidationWorker < Fractor::Worker
33
+ def process(work)
34
+ # Simulate validation errors
35
+ if work.input[:invalid]
36
+ Fractor::WorkResult.new(
37
+ error: ArgumentError.new("Invalid input"),
38
+ error_code: :validation_failed,
39
+ error_context: { field: "email", value: work.input[:value] },
40
+ work: work,
41
+ )
42
+ else
43
+ Fractor::WorkResult.new(result: "Valid", work: work)
44
+ end
45
+ end
46
+ end
47
+
48
+ class CriticalWorker < Fractor::Worker
49
+ def process(work)
50
+ # Simulate critical errors
51
+ if work.input[:critical]
52
+ Fractor::WorkResult.new(
53
+ error: SystemStackError.new("Stack overflow"),
54
+ error_severity: :critical,
55
+ error_context: { stack_depth: 10000 },
56
+ work: work,
57
+ )
58
+ else
59
+ Fractor::WorkResult.new(result: "Success", work: work)
60
+ end
61
+ end
62
+ end
63
+
64
+ puts "=" * 80
65
+ puts "Error Reporting Example"
66
+ puts "=" * 80
67
+ puts ""
68
+
69
+ # Initialize error reporter
70
+ reporter = Fractor::ErrorReporter.new
71
+
72
+ # Set up error handler for real-time notifications
73
+ reporter.on_error do |work_result, job_name|
74
+ if work_result.critical?
75
+ puts "🚨 CRITICAL ERROR DETECTED!"
76
+ puts " Job: #{job_name || 'unknown'}"
77
+ puts " Error: #{work_result.error.message}"
78
+ puts ""
79
+ end
80
+ end
81
+
82
+ puts "1. Recording various work results..."
83
+ puts "-" * 80
84
+
85
+ # Simulate successful work
86
+ 5.times do |i|
87
+ work = Fractor::Work.new({ endpoint: "api.example.com", id: i })
88
+ result = NetworkWorker.new.process(work)
89
+ reporter.record(result, job_name: "network_job")
90
+ end
91
+ puts "✓ Recorded 5 successful network operations"
92
+
93
+ # Simulate network errors
94
+ 3.times do |i|
95
+ work = Fractor::Work.new({ fail: true, endpoint: "api.example.com", id: i })
96
+ result = NetworkWorker.new.process(work)
97
+ reporter.record(result, job_name: "network_job")
98
+ end
99
+ puts "✗ Recorded 3 network errors"
100
+
101
+ # Simulate validation errors
102
+ 4.times do |i|
103
+ work = Fractor::Work.new({ invalid: true, value: "bad-email-#{i}" })
104
+ result = ValidationWorker.new.process(work)
105
+ reporter.record(result, job_name: "validation_job")
106
+ end
107
+ puts "✗ Recorded 4 validation errors"
108
+
109
+ # Simulate critical error
110
+ work = Fractor::Work.new({ critical: true })
111
+ result = CriticalWorker.new.process(work)
112
+ reporter.record(result, job_name: "critical_job")
113
+ puts "🚨 Recorded 1 critical error"
114
+
115
+ # Add some successful validations
116
+ 3.times do |i|
117
+ work = Fractor::Work.new({ valid: true, value: "good-email-#{i}@example.com" })
118
+ result = ValidationWorker.new.process(work)
119
+ reporter.record(result, job_name: "validation_job")
120
+ end
121
+ puts "✓ Recorded 3 successful validations"
122
+
123
+ puts ""
124
+ puts "2. Overall Statistics"
125
+ puts "-" * 80
126
+ puts "Total Successes: #{reporter.total_successes}"
127
+ puts "Total Errors: #{reporter.total_errors}"
128
+ puts "Error Rate: #{reporter.overall_error_rate}%"
129
+ puts ""
130
+
131
+ puts "3. Top Error Categories"
132
+ puts "-" * 80
133
+ reporter.top_categories.each do |category, count|
134
+ puts "#{category.to_s.ljust(15)}: #{count} errors"
135
+ end
136
+ puts ""
137
+
138
+ puts "4. Top Error Jobs"
139
+ puts "-" * 80
140
+ reporter.top_jobs.each do |job, count|
141
+ puts "#{job.to_s.ljust(20)}: #{count} errors"
142
+ end
143
+ puts ""
144
+
145
+ puts "5. Critical Errors"
146
+ puts "-" * 80
147
+ critical = reporter.critical_errors
148
+ if critical.empty?
149
+ puts "No critical errors"
150
+ else
151
+ critical.each do |error_info|
152
+ puts "Category: #{error_info[:category]}"
153
+ puts "Count: #{error_info[:count]}"
154
+ puts "Recent:"
155
+ error_info[:recent].each do |err|
156
+ puts " - #{err[:error_class]}: #{err[:error_message]}"
157
+ end
158
+ end
159
+ end
160
+ puts ""
161
+
162
+ puts "6. Errors by Severity"
163
+ puts "-" * 80
164
+ reporter.errors_by_severity.each do |severity, count|
165
+ icon = case severity
166
+ when :critical then "🚨"
167
+ when :error then "❌"
168
+ when :warning then "âš ī¸"
169
+ else "â„šī¸"
170
+ end
171
+ puts "#{icon} #{severity.to_s.ljust(10)}: #{count}"
172
+ end
173
+ puts ""
174
+
175
+ puts "7. Job-Specific Statistics"
176
+ puts "-" * 80
177
+ reporter.top_jobs.each_key do |job_name|
178
+ stats = reporter.job_stats(job_name)
179
+ puts "Job: #{job_name}"
180
+ puts " Total Errors: #{stats[:total_count]}"
181
+ puts " Error Rate: #{stats[:error_rate]}/s"
182
+ puts " Most Common Code: #{stats[:most_common_code]}"
183
+ puts " Highest Severity: #{stats[:highest_severity]}"
184
+ puts " Trend: #{stats[:trending]}"
185
+ puts ""
186
+ end
187
+
188
+ puts "8. Formatted Text Report"
189
+ puts "-" * 80
190
+ puts ""
191
+ puts reporter.formatted_report
192
+ puts ""
193
+
194
+ puts "9. Prometheus Metrics Export"
195
+ puts "-" * 80
196
+ puts reporter.to_prometheus
197
+ puts ""
198
+
199
+ puts "10. JSON Export"
200
+ puts "-" * 80
201
+ require "json"
202
+ puts JSON.pretty_generate(reporter.report)
203
+ puts ""
204
+
205
+ puts "=" * 80
206
+ puts "Example completed successfully!"
207
+ puts "=" * 80