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.
- checksums.yaml +4 -4
- data/.rubocop-https---raw-githubusercontent-com-riboseinc-oss-guides-main-ci-rubocop-yml +552 -0
- data/.rubocop.yml +14 -8
- data/.rubocop_todo.yml +284 -43
- data/README.adoc +111 -950
- 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/auto_detection/auto_detection.rb +9 -9
- data/examples/continuous_chat_common/message_protocol.rb +53 -0
- data/examples/continuous_chat_fractor/README.adoc +217 -0
- data/examples/continuous_chat_fractor/chat_client.rb +303 -0
- data/examples/continuous_chat_fractor/chat_common.rb +83 -0
- data/examples/continuous_chat_fractor/chat_server.rb +167 -0
- data/examples/continuous_chat_fractor/simulate.rb +345 -0
- data/examples/continuous_chat_server/README.adoc +135 -0
- data/examples/continuous_chat_server/chat_client.rb +303 -0
- data/examples/continuous_chat_server/chat_server.rb +359 -0
- data/examples/continuous_chat_server/simulate.rb +343 -0
- 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/hierarchical_hasher/hierarchical_hasher.rb +12 -8
- 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/multi_work_type/multi_work_type.rb +30 -29
- data/examples/performance_monitoring.rb +120 -0
- data/examples/pipeline_processing/README.adoc +740 -26
- data/examples/pipeline_processing/pipeline_processing.rb +16 -16
- data/examples/priority_work_example.rb +155 -0
- data/examples/producer_subscriber/README.adoc +889 -46
- data/examples/producer_subscriber/producer_subscriber.rb +20 -16
- data/examples/scatter_gather/README.adoc +829 -27
- data/examples/scatter_gather/scatter_gather.rb +29 -28
- data/examples/simple/README.adoc +347 -0
- data/examples/simple/sample.rb +5 -5
- data/examples/specialized_workers/README.adoc +622 -26
- data/examples/specialized_workers/specialized_workers.rb +88 -45
- 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 +183 -0
- 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 +33 -1
- data/lib/fractor/shutdown_handler.rb +168 -0
- data/lib/fractor/signal_handler.rb +80 -0
- data/lib/fractor/supervisor.rb +430 -144
- 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 +88 -0
- data/lib/fractor/work_result.rb +181 -9
- data/lib/fractor/worker.rb +75 -1
- 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 -91
- data/lib/fractor/wrapped_ractor3.rb +161 -0
- data/lib/fractor/wrapped_ractor4.rb +242 -0
- data/lib/fractor.rb +93 -3
- metadata +192 -6
- data/tests/sample.rb.bak +0 -309
- data/tests/sample_working.rb.bak +0 -209
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require_relative "../../lib/fractor"
|
|
5
|
+
require "json"
|
|
6
|
+
require "time"
|
|
7
|
+
|
|
8
|
+
# Event data structure
|
|
9
|
+
class Event < Fractor::Work
|
|
10
|
+
def initialize(type:, data:, timestamp: Time.now)
|
|
11
|
+
super({
|
|
12
|
+
type: type,
|
|
13
|
+
data: data,
|
|
14
|
+
timestamp: timestamp
|
|
15
|
+
})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def type
|
|
19
|
+
input[:type]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def data
|
|
23
|
+
input[:data]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def timestamp
|
|
27
|
+
input[:timestamp]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_s
|
|
31
|
+
"Event(#{type}, #{timestamp.iso8601})"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Worker for processing events
|
|
36
|
+
class EventProcessorWorker < Fractor::Worker
|
|
37
|
+
def process(work)
|
|
38
|
+
return nil unless work.is_a?(Event)
|
|
39
|
+
|
|
40
|
+
# Process event
|
|
41
|
+
{
|
|
42
|
+
type: work.type,
|
|
43
|
+
data: work.data,
|
|
44
|
+
timestamp: work.timestamp,
|
|
45
|
+
processed_at: Time.now,
|
|
46
|
+
processing_time: (Time.now - work.timestamp) * 1000 # ms
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Real-time stream processor (simplified for testing)
|
|
52
|
+
class StreamProcessor
|
|
53
|
+
attr_reader :processed_count, :error_count, :window_size, :metrics
|
|
54
|
+
|
|
55
|
+
def initialize(window_size: 5, num_workers: 4)
|
|
56
|
+
@window_size = window_size
|
|
57
|
+
@num_workers = num_workers
|
|
58
|
+
@processed_count = 0
|
|
59
|
+
@error_count = 0
|
|
60
|
+
@events_in_window = []
|
|
61
|
+
@metrics = {
|
|
62
|
+
total_events: 0,
|
|
63
|
+
events_per_second: 0.0,
|
|
64
|
+
average_processing_time: 0.0,
|
|
65
|
+
current_window_count: 0
|
|
66
|
+
}
|
|
67
|
+
@start_time = Time.now
|
|
68
|
+
@mutex = Mutex.new
|
|
69
|
+
@supervisor = nil
|
|
70
|
+
@running = false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def start
|
|
74
|
+
puts "Starting Stream Processor..."
|
|
75
|
+
puts "Window size: #{@window_size} seconds"
|
|
76
|
+
puts "Workers: #{@num_workers}"
|
|
77
|
+
puts
|
|
78
|
+
|
|
79
|
+
@supervisor = Fractor::Supervisor.new(
|
|
80
|
+
worker_pools: [
|
|
81
|
+
{ worker_class: EventProcessorWorker, num_workers: @num_workers }
|
|
82
|
+
]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@running = true
|
|
86
|
+
@start_time = Time.now
|
|
87
|
+
|
|
88
|
+
self
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def add_event(event)
|
|
92
|
+
return unless @supervisor && @running
|
|
93
|
+
|
|
94
|
+
@supervisor.add_work_item(event)
|
|
95
|
+
|
|
96
|
+
@mutex.synchronize do
|
|
97
|
+
@metrics[:total_events] += 1
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def process_events
|
|
102
|
+
return unless @supervisor && @running
|
|
103
|
+
|
|
104
|
+
@supervisor.run
|
|
105
|
+
|
|
106
|
+
results_obj = @supervisor.results
|
|
107
|
+
all_results = results_obj.results + results_obj.errors
|
|
108
|
+
|
|
109
|
+
all_results.each do |work_result|
|
|
110
|
+
result = work_result.respond_to?(:result) ? work_result.result : work_result
|
|
111
|
+
|
|
112
|
+
next unless result.is_a?(Hash)
|
|
113
|
+
|
|
114
|
+
process_result(result)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def stop
|
|
119
|
+
puts "\nStopping Stream Processor..."
|
|
120
|
+
@running = false
|
|
121
|
+
print_final_summary
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
private
|
|
125
|
+
|
|
126
|
+
def process_result(result)
|
|
127
|
+
@mutex.synchronize do
|
|
128
|
+
@processed_count += 1
|
|
129
|
+
|
|
130
|
+
# Add to current window
|
|
131
|
+
@events_in_window << result
|
|
132
|
+
|
|
133
|
+
# Remove events outside window
|
|
134
|
+
cutoff_time = Time.now - @window_size
|
|
135
|
+
@events_in_window.reject! do |r|
|
|
136
|
+
r[:processed_at] < cutoff_time
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Update metrics
|
|
140
|
+
@metrics[:current_window_count] = @events_in_window.size
|
|
141
|
+
|
|
142
|
+
if @processed_count > 0
|
|
143
|
+
elapsed = Time.now - @start_time
|
|
144
|
+
@metrics[:events_per_second] = @processed_count / elapsed
|
|
145
|
+
|
|
146
|
+
total_time = @events_in_window.sum { |r| r[:processing_time] }
|
|
147
|
+
@metrics[:average_processing_time] =
|
|
148
|
+
@events_in_window.empty? ? 0.0 : total_time / @events_in_window.size
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def print_metrics
|
|
154
|
+
@mutex.synchronize do
|
|
155
|
+
print "\r"
|
|
156
|
+
print "Events: #{@metrics[:total_events]} | "
|
|
157
|
+
print "Processed: #{@processed_count} | "
|
|
158
|
+
print "Rate: #{@metrics[:events_per_second].round(2)} e/s | "
|
|
159
|
+
print "Window: #{@metrics[:current_window_count]} | "
|
|
160
|
+
print "Avg Time: #{@metrics[:average_processing_time].round(2)} ms"
|
|
161
|
+
$stdout.flush
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def print_final_summary
|
|
166
|
+
puts "\n"
|
|
167
|
+
puts "=" * 60
|
|
168
|
+
puts "FINAL SUMMARY"
|
|
169
|
+
puts "=" * 60
|
|
170
|
+
puts format("Total Events: %d", @metrics[:total_events])
|
|
171
|
+
puts format("Processed: %d", @processed_count)
|
|
172
|
+
puts format("Errors: %d", @error_count)
|
|
173
|
+
elapsed = Time.now - @start_time
|
|
174
|
+
puts format("Duration: %.2f seconds", elapsed)
|
|
175
|
+
rate = @processed_count > 0 ? @processed_count / elapsed : 0.0
|
|
176
|
+
puts format("Average Rate: %.2f events/second", rate)
|
|
177
|
+
puts "=" * 60
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Event generator for testing
|
|
182
|
+
class EventGenerator
|
|
183
|
+
def self.generate_stream(processor, duration: 10, rate: 10)
|
|
184
|
+
puts "Generating event stream for #{duration} seconds at #{rate} events/second..."
|
|
185
|
+
|
|
186
|
+
start_time = Time.now
|
|
187
|
+
event_count = 0
|
|
188
|
+
|
|
189
|
+
while (Time.now - start_time) < duration
|
|
190
|
+
event = Event.new(
|
|
191
|
+
type: [:click, :view, :purchase, :signup].sample,
|
|
192
|
+
data: {
|
|
193
|
+
user_id: rand(1..1000),
|
|
194
|
+
value: rand(1.0..100.0).round(2)
|
|
195
|
+
}
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
processor.add_event(event)
|
|
199
|
+
event_count += 1
|
|
200
|
+
|
|
201
|
+
# Control rate
|
|
202
|
+
sleep(1.0 / rate)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
puts "\nGenerated #{event_count} events"
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
# Run example if executed directly
|
|
210
|
+
if __FILE__ == $PROGRAM_NAME
|
|
211
|
+
require "optparse"
|
|
212
|
+
|
|
213
|
+
options = {
|
|
214
|
+
workers: 4,
|
|
215
|
+
window_size: 5,
|
|
216
|
+
duration: 30,
|
|
217
|
+
rate: 10
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
OptionParser.new do |opts|
|
|
221
|
+
opts.banner = "Usage: stream_processor.rb [options]"
|
|
222
|
+
|
|
223
|
+
opts.on("-w", "--workers NUM", Integer, "Number of workers (default: 4)") do |n|
|
|
224
|
+
options[:workers] = n
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
opts.on("--window SIZE", Integer, "Window size in seconds (default: 5)") do |s|
|
|
228
|
+
options[:window_size] = s
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
opts.on("-d", "--duration SECONDS", Integer, "Test duration (default: 30)") do |d|
|
|
232
|
+
options[:duration] = d
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
opts.on("-r", "--rate NUM", Integer, "Events per second (default: 10)") do |r|
|
|
236
|
+
options[:rate] = r
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
opts.on("-h", "--help", "Show this message") do
|
|
240
|
+
puts opts
|
|
241
|
+
exit
|
|
242
|
+
end
|
|
243
|
+
end.parse!
|
|
244
|
+
|
|
245
|
+
# Create processor
|
|
246
|
+
processor = StreamProcessor.new(
|
|
247
|
+
window_size: options[:window_size],
|
|
248
|
+
num_workers: options[:workers]
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
processor.start
|
|
252
|
+
|
|
253
|
+
# Handle graceful shutdown
|
|
254
|
+
trap("INT") do
|
|
255
|
+
processor.stop
|
|
256
|
+
exit
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# Generate test events in background
|
|
260
|
+
generator_thread = Thread.new do
|
|
261
|
+
EventGenerator.generate_stream(
|
|
262
|
+
processor,
|
|
263
|
+
duration: options[:duration],
|
|
264
|
+
rate: options[:rate]
|
|
265
|
+
)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
# Process events periodically
|
|
269
|
+
metrics_thread = Thread.new do
|
|
270
|
+
while processor.instance_variable_get(:@running)
|
|
271
|
+
sleep(1)
|
|
272
|
+
processor.send(:print_metrics)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Wait for generation to complete
|
|
277
|
+
generator_thread.join
|
|
278
|
+
|
|
279
|
+
# Process remaining events
|
|
280
|
+
processor.process_events
|
|
281
|
+
|
|
282
|
+
metrics_thread.kill
|
|
283
|
+
processor.stop
|
|
284
|
+
end
|