pants 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 @@
1
+
@@ -0,0 +1,23 @@
1
+ # rcov generated
2
+ coverage
3
+
4
+ # rdoc generated
5
+ rdoc
6
+
7
+ # yard generated
8
+ doc
9
+ .yardoc
10
+
11
+ # bundler
12
+ .bundle
13
+
14
+ # packaging
15
+ pkg
16
+ pants*.gem
17
+
18
+ # Rubinius
19
+ .rbx
20
+
21
+ # IDE files
22
+ .idea/
23
+ atlassian-ide-plugin.xml
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --tty
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
@@ -0,0 +1,42 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ pants (0.1.0)
5
+ eventmachine (>= 1.0.0)
6
+ log_switch (>= 0.4.0)
7
+ thor
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ diff-lcs (1.1.3)
13
+ eventmachine (1.0.0)
14
+ eventmachine (1.0.0-java)
15
+ log_switch (0.4.0)
16
+ multi_json (1.5.0)
17
+ rake (10.0.3)
18
+ rspec (2.12.0)
19
+ rspec-core (~> 2.12.0)
20
+ rspec-expectations (~> 2.12.0)
21
+ rspec-mocks (~> 2.12.0)
22
+ rspec-core (2.12.2)
23
+ rspec-expectations (2.12.1)
24
+ diff-lcs (~> 1.1.3)
25
+ rspec-mocks (2.12.1)
26
+ simplecov (0.7.1)
27
+ multi_json (~> 1.0)
28
+ simplecov-html (~> 0.7.1)
29
+ simplecov-html (0.7.1)
30
+ thor (0.17.0)
31
+ yard (0.8.3)
32
+
33
+ PLATFORMS
34
+ java
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ pants!
39
+ rake
40
+ rspec (>= 2.6.0)
41
+ simplecov
42
+ yard (>= 0.7.2)
@@ -0,0 +1,6 @@
1
+ === 0.1.0 / 2012-12-15
2
+
3
+ * 1 major enhancement
4
+
5
+ * Birthday!
6
+
@@ -0,0 +1,339 @@
1
+ = pants
2
+
3
+ * https://github.com/turboladen/pants
4
+
5
+ {<img src="https://travis-ci.org/turboladen/pants.png?branch=master" alt="Build Status" />}[https://travis-ci.org/turboladen/pants]
6
+ {<img src="https://codeclimate.com/badge.png" />}[https://codeclimate.com/github/turboladen/pants]
7
+
8
+ == DESCRIPTION:
9
+
10
+ Pants redirects IO using {http://rubyeventmachine.com EventMachine} from one
11
+ input source to many different destinations. In some senses, pants is like a
12
+ *nix pipe that (works on Windows and) allows for duplicating data across many
13
+ pipes (like splice and tee).
14
+
15
+ == FEATURES/PROBLEMS:
16
+
17
+ Read raw data from a
18
+ * file
19
+ * UDP socket (unicast or multicast)
20
+
21
+ ...and write to any number and combination of
22
+ * files
23
+ * UDP sockets (unicast or multicast)
24
+ * pants seams
25
+
26
+ Also:
27
+ * All readers can write to any other writer
28
+ * Pluggable: write your own reader or writer
29
+
30
+
31
+ == SYNOPSIS:
32
+
33
+ === Concepts
34
+
35
+ ==== Readers & Writers
36
+
37
+ The core concept of pants is that without using threads and system calls, it's
38
+ difficult to duplicate data. Pants simplifies duplicating data from a single
39
+ source by using "readers" to read data from a source, then packetize it in some
40
+ form that "writers" can consume and use for their individual needs. These
41
+ readers and writers are similar to read and write ends of a *nix pipe, but with
42
+ pants there can be more than one write end of the pipe.
43
+
44
+ For example, you can use a UDP reader to read data on an IP and port, then
45
+ simultaneously write that data to a file and forward it on to another IP and
46
+ port using writers.
47
+
48
+ ==== Seams
49
+
50
+ Next, pants also uses "seams" to act as a middle-man for readers and writers. A
51
+ seam is just like another reader, but it can read from other readers. Seams are
52
+ primarily useful for doing something to the data from a reader before passing it
53
+ on to writers. Think of it like a pipe with one read end and the ability to
54
+ have many write ends.
55
+
56
+ For example, the {rtp}[https://github.com/turboladen/rtp] gem
57
+ is responsible for (amongst other things) pulling an A/V stream out of an A/V
58
+ file, making sure those chunks of data (A/V frames) are sized well for sending
59
+ out via UDP, then adding some headers to each on of those chunks (packets), then
60
+ actually sending each of those packets to some number of UDP endpoints. To do
61
+ this with pants, it would:
62
+
63
+ * Use a reader (well, a demuxer to be clear) to read the A/V stream in the file
64
+ * Use a seam to accept the A/V stream data chunks, make sure the chunks are
65
+ sized right, then add an RTP header to each chunk
66
+ * Use a writer (or many writers) to read from the seam and send the data out to
67
+ a UDP IP/port (or many UDP IPs/ports)
68
+
69
+ === Pros and Cons
70
+
71
+ ==== Pros
72
+
73
+ If you plan to do the above for just one UDP client, the benefits of pants might
74
+ not quite shine through (although it will do this quite quickly, and only in a
75
+ maybe three lines of code); if you intend to be sending the RTP-encoded data to
76
+ a number of UDP clients though, pants shines in that you will have only had to
77
+ spend system resources for reading the file once and encoding the data (which
78
+ can be an expensive operation) once before sending it out over the network to
79
+ all of your clients. Quite often, network servers will use multicast to achieve
80
+ this, but pants can do this with both unicast and multicast--or both at the same
81
+ time, while writing to a file (<-- maybe for debug purposes, for example?).
82
+
83
+ Also, since pants uses eventmachine, you don't have to worry about dealing with
84
+ threads. If you're writing your own reader or writer, you do, however, need to
85
+ consider if you should <tt>defer</tt> (eventmachine's means of putting your code
86
+ into a thread) the code you want to do the reading/writing. This is just a
87
+ matter of wrapping that code in block though, while eventmachine handles the
88
+ threading for you.
89
+
90
+ ==== Cons
91
+
92
+ The amount of data you can replicate really depends, of course, on system
93
+ resources. On an i7 MacBook Pro with 16GB RAM and 2 wired NICs, I've been able
94
+ to duplicate a single 720p video + audio stream over unicast UDP 200 times (pushing ~1.4
95
+ Gbps out) with almost no quality loss on client ends. If you plan to duplicate
96
+ more than 20 streams, you'll need to start tweaking EventMachine thread pool
97
+ settings; generally you should set EventMachines +threadpool_size+ to the number
98
+ of output streams so it can process all of the data concurrently. If you're on
99
+ OSX or *nix, you might benefit from using EventMachine's .epoll and .kqueue
100
+ feature. More on that {http://eventmachine.rubyforge.org/docs/EPOLL.html here}.
101
+
102
+ Just like in any case when dealing with I/O, file reading/writing also depends
103
+ on many factors, such as the number and size of files, disk capabilities, and
104
+ general I/O capabilities of your system. If you'd like to benchmark, there's a
105
+ {Thor}[http://whatisthor.com] task in +tasks/+ that will compare pants to
106
+ FileUtils.cp and `cp` in copying a file some number of times (default to 100).
107
+
108
+ Example:
109
+
110
+ $ tasks/pantsmark.thor file_copy some_song.mp3 --times=50
111
+
112
+ If you're wanting to write your own readers/writers/seams and you're not
113
+ familiar with eventmachine, getting plugged in to some of its concepts can bek
114
+ frustrating, as it's a different paradigm to work off than the standard paradigm
115
+ for doing I/O. Some may say that this is a con, and some may not.
116
+
117
+ === Examples
118
+
119
+ ==== As a library
120
+
121
+ Read unicast UDP inbound data and write to a number of other unicast UDP clients:
122
+
123
+ Pants.read('udp://0.0.0.0:1234') do |reader|
124
+ reader.write_to 'udp://10.0.0.10:1234'
125
+ reader.write_to 'udp://10.0.0.11:1234'
126
+ end
127
+
128
+ Read multicast UDP inbound data and write to a number of other unicast UDP clients:
129
+
130
+ Pants.read('udp://239.0.0.1:1234') do |reader|
131
+ reader.write_to 'udp://10.0.0.10:1234'
132
+ reader.write_to 'udp://10.0.0.11:1234'
133
+ end
134
+
135
+ Read unicast UDP inbound data and write to a UDP client and a file:
136
+
137
+ Pants.read('udp://0.0.0.0:1234') do |reader|
138
+ reader.write_to 'udp://10.0.0.10:1234'
139
+ reader.write_to 'socket_data.raw'
140
+ end
141
+
142
+ Read a file and send out via UDP:
143
+
144
+ Pants.read('socket_data.raw') do |reader|
145
+ reader.write_to 'udp://10.0.0.10:1234'
146
+ reader.write_to 'udp://239.0.0.1:1234'
147
+ end
148
+
149
+ Get kray-kray:
150
+
151
+ EM.threadpool_size = 110
152
+ EM.kqueue # This has been problematic for me...
153
+ EM.epoll
154
+
155
+ Pants.read('udp://0.0.0.0:1234') do |reader|
156
+ 100.times do |i|
157
+ reader.write_to "udp://10.0.0.10:#{1235 + i}"
158
+ end
159
+
160
+ 10.times do |i|
161
+ reader.write_to "socket_data_#{i}.raw"
162
+ end
163
+ end
164
+
165
+ The block form above is really just some syntactic sugar for doing:
166
+
167
+ core = Pants::Core.new
168
+ reader = core.read 'udp://0.0.0.0:1234'
169
+ reader.write_to 'udp://10.0.0.10:1234'
170
+ reader.write_to 'udp://10.0.0.11:1234'
171
+ core.run
172
+
173
+ ...and that's actually short for for:
174
+
175
+ core = Pants::Core.new
176
+ reader = core.add_reader(Pants::Readers::UDPReader, '0.0.0.0', 1234)
177
+ reader.add_writer(Pants::Writers::UDPWriter, '10.0.0.10', 1234)
178
+ reader.add_writer(Pants::Writers::UDPWriter, '10.0.0.11', 1234)
179
+ core.run
180
+
181
+ ...which can be made even longer (but potentially more helpful):
182
+
183
+ core = Pants::Core.new
184
+ reader = Pants::Readers::UDPReader.new('0.0.0.0', 1234, core.callback)
185
+ core.add_reader(reader)
186
+
187
+ writer1 = Pants::Writers::UDPWriter.new('10.0.0.10', 1234, reader.write_to_channel)
188
+ reader.add_writer(writer1)
189
+
190
+ writer2 = Pants::Writers::UDPWriter.new('10.0.0.11', 1234, reader.write_to_channel)
191
+ reader.add_writer(writer2)
192
+
193
+ core.run
194
+
195
+ Using this unsugared form, does give you some flexibility though to use within
196
+ your code. If, for example, you're writing a server of some sort that needs to
197
+ handle a varying number of clients, you can add and remove writers at will:
198
+
199
+ require 'sinatra'
200
+
201
+ core = Pants::Core.new
202
+ reader = core.read 'udp://0.0.0.0:1234'
203
+
204
+ post '/:client' do
205
+ reader.write_to "udp://#{params[:client]}"
206
+ end
207
+
208
+ delete '/:client' do
209
+ reader.remove_writer("udp://#{params[:client]}")
210
+ end
211
+
212
+ core.run
213
+
214
+ ==== As an executable
215
+
216
+ The examples from above can also be run via the command-line app like this:
217
+
218
+ $ pants udp://0.0.0.0:1234 --dest=udp://10.0.0.10:1234 udp://10.0.0.11:1234 udp://10.0.0.11:2345
219
+ $ pants udp://239.0.0.1:1234 --dest=udp://10.0.0.10:1234 udp://10.0.0.11:1234 udp://10.0.0.11:2345
220
+ $ pants udp://0.0.0.0:1234 --dest=udp://10.0.0.10:1234 file:///home/me/socket_data.raw
221
+ $ pants /home/me/socket_data.raw --dest=udp://10.0.0.10:1234 udp://239.0.0.1:1234
222
+
223
+ ==== Use a seam to inspect and alter packets
224
+
225
+ require 'pants'
226
+
227
+ class PantsInspector < Pants::Seam
228
+ def initialize(core_callback, reader_channel, host)
229
+ @host = host
230
+
231
+ super(core_callback, reader_channel)
232
+ end
233
+
234
+ # Pants will call this for you when it starts the reader that the seam is
235
+ # reading from.
236
+ def start
237
+
238
+ # You need to define a callback here so pants can call you back after its
239
+ # made sure that the seam's writers have been started. This makes sure
240
+ # the seam doesn't start reading and pushing out data before the writers
241
+ # are ready to receive it.
242
+ callback = EM.Callback do
243
+ puts "Starting #{self.class}..."
244
+
245
+ read_items do |data|
246
+ if data.match(/pants/mi)
247
+ data << "Pants party at #{@host}!!!!!!"
248
+ end
249
+
250
+ write data
251
+ end
252
+ end
253
+
254
+ super(callback)
255
+ end
256
+ end
257
+
258
+ # Assuming you have data coming in on this IP and UDP port, this reads each
259
+ # packet and hands it over to its writers--in this case, the file dump file
260
+ # writer, the UDP writer that sends to 10.0.0.50:9001, and our PacketInspector
261
+ # seam.
262
+ Pants.read('udp://127.0.0.1:1234') do |reader|
263
+
264
+ # Dump UDP packets to a file
265
+ reader.write_to 'file_dump_of_udp_packets.udp'
266
+
267
+ # Redirect the UDP packets to another socket
268
+ reader.write_to 'udp://127.0.0.1:9000'
269
+
270
+ # Inspect the UDP packets and notify about the party going on
271
+ pants_inspector = reader.add_seam(PantsInspector, 'localhost')
272
+
273
+ # The packet inspector seam behaves like a reader, which lets writers read
274
+ # from it. Dump those party packets to a file...
275
+ pants_inspector.write_to 'file_dump_of_party_packets.party'
276
+
277
+ # Forward our partying packets on to another host.
278
+ pants_inspector.write_to 'udp://10.0.0.1:1234'
279
+ end
280
+
281
+ If you actually run this, you'll have to ctrl-c out of it, as pants will read on
282
+ that UDP socket indefinitely. After doing that, you'll see that you've now got
283
+ a 'file_dump_of_udp_packets.udp', which contains the raw UDP data that came in
284
+ on port 1234, and a 'file_dump_of_party_packets.party' file, which contains
285
+ those UDP packets with our party message at the end of each packet. If you had
286
+ a client listening at 127.0.0.1:9000 and 10.0.0.1:1234, it would received the
287
+ exact same data that was written to the respective files.
288
+
289
+ == REQUIREMENTS:
290
+
291
+ * Rubies (tested)
292
+ * MRI 1.9.3
293
+ * JRuby 1.7.2 (failing due to what looks like EventMachine bugs)
294
+ * Rubinius (failing due to what looks like EventMachine bugs)
295
+ * Gems
296
+ * eventmachine (>=1.0.0)
297
+ * log_switch
298
+ * thor
299
+
300
+ _NOTE:_ Multicasting with JRuby doesn't seem to work; EM fails to allow setting
301
+ socket options, which is necessary to do multicasting.
302
+
303
+ == INSTALL:
304
+
305
+ * (sudo) gem install
306
+
307
+ == DEVELOPERS:
308
+
309
+ After checking out the source, run:
310
+
311
+ $ bundle install
312
+
313
+ This task will install any missing dependencies, run the tests/specs,
314
+ and generate the RDoc.
315
+
316
+ == LICENSE:
317
+
318
+ (The MIT License)
319
+
320
+ Copyright (c) 2013 Steve Loveless
321
+
322
+ Permission is hereby granted, free of charge, to any person obtaining
323
+ a copy of this software and associated documentation files (the
324
+ 'Software'), to deal in the Software without restriction, including
325
+ without limitation the rights to use, copy, modify, merge, publish,
326
+ distribute, sublicense, and/or sell copies of the Software, and to
327
+ permit persons to whom the Software is furnished to do so, subject to
328
+ the following conditions:
329
+
330
+ The above copyright notice and this permission notice shall be
331
+ included in all copies or substantial portions of the Software.
332
+
333
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
334
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
335
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
336
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
337
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
338
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
339
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'bundler/gem_tasks'
5
+ require 'rspec/core/rake_task'
6
+ require 'yard'
7
+
8
+ YARD::Rake::YardocTask.new do |t|
9
+ t.files = %w(lib/**/*.rb - History.rdoc)
10
+ t.options = %w(--title pants Documentation (#{Pants::VERSION}))
11
+ t.options += %w(--main README.rdoc)
12
+ end
13
+
14
+ RSpec::Core::RakeTask.new do |t|
15
+ t.ruby_opts = %w(-w)
16
+ end
17
+
18
+ # Alias for rubygems-test
19
+ task :test => :spec
20
+
21
+ task :default => :test
22
+
23
+ # vim: syntax=ruby