pipeline_toolkit 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/README.rdoc ADDED
@@ -0,0 +1,54 @@
1
+ = Pipeline Toolkit
2
+ by VisFleet
3
+
4
+ Command line tools for processing messages by constructing a pipeline of workers. AMQP and Unix pipes are used to construct the pipeline. Messages are simple Hashs (serialized as YAML) so they can hold any values and change throughout the processing.
5
+
6
+ Provides:
7
+ - Processing acknowledgments, ensuring the a message is only disposed of once it has been successful processed
8
+ - Performance. Messages are moved through the pipeline fast
9
+ - Command line tools for:
10
+ - Subscribing to messages from an AMQP queue
11
+ - Pushing messages back onto an AMQP exchange
12
+ - Monitoring performance (see msg_probe)
13
+ - A base module (MessageCommand) to include into your own classes to quickly make workers.
14
+
15
+ == Install
16
+
17
+ > gem sources -a http://gems.github.com
18
+ > sudo gem install visfleet-pipeline_toolkit
19
+
20
+ === Dependancies
21
+
22
+ It is assumed that you have:
23
+ - An AMQP msg server to pop and push messages to (e.g. http://www.rabbitmq.com/)
24
+ - A *nix system, such as Linux or Mac OS X.
25
+
26
+ == Usage
27
+
28
+ 1. Create your worker
29
+
30
+ class MyWorker
31
+ include MessageCommand
32
+
33
+ def process_message(msg)
34
+ # do stuff here
35
+ msg
36
+ end
37
+ end
38
+ MyWorker.new.start
39
+
40
+ 2. Hook it up to the main pipe (i.e. the AMQP server)
41
+
42
+ > msg_subscribe.rb -q source | my_worker.rb | msg_push.rb -x dest
43
+
44
+ You can learn more about the command line tools and what options are available by using their help command.
45
+
46
+ > msg_subscriber --help
47
+ > msg_push --help
48
+ > msg_sink --help
49
+ > msg_probe --help
50
+
51
+
52
+
53
+
54
+
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "pipeline_toolkit"
8
+ gem.summary = %Q{Toolkit for building processing pipelines using Unix Pipes and AMQP messages}
9
+ gem.email = "labs@visfleet.com"
10
+ gem.homepage = "http://github.com/visfleet/pipeline_toolkit"
11
+ gem.authors = ["Aisha Fenton"]
12
+ gem.executables = ["msg_probe.rb", "msg_subscribe.rb", "msg_push.rb", "msg_sink.rb", "msg_generator.rb"]
13
+ gem.add_runtime_dependency('amqp', ">=0.6.4")
14
+ gem.add_runtime_dependency('trollop', ">=1.14")
15
+ gem.add_runtime_dependency('eventmachine', ">=0.12.8")
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
21
+ end
22
+
23
+ task :default => :build
24
+
25
+ require 'rake/rdoctask'
26
+ Rake::RDocTask.new do |rdoc|
27
+ if File.exist?('VERSION.yml')
28
+ config = YAML.load(File.read('VERSION.yml'))
29
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
30
+ else
31
+ version = ""
32
+ end
33
+
34
+ rdoc.rdoc_dir = 'rdoc'
35
+ rdoc.title = "pipeline_toolkit #{version}"
36
+ rdoc.rdoc_files.include('README*')
37
+ rdoc.rdoc_files.include('lib/**/*.rb')
38
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'trollop'
4
+ require 'pipeline_toolkit'
5
+
6
+ opts = Trollop::options do
7
+ banner <<-EOS
8
+ Generates messages for testing the pipeline
9
+ Usage:
10
+ msg_generator.rb -m "My message"
11
+ EOS
12
+ opt :msg, "The message to send", :short => "m", :default => "Test message"
13
+ opt :delay, "Sleep time between sends in seconds", :short => "d", :type => :float
14
+ end
15
+
16
+ class MsgGenerator
17
+
18
+ def initialize(opts)
19
+ @delay = opts.delay
20
+ @msg = opts.msg
21
+ end
22
+
23
+ def start
24
+ loop do
25
+ self.send_msg(@msg)
26
+ sleep(@delay) unless @delay.nil?
27
+ end
28
+ end
29
+
30
+ def send_msg(msg)
31
+ puts MessageCoder.encode(@msg)
32
+ $stdout.flush
33
+ end
34
+ end
35
+
36
+ MsgGenerator.new(opts).start
data/bin/msg_probe.rb ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'trollop'
5
+ require 'pipeline_toolkit'
6
+
7
+ opts = Trollop::options do
8
+ opt :interval, "Time in seconds between updates", :short => "i", :default => 2
9
+ opt :http_port, "The HTTP server port for accessing the stats", :default => 9070
10
+ opt :name, "The name of the probe. Used in monitoring", :type => :string, :short => 'n'
11
+ end
12
+
13
+ MessageProbe.new(opts).start
data/bin/msg_push.rb ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'trollop'
5
+ require 'pipeline_toolkit'
6
+
7
+ opts = Trollop::options do
8
+ opt :exchanges, "The destination exchange(s)", :short => "x", :type => :strings
9
+ opt :key_eval, "A string of ruby code that is evaluated to produce a routing key for a given message.
10
+ By default each message has no routing key", :short => "e", :type => :string
11
+ opt :key_file, "A ruby file that gets included which contains a custom route_key method", :short => "f", :type => :string
12
+
13
+ # Msg server
14
+ opt :host, "The AMQP message server host", :default => "localhost"
15
+ opt :port, "The AMQP message server port", :default => "5672"
16
+ opt :user, "The AMQP message server username", :default => "guest"
17
+ opt :pass, "The AMQP message server username", :default => "guest"
18
+ opt :vhost, "The AMQP message server vhost", :default => "/"
19
+ end
20
+
21
+ mp = MessagePusher.new(opts)
22
+ # FIXME. Should be in MessageCommand class
23
+ Signal.trap('INT') { mp.stop }
24
+ Signal.trap('TERM'){ mp.stop }
25
+ mp.start
data/bin/msg_sink.rb ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'trollop'
5
+ require 'pipeline_toolkit'
6
+
7
+ # opts = Trollop::options do
8
+ # opt :interval, "Interval", :short => "i", :default => 100
9
+ # end
10
+
11
+ MessageSink.new.start
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'trollop'
4
+ require 'pipeline_toolkit'
5
+
6
+ opts = Trollop::options do
7
+ opt :exchange, "The exchange to subscribe to", :short => "x", :type => :string
8
+ opt :queue, "The source queue", :short => "q", :type => :string
9
+ opt :ack, "Switch that requires that messages are successfully processed before continuing with the next message", :short => "a"
10
+ opt :topic, "The queue topic to subscribe to", :short => "t", :type => :string
11
+ opt :max_unackd, "The maximum number of unaknowledged messages to buffer before waiting for them to be acknowledged. Defaults to 100", :default => 100
12
+
13
+ # Msg server
14
+ opt :host, "The AMQP message server host", :default => "localhost"
15
+ opt :port, "The AMQP message server port", :default => "5672"
16
+ opt :user, "The AMQP message server username", :default => "guest"
17
+ opt :pass, "The AMQP message server username", :default => "guest", :short => "w"
18
+ opt :vhost, "The AMQP message server vhost", :default => "/"
19
+ end
20
+
21
+ ms = MessageSubscriber.new(opts).start
@@ -0,0 +1,8 @@
1
+ require "pipeline_toolkit/default_logger"
2
+ require "pipeline_toolkit/message_coder"
3
+ require "pipeline_toolkit/message_command"
4
+ require "pipeline_toolkit/message_probe"
5
+ require "pipeline_toolkit/message_pusher"
6
+ require "pipeline_toolkit/message_subscriber"
7
+ require "pipeline_toolkit/message_sink"
8
+ require "pipeline_toolkit/open_hash"
@@ -0,0 +1,18 @@
1
+ require "logger"
2
+ require 'syslog_logger'
3
+
4
+ # OPTIMIZE. Is there a better way to handle default/common/shared logging?
5
+ module DefaultLogger
6
+
7
+ # Return the logger for this class
8
+ def log
9
+ @logger ||= SyslogLogger.new(self.syslog_name)
10
+ end
11
+
12
+ def syslog_name
13
+ "pipeline-" + Process.ppid.to_s
14
+ end
15
+
16
+ end
17
+
18
+
@@ -0,0 +1,19 @@
1
+
2
+ # Encodes messages
3
+ #
4
+ class MessageCoder
5
+
6
+ def self.encode(msg)
7
+ # NB: Using Marshal here because it's 9-10x faster than to_yaml
8
+ # See http://gist.github.com/190849
9
+ str = Marshal.dump(msg)
10
+ str.gsub!("\n", '--\\n')
11
+ str
12
+ end
13
+
14
+ def self.decode(str)
15
+ str.gsub!('--\\n', "\n")
16
+ Marshal.load(str)
17
+ end
18
+
19
+ end
@@ -0,0 +1,97 @@
1
+ require "rubygems"
2
+ require "eventmachine"
3
+
4
+ module MessageCommand
5
+ include DefaultLogger
6
+
7
+ attr_reader :sys_pipe
8
+
9
+ def start
10
+ log.info("starting")
11
+
12
+ Signal.trap('INT') { EM.stop }
13
+ Signal.trap('TERM'){ EM.stop }
14
+
15
+ @ack_buffer ||= ""
16
+
17
+ begin
18
+ EM.run do
19
+ self.init_loop
20
+ conn = EM.watch($stdin, ProcessLine, self)
21
+ conn.notify_readable = true
22
+ end
23
+ rescue StandardError => e
24
+ log.info e
25
+ raise e
26
+ ensure
27
+ self.shutdown
28
+ end
29
+ end
30
+
31
+ def shutdown
32
+ log.info("shutting down")
33
+ @sys_pipe && @sys_pipe.close
34
+ end
35
+
36
+ def process_line(line)
37
+ msg = MessageCoder.decode(line)
38
+
39
+ case msg[:msg_type]
40
+ when :system
41
+ result = process_system(msg)
42
+ else
43
+ result = process_message(msg)
44
+ end
45
+
46
+ case result
47
+ when :ack
48
+ self.ack_msg(msg)
49
+ else
50
+ self.pass_on_msg(result)
51
+ end
52
+ end
53
+
54
+ def ack_msg(msg)
55
+ return unless @use_ack
56
+ @sys_pipe.syswrite(msg.ack_id + "\n")
57
+ end
58
+
59
+ def pass_on_msg(msg)
60
+ $stdout.syswrite(MessageCoder.encode(msg) << "\n")
61
+ end
62
+
63
+ # Override in included class. Provides a chance to initialize any
64
+ # code that needs to take place once the EM loop has started.
65
+ def init_loop
66
+ # Implemented in class that includes me
67
+ end
68
+
69
+ # Override in included class. Processes a message. This method
70
+ # must return either a msg object -- which may or may not have been modified -- or the symbol :ack.
71
+ # Returning :ack mean that the message has been dealt with and can be acknowledged back to the queue
72
+ # server. All messages must be acknowledged by at least one message_command.
73
+ def process_message(msg)
74
+ # Implemented in class that includes me
75
+ msg
76
+ end
77
+
78
+ def process_system(msg)
79
+ @sys_pipe = File.open(msg.sys_pipe, "w")
80
+ @use_ack = msg.use_ack
81
+ @max_unackd = msg.max_unackd
82
+ msg
83
+ end
84
+
85
+ module ProcessLine
86
+ include DefaultLogger
87
+
88
+ def initialize(msg_command)
89
+ @msg_command = msg_command
90
+ end
91
+
92
+ def notify_readable
93
+ @msg_command.process_line(@io.gets)
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,107 @@
1
+ require 'evma_httpserver'
2
+
3
+ class MessageProbe
4
+ include MessageCommand
5
+ attr_reader :mps, :uptime, :name
6
+
7
+ def initialize(opts)
8
+ @interval = opts.interval
9
+ @http_port= opts.http_port
10
+ @start_time = Time.now
11
+ @name = opts.name
12
+ self.reset
13
+ end
14
+
15
+ def init_loop
16
+ EM.start_server('0.0.0.0', @http_port, ProbeHttpRequest, self)
17
+ EM.add_periodic_timer(@interval) { self.tick }
18
+ end
19
+
20
+ def tick
21
+ @time_delta = Time.now - @prev_time
22
+ @mps = @count / @time_delta
23
+
24
+ self.reset
25
+ end
26
+
27
+ def reset
28
+ @count = 0
29
+ @prev_time = Time.now
30
+ end
31
+
32
+ def uptime
33
+ Time.now - @start_time
34
+ end
35
+
36
+ def process_message(msg)
37
+ @count += 1
38
+ msg
39
+ end
40
+
41
+ end
42
+
43
+ class ProbeHttpRequest < EM::Connection
44
+ include EM::HttpServer
45
+
46
+ def initialize(probe)
47
+ @probe = probe
48
+ end
49
+
50
+ def post_init
51
+ super
52
+ no_environment_strings
53
+ end
54
+
55
+ def process_http_request
56
+ response = EM::DelegatedHttpResponse.new(self)
57
+ response.status = 200
58
+ response.content_type 'text/html'
59
+ response.content = <<-EOL
60
+ <html>
61
+ <head>
62
+ <title>Message Probe</title>
63
+ <style type="text/css">
64
+ body {
65
+ background: black;
66
+ color: #80c0c0;
67
+ }
68
+ h1 {
69
+ font: 12pt Monospace;
70
+ text-align:center;
71
+ }
72
+ table {
73
+ font: 10pt Monospace;
74
+ margin-left:auto;
75
+ margin-right:auto;
76
+ text-align:right;
77
+ }
78
+ .page {
79
+ position:relative;
80
+ top: 20%;
81
+ # border-style:solid;
82
+ # border-width:5px;
83
+ width: 30%;
84
+ margin-left:auto;
85
+ margin-right:auto;
86
+ }
87
+ </style>
88
+ </head>
89
+ <body>
90
+ <div class=page>
91
+ <h1><span class="name">#{@probe.name}</span></h1>
92
+ <table>
93
+ <tr>
94
+ <td>messages per second:</td><td><span class="mps">#{@probe.mps}</span></td><td></td>
95
+ </tr>
96
+ <tr>
97
+ <td>uptime:</td><td><span class="uptime">#{@probe.uptime.to_i / 60}</span></td><td>mins</td
98
+ </tr>
99
+ </table>
100
+ </div>
101
+ </body>
102
+ </html>
103
+ EOL
104
+
105
+ response.send_response
106
+ end
107
+ end
@@ -0,0 +1,54 @@
1
+ require "mq"
2
+
3
+ class MessagePusher
4
+ include MessageCommand
5
+
6
+ def initialize(opts)
7
+ @key_eval = opts.key_eval
8
+ if opts.key_file
9
+ @key_file = opts.key_file
10
+ load_route(@key_file)
11
+ self.init_route
12
+ end
13
+ @exchange_names = opts.exchanges.map { |str| str.split(":") }
14
+ @msg_server_config = opts.select_keys(:host, :port, :user, :pass, :vhost)
15
+ end
16
+
17
+ def init_loop
18
+ @msg_server = MQ.new(AMQP.connect(@msg_server_config))
19
+ self.setup_exchanges
20
+ end
21
+
22
+ def setup_exchanges
23
+ @exchanges = []
24
+ @exchange_names.each do |name, type|
25
+ type ||= :fanout
26
+ @exchanges << MQ::Exchange.new(@msg_server, type.to_sym, name, :durable => true, :passive => false)
27
+ end
28
+ end
29
+
30
+ def load_route(key_file)
31
+ require key_file
32
+ self.extend eval(classify(key_file.gsub(".rb", "")))
33
+ end
34
+
35
+ # Turn path Class or Module name (i.e. strip directories and turn into camel-case)
36
+ def classify(str)
37
+ str.gsub(/^.*\//, '').gsub(".rb","").gsub(/(?:^|_)(.)/) { $1.upcase }
38
+ end
39
+
40
+ # is overriden by included fork_file if specified
41
+ def route_key(msg)
42
+ @key_eval ? eval(@key_eval) : nil
43
+ end
44
+
45
+ def process_message(msg)
46
+ @exchanges.each do |exchange|
47
+ key = route_key(msg)
48
+ exchange.publish(msg.to_yaml, :routing_key => key)
49
+ # OPTIMIZE. Using MessageCoder.encode(msg) instead of to_yaml is 2x faster. But won't be easy to debug. Worth it?
50
+ end
51
+ :ack
52
+ end
53
+
54
+ end
@@ -0,0 +1,8 @@
1
+ class MessageSink
2
+ include MessageCommand
3
+
4
+ def process_message(msg)
5
+ :ack
6
+ end
7
+
8
+ end
@@ -0,0 +1,138 @@
1
+ require "eventmachine"
2
+ require "mq"
3
+ require "time"
4
+ require "socket"
5
+
6
+ class MessageSubscriber
7
+ include DefaultLogger
8
+
9
+ PIPE_PATH = "/tmp"
10
+
11
+ def initialize(opts)
12
+ @exchange_name, @exchange_type = opts[:exchange].split(":")
13
+ @queue_name = opts[:queue]
14
+ @use_ack = opts[:ack]
15
+ @topic = opts[:topic]
16
+ @msg_server_opts = opts.select_keys(:host, :port, :user, :pass, :vhost)
17
+ @unackd_msgs = {}
18
+ @max_unackd = opts[:max_unackd]
19
+ end
20
+
21
+ def start
22
+ Signal.trap('INT') { AMQP.stop{ EM.stop } }
23
+ Signal.trap('TERM'){ AMQP.stop{ EM.stop } }
24
+
25
+ begin
26
+ self.create_sys_pipe
27
+ AMQP.start(@msg_server_opts) do
28
+ # For ack to work appropriatly you must shutdown AMQP gracefully,
29
+ # otherwise all items in your queue will be returned
30
+ # FIXME. Doesn't shut down cleanly with these commands included. Why?
31
+
32
+ self.setup_queue
33
+ # NB. prefetch limits the amount of unknowledged messages that come down the pipe.
34
+ MQ.prefetch(@max_unackd)
35
+ @queue.subscribe(:ack => @use_ack) do |header, body|
36
+ self.process_msg(header, body)
37
+ end
38
+
39
+ EM.attach(@sys_pipe, HandleAcks, @sys_pipe, @unackd_msgs)
40
+ end
41
+ rescue StandardError => e
42
+ log.info e
43
+ raise e
44
+ ensure
45
+ self.shutdown
46
+ end
47
+ end
48
+
49
+ def shutdown
50
+ log.info "Shutting down"
51
+ self.destroy_sys_pipe
52
+ end
53
+
54
+ def setup_queue
55
+ # If a queue_name is given then we treat the queue as fixed, otherwise as temporary
56
+ @queue = @queue_name ? MQ.queue(@queue_name, :durable => true) :
57
+ self.generate_temporary_queue
58
+ if @exchange_name
59
+ create_exchange(@exchange_name, (@exchange_type || :fanout))
60
+ log.info("Binding to exchange:#{@exchange_str} #{@topic ? "using topic:" + @topic : ""}")
61
+ @queue.bind(@exchange_str, :key => @topic)
62
+ end
63
+ end
64
+
65
+ def create_exchange(name, type)
66
+ MQ::Exchange.new(MQ.default, type.to_sym, @exchange_name, :durable => true, :passive => false)
67
+ end
68
+
69
+ def create_sys_pipe
70
+ log.debug("creating sys-pipe")
71
+ name = File.join(PIPE_PATH, "sys_pipe_#{self.generate_guid}")
72
+ `mkfifo #{name}`
73
+ @sys_pipe = File.new(name, "r+")
74
+ $stdout.puts(MessageCoder.encode({:msg_type => :system,
75
+ :sys_pipe => name,
76
+ :use_ack => @use_ack,
77
+ :max_unackd => @max_unackd}))
78
+ $stdout.flush
79
+ end
80
+
81
+ def destroy_sys_pipe
82
+ `rm #{@sys_pipe.path}`
83
+ end
84
+
85
+ def generate_temporary_queue
86
+ qname = "#{Socket.gethostname}_#{self.generate_guid}"
87
+ log.debug("Binding temporary queue #{qname}")
88
+ MQ.queue(qname, :auto_delete => true, :durable => false)
89
+ end
90
+
91
+ # Generates a guid. Stolen from EM.
92
+ def generate_guid
93
+ # Cache uuidgen seed for better performance
94
+ if @ix and @ix >= 10_000
95
+ @ix = nil
96
+ @seed = nil
97
+ end
98
+
99
+ # NB. This will only work on *nix platforms
100
+ @seed ||= `uuidgen`.chomp.gsub(/-/,"")
101
+ @ix ||= 0
102
+
103
+ "#{@seed}#{@ix += 1}"
104
+ end
105
+
106
+ def process_msg(header, body)
107
+ msg = YAML.load(body)
108
+ store_ack(msg, header) if @use_ack
109
+ write_msg(msg)
110
+ end
111
+
112
+ def write_msg(msg)
113
+ $stdout.syswrite(MessageCoder.encode(msg) << "\n")
114
+ end
115
+
116
+ def store_ack(msg, header)
117
+ msg.ack_id = header.delivery_tag.to_s
118
+ @unackd_msgs[msg.ack_id] = header
119
+ end
120
+
121
+ # Handles msg acks
122
+ module HandleAcks
123
+ include DefaultLogger
124
+
125
+ def initialize(sys_pipe, unackd_msgs)
126
+ @sys_pipe = sys_pipe
127
+ @unackd_msgs = unackd_msgs
128
+ end
129
+
130
+ def notify_readable
131
+ ack_id = @sys_pipe.gets.chomp!
132
+ header = @unackd_msgs.delete(ack_id)
133
+ header.ack
134
+ end
135
+ end
136
+
137
+ end
138
+
@@ -0,0 +1,24 @@
1
+ # Stolen from
2
+ # http://github.com/karottenreibe/ohash/
3
+ module OpenHash
4
+ def method_missing(meth, *args)
5
+ method = meth.to_s
6
+
7
+ if method =~ %r{.+=$}
8
+ super unless args.length == 1
9
+ self[method[0...-1].to_sym] = args.first
10
+ else
11
+ self[meth]
12
+ end
13
+ end
14
+
15
+ def select_keys(*keys)
16
+ h = {}
17
+ self.each do |key, value|
18
+ h[key] = value if keys.include?(key)
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ Hash.send(:include, OpenHash)
data/monitor/munin.rb ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'open-uri'
4
+ require 'trollop'
5
+ require 'nokogiri'
6
+
7
+ class MuninPlugin
8
+
9
+ def initialize(is_config, opts)
10
+ @probe_urls = opts.probe_urls
11
+
12
+ self.get_probes
13
+
14
+ if is_config
15
+ self.config
16
+ else
17
+ self.data
18
+ end
19
+ end
20
+
21
+ def get_probes
22
+ @probes = []
23
+ @probe_urls.each_with_index do |url, idx|
24
+ @probes << get_probe(url)
25
+ end
26
+ @probes
27
+ end
28
+
29
+ def get_probe(url)
30
+ probe = Probe.new
31
+ doc = Nokogiri::HTML(open(url))
32
+ probe.name = extract_mf_value(doc, "name")
33
+ probe.mps = extract_mf_value(doc, "mps")
34
+ probe.uptime = extract_mf_value(doc, "uptime")
35
+ probe
36
+ end
37
+
38
+ # Extracts the microformat value from the HTML doc
39
+ def extract_mf_value(doc, key)
40
+ doc.css(".#{key}").first.content
41
+ end
42
+
43
+ def config
44
+ config=<<-EOL
45
+ graph_title Message Probe Throughput
46
+ graph_vlabel messages per second
47
+ graph_category Message Probe
48
+ EOL
49
+
50
+ @probes.each do |probe|
51
+ config << "probe_#{probe.name}.label #{probe.name}\n"
52
+ end
53
+
54
+ puts config
55
+ end
56
+
57
+ def data
58
+ data = ""
59
+ @probes.each do |probe|
60
+ data << "#{probe.name}.value #{probe.mps}\n"
61
+ end
62
+
63
+ puts data
64
+ end
65
+
66
+ end
67
+
68
+ class Probe
69
+ attr_accessor :name, :mps, :uptime
70
+ end
71
+
72
+ SUB_COMMANDS = %w{run config}
73
+
74
+ global_opts = Trollop::options do
75
+ banner <<-EOS
76
+ Munin plugin
77
+ Usage:
78
+ munin.rb [run|config] [options]
79
+
80
+ For more help run
81
+ munin.rb [command] --help
82
+ EOS
83
+ stop_on SUB_COMMANDS
84
+ end
85
+
86
+ is_config = (ARGV.shift == "config")
87
+ opts = Trollop::options do
88
+ opt :probe_urls, "The probe's URL", :short => "p", :type => :strings, :default => ["http://127.0.0.1:9070"]
89
+ end
90
+
91
+ MuninPlugin.new(is_config, opts)
@@ -0,0 +1,65 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{pipeline_toolkit}
8
+ s.version = "1.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Aisha Fenton"]
12
+ s.date = %q{2009-12-06}
13
+ s.email = %q{labs@visfleet.com}
14
+ s.executables = ["msg_probe.rb", "msg_subscribe.rb", "msg_push.rb", "msg_sink.rb", "msg_generator.rb"]
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "bin/msg_generator.rb",
24
+ "bin/msg_probe.rb",
25
+ "bin/msg_push.rb",
26
+ "bin/msg_sink.rb",
27
+ "bin/msg_subscribe.rb",
28
+ "lib/pipeline_toolkit.rb",
29
+ "lib/pipeline_toolkit/default_logger.rb",
30
+ "lib/pipeline_toolkit/message_coder.rb",
31
+ "lib/pipeline_toolkit/message_command.rb",
32
+ "lib/pipeline_toolkit/message_probe.rb",
33
+ "lib/pipeline_toolkit/message_pusher.rb",
34
+ "lib/pipeline_toolkit/message_sink.rb",
35
+ "lib/pipeline_toolkit/message_subscriber.rb",
36
+ "lib/pipeline_toolkit/open_hash.rb",
37
+ "monitor/munin.rb",
38
+ "pipeline_toolkit.gemspec"
39
+ ]
40
+ s.homepage = %q{http://github.com/visfleet/pipeline_toolkit}
41
+ s.rdoc_options = ["--charset=UTF-8"]
42
+ s.require_paths = ["lib"]
43
+ s.rubygems_version = %q{1.3.5}
44
+ s.summary = %q{Toolkit for building processing pipelines using Unix Pipes and AMQP messages}
45
+
46
+ if s.respond_to? :specification_version then
47
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
48
+ s.specification_version = 3
49
+
50
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
51
+ s.add_runtime_dependency(%q<amqp>, [">= 0.6.4"])
52
+ s.add_runtime_dependency(%q<trollop>, [">= 1.14"])
53
+ s.add_runtime_dependency(%q<eventmachine>, [">= 0.12.8"])
54
+ else
55
+ s.add_dependency(%q<amqp>, [">= 0.6.4"])
56
+ s.add_dependency(%q<trollop>, [">= 1.14"])
57
+ s.add_dependency(%q<eventmachine>, [">= 0.12.8"])
58
+ end
59
+ else
60
+ s.add_dependency(%q<amqp>, [">= 0.6.4"])
61
+ s.add_dependency(%q<trollop>, [">= 1.14"])
62
+ s.add_dependency(%q<eventmachine>, [">= 0.12.8"])
63
+ end
64
+ end
65
+
metadata ADDED
@@ -0,0 +1,107 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pipeline_toolkit
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Aisha Fenton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-12-06 00:00:00 +13:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: amqp
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.6.4
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: trollop
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "1.14"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: eventmachine
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.12.8
44
+ version:
45
+ description:
46
+ email: labs@visfleet.com
47
+ executables:
48
+ - msg_probe.rb
49
+ - msg_subscribe.rb
50
+ - msg_push.rb
51
+ - msg_sink.rb
52
+ - msg_generator.rb
53
+ extensions: []
54
+
55
+ extra_rdoc_files:
56
+ - README.rdoc
57
+ files:
58
+ - .gitignore
59
+ - README.rdoc
60
+ - Rakefile
61
+ - VERSION
62
+ - bin/msg_generator.rb
63
+ - bin/msg_probe.rb
64
+ - bin/msg_push.rb
65
+ - bin/msg_sink.rb
66
+ - bin/msg_subscribe.rb
67
+ - lib/pipeline_toolkit.rb
68
+ - lib/pipeline_toolkit/default_logger.rb
69
+ - lib/pipeline_toolkit/message_coder.rb
70
+ - lib/pipeline_toolkit/message_command.rb
71
+ - lib/pipeline_toolkit/message_probe.rb
72
+ - lib/pipeline_toolkit/message_pusher.rb
73
+ - lib/pipeline_toolkit/message_sink.rb
74
+ - lib/pipeline_toolkit/message_subscriber.rb
75
+ - lib/pipeline_toolkit/open_hash.rb
76
+ - monitor/munin.rb
77
+ - pipeline_toolkit.gemspec
78
+ has_rdoc: true
79
+ homepage: http://github.com/visfleet/pipeline_toolkit
80
+ licenses: []
81
+
82
+ post_install_message:
83
+ rdoc_options:
84
+ - --charset=UTF-8
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: "0"
92
+ version:
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: "0"
98
+ version:
99
+ requirements: []
100
+
101
+ rubyforge_project:
102
+ rubygems_version: 1.3.5
103
+ signing_key:
104
+ specification_version: 3
105
+ summary: Toolkit for building processing pipelines using Unix Pipes and AMQP messages
106
+ test_files: []
107
+