tmm1-amqp 0.5.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,99 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'mq'
3
+
4
+ # check MAX numbers for prime-ness
5
+ MAX = 1000
6
+
7
+ # logging
8
+ def log *args
9
+ p args
10
+ end
11
+
12
+ # spawn workers
13
+ workers = ARGV[0] ? (Integer(ARGV[0]) rescue 1) : 1
14
+ EM.fork(workers) do
15
+
16
+ log MQ.id, :started
17
+
18
+ class Fixnum
19
+ def prime?
20
+ ('1' * self) !~ /^1?$|^(11+?)\1+$/
21
+ end
22
+ end
23
+
24
+ class PrimeChecker
25
+ def is_prime? number
26
+ log "prime checker #{MQ.id}", :prime?, number
27
+ number.prime?
28
+ end
29
+ end
30
+
31
+ MQ.rpc('prime checker', PrimeChecker.new)
32
+
33
+ end
34
+
35
+ # use workers to check which numbers are prime
36
+ EM.run{
37
+
38
+ prime_checker = MQ.rpc('prime checker')
39
+
40
+ (10_000...(10_000+MAX)).each do |num|
41
+ log :checking, num
42
+
43
+ prime_checker.is_prime?(num) { |is_prime|
44
+ log :prime?, num, is_prime
45
+ (@primes||=[]) << num if is_prime
46
+
47
+ if (@responses = (@responses || 0) + 1) == MAX
48
+ log :primes=, @primes
49
+ EM.stop_event_loop
50
+ end
51
+ }
52
+
53
+ end
54
+
55
+ }
56
+
57
+ __END__
58
+
59
+ $ uname -a
60
+ Linux gc 2.6.24-ARCH #1 SMP PREEMPT Sun Mar 30 10:50:22 CEST 2008 x86_64 Intel(R) Xeon(R) CPU X3220 @ 2.40GHz GenuineIntel GNU/Linux
61
+
62
+ $ cat /proc/cpuinfo | grep processor | wc -l
63
+ 4
64
+
65
+ $ time ruby primes-simple.rb
66
+
67
+ real 0m16.055s
68
+ user 0m16.052s
69
+ sys 0m0.000s
70
+
71
+ $ time ruby primes.rb 1 >/dev/null
72
+
73
+ real 0m18.278s
74
+ user 0m0.993s
75
+ sys 0m0.027s
76
+
77
+ $ time ruby primes.rb 2 >/dev/null
78
+
79
+ real 0m17.316s
80
+ user 0m0.967s
81
+ sys 0m0.053s
82
+
83
+ $ time ruby primes.rb 4 >/dev/null
84
+
85
+ real 0m8.229s
86
+ user 0m1.010s
87
+ sys 0m0.030s
88
+
89
+ $ time ruby primes.rb 8 >/dev/null
90
+
91
+ real 0m5.893s
92
+ user 0m1.023s
93
+ sys 0m0.050s
94
+
95
+ $ time ruby primes.rb 16 >/dev/null
96
+
97
+ real 0m5.601s
98
+ user 0m0.990s
99
+ sys 0m0.043s
@@ -0,0 +1,77 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'amqp'
3
+
4
+ module SimpleClient
5
+ def process_frame frame
6
+ case frame
7
+ when Frame::Body
8
+ EM.stop_event_loop
9
+
10
+ when Frame::Method
11
+ case method = frame.payload
12
+ when Protocol::Connection::Start
13
+ send Protocol::Connection::StartOk.new({:platform => 'Ruby/EventMachine',
14
+ :product => 'AMQP',
15
+ :information => 'http://github.com/tmm1/amqp',
16
+ :version => '0.1.0'},
17
+ 'AMQPLAIN',
18
+ {:LOGIN => 'guest',
19
+ :PASSWORD => 'guest'},
20
+ 'en_US')
21
+
22
+ when Protocol::Connection::Tune
23
+ send Protocol::Connection::TuneOk.new(:channel_max => 0,
24
+ :frame_max => 131072,
25
+ :heartbeat => 0)
26
+
27
+ send Protocol::Connection::Open.new(:virtual_host => '/',
28
+ :capabilities => '',
29
+ :insist => false)
30
+
31
+ when Protocol::Connection::OpenOk
32
+ send Protocol::Channel::Open.new, :channel => 1
33
+
34
+ when Protocol::Channel::OpenOk
35
+ send Protocol::Access::Request.new(:realm => '/data',
36
+ :read => true,
37
+ :write => true,
38
+ :active => true), :channel => 1
39
+
40
+ when Protocol::Access::RequestOk
41
+ @ticket = method.ticket
42
+ send Protocol::Queue::Declare.new(:ticket => @ticket,
43
+ :queue => '',
44
+ :exclusive => false,
45
+ :auto_delete => true), :channel => 1
46
+
47
+ when Protocol::Queue::DeclareOk
48
+ @queue = method.queue
49
+ send Protocol::Queue::Bind.new(:ticket => @ticket,
50
+ :queue => @queue,
51
+ :exchange => '',
52
+ :routing_key => 'test_route'), :channel => 1
53
+
54
+ when Protocol::Queue::BindOk
55
+ send Protocol::Basic::Consume.new(:ticket => @ticket,
56
+ :queue => @queue,
57
+ :no_local => false,
58
+ :no_ack => true), :channel => 1
59
+
60
+ when Protocol::Basic::ConsumeOk
61
+ data = "this is a test!"
62
+
63
+ send Protocol::Basic::Publish.new(:ticket => @ticket,
64
+ :exchange => '',
65
+ :routing_key => 'test_route'), :channel => 1
66
+ send Protocol::Header.new(Protocol::Basic, data.length, :content_type => 'application/octet-stream',
67
+ :delivery_mode => 1,
68
+ :priority => 0), :channel => 1
69
+ send Frame::Body.new(data), :channel => 1
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ AMQP.logging = true
76
+ AMQP.client = SimpleClient
77
+ AMQP.start
@@ -0,0 +1,56 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'mq'
3
+
4
+ EM.run{
5
+
6
+ def log *args
7
+ p [ Time.now, *args ]
8
+ end
9
+
10
+ # AMQP.logging = true
11
+
12
+ EM.add_periodic_timer(1){
13
+ puts
14
+
15
+ log :publishing, 'stock.usd.appl', price = 170+rand(1000)/100.0
16
+ MQ.topic.publish(price, :key => 'stock.usd.appl', :headers => {:symbol => 'appl'})
17
+
18
+ log :publishing, 'stock.usd.msft', price = 22+rand(500)/100.0
19
+ MQ.topic.publish(price, :key => 'stock.usd.msft', :headers => {:symbol => 'msft'})
20
+ }
21
+
22
+ Thread.new{
23
+ amq = MQ.new
24
+ amq.queue('apple stock').bind(amq.topic, :key => 'stock.usd.appl').subscribe{ |price|
25
+ log 'apple stock', price
26
+ }
27
+ }
28
+
29
+ Thread.new{
30
+ amq = MQ.new
31
+ amq.queue('us stocks').bind(amq.topic, :key => 'stock.usd.*').subscribe{ |info, price|
32
+ log 'us stock', info.headers[:symbol], price
33
+ }
34
+ }
35
+
36
+ }
37
+
38
+ __END__
39
+
40
+ [Thu Jul 17 14:51:07 -0700 2008, :publishing, "stock.usd.appl", 170.84]
41
+ [Thu Jul 17 14:51:07 -0700 2008, :publishing, "stock.usd.msft", 23.68]
42
+ [Thu Jul 17 14:51:07 -0700 2008, "apple stock", "170.84"]
43
+ [Thu Jul 17 14:51:07 -0700 2008, "us stock", "appl", "170.84"]
44
+ [Thu Jul 17 14:51:07 -0700 2008, "us stock", "msft", "23.68"]
45
+
46
+ [Thu Jul 17 14:51:08 -0700 2008, :publishing, "stock.usd.appl", 173.61]
47
+ [Thu Jul 17 14:51:08 -0700 2008, :publishing, "stock.usd.msft", 25.8]
48
+ [Thu Jul 17 14:51:08 -0700 2008, "apple stock", "173.61"]
49
+ [Thu Jul 17 14:51:08 -0700 2008, "us stock", "appl", "173.61"]
50
+ [Thu Jul 17 14:51:08 -0700 2008, "us stock", "msft", "25.8"]
51
+
52
+ [Thu Jul 17 14:51:09 -0700 2008, :publishing, "stock.usd.appl", 173.94]
53
+ [Thu Jul 17 14:51:09 -0700 2008, :publishing, "stock.usd.msft", 24.88]
54
+ [Thu Jul 17 14:51:09 -0700 2008, "apple stock", "173.94"]
55
+ [Thu Jul 17 14:51:09 -0700 2008, "us stock", "appl", "173.94"]
56
+ [Thu Jul 17 14:51:09 -0700 2008, "us stock", "msft", "24.88"]
data/lib/amqp.rb ADDED
@@ -0,0 +1,14 @@
1
+ module AMQP
2
+ DIR = File.expand_path(File.dirname(File.expand_path(__FILE__)))
3
+
4
+ $:.unshift DIR
5
+
6
+ %w[ buffer spec protocol frame client ].each do |file|
7
+ require "amqp/#{file}"
8
+ end
9
+
10
+ class << self
11
+ @logging = false
12
+ attr_accessor :logging
13
+ end
14
+ end
@@ -0,0 +1,395 @@
1
+ if [].map.respond_to? :with_index
2
+ class Array
3
+ def enum_with_index
4
+ each.with_index
5
+ end
6
+ end
7
+ else
8
+ require 'enumerator'
9
+ end
10
+
11
+ module AMQP
12
+ class Buffer
13
+ class Overflow < Exception; end
14
+ class InvalidType < Exception; end
15
+
16
+ def initialize data = ''
17
+ @data = data
18
+ @pos = 0
19
+ end
20
+
21
+ attr_reader :pos
22
+
23
+ def data
24
+ @data.clone
25
+ end
26
+ alias :contents :data
27
+ alias :to_s :data
28
+
29
+ def << data
30
+ @data << data.to_s
31
+ self
32
+ end
33
+
34
+ def length
35
+ @data.length
36
+ end
37
+
38
+ def empty?
39
+ pos == length
40
+ end
41
+
42
+ def rewind
43
+ @pos = 0
44
+ end
45
+
46
+ def read_properties *types
47
+ types.shift if types.first == :properties
48
+
49
+ i = 0
50
+ values = []
51
+
52
+ while props = read(:short)
53
+ (0..14).each do |n|
54
+ # no more property types
55
+ break unless types[i]
56
+
57
+ # if flag is set
58
+ if props & (1<<(15-n)) != 0
59
+ if types[i] == :bit
60
+ # bit values exist in flags only
61
+ values << true
62
+ else
63
+ # save type name for later reading
64
+ values << types[i]
65
+ end
66
+ else
67
+ # property not set or is false bit
68
+ values << (types[i] == :bit ? false : nil)
69
+ end
70
+
71
+ i+=1
72
+ end
73
+
74
+ # bit(0) == 0 means no more property flags
75
+ break unless props & 1 == 1
76
+ end
77
+
78
+ values.map do |value|
79
+ value.is_a?(Symbol) ? read(value) : value
80
+ end
81
+ end
82
+
83
+ def read *types
84
+ if types.first == :properties
85
+ return read_properties(*types)
86
+ end
87
+
88
+ values = types.map do |type|
89
+ case type
90
+ when :octet
91
+ _read(1, 'C')
92
+ when :short
93
+ _read(2, 'n')
94
+ when :long
95
+ _read(4, 'N')
96
+ when :longlong
97
+ upper, lower = _read(8, 'NN')
98
+ upper << 32 | lower
99
+ when :shortstr
100
+ _read read(:octet)
101
+ when :longstr
102
+ _read read(:long)
103
+ when :timestamp
104
+ Time.at read(:longlong)
105
+ when :table
106
+ t = Hash.new
107
+
108
+ table = Buffer.new(read(:longstr))
109
+ until table.empty?
110
+ key, type = table.read(:shortstr, :octet)
111
+ key = key.intern
112
+ t[key] ||= case type
113
+ when 83 # 'S'
114
+ table.read(:longstr)
115
+ when 73 # 'I'
116
+ table.read(:long)
117
+ when 68 # 'D'
118
+ exp = table.read(:octet)
119
+ num = table.read(:long)
120
+ num / 10.0**exp
121
+ when 84 # 'T'
122
+ table.read(:timestamp)
123
+ when 70 # 'F'
124
+ table.read(:table)
125
+ end
126
+ end
127
+
128
+ t
129
+ when :bit
130
+ if (@bits ||= []).empty?
131
+ val = read(:octet)
132
+ @bits = (0..7).map{|i| (val & 1<<i) != 0 }
133
+ end
134
+
135
+ @bits.shift
136
+ else
137
+ raise InvalidType, "Cannot read data of type #{type}"
138
+ end
139
+ end
140
+
141
+ types.size == 1 ? values.first : values
142
+ end
143
+
144
+ def write type, data
145
+ case type
146
+ when :octet
147
+ _write(data, 'C')
148
+ when :short
149
+ _write(data, 'n')
150
+ when :long
151
+ _write(data, 'N')
152
+ when :longlong
153
+ lower = data & 0xffffffff
154
+ upper = (data & ~0xffffffff) >> 32
155
+ _write([upper, lower], 'NN')
156
+ when :shortstr
157
+ data = (data || '').to_s
158
+ _write([data.length, data], 'Ca*')
159
+ when :longstr
160
+ if data.is_a? Hash
161
+ write(:table, data)
162
+ else
163
+ data = (data || '').to_s
164
+ _write([data.length, data], 'Na*')
165
+ end
166
+ when :timestamp
167
+ write(:longlong, data.to_i)
168
+ when :table
169
+ data ||= {}
170
+ write :longstr, (data.inject(Buffer.new) do |table, (key, value)|
171
+ table.write(:shortstr, key.to_s)
172
+
173
+ case value
174
+ when String
175
+ table.write(:octet, 83) # 'S'
176
+ table.write(:longstr, value.to_s)
177
+ when Fixnum
178
+ table.write(:octet, 73) # 'I'
179
+ table.write(:long, value)
180
+ when Float
181
+ table.write(:octet, 68) # 'D'
182
+ # XXX there's gotta be a better way to do this..
183
+ exp = value.to_s.split('.').last.length
184
+ num = value * 10**exp
185
+ table.write(:octet, exp)
186
+ table.write(:long, num)
187
+ when Time
188
+ table.write(:octet, 84) # 'T'
189
+ table.write(:timestamp, value)
190
+ when Hash
191
+ table.write(:octet, 70) # 'F'
192
+ table.write(:table, value)
193
+ end
194
+
195
+ table
196
+ end)
197
+ when :bit
198
+ [*data].to_enum(:each_slice, 8).each{|bits|
199
+ write(:octet, bits.enum_with_index.inject(0){ |byte, (bit, i)|
200
+ byte |= 1<<i if bit
201
+ byte
202
+ })
203
+ }
204
+ when :properties
205
+ values = []
206
+ data.enum_with_index.inject(0) do |short, ((type, value), i)|
207
+ n = i % 15
208
+ last = i+1 == data.size
209
+
210
+ if (n == 0 and i != 0) or last
211
+ if data.size > i+1
212
+ short |= 1<<0
213
+ elsif last and value
214
+ values << [type,value]
215
+ short |= 1<<(15-n)
216
+ end
217
+
218
+ write(:short, short)
219
+ short = 0
220
+ end
221
+
222
+ if value and !last
223
+ values << [type,value]
224
+ short |= 1<<(15-n)
225
+ end
226
+
227
+ short
228
+ end
229
+
230
+ values.each do |type, value|
231
+ write(type, value) unless type == :bit
232
+ end
233
+ else
234
+ raise InvalidType, "Cannot write data of type #{type}"
235
+ end
236
+
237
+ self
238
+ end
239
+
240
+ def extract
241
+ begin
242
+ cur_data, cur_pos = @data.clone, @pos
243
+ yield self
244
+ rescue Overflow
245
+ @data, @pos = cur_data, cur_pos
246
+ nil
247
+ end
248
+ end
249
+
250
+ def _read size, pack = nil
251
+ if @pos + size > length
252
+ raise Overflow
253
+ else
254
+ data = @data[@pos,size]
255
+ @data[@pos,size] = ''
256
+ if pack
257
+ data = data.unpack(pack)
258
+ data = data.pop if data.size == 1
259
+ end
260
+ data
261
+ end
262
+ end
263
+
264
+ def _write data, pack = nil
265
+ data = [*data].pack(pack) if pack
266
+ @data[@pos,0] = data
267
+ @pos += data.length
268
+ end
269
+ end
270
+ end
271
+
272
+ if $0 =~ /bacon/ or $0 == __FILE__
273
+ require 'bacon'
274
+ include AMQP
275
+
276
+ describe Buffer do
277
+ before do
278
+ @buf = Buffer.new
279
+ end
280
+
281
+ should 'have contents' do
282
+ @buf.contents.should == ''
283
+ end
284
+
285
+ should 'initialize with data' do
286
+ @buf = Buffer.new('abc')
287
+ @buf.contents.should == 'abc'
288
+ end
289
+
290
+ should 'append raw data' do
291
+ @buf << 'abc'
292
+ @buf << 'def'
293
+ @buf.contents.should == 'abcdef'
294
+ end
295
+
296
+ should 'append other buffers' do
297
+ @buf << Buffer.new('abc')
298
+ @buf.data.should == 'abc'
299
+ end
300
+
301
+ should 'have a position' do
302
+ @buf.pos.should == 0
303
+ end
304
+
305
+ should 'have a length' do
306
+ @buf.length.should == 0
307
+ @buf << 'abc'
308
+ @buf.length.should == 3
309
+ end
310
+
311
+ should 'know the end' do
312
+ @buf.empty?.should == true
313
+ end
314
+
315
+ should 'read and write data' do
316
+ @buf._write('abc')
317
+ @buf.rewind
318
+ @buf._read(2).should == 'ab'
319
+ @buf._read(1).should == 'c'
320
+ end
321
+
322
+ should 'raise on overflow' do
323
+ lambda{ @buf._read(1) }.should.raise Buffer::Overflow
324
+ end
325
+
326
+ should 'raise on invalid types' do
327
+ lambda{ @buf.read(:junk) }.should.raise Buffer::InvalidType
328
+ lambda{ @buf.write(:junk, 1) }.should.raise Buffer::InvalidType
329
+ end
330
+
331
+ { :octet => 0b10101010,
332
+ :short => 100,
333
+ :long => 100_000_000,
334
+ :longlong => 666_555_444_333_222_111,
335
+ :shortstr => 'hello',
336
+ :longstr => 'bye'*500,
337
+ :timestamp => time = Time.at(Time.now.to_i),
338
+ :table => { :this => 'is', :a => 'hash', :with => {:nested => 123, :and => time, :also => 123.456} },
339
+ :bit => true
340
+ }.each do |type, value|
341
+
342
+ should "read and write a #{type}" do
343
+ @buf.write(type, value)
344
+ @buf.rewind
345
+ @buf.read(type).should == value
346
+ @buf.should.be.empty
347
+ end
348
+
349
+ end
350
+
351
+ should 'read and write multiple bits' do
352
+ bits = [true, false, false, true, true, false, false, true, true, false]
353
+ @buf.write(:bit, bits)
354
+ @buf.write(:octet, 100)
355
+
356
+ @buf.rewind
357
+
358
+ bits.map do
359
+ @buf.read(:bit)
360
+ end.should == bits
361
+ @buf.read(:octet).should == 100
362
+ end
363
+
364
+ should 'read and write properties' do
365
+ properties = ([
366
+ [:octet, 1],
367
+ [:shortstr, 'abc'],
368
+ [:bit, true],
369
+ [:bit, false],
370
+ [:shortstr, nil],
371
+ [:timestamp, nil],
372
+ [:table, { :a => 'hash' }],
373
+ ]*5).sort_by{rand}
374
+
375
+ @buf.write(:properties, properties)
376
+ @buf.rewind
377
+ @buf.read(:properties, *properties.map{|type,_| type }).should == properties.map{|_,value| value }
378
+ @buf.should.be.empty
379
+ end
380
+
381
+ should 'do transactional reads with #extract' do
382
+ @buf.write :octet, 8
383
+ orig = @buf.to_s
384
+
385
+ @buf.rewind
386
+ @buf.extract do |b|
387
+ b.read :octet
388
+ b.read :short
389
+ end
390
+
391
+ @buf.pos.should == 0
392
+ @buf.data.should == orig
393
+ end
394
+ end
395
+ end