2009-09-16 22:53:49 +00:00
|
|
|
require 'thread'
|
2009-10-01 22:00:22 +00:00
|
|
|
require 'active_support/core_ext/module/delegation'
|
2009-09-16 22:53:49 +00:00
|
|
|
|
|
|
|
module ActiveSupport
|
2009-10-15 21:06:15 +00:00
|
|
|
# Orchestra provides an instrumentation API for Ruby. To instrument an action
|
2009-09-16 22:53:49 +00:00
|
|
|
# in Ruby you just need to:
|
|
|
|
#
|
2009-10-15 21:06:15 +00:00
|
|
|
# ActiveSupport::Orchestra.instrument(:render, :extra => :information) do
|
2009-09-16 22:53:49 +00:00
|
|
|
# render :text => "Foo"
|
|
|
|
# end
|
|
|
|
#
|
|
|
|
# Those actions are consumed by listeners. A listener is anything that responds
|
|
|
|
# to push. You can even register an array:
|
|
|
|
#
|
|
|
|
# @listener = []
|
2009-10-15 21:06:15 +00:00
|
|
|
# ActiveSupport::Orchestra.register @listener
|
2009-09-16 22:53:49 +00:00
|
|
|
#
|
2009-10-15 21:06:15 +00:00
|
|
|
# ActiveSupport::Orchestra.instrument(:render, :extra => :information) do
|
2009-09-16 22:53:49 +00:00
|
|
|
# render :text => "Foo"
|
|
|
|
# end
|
|
|
|
#
|
2009-10-15 21:06:15 +00:00
|
|
|
# event #=> ActiveSupport::Orchestra::Event
|
2009-09-16 22:53:49 +00:00
|
|
|
# event.name #=> :render
|
|
|
|
# event.duration #=> 10 (in miliseconds)
|
|
|
|
# event.result #=> "Foo"
|
|
|
|
# event.payload #=> { :extra => :information }
|
|
|
|
#
|
2009-10-15 21:06:15 +00:00
|
|
|
# Orchestra ships with a default listener implementation which puts events in
|
2009-09-16 22:53:49 +00:00
|
|
|
# a stream and consume them in a Thread. This implementation is thread safe
|
2009-10-15 21:06:15 +00:00
|
|
|
# and is available at ActiveSupport::Orchestra::Listener.
|
2009-09-16 22:53:49 +00:00
|
|
|
#
|
2009-10-15 21:06:15 +00:00
|
|
|
module Orchestra
|
2009-09-30 11:59:15 +00:00
|
|
|
mattr_accessor :queue
|
|
|
|
|
2009-10-01 22:00:22 +00:00
|
|
|
class << self
|
|
|
|
delegate :instrument, :to => :instrumenter
|
|
|
|
|
|
|
|
def instrumenter
|
|
|
|
Thread.current[:orchestra_instrumeter] ||= Instrumenter.new(publisher)
|
|
|
|
end
|
|
|
|
|
|
|
|
def publisher
|
|
|
|
@publisher ||= Publisher.new(queue)
|
|
|
|
end
|
|
|
|
|
|
|
|
def subscribe(pattern=nil, &block)
|
|
|
|
Subscriber.new(queue).bind(pattern).subscribe(&block)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Instrumenter
|
|
|
|
def initialize(publisher)
|
|
|
|
@publisher = publisher
|
|
|
|
@stack = []
|
|
|
|
end
|
|
|
|
|
|
|
|
def instrument(name, payload=nil)
|
|
|
|
event = Event.new(name, @stack.last, payload)
|
|
|
|
@stack << event
|
|
|
|
event.result = yield
|
|
|
|
event
|
|
|
|
ensure
|
|
|
|
event.finish!
|
|
|
|
@stack.pop
|
|
|
|
@publisher.publish(event)
|
|
|
|
end
|
2009-09-16 22:53:49 +00:00
|
|
|
end
|
|
|
|
|
2009-10-01 22:00:22 +00:00
|
|
|
class Publisher
|
|
|
|
def initialize(queue)
|
|
|
|
@queue = queue
|
|
|
|
end
|
|
|
|
|
|
|
|
def publish(event)
|
|
|
|
@queue.publish(event)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class Subscriber
|
|
|
|
def initialize(queue)
|
|
|
|
@queue = queue
|
|
|
|
end
|
|
|
|
|
|
|
|
def bind(pattern)
|
|
|
|
@pattern = pattern
|
|
|
|
self
|
|
|
|
end
|
|
|
|
|
|
|
|
def subscribe
|
|
|
|
@queue.subscribe(@pattern) do |event|
|
|
|
|
yield event
|
|
|
|
end
|
|
|
|
end
|
2009-09-16 22:53:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
class Event
|
|
|
|
attr_reader :name, :time, :duration, :parent, :thread_id, :payload
|
|
|
|
attr_accessor :result
|
|
|
|
|
|
|
|
def initialize(name, parent=nil, payload=nil)
|
|
|
|
@name = name
|
|
|
|
@time = Time.now
|
|
|
|
@thread_id = Thread.current.object_id
|
|
|
|
@parent = parent
|
|
|
|
@payload = payload
|
|
|
|
end
|
|
|
|
|
|
|
|
def finish!
|
|
|
|
@duration = 1000 * (Time.now.to_f - @time.to_f)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-09-30 11:59:15 +00:00
|
|
|
# This is a default queue implementation that ships with Orchestra. It
|
|
|
|
# consumes events in a thread and publish them to all registered subscribers.
|
|
|
|
#
|
|
|
|
class LittleFanout
|
2009-09-16 22:53:49 +00:00
|
|
|
def initialize
|
2009-09-30 11:59:15 +00:00
|
|
|
@listeners, @stream = [], []
|
|
|
|
|
2009-09-16 22:53:49 +00:00
|
|
|
@thread = Thread.new do
|
|
|
|
loop do
|
2009-09-30 11:59:15 +00:00
|
|
|
(event = @stream.shift) ? consume(event) : Thread.stop
|
2009-09-16 22:53:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2009-10-01 22:00:22 +00:00
|
|
|
def publish(event)
|
2009-09-30 11:59:15 +00:00
|
|
|
@stream.push(event)
|
|
|
|
@thread.run
|
2009-09-16 22:53:49 +00:00
|
|
|
end
|
|
|
|
|
2009-09-30 11:59:15 +00:00
|
|
|
def subscribe(pattern=nil, &block)
|
|
|
|
@listeners << Listener.new(pattern, &block)
|
2009-09-16 22:53:49 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def consume(event)
|
2009-09-30 11:59:15 +00:00
|
|
|
@listeners.each { |l| l.publish(event) }
|
|
|
|
end
|
|
|
|
|
|
|
|
class Listener
|
|
|
|
def initialize(pattern, &block)
|
|
|
|
@pattern = pattern
|
|
|
|
@subscriber = block
|
|
|
|
end
|
|
|
|
|
|
|
|
def publish(event)
|
|
|
|
unless @pattern && event.name.to_s !~ @pattern
|
|
|
|
@subscriber.call(event)
|
|
|
|
end
|
|
|
|
end
|
2009-09-16 22:53:49 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2009-09-30 11:59:15 +00:00
|
|
|
|
|
|
|
Orchestra.queue = Orchestra::LittleFanout.new
|
2009-09-16 22:53:49 +00:00
|
|
|
end
|