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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +227 -102
- data/README.adoc +113 -1940
- data/docs/.lycheeignore +16 -0
- data/docs/Gemfile +24 -0
- data/docs/README.md +157 -0
- data/docs/_config.yml +151 -0
- data/docs/_features/error-handling.adoc +1192 -0
- data/docs/_features/index.adoc +80 -0
- data/docs/_features/monitoring.adoc +589 -0
- data/docs/_features/signal-handling.adoc +202 -0
- data/docs/_features/workflows.adoc +1235 -0
- data/docs/_guides/continuous-mode.adoc +736 -0
- data/docs/_guides/cookbook.adoc +1133 -0
- data/docs/_guides/index.adoc +55 -0
- data/docs/_guides/pipeline-mode.adoc +730 -0
- data/docs/_guides/troubleshooting.adoc +358 -0
- data/docs/_pages/architecture.adoc +1390 -0
- data/docs/_pages/core-concepts.adoc +1392 -0
- data/docs/_pages/design-principles.adoc +862 -0
- data/docs/_pages/getting-started.adoc +290 -0
- data/docs/_pages/installation.adoc +143 -0
- data/docs/_reference/api.adoc +1080 -0
- data/docs/_reference/error-reporting.adoc +670 -0
- data/docs/_reference/examples.adoc +181 -0
- data/docs/_reference/index.adoc +96 -0
- data/docs/_reference/troubleshooting.adoc +862 -0
- data/docs/_tutorials/complex-workflows.adoc +1022 -0
- data/docs/_tutorials/data-processing-pipeline.adoc +740 -0
- data/docs/_tutorials/first-application.adoc +384 -0
- data/docs/_tutorials/index.adoc +48 -0
- data/docs/_tutorials/long-running-services.adoc +931 -0
- data/docs/assets/images/favicon-16.png +0 -0
- data/docs/assets/images/favicon-32.png +0 -0
- data/docs/assets/images/favicon-48.png +0 -0
- data/docs/assets/images/favicon.ico +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/images/favicon.svg +45 -0
- data/docs/assets/images/fractor-icon.svg +49 -0
- data/docs/assets/images/fractor-logo.svg +61 -0
- data/docs/index.adoc +131 -0
- data/docs/lychee.toml +39 -0
- data/examples/api_aggregator/README.adoc +627 -0
- data/examples/api_aggregator/api_aggregator.rb +376 -0
- data/examples/auto_detection/README.adoc +407 -29
- data/examples/continuous_chat_common/message_protocol.rb +1 -1
- data/examples/error_reporting.rb +207 -0
- data/examples/file_processor/README.adoc +170 -0
- data/examples/file_processor/file_processor.rb +615 -0
- data/examples/file_processor/sample_files/invalid.csv +1 -0
- data/examples/file_processor/sample_files/orders.xml +24 -0
- data/examples/file_processor/sample_files/products.json +23 -0
- data/examples/file_processor/sample_files/users.csv +6 -0
- data/examples/hierarchical_hasher/README.adoc +629 -41
- data/examples/image_processor/README.adoc +610 -0
- data/examples/image_processor/image_processor.rb +349 -0
- data/examples/image_processor/processed_images/sample_10_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_1_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_2_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_3_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_4_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_5_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_6_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_7_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_8_processed.jpg.json +12 -0
- data/examples/image_processor/processed_images/sample_9_processed.jpg.json +12 -0
- data/examples/image_processor/test_images/sample_1.png +1 -0
- data/examples/image_processor/test_images/sample_10.png +1 -0
- data/examples/image_processor/test_images/sample_2.png +1 -0
- data/examples/image_processor/test_images/sample_3.png +1 -0
- data/examples/image_processor/test_images/sample_4.png +1 -0
- data/examples/image_processor/test_images/sample_5.png +1 -0
- data/examples/image_processor/test_images/sample_6.png +1 -0
- data/examples/image_processor/test_images/sample_7.png +1 -0
- data/examples/image_processor/test_images/sample_8.png +1 -0
- data/examples/image_processor/test_images/sample_9.png +1 -0
- data/examples/log_analyzer/README.adoc +662 -0
- data/examples/log_analyzer/log_analyzer.rb +579 -0
- data/examples/log_analyzer/sample_logs/apache.log +20 -0
- data/examples/log_analyzer/sample_logs/json.log +15 -0
- data/examples/log_analyzer/sample_logs/nginx.log +15 -0
- data/examples/log_analyzer/sample_logs/rails.log +29 -0
- data/examples/multi_work_type/README.adoc +576 -26
- data/examples/performance_monitoring.rb +120 -0
- data/examples/pipeline_processing/README.adoc +740 -26
- data/examples/pipeline_processing/pipeline_processing.rb +2 -2
- data/examples/priority_work_example.rb +155 -0
- data/examples/producer_subscriber/README.adoc +889 -46
- data/examples/scatter_gather/README.adoc +829 -27
- data/examples/simple/README.adoc +347 -0
- data/examples/specialized_workers/README.adoc +622 -26
- data/examples/specialized_workers/specialized_workers.rb +44 -8
- data/examples/stream_processor/README.adoc +206 -0
- data/examples/stream_processor/stream_processor.rb +284 -0
- data/examples/web_scraper/README.adoc +625 -0
- data/examples/web_scraper/web_scraper.rb +285 -0
- data/examples/workflow/README.adoc +406 -0
- data/examples/workflow/circuit_breaker/README.adoc +360 -0
- data/examples/workflow/circuit_breaker/circuit_breaker_workflow.rb +225 -0
- data/examples/workflow/conditional/README.adoc +483 -0
- data/examples/workflow/conditional/conditional_workflow.rb +215 -0
- data/examples/workflow/dead_letter_queue/README.adoc +374 -0
- data/examples/workflow/dead_letter_queue/dead_letter_queue_workflow.rb +217 -0
- data/examples/workflow/fan_out/README.adoc +381 -0
- data/examples/workflow/fan_out/fan_out_workflow.rb +202 -0
- data/examples/workflow/retry/README.adoc +248 -0
- data/examples/workflow/retry/retry_workflow.rb +195 -0
- data/examples/workflow/simple_linear/README.adoc +267 -0
- data/examples/workflow/simple_linear/simple_linear_workflow.rb +175 -0
- data/examples/workflow/simplified/README.adoc +329 -0
- data/examples/workflow/simplified/simplified_workflow.rb +222 -0
- data/exe/fractor +10 -0
- data/lib/fractor/cli.rb +288 -0
- data/lib/fractor/configuration.rb +307 -0
- data/lib/fractor/continuous_server.rb +60 -65
- data/lib/fractor/error_formatter.rb +72 -0
- data/lib/fractor/error_report_generator.rb +152 -0
- data/lib/fractor/error_reporter.rb +244 -0
- data/lib/fractor/error_statistics.rb +147 -0
- data/lib/fractor/execution_tracer.rb +162 -0
- data/lib/fractor/logger.rb +230 -0
- data/lib/fractor/main_loop_handler.rb +406 -0
- data/lib/fractor/main_loop_handler3.rb +135 -0
- data/lib/fractor/main_loop_handler4.rb +299 -0
- data/lib/fractor/performance_metrics_collector.rb +181 -0
- data/lib/fractor/performance_monitor.rb +215 -0
- data/lib/fractor/performance_report_generator.rb +202 -0
- data/lib/fractor/priority_work.rb +93 -0
- data/lib/fractor/priority_work_queue.rb +189 -0
- data/lib/fractor/result_aggregator.rb +32 -0
- data/lib/fractor/shutdown_handler.rb +168 -0
- data/lib/fractor/signal_handler.rb +80 -0
- data/lib/fractor/supervisor.rb +382 -269
- data/lib/fractor/supervisor_logger.rb +88 -0
- data/lib/fractor/version.rb +1 -1
- data/lib/fractor/work.rb +12 -0
- data/lib/fractor/work_distribution_manager.rb +151 -0
- data/lib/fractor/work_queue.rb +20 -0
- data/lib/fractor/work_result.rb +181 -9
- data/lib/fractor/worker.rb +73 -0
- data/lib/fractor/workflow/builder.rb +210 -0
- data/lib/fractor/workflow/chain_builder.rb +169 -0
- data/lib/fractor/workflow/circuit_breaker.rb +183 -0
- data/lib/fractor/workflow/circuit_breaker_orchestrator.rb +208 -0
- data/lib/fractor/workflow/circuit_breaker_registry.rb +112 -0
- data/lib/fractor/workflow/dead_letter_queue.rb +334 -0
- data/lib/fractor/workflow/execution_hooks.rb +39 -0
- data/lib/fractor/workflow/execution_strategy.rb +225 -0
- data/lib/fractor/workflow/execution_trace.rb +134 -0
- data/lib/fractor/workflow/helpers.rb +191 -0
- data/lib/fractor/workflow/job.rb +290 -0
- data/lib/fractor/workflow/job_dependency_validator.rb +120 -0
- data/lib/fractor/workflow/logger.rb +110 -0
- data/lib/fractor/workflow/pre_execution_context.rb +193 -0
- data/lib/fractor/workflow/retry_config.rb +156 -0
- data/lib/fractor/workflow/retry_orchestrator.rb +184 -0
- data/lib/fractor/workflow/retry_strategy.rb +93 -0
- data/lib/fractor/workflow/structured_logger.rb +30 -0
- data/lib/fractor/workflow/type_compatibility_validator.rb +222 -0
- data/lib/fractor/workflow/visualizer.rb +211 -0
- data/lib/fractor/workflow/workflow_context.rb +132 -0
- data/lib/fractor/workflow/workflow_executor.rb +669 -0
- data/lib/fractor/workflow/workflow_result.rb +55 -0
- data/lib/fractor/workflow/workflow_validator.rb +295 -0
- data/lib/fractor/workflow.rb +333 -0
- data/lib/fractor/wrapped_ractor.rb +66 -101
- data/lib/fractor/wrapped_ractor3.rb +161 -0
- data/lib/fractor/wrapped_ractor4.rb +242 -0
- data/lib/fractor.rb +92 -4
- metadata +179 -6
- data/tests/sample.rb.bak +0 -309
- 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
|
-
|
|
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
|
-
|
|
9
|
+
This example focuses on demonstrating:
|
|
10
10
|
|
|
11
|
-
*
|
|
11
|
+
* Automatic detection of available processors using `Etc.nprocessors`
|
|
12
12
|
* Comparison between auto-detection and explicit worker configuration
|
|
13
|
-
* Mixed configuration (
|
|
14
|
-
*
|
|
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
|
-
|
|
63
|
+
Auto-Detection:
|
|
64
|
+
4 workers created 16 workers created
|
|
65
|
+
100% CPU utilization 100% CPU utilization
|
|
66
|
+
â Optimal â Optimal
|
|
17
67
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
426
|
+
After understanding auto-detection, explore:
|
|
48
427
|
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
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
|