fbp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,91 @@
1
+ require "thread-pool/thread-pool.rb"
2
+
3
+ module Fbp
4
+
5
+ =begin rdoc
6
+ === Description
7
+ The Node pool class is built upon Kim Burgestrand Pool class which provides thread pool management
8
+ for Ruby. The Node pool provides a standard set of module functions that allow for setting the
9
+ number of threads in the Pool and to shutdown the Pool.
10
+ =end
11
+ class Node_pool #:nodoc:
12
+
13
+ attr_reader :pool #:nodoc:
14
+ attr_reader :num_threads #:nodoc:
15
+
16
+ def initialize (num_threads = 10) #:nodoc:
17
+ @num_threads = num_threads
18
+ @pool = nil
19
+ end
20
+
21
+ def make_pool #:nodoc:
22
+ return false if !@pool.nil?
23
+ @pool = Pool.new(@num_threads)
24
+ end
25
+
26
+ def num_threads=(num_threads) #:nodoc:
27
+ @num_threads = num_threads if @pool.nil?
28
+ end
29
+
30
+ def shutdown #:nodoc:
31
+ @pool.shutdown if !@pool.nil?
32
+ @pool = nil
33
+ end
34
+
35
+ end
36
+
37
+ @@node_pool = Node_pool.new #:nodoc:
38
+
39
+ =begin rdoc
40
+ The num_threads= sets the number of threads that will be used by the thread pool
41
+ that will be used for any Fbp network. The default value is 10 threads. This
42
+ function <b>only</b> works <b>before></b> calling Fbp::make_pool. After that this
43
+ function will do nothing
44
+ === Parameters
45
+ +num_threads+ - The number of threads that should be used in the Thread Pool
46
+ =end
47
+ public
48
+ def self.num_threads= (num_threads)
49
+ @@node_pool.num_threads = num_threads
50
+ end
51
+
52
+ =begin
53
+ Returns that number of threads that are in the Pool
54
+ =end
55
+ public
56
+ def self.num_threads
57
+ @@node_pool.num_threads
58
+ end
59
+
60
+ =begin
61
+ This function will create the thread pool that will be used by any Fbp network.
62
+ This function <b>must</b> be called before executing any Fbp network.
63
+ =end
64
+ public
65
+ def self.make_pool
66
+ @@node_pool.make_pool
67
+ end
68
+
69
+ =begin
70
+ This function will return back if Fbp::make_pool has been called and a Pool has
71
+ been created.
72
+ =end
73
+ public
74
+ def self.has_pool
75
+ !@@node_pool.pool.nil?
76
+ end
77
+
78
+ =begin
79
+ This function will stop all running threads in the Fbp pool. Calling this function
80
+ will kill all Fbp processing.
81
+ =end
82
+ public
83
+ def self.shutdown
84
+ !@@node_pool.shutdown
85
+ end
86
+
87
+ protected
88
+ def self.schedule(&block) #:nodoc:
89
+ @@node_pool.pool.schedule { block.call } if !@@node_pool.pool.nil?
90
+ end
91
+ end
@@ -0,0 +1,131 @@
1
+ require "fbp/constants"
2
+
3
+ module Fbp
4
+
5
+ =begin rdoc
6
+ <b>Selector_node</b>
7
+ === Description
8
+ The Selector node class provides for comparing the value(s) in an IP and with set value(s) and the
9
+ out come of the comparison will determine which output channel the IP will be sent to and thus splitting the
10
+ incoming stream to different downstream nodes.
11
+ ===
12
+ The Selector_node class requires an IIP of the form:
13
+
14
+ Selector
15
+ {:key => {:comparison => comparison, :value => value, :match => :output_name, :reject => :output_name}}
16
+
17
+ :key
18
+ This is the symbol that will be used to match against keys in an IP
19
+
20
+ :comparison
21
+ This is a constant define in constants.rb for comparison operations
22
+
23
+ :value
24
+ This is the value that will be used to check against the corresponding value in an IP
25
+
26
+ :match
27
+ The name of the output queue that will be written to for IPs that match the selector
28
+
29
+ :reject
30
+ The name of the output queue that will be written to for IPs that do not match the selector
31
+
32
+ There may be multiple selectors with different key values that will match different keys in an IP
33
+
34
+ When an IP arrives at a Selector_node, the do_node_work will match keys in the IP with keys in the
35
+ selectors hash. If there is a match, then the comparison stipulated in the selector will be done on
36
+ the value in the IP against the value in the selector. If the comparison is a match then the IP
37
+ will be written to the output named in the :match item otherwise it will be written to the output
38
+ named in the :reject item.
39
+
40
+ If an IP does not have any keys that match any selectors the IP will be written to the output named :output
41
+ =end
42
+ class Selector_node < Node
43
+ =begin rdoc
44
+ When creating a new Selector_node instance one can provide an
45
+ array of selector records.
46
+ =end
47
+ def initialize(selector = nil)
48
+ super()
49
+ @options[:selectors] = selector if !selector.nil?
50
+ end
51
+
52
+ =begin rdoc
53
+ Checks to see if this Selector_node instance has an array of Selector records.
54
+ If it does have an array of Selector records then true will be returned
55
+ otherwise false. A Selector_node instance will not execute until it has
56
+ an array of Selector records
57
+ =end
58
+ def is_ready_to_run?
59
+ @options.has_key? :selectors
60
+ end
61
+
62
+ =begin rdoc
63
+ Each IP that is presented for processing will be compared to the array of
64
+ selector records associated with this instance. The selector records
65
+ determine how the IP will be output. If there is a match, then the IP
66
+ will be written to the output channel defined for a match in the selector record.
67
+ If it does not match the the IP will be written to the output channel defined
68
+ for a reject in the selector record.
69
+
70
+ If the incoming IP does not have any of the keys defined in the selector
71
+ records the IP will be written to the output channel.
72
+ =end
73
+ def do_node_work(args)
74
+ return false if args.has_key? :completed
75
+ return true if args.has_key? :start
76
+ key_match = false
77
+
78
+ selectors = @options[:selectors]
79
+
80
+
81
+ multi_ip = args.has_key? :ips
82
+ ips = multi_ip ? args[:ips] : [args]
83
+ ips.each do |ip|
84
+
85
+ selectors.each do |selector_hash|
86
+
87
+ selector_hash.each do |key, h|
88
+ if ip.has_key? key
89
+ key_match = true
90
+ value = ip[key]
91
+ compare_value = h[:value]
92
+ comparision = h[:comparison]
93
+
94
+ pass = case comparision
95
+ when NOT_EQUAL_COMPARE
96
+ compare_value != value
97
+ when EQUAL_COMPARE
98
+ compare_value == value
99
+ when GREATER_THAN
100
+ value > compare_value
101
+ when GREATER_THAN_OR_EQUAL
102
+ value >= compare_value
103
+ when LESS_THAN
104
+ value < compare_value
105
+ when LESS_THAN_OR_EQUAL
106
+ value <= compare_value
107
+ when CONTAINS
108
+ value.to_s.include?(compare_value.to_s)
109
+ when DOES_NOT_CONTAINS
110
+ !value.to_s.include?(compare_value.to_s)
111
+ when STARTS_WITH
112
+ value.start_with?(compare_value.to_s)
113
+ when ENDS_WITH
114
+ value.end_with?(compare_value.to_s)
115
+ when MATCHES
116
+ !value.match(compare_value.to_s).nil?
117
+ else
118
+ false
119
+ end
120
+ output_name = pass ? h[:match] : h[:reject]
121
+ write_to_output(ip, output_name) if !output.nil?
122
+ end
123
+ end
124
+ end
125
+ end
126
+ write_to_output(args, :output) if !key_match
127
+ true
128
+ end
129
+ end
130
+
131
+ end
@@ -0,0 +1,68 @@
1
+ module Fbp
2
+ =begin
3
+ === Description
4
+ The Sort node will take its input IP and sort that data according to a set of hash keys that
5
+ specify which data should be sorted.
6
+
7
+ === Discussion
8
+ This implementation requires that all of the data to be sorted be available. This is NOT a scalable
9
+ solution.
10
+
11
+ I have read some of the literature on sorting data streams but currently I do not have a ready
12
+ solution. For now this will have to serve.
13
+ =end
14
+ class Sort_node < Node
15
+ =begin rdoc
16
+ When creating a new Sort_node instance one can provide an array of
17
+ symbols which would be used to match keys in an incoming IP. The
18
+ order of the keys in the array determine the sort order. The
19
+ first key in the array will be used to sort the data then the
20
+ subsequent keys if any.
21
+ =end
22
+ def initialize(sort_keys = nil)
23
+ super()
24
+ @options[:sort_keys] = sort_keys
25
+ write_to_input({:begin_transaction => true})
26
+ end
27
+ =begin rdoc
28
+ Checks to see if this Sort_node instance has any sort keys set.
29
+ True will be returned if any keys are set, false otherwise.
30
+ =end
31
+ def is_ready_to_run?
32
+ @options.has_key? :sort_keys
33
+ end
34
+
35
+ =begin rdoc
36
+ The Sort_node assumes that will will receive an IP with a :ips key
37
+ which contains all of the IPs that need to be sorted. If the :ips key
38
+ is not present then the IP is sent unchanged to the down stream node.
39
+
40
+ The IPs will be sorted based upon the sort_keys parameter. For each
41
+ sort_key all of the IPs will be sorted based upon the value in the IP
42
+ that matches the sort_key. If an IP does not have the sort_key then
43
+ that IP is excluded from the sort and is disregarded.
44
+
45
+ Once the IPs have been sorted the IP has its :ips value replaced by
46
+ the sorted data and the IP is then sent to the down stream node.
47
+ =end
48
+ def do_node_work(args)
49
+ args.delete :completed
50
+ ip_array = args[:ips]
51
+ return super(args) if ip_array.nil?
52
+
53
+ sort_keys_array = @options[:sort_keys].respond_to?('each') ? @options[:sort_keys] : [@options[:sort_keys]]
54
+
55
+ data_to_sort = Array.new
56
+
57
+ ip_array.each {|h| sort_keys_array.each {|k| data_to_sort << h if h.has_key?(k) && !data_to_sort.include?(h)}}
58
+ sort_keys_array.each {|k| data_to_sort.sort! {|a,b| a[k] <=> b[k]}}
59
+ data_to_sort.each {|a| write_to_output(a)}
60
+
61
+ # Update the input IP with the sorted list of IPs
62
+ args[:ips] = data_to_sort
63
+ super(args)
64
+ false
65
+ end
66
+ end
67
+
68
+ end
@@ -0,0 +1,80 @@
1
+ module Fbp
2
+ =begin
3
+ === Description
4
+ The Text file reader node takes in a file path as an IIP and will open that
5
+ file and read a line at a time and package that data as an IP and send that
6
+ data to the downstream node.
7
+
8
+ === Discussion
9
+ The Test_file_reader_node requires the file to be read exists before running this
10
+ node.
11
+ =end
12
+ class Test_file_reader_node < Node
13
+ =begin rdoc
14
+ When creating a new Test_file_reader_node instance one can provide
15
+ a string which is the full path to the file to be read.
16
+ =end
17
+ def initialize(file_name = nil)
18
+ super()
19
+ @options[:requires_input] = false
20
+ @options[:file_name] = file_name if !file_name.nil?
21
+ @file = nil
22
+ end
23
+
24
+ =begin rdoc
25
+ Checks to see if this Test_file_reader_node instance has
26
+ a file_name set. If so then true will be returned otherwise
27
+ false.
28
+ =end
29
+ def is_ready_to_run?()
30
+ @options.has_key? :file_name
31
+ end
32
+
33
+ =begin
34
+ Once this instance is executed, it will open the file
35
+ specified in the :file_name option and read a line
36
+ at a time. A line is defined as a series of characters
37
+ up to a return character.
38
+
39
+ Once a line is read, an IP is made with that data
40
+ keys with :data and sent to the down stream node.
41
+
42
+ When the EOF is reached the file will be closed.
43
+ =end
44
+ def do_node_work(args)
45
+
46
+ if args.has_key? :completed
47
+ @file.close if !@file.nil?
48
+ @file = nil
49
+ return false
50
+ end
51
+
52
+ if args.has_key? :continue
53
+
54
+ file_line = nil
55
+ close_file = false
56
+ @file = File.new(@options[:file_name], 'r') if @file.nil?
57
+
58
+
59
+ begin
60
+ file_line = @file.gets
61
+ close_file = true if file_line.nil?
62
+ close_file = true if @file.eof?
63
+ rescue
64
+ close_file = true
65
+ end
66
+
67
+ write_to_output({:data => file_line}) if !file_line.nil?
68
+
69
+ if close_file
70
+ @file.close
71
+ @file = nil
72
+ return false
73
+ end
74
+ end
75
+
76
+ true
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,65 @@
1
+ module Fbp
2
+ =begin
3
+ === Description
4
+ The Text file writer node takes in a file path as an IIP and will open that
5
+ file and will take in IPs and write the value of the :data item in the IP to
6
+ the file.
7
+
8
+ == Discussion
9
+ The Text_file_writer_node does allow for setting the file open mode by having
10
+ an IIP with a :file_open_mode key specifying the open mode. Setting this value
11
+ using an IIP allows for setting the open mode to a+ so appending instead of the
12
+ the default which to overwrite the file using "w"
13
+ =end
14
+ class Text_file_writer_node < Node
15
+ =begin rdoc
16
+ When creating a new Text_file_writer_node instance one can provide
17
+ a string which is the full path to the file to be written.
18
+ =end
19
+ def initialize(file_name = nil)
20
+ super()
21
+ @options[:file_name] = file_name if !file_name.nil?
22
+ @options[:file_open_mode] = 'w'
23
+ @file = nil
24
+ end
25
+ =begin rdoc
26
+ Checks to see if this Text_file_writer_node instance has
27
+ a file_name and file_open_mode set. If so then true will be
28
+ returned otherwise false.
29
+ =end
30
+ def is_ready_to_run?()
31
+ @options.has_key?(:file_name) && @options.has_key?(:file_open_mode)
32
+ end
33
+ =begin rdoc
34
+ When an IP is received the IP is checked to see if it has a :data key.
35
+ If it does then that data is written to the file specified by the
36
+ :file_name option. This will continue until an IP is sent with the
37
+ :completed key at which will cause the file to be closed.
38
+ =end
39
+ def do_node_work(args)
40
+
41
+ if args.has_key? :completed
42
+ @file.close if !@file.nil?
43
+ @file = nil
44
+ return false
45
+ end
46
+
47
+ if args.has_key? :data
48
+ data = args[:data]
49
+
50
+ if data.nil?
51
+ @file.close if !@file.nil?
52
+ @file = nil
53
+ return false
54
+ end
55
+
56
+ @file = File.new(@options[:file_name], @options[:file_open_mode]) if @file.nil?
57
+ @file.write(data)
58
+ @file.flush
59
+ end
60
+
61
+ true
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,3 @@
1
+ module Fbp
2
+ VERSION = "0.1.0" #:nodoc:
3
+ end
@@ -0,0 +1,150 @@
1
+ # Ruby Thread Pool
2
+ # ================
3
+ # A thread pool is useful when you wish to do some work in a thread, but do
4
+ # not know how much work you will be doing in advance. Spawning one thread
5
+ # for each task is potentially expensive, as threads are not free.
6
+ #
7
+ # In this case, it might be more beneficial to start a predefined set of
8
+ # threads and then hand off work to them as it becomes available. This is
9
+ # the pure essence of what a thread pool is: an array of threads, all just
10
+ # waiting to do some work for you!
11
+ #
12
+ # Prerequisites
13
+ # -------------
14
+
15
+ # We need the [Queue](http://rdoc.info/stdlib/thread/1.9.2/Queue), as our
16
+ # thread pool is largely dependent on it. Thanks to this, the implementation
17
+ # becomes very simple!
18
+ require 'thread'
19
+
20
+ # Public Interface
21
+ # ----------------
22
+
23
+ # `Pool` is our thread pool class. It will allow us to do three operations:
24
+ #
25
+ # - `.new(size)` creates a thread pool of a given size
26
+ # - `#schedule(*args, &job)` schedules a new job to be executed
27
+ # - `#shutdown` shuts down all threads (after letting them finish working, of course)
28
+ class Pool
29
+
30
+ # ### initialization, or `Pool.new(size)`
31
+ # Creating a new `Pool` involves a certain amount of work. First, however,
32
+ # we need to define its’ `size`. It defines how many threads we will have
33
+ # working internally.
34
+ #
35
+ # Which size is best for you is hard to answer. You do not want it to be
36
+ # too low, as then you won’t be able to do as many things concurrently.
37
+ # However, if you make it too high Ruby will spend too much time switching
38
+ # between threads, and that will also degrade performance!
39
+ def initialize(size)
40
+ # Before we do anything else, we need to store some information about
41
+ # our pool. `@size` is useful later, when we want to shut our pool down,
42
+ # and `@jobs` is the heart of our pool that allows us to schedule work.
43
+ @size = size
44
+ @jobs = Queue.new
45
+
46
+ # #### Creating our pool of threads
47
+ # Once preparation is done, it’s time to create our pool of threads.
48
+ # Each thread store its’ index in a thread-local variable, in case we
49
+ # need to know which thread a job is executing in later on.
50
+ @pool = Array.new(@size) do |i|
51
+ Thread.new do
52
+ Thread.current[:id] = i
53
+
54
+ # We start off by defining a `catch` around our worker loop. This
55
+ # way we’ve provided a method for graceful shutdown of our threads.
56
+ # Shutting down is merely a `#schedule { throw :exit }` away!
57
+ catch(:exit) do
58
+ # The worker thread life-cycle is very simple. We continuously wait
59
+ # for tasks to be put into our job `Queue`. If the `Queue` is empty,
60
+ # we will wait until it’s not.
61
+ loop do
62
+ # Once we have a piece of work to be done, we will pull out the
63
+ # information we need and get to work.
64
+ job, args = @jobs.pop
65
+ job.call(*args)
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # ### Work scheduling
73
+
74
+ # To schedule a piece of work to be done is to say to the `Pool` that you
75
+ # want something done.
76
+ def schedule(*args, &block)
77
+ # Your given task will not be run immediately; rather, it will be put
78
+ # into the work `Queue` and executed once a thread is ready to work.
79
+ @jobs << [block, args]
80
+ end
81
+
82
+ # ### Graceful shutdown
83
+
84
+ # If you ever wish to close down your application, I took the liberty of
85
+ # making it easy for you to wait for any currently executing jobs to finish
86
+ # before you exit.
87
+ def shutdown
88
+ # A graceful shutdown involves threads exiting cleanly themselves, and
89
+ # since we’ve defined a `catch`-handler around the threads’ worker loop
90
+ # it is simply a matter of throwing `:exit`. Thus, if we throw one `:exit`
91
+ # for each thread in our pool, they will all exit eventually!
92
+ @size.times do
93
+ schedule { throw :exit }
94
+ end
95
+
96
+ # And now one final thing: wait for our `throw :exit` jobs to be run on
97
+ # all our worker threads. This call will not return until all worker threads
98
+ # have exited.
99
+ @pool.map(&:join)
100
+ end
101
+ end
102
+
103
+ # Demonstration
104
+ # -------------
105
+ # Running this file will display how the thread pool works.
106
+ if $0 == __FILE__
107
+ # - First, we create a new thread pool with a size of 10. This number is
108
+ # lower than our planned amount of work, to show that threads do not
109
+ # exit once they have finished a task.
110
+ p = Pool.new(10)
111
+
112
+ # - Next we simulate some workload by scheduling a large amount of work
113
+ # to be done. The actual time taken for each job is randomized. This
114
+ # is to demonstrate that even if two tasks are scheduled approximately
115
+ # at the same time, the one that takes less time to execute is likely
116
+ # to finish before the other one.
117
+ 20.times do |i|
118
+ p.schedule do
119
+ sleep rand(4) + 2
120
+ puts "Job #{i} finished by thread #{Thread.current[:id]}"
121
+ end
122
+ end
123
+
124
+ # - Finally, register an `at_exit`-hook that will wait for our thread pool
125
+ # to properly shut down before allowing our script to completely exit.
126
+ at_exit { p.shutdown }
127
+ end
128
+
129
+ # License (X11 License)
130
+ # =====================
131
+ #
132
+ # Copyright (c) 2012, Kim Burgestrand <kim@burgestrand.se>
133
+ #
134
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
135
+ # of this software and associated documentation files (the "Software"), to deal
136
+ # in the Software without restriction, including without limitation the rights
137
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
138
+ # copies of the Software, and to permit persons to whom the Software is
139
+ # furnished to do so, subject to the following conditions:
140
+ #
141
+ # The above copyright notice and this permission notice shall be included in
142
+ # all copies or substantial portions of the Software.
143
+ #
144
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
145
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
146
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
147
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
148
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
149
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
150
+ # SOFTWARE.