andromeda 0.1

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,11 @@
1
+ .idea
2
+ .bundle
3
+ .yardoc
4
+ doc
5
+ db
6
+ pkg
7
+ html
8
+ package
9
+ coverage
10
+ bin
11
+ *.gem
data/.rvmrc ADDED
@@ -0,0 +1,2 @@
1
+ rvm --create gemset use andromeda
2
+ export JRUBY_OPTS="$JRUBY_OPTS -Xcompat.version=1.9"
data/AUTHORS ADDED
@@ -0,0 +1 @@
1
+ Stefan Plantikow <stefanp@moviepilot.com>
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem 'json', '>=1.6.5'
4
+ gem 'threadpool'
5
+ gem 'facter'
6
+ gem 'atomic'
7
+
8
+ group :development do
9
+ gem 'rake'
10
+ gem 'redcarpet', :require => false
11
+ gem 'yard', :require => false
12
+ gem 'irbtools', :require => false
13
+ end
14
+
15
+ group :jruby do
16
+ gem 'maruku'
17
+ end
18
+
19
+ group :test do
20
+ gem 'rspec', '2.6.0'
21
+ gem 'simplecov'
22
+ end
@@ -0,0 +1,85 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ atomic (1.0.0)
5
+ awesome_print (1.0.2)
6
+ boson (1.1.1)
7
+ clipboard (1.0.1)
8
+ coderay (1.0.6)
9
+ diff-lcs (1.1.3)
10
+ every_day_irb (1.2.2)
11
+ facter (1.6.8)
12
+ fancy_irb (0.7.2)
13
+ paint (>= 0.8.1)
14
+ unicode-display_width (>= 0.1.1)
15
+ g (1.6.0)
16
+ ruby_gntp
17
+ hirb (0.6.2)
18
+ interactive_editor (0.0.10)
19
+ spoon (>= 0.0.1)
20
+ irbtools (1.2.2)
21
+ awesome_print (~> 1.0.2)
22
+ boson (~> 1.1.1)
23
+ clipboard (~> 1.0.1)
24
+ coderay (~> 1.0.5)
25
+ every_day_irb (>= 1.2.2)
26
+ fancy_irb (>= 0.7.2)
27
+ g (>= 1.5.0)
28
+ hirb (~> 0.6.1)
29
+ interactive_editor (>= 0.0.10)
30
+ method_locator (>= 0.0.4)
31
+ method_source (>= 0.7.0)
32
+ methodfinder (>= 1.2.5)
33
+ ori (~> 0.1.0)
34
+ paint (>= 0.8.4)
35
+ sketches (>= 0.1.1)
36
+ wirb (>= 0.4.2)
37
+ zucker (>= 12.1)
38
+ json (1.6.6)
39
+ maruku (0.6.0)
40
+ syntax (>= 1.0.0)
41
+ method_locator (0.0.4)
42
+ method_source (0.7.1)
43
+ methodfinder (1.2.5)
44
+ multi_json (1.3.2)
45
+ ori (0.1.0)
46
+ paint (0.8.4)
47
+ rake (0.9.2.2)
48
+ redcarpet (2.1.1)
49
+ rspec (2.6.0)
50
+ rspec-core (~> 2.6.0)
51
+ rspec-expectations (~> 2.6.0)
52
+ rspec-mocks (~> 2.6.0)
53
+ rspec-core (2.6.4)
54
+ rspec-expectations (2.6.0)
55
+ diff-lcs (~> 1.1.2)
56
+ rspec-mocks (2.6.0)
57
+ ruby_gntp (0.3.4)
58
+ simplecov (0.6.2)
59
+ multi_json (~> 1.3)
60
+ simplecov-html (~> 0.5.3)
61
+ simplecov-html (0.5.3)
62
+ sketches (0.1.1)
63
+ spoon (0.0.1)
64
+ syntax (1.0.0)
65
+ threadpool (0.1.0.1)
66
+ unicode-display_width (0.1.1)
67
+ wirb (0.4.2)
68
+ yard (0.7.5)
69
+ zucker (12.1)
70
+
71
+ PLATFORMS
72
+ ruby
73
+
74
+ DEPENDENCIES
75
+ atomic
76
+ facter
77
+ irbtools
78
+ json (>= 1.6.5)
79
+ maruku
80
+ rake
81
+ redcarpet
82
+ rspec (= 2.6.0)
83
+ simplecov
84
+ threadpool
85
+ yard
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2012 Stefan Plantikow
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
@@ -0,0 +1,28 @@
1
+ # andromeda
2
+
3
+ Andromeda is a ultra light weight multicore stream processing framework based on a small dataflow DSL
4
+
5
+ It is currently untested and undocumented.
6
+
7
+ Below is an example that writes events to a file and reads them back in, to give an idea of what it does:
8
+
9
+ require 'andromeda'
10
+ w = Andromeda::CommandoWriter.new path: '/tmp/some_file'
11
+ w << (Commando.new :test)
12
+ w << (Commando.new :test, weight: 40)
13
+ w << (Commando.new :test, height: 20)
14
+ w << :close
15
+
16
+ r = Andromeda::CommandParser.new path: '/tmp/some_file'
17
+ # make r process events using a global thread pool of num_cpus threads
18
+ r.pool = :global
19
+ t = Andromeda::Tee.new
20
+ # make r output to t
21
+ r >> t
22
+ # start reading
23
+ r << :start
24
+ # t will log to a Logger.new(STDERR) by default
25
+
26
+ There is much more, dig the source, luke!
27
+
28
+
@@ -0,0 +1,39 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec'
4
+ require 'rspec/core/rake_task'
5
+
6
+ require 'yard'
7
+ require 'yard/rake/yardoc_task'
8
+
9
+ desc 'Run all rspecs'
10
+ RSpec::Core::RakeTask.new(:spec) do |spec|
11
+ spec.fail_on_error = true
12
+ spec.verbose = false
13
+ # spec.rspec_opts = ['--backtrace']
14
+ end
15
+
16
+ desc 'Run yardoc over project sources'
17
+ YARD::Rake::YardocTask.new(:ydoc) do |t|
18
+ t.options = ['--verbose']
19
+ t.files = ['lib/**/*.rb', '-', 'README.md', 'AUTHORS', 'LICENSE.txt']
20
+ end
21
+
22
+ #RDoc::Task.new(:rdoc) do |rdoc|
23
+ # # rdoc.main = "README.rdoc"
24
+ # rdoc.rdoc_files.include("lib/**/*.rb")
25
+ #end
26
+
27
+ desc 'Run irb in project environment'
28
+ task :console do
29
+ require 'irb'
30
+ ARGV.clear
31
+ IRB.conf[:USE_READLINE] = false if ENV['JRUBY_OPTS'] =~ /--ng/
32
+ IRB.start
33
+ end
34
+
35
+ task :doc => :ydoc
36
+ task :docs => :ydoc
37
+ task :test => :spec
38
+ task :tests => :spec
39
+ task :irb => :console
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'andromeda/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'andromeda'
7
+ s.version = Andromeda::VERSION
8
+ s.summary = 'Ultra light weight multicore stream processing framework based on a dataflow DSL'
9
+ s.description = s.summary
10
+ s.author = 'Stefan Plantikow'
11
+ s.email = 'stefanp@moviepilot.com'
12
+ s.homepage = 'https://github.com/moviepilot/andromeda'
13
+ s.rubyforge_project = 'andromeda'
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ s.bindir = 'script'
21
+ s.executables = `git ls-files -- script/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.licenses = ['PUBLIC DOMAIN WITHOUT ANY WARRANTY']
23
+ end
@@ -0,0 +1,14 @@
1
+ require 'json'
2
+ require 'logger'
3
+ require 'threadpool'
4
+ require 'facter'
5
+ require 'thread'
6
+ Facter.loadfacts
7
+
8
+ require 'andromeda/id'
9
+ require 'andromeda/pools'
10
+ require 'andromeda/scope'
11
+ require 'andromeda/andromeda'
12
+ require 'andromeda/helpers'
13
+ require 'andromeda/join'
14
+ require 'andromeda/commando'
@@ -0,0 +1,225 @@
1
+ # TODO
2
+ # - Turn into separate gem
3
+ # - Write Tests
4
+ # - Write fusor for synchronization (extra class that blocks until ready to submit)
5
+ # - Write docs, add yard support for documenting bases, attrs, etc.
6
+ # - Make nice slideshow and become very famous and rich. yay!
7
+ module Andromeda
8
+
9
+ module Internal
10
+ class Transplanting
11
+ attr_reader :orig
12
+
13
+ def initialize(init_opts = nil)
14
+ @opts = init_opts
15
+ @orig = self
16
+ end
17
+
18
+ protected
19
+
20
+ def transplant(new_opts = nil)
21
+ return self if @opts == new_opts
22
+ obj = self.clone
23
+ obj.instance_variable_set '@opts', new_opts
24
+ obj
25
+ end
26
+ end
27
+ end
28
+
29
+ class Dest < Internal::Transplanting
30
+ attr_reader :base
31
+ attr_reader :meth
32
+
33
+ def initialize(base, meth, init_opts = nil)
34
+ super init_opts
35
+ raise ArgumentError, "'#{meth}' is not a symbol" unless meth.kind_of?(Symbol)
36
+ raise ArgumentError, "'#{base}' is not a base" unless base.kind_of?(Base)
37
+ raise NoMethodError, "'#{base}' does not respond to '#{meth}'" unless base.respond_to?(meth)
38
+ @base = base
39
+ @meth = meth
40
+ end
41
+
42
+ def <<(chunk, opts = {}) ; submit chunk, opts ; self end
43
+ def submit(chunk, opts = {}) ; submit_to nil, chunk, opts end
44
+ def submit_now(chunk, opts = {}) ; submit_to :local, chunk, opts end
45
+
46
+ def submit_to(target_pool, chunk, opts = {})
47
+ if @opts
48
+ new_opts = @opts.clone
49
+ opts.each { |k, v| new_opts[k] = v }
50
+ else
51
+ new_opts = opts
52
+ end
53
+ new_opts[:scope] ||= Scope.new
54
+ new_opts[:mark] ||= Id.zero
55
+ base.transplant(new_opts).process target_pool, new_opts[:scope], self.meth, chunk
56
+ new_opts
57
+ end
58
+
59
+ def entry ; self end
60
+ end
61
+
62
+ class Base < Internal::Transplanting
63
+ attr_reader :id
64
+ attr_reader :opts
65
+
66
+ attr_accessor :log
67
+ attr_accessor :mark
68
+ attr_accessor :emit
69
+ attr_accessor :pool
70
+
71
+ attr_reader :trace_enter
72
+ attr_reader :trace_exit
73
+
74
+ def initialize(config = {})
75
+ super config[:init_opts]
76
+ @id = Id.gen
77
+ set_from_config init_from_config, config
78
+ @trace_enter ||= init_trace_hash :enter
79
+ @trace_exit ||= init_trace_hash :emit
80
+ @pool ||= init_pool_config
81
+ end
82
+
83
+ def trace=(new_trace)
84
+ raise ArgumentError, "'#{new_trace}' is not a Hash" unless new_trace.kind_of?(Hash)
85
+ @trace = new_trace
86
+ end
87
+
88
+ def init_trace_hash(kind) ; {} end
89
+ def init_pool_config ; nil end
90
+ def init_from_config ; [:readers, :writers] end
91
+
92
+ def log ; @log = Logger.new(STDERR) unless @log ; @log end
93
+ def mark ; @mark = Id.zero unless @mark ; @mark end
94
+
95
+ def on_enter(c)
96
+ emit << c rescue nil
97
+ end
98
+
99
+ # @param [nil, :local, :spawn, :single, :fifo, :default, :global, #process, #process_base] pool_descr
100
+ # If nil, uses self.pool as pool_descr and continues. If that is nil, too, defaults to :local.
101
+ # If #process_base, uses target_pool.process_base(meth) as pool_descr and continues.
102
+ # If pool_descr is :spawn, uses SpawnPool.default_pool
103
+ # If pool_descr is :single, uses PoolSupport.new_single_pool
104
+ # If pool_descr is :fifo, uses PoolSupport.new_fifo_pool
105
+ # If pool_descr is :global, uses the globally shared PoolSupport.global_pool
106
+ # If pool_descr is :default uses PoolSupport.new_default_pool
107
+ # Finally, if #process, runs by calling #process. If :local, runs in current thread.
108
+ # Otherwise, the behaviour is undefined.
109
+ def process(pool_descr, scope, meth, chunk)
110
+ this = self
111
+ run target_pool(pool_descr, meth), scope, meth, chunk do
112
+ begin
113
+ enter_level = trace_enter[meth]
114
+ exit_level = trace_exit[meth]
115
+ trace :enter, enter_level, meth, chunk if enter_level
116
+ send meth, chunk
117
+ trace :exit, exit_level, meth, chunk if exit_level
118
+ rescue Exception => e
119
+ handle_exception meth, chunk, e
120
+ ensure
121
+ scope.leave if scope
122
+ end
123
+ end
124
+ end
125
+
126
+ def check_mark
127
+ mark = @opts[:mark]
128
+ raise RuntimeError, 'invalid mark' if mark && !mark.zero?
129
+ end
130
+
131
+ def intern(dest) ; dest.transplant(opts) end
132
+
133
+ def dest(name)
134
+ result = if self.respond_to?(name)
135
+ then intern self.send(name)
136
+ else Dest.new self, "on_#{name}".to_sym, opts end
137
+ raise ArgumentError, "unknown or invalid dest: '#{name}'" unless result.kind_of?(Dest)
138
+ result
139
+ end
140
+
141
+ def trace(kind, level, method, chunk)
142
+ log_ = log
143
+ log_.send level, "TRACE #{id.to_s} :#{kind} :#{method} chunk: '#{chunk}'" if log_
144
+ end
145
+
146
+ protected
147
+
148
+ def set_from_config(what, config = {})
149
+ init_readers = what.include? :readers
150
+ init_writers = what.include? :writers
151
+ config.each_pair do |k, v|
152
+ k = k.to_sym rescue nil
153
+ if init_writers
154
+ writer = "#{k}=".to_sym rescue nil
155
+ if writer && self.respond_to?(writer)
156
+ then self.send writer, v
157
+ else instance_variable_set "@#{k}", v if init_readers && self.respond_to?(k) end
158
+ else
159
+ instance_variable_set "@#{k}", v if init_readers && self.respond_to?(k)
160
+ end
161
+ end
162
+ end
163
+
164
+ def mark_opts
165
+ if @mark && !@mark.zero?
166
+ if @opts[:mark]
167
+ then @opts[:mark] = @opts[:mark].xor(mark)
168
+ else @opts[:mark] = mark end
169
+ end
170
+ end
171
+
172
+ def run(pool, scope, meth, chunk, &thunk)
173
+ scope.enter
174
+ begin
175
+ if pool && pool.respond_to?(:process)
176
+ then pool.process(&thunk)
177
+ else thunk.call end
178
+ rescue Exception => e
179
+ handle_exception meth, chunk, e
180
+ scope.leave if scope
181
+ end
182
+ self
183
+ end
184
+
185
+ def target_pool(pool_descr, meth)
186
+ pool_descr = pool unless pool_descr
187
+ case pool_descr
188
+ when nil then :local
189
+ when :local then :local
190
+ when :spawn then SpawnPool.default_pool
191
+ when :global then PoolSupport.global_pool
192
+ when :default then PoolSupport.new_default_pool
193
+ when :single then PoolSupport.new_single_pool
194
+ when :fifo then PoolSupport.new_fifo_pool
195
+ else
196
+ if pool_descr.respond_to(:process_base)
197
+ then pool_descr.process_base(meth)
198
+ else pool_descr end
199
+ end
200
+ end
201
+
202
+ def handle_exception(meth, chunk, e)
203
+ if log
204
+ trace = ''
205
+ e.backtrace.each { |s| trace << "\n #{s}" }
206
+ log.error "Caught '#{e}' when processing chunk: '#{chunk}' via meth: '#{meth}' with backtrace: '#{trace}'"
207
+ end
208
+ end
209
+
210
+ public
211
+
212
+ def >>(dest) ; self.emit = dest.entry end
213
+
214
+ def drop ; self.emit = nil end
215
+
216
+ def entry ; dest(:enter) end
217
+ alias_method :exit, :emit
218
+
219
+ def <<(chunk, opts = {}) ; entry.<< chunk, opts end
220
+ def submit(chunk, opts = {}) ; entry.submit chunk, opts end
221
+ def submit_now(chunk, opts = {}) ; entry.submit_now chunk, opts end
222
+ def submit_to(target_pool, chunk, opts = {}) ; entry.submit_to target_pool, chunk, opts end
223
+ end
224
+ end
225
+
@@ -0,0 +1,106 @@
1
+ module Andromeda
2
+
3
+ class CommandoBase < Base
4
+ attr_reader :file
5
+ attr_reader :path
6
+ end
7
+
8
+ class Commando
9
+ attr_reader :cmd
10
+ attr_reader :data
11
+ attr_reader :time
12
+
13
+ def initialize(cmd, data = {})
14
+ raise ArgumentError unless cmd.kind_of?(Symbol)
15
+ @cmd = cmd
16
+ @data = data
17
+ @time = Time.now.to_i
18
+ end
19
+
20
+ def to_hash
21
+ { :cmd => cmd, :data => data, :time => time }
22
+ end
23
+ end
24
+
25
+ class CommandoWriter < CommandoBase
26
+
27
+ def initialize(config = {})
28
+ super config
29
+ @mode ||= 'a+'
30
+ @file = File.open path, @mode
31
+ end
32
+
33
+ def on_enter(c)
34
+ if c == :close
35
+ file.sync
36
+ file.fsync rescue nil
37
+ file.close
38
+ else
39
+ c = c.to_hash if c.kind_of?(Commando)
40
+ cmd = c[:cmd]
41
+ raise ArgumentError, "invalid commando" unless cmd.kind_of?(Symbol)
42
+ data = c[:data]
43
+ str = if data then data.to_json else '' end
44
+ len = str.length
45
+ len += 1 unless str.end_with?('\n')
46
+ tim = c[:time] if c[:time]
47
+ tim = Time.now unless tim
48
+ tim = tim.to_i unless tim.kind_of?(Fixnum)
49
+ file.write ">>> ANDROMEDA_COMMANDO :#{cmd} TIME #{tim} LEN #{len.to_i} START\n"
50
+ file.write(str) if data
51
+ if str.end_with?('\n')
52
+ file.write "<<< ANDROMEDA_COMMANDO :#{cmd} END\n"
53
+ else
54
+ file.write "\n<<< ANDROMEDA_COMMANDO :#{cmd} END\n"
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ class CommandoParser < CommandoBase
61
+
62
+ def initialize(config = {})
63
+ super config
64
+ @file = File.open path, 'r'
65
+ end
66
+
67
+ def on_enter(c)
68
+ parser = dest(:parse)
69
+ start_matcher = />>> ANDROMEDA_COMMANDO :(\w+) TIME (\d+) LEN (\d+) START/
70
+ end_matcher = /<<< ANDROMEDA_COMMANDO :(\w+) END/
71
+ while (line = file.gets)
72
+ line = line.chomp
73
+ match = start_matcher.match line
74
+ if match
75
+ cmd = match[1].to_sym
76
+ tim = match[2].to_i
77
+ len = match[3].to_i
78
+ buf = if len == 0 then '' else file.gets end
79
+ while buf.length < len
80
+ log.debug line
81
+ buf << line
82
+ end
83
+ line = file.gets.chomp
84
+ match = end_matcher.match line
85
+ if match
86
+ end_cmd = match[1].to_sym
87
+ raise ArgumentError, "command name mismatch between START ('#{cmd}') and END ('#{end_cmd}')" unless cmd == end_cmd
88
+ raise ArgumentError, "length mismatch" unless len == buf.length
89
+ h = { :cmd => end_cmd, :data => buf, :time => tim }
90
+ parser << h
91
+ else
92
+ raise ArgumentError, "garbage commando end: '#{line}'"
93
+ end
94
+ else
95
+ raise ArgumentError, "garbage commando start: '#{line}'"
96
+ end
97
+ end
98
+ end
99
+
100
+ def on_parse(c)
101
+ data = c[:data]
102
+ c[:data] = if data.chomp == '' then nil else JSON::parse(data) end
103
+ emit << c rescue nil
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,134 @@
1
+ module Andromeda
2
+
3
+
4
+ class Join < Base
5
+
6
+ def initialize(config = {})
7
+ super config
8
+ @mutex = Mutex.new
9
+ @cv = ConditionVariable.new
10
+ # box value to keep ref after clone
11
+ @state = [ state_init ]
12
+ end
13
+
14
+ protected
15
+
16
+ def state_init ; {} end
17
+ def state_key(chunk) ; chunk end
18
+ def state_complete?(state) ; state end
19
+ def state_updatable?(state, chunk) ; state[state_key(chunk)] == nil end
20
+ def state_update(state, chunk) ; state[state_key(chunk)] = chunk end
21
+
22
+ def run(pool, scope, meth, chunk, &thunk)
23
+ @mutex.synchronize {
24
+ state = @state[0]
25
+ while true
26
+ if state_updatable?(state, chunk)
27
+ @state[0] = (state = state_update(state, chunk))
28
+ if state_complete?(state)
29
+ super pool, scope, meth, state, &thunk
30
+ @state[0] = state_init
31
+ end
32
+ cv.signal
33
+ return
34
+ else
35
+ cv.wait
36
+ end
37
+ end
38
+ }
39
+ end
40
+
41
+ end
42
+
43
+ class Transf < Base
44
+ attr_accessor :filter
45
+ attr_accessor :mapper
46
+ attr_accessor :reducer
47
+
48
+ def output(c)
49
+ filter_ = filter
50
+ mapper_ = mapper
51
+ reducer_ = reducer
52
+ if !filter_ || filter_.call(c)
53
+ c = if mapper_ then mapper_.call c else [c] end
54
+ c = reducer_.call c if reducer_
55
+ yield c
56
+ end
57
+ end
58
+
59
+ def on_enter(c)
60
+ output(c) { |o| super o }
61
+ end
62
+ end
63
+
64
+ class Tee < Transf
65
+ attr_accessor :level
66
+
67
+ def init_pool_config ; :local end
68
+
69
+ def initialize(config = {})
70
+ super config
71
+ @level ||= :info
72
+ end
73
+
74
+ def on_enter(c)
75
+ log_ = log
76
+ level_ = level
77
+ log_.send level, "#{c}" if log_ && level_
78
+ super c
79
+ end
80
+ end
81
+
82
+ class Targeting < Transf
83
+ attr_accessor :targets
84
+
85
+ def initialize(config = {})
86
+ super config
87
+ @targets ||= {}
88
+ end
89
+
90
+ def target_values
91
+ t = targets
92
+ if t.kind_of?(Hash) then t.values else t end
93
+ end
94
+
95
+ def switch_target(c)
96
+ switch_ = switch
97
+ switch_ = switch_.call(c) if switch_
98
+ targets[switch_]
99
+ end
100
+ end
101
+
102
+ class Broadc < Targeting
103
+ def on_enter(c)
104
+ output(c) do |o|
105
+ target_values { |t| intern(t) << o rescue nil }
106
+ end
107
+ end
108
+ end
109
+
110
+ class Switch < Targeting
111
+ attr_accessor :switch
112
+
113
+ def on_enter(c)
114
+ target_ = intern(switch_target c) rescue emit
115
+ output(c) { |o| target_ << o }
116
+ end
117
+ end
118
+
119
+ class Router < Targeting
120
+ def on_enter(c)
121
+ target_ = intern(switch_target c[0]) rescue emit
122
+ output(c[1]) { |o| target_ << o }
123
+ end
124
+ end
125
+
126
+ class FifoBase < Base
127
+ def init_pool_config ; :fifo end
128
+ end
129
+
130
+ class LocalBase < Base
131
+ def init_pool_config ; :local end
132
+ end
133
+
134
+ end
@@ -0,0 +1,92 @@
1
+ module Andromeda
2
+
3
+ # Generator for random xorable ids (used in marks)
4
+ class Id
5
+ # Default length if generated ids
6
+ NUM_BYTES = 12
7
+
8
+ protected
9
+
10
+ def initialize(len = NUM_BYTES, random = true, init_data = nil)
11
+ raise ArgumentError unless len.kind_of?(Fixnum)
12
+ raise ArgumentError unless len >= 0
13
+
14
+ @data = if init_data
15
+ init_data
16
+ else
17
+ if random
18
+ then len.times.map { Id.rnd_byte }
19
+ else len.times.map { 0 } end
20
+ end
21
+ end
22
+
23
+ public
24
+
25
+ def length ; @data.length end
26
+
27
+ def zero?
28
+ each { |b| return false unless b == 0 }
29
+ true
30
+ end
31
+
32
+ def each ; this = self ; 0.upto(length-1).each { |i| yield this[i] } end
33
+
34
+ def each_with_index ; this = self ; 0.upto(length-1).each { |i| yield i, this[i] } end
35
+
36
+ def zip_bytes(b)
37
+ return ArgumentError unless same_id_kind?(b)
38
+ a = self
39
+ 0.upto(length-1).each { |i| yield a[i], b[i] }
40
+ end
41
+ def [](key) ; @data[key] end
42
+
43
+ def same_id_kind?(obj) ; obj.kind_of?(Id) && obj.length == self.length end
44
+
45
+ # Compare self to b
46
+ # @param [Id] b
47
+ def eq?(b)
48
+ zip_bytes(b) { |i,j| return false if i != j }
49
+ true
50
+ end
51
+
52
+ alias_method :==, :eq?
53
+
54
+ # xor self and b's ids component-wise
55
+ # @param [Array<Fixnum>] b
56
+ # @return [Id]
57
+ def xor(b)
58
+ r = []
59
+ zip_bytes(b) { |i,j| r << (i ^ j) }
60
+ Id.new r.length, false, r
61
+ end
62
+
63
+ def to_s
64
+ r = "#<#{self.class}:"
65
+ each { |b| r << Id.twochars(b.to_s(16)) }
66
+ r << '>'
67
+ r
68
+ end
69
+
70
+ # @param [Fixnum] length
71
+ # @return [Id] random id
72
+ def self.gen(length = NUM_BYTES) ; Id.new length, true end
73
+
74
+ # @param [Fixnum] length
75
+ # @return [Id] empty (zero) id
76
+ def self.zero(length = NUM_BYTES) ; Id.new length, false end
77
+
78
+ private
79
+
80
+ def self.rnd_byte ; Random.rand(256) end
81
+
82
+ def self.twochars(s)
83
+ case s.length
84
+ when 0 then '00'
85
+ when 1 then "0#{s}"
86
+ else s
87
+ end
88
+ end
89
+
90
+ end
91
+
92
+ end
@@ -0,0 +1,48 @@
1
+ module Andromeda
2
+
3
+ # untested as in not at all but should would perfectly fine according to theory
4
+
5
+ class Join < Base
6
+
7
+ def initialize(config = {})
8
+ super config
9
+ @mutex = Mutex.new
10
+ @cv = ConditionVariable.new
11
+ # box value to keep ref after clone
12
+ @state = [ state_init ]
13
+ end
14
+
15
+ protected
16
+
17
+ def state_init ; {} end
18
+
19
+ # typical usages need to override these two
20
+ def state_key(chunk) ; chunk end
21
+ def state_complete?(state) state end
22
+
23
+ def state_updatable?(state, chunk) ; state[state_key(chunk)] == nil end
24
+ def state_update(state, chunk) ; state[state_key(chunk)] = chunk; state end
25
+
26
+ def run(pool, scope, meth, chunk, &thunk)
27
+ @mutex.synchronize do
28
+ state = @state[0]
29
+ while true
30
+ if state_updatable?(state, chunk)
31
+ @state[0] = (state = state_update(state, chunk))
32
+ if state_complete?(state)
33
+ @state[0] = state_init
34
+ cv.signal
35
+ return super pool, scope, meth, state, &thunk
36
+ else
37
+ cv.signal
38
+ return self
39
+ end
40
+ else
41
+ cv.wait @mutex
42
+ end
43
+ end
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,69 @@
1
+ module Andromeda
2
+
3
+ # Helper class for easily obtaining a thread pool with num_processors threads
4
+ class PoolSupport
5
+ # @return [Fixnum] number of processors as determined by Facter
6
+ def self.num_processors ; Facter.sp_number_processors.strip.to_i end
7
+
8
+ # @return [ThreadPool] a new thread pool with num_processors threads
9
+ def self.new_default_pool ; ThreadPool.new self.num_processors end
10
+
11
+ # @return [ThreadPool] a globally shared thread pool with num_processors threads
12
+ def self.global_pool(reset = false)
13
+ @pool = self.new_default_pool unless @pool || reset
14
+ @pool
15
+ end
16
+
17
+ # @return [ThreadPool] of size 1
18
+ def self.new_single_pool ; ThreadPool.new(1) end
19
+
20
+ # @return [ThreadPool] that guarantees fifo processing of requests
21
+ def self.new_fifo_pool ; new_single_pool end
22
+ end
23
+
24
+ # Fake thread pool that spawns an unlimited number of threads
25
+ class SpawnPool
26
+ def process(&block) ; Thread.new &block end
27
+
28
+ # @return [SpawnPool] a globally shared SpawnPool instance
29
+ def self.default_pool
30
+ @pool ||= SpawnPool.new
31
+ @pool
32
+ end
33
+
34
+ # Does nothing
35
+ def shutdown ; end
36
+ end
37
+
38
+ # Caching factory for thread pools
39
+ class PoolFactory < Hash
40
+
41
+ attr_reader :pool_maker
42
+
43
+ # @yield [Proc] factory/maker for building thread pools for a given key
44
+ def initialize(&pool_maker)
45
+ @pool_maker = pool_maker
46
+ end
47
+
48
+ def [](key)
49
+ current = super key
50
+ if ! current
51
+ current = pool_maker.call key
52
+ self[key] = current
53
+ end
54
+ current
55
+ end
56
+
57
+ def []=(key, value)
58
+ raise ArgumentError, "Not a ThreadPool" unless value.respond_to?(:process)
59
+ super key, value
60
+ end
61
+
62
+ def shutdown
63
+ values.each { |pool| pool.shutdown }
64
+ end
65
+
66
+ alias_method :process_stage, :[]
67
+ end
68
+
69
+ end
@@ -0,0 +1,38 @@
1
+ require 'atomic'
2
+
3
+ module Andromeda
4
+
5
+ class Scope
6
+
7
+ def initialize(init_value = 0)
8
+ raise ArgumentError unless init_value.kind_of?(Fixnum)
9
+ @count = Atomic.new init_value
10
+ end
11
+
12
+ def value ; @count.value end
13
+
14
+ def enter(amount = 1)
15
+ raise ArgumentError unless amount.kind_of?(Fixnum)
16
+ raise ArgumentError unless amount >= 0
17
+ @count.update { |v| v + amount }
18
+ end
19
+
20
+ def leave(amount = 1)
21
+ raise ArgumentError unless amount >= 0
22
+ raise ArgumentError unless amount.kind_of?(Fixnum)
23
+ @count.update { |v| v - amount }
24
+ end
25
+
26
+ def wait_while(&test)
27
+ while test.call(value)
28
+ Thread::pass
29
+ end
30
+ end
31
+
32
+ def wait_for(val = 0)
33
+ raise ArgumentError unless val.kind_of?(Fixnum)
34
+ wait_while { |v| v != val }
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,3 @@
1
+ module Andromeda
2
+ VERSION = '0.1'
3
+ end
@@ -0,0 +1,5 @@
1
+ require 'bundler'
2
+ Bundler.require
3
+
4
+ require 'simplecov'
5
+ SimpleCov.start
metadata ADDED
@@ -0,0 +1,68 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: andromeda
3
+ version: !ruby/object:Gem::Version
4
+ version: '0.1'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Stefan Plantikow
9
+ autorequire:
10
+ bindir: script
11
+ cert_chain: []
12
+ date: 2012-04-24 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description: Ultra light weight multicore stream processing framework based on a dataflow
15
+ DSL
16
+ email: stefanp@moviepilot.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .rvmrc
23
+ - AUTHORS
24
+ - Gemfile
25
+ - Gemfile.lock
26
+ - LICENSE.txt
27
+ - README.md
28
+ - Rakefile
29
+ - andromeda.gemspec
30
+ - lib/andromeda.rb
31
+ - lib/andromeda/andromeda.rb
32
+ - lib/andromeda/commando.rb
33
+ - lib/andromeda/helpers.rb
34
+ - lib/andromeda/id.rb
35
+ - lib/andromeda/join.rb
36
+ - lib/andromeda/pools.rb
37
+ - lib/andromeda/scope.rb
38
+ - lib/andromeda/version.rb
39
+ - spec/spec_helper.rb
40
+ homepage: https://github.com/moviepilot/andromeda
41
+ licenses:
42
+ - PUBLIC DOMAIN WITHOUT ANY WARRANTY
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project: andromeda
61
+ rubygems_version: 1.8.17
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Ultra light weight multicore stream processing framework based on a dataflow
65
+ DSL
66
+ test_files:
67
+ - spec/spec_helper.rb
68
+ has_rdoc: