josevalim-orchestra 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ == First version
2
+
3
+ Coming soon...
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Engine Yard
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.
data/README.rdoc ADDED
@@ -0,0 +1,5 @@
1
+ == Instrumentation
2
+
3
+ Orchestra.instrument(:render, "template/path") do
4
+ render(...)
5
+ end
data/examples/bench.rb ADDED
@@ -0,0 +1,32 @@
1
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'orchestra'
4
+ Thread.abort_on_exception = true
5
+
6
+ require 'benchmark'
7
+
8
+ LISTENERS = 3
9
+ EVENTS = 10000
10
+
11
+ Benchmark.bmbm(10) do |x|
12
+ x.report("publisher") do
13
+ publisher = Orchestra::Publisher.new
14
+
15
+ 1.upto(LISTENERS) do |i|
16
+ subscriber = Orchestra::Subscriber.new(publisher)
17
+ subscriber.consume do |event|
18
+ if event.payload.first == "payload#{EVENTS}"
19
+ subscriber.disconnect!
20
+ end
21
+ end
22
+ end
23
+
24
+ 1.upto(EVENTS) do |j|
25
+ publisher.push("hello", "payload#{j}")
26
+ end
27
+
28
+ loop do
29
+ break unless publisher.publishing?
30
+ end
31
+ end
32
+ end
data/lib/orchestra.rb ADDED
@@ -0,0 +1,36 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'thread'
4
+ require 'orchestra/event'
5
+ require 'orchestra/publisher'
6
+ require 'orchestra/subscriber'
7
+
8
+ module Orchestra
9
+ @publisher = Orchestra::Publisher.new
10
+ @stacked_events = Hash.new { |h,k| h[k] = [] }
11
+
12
+ class << self
13
+ attr_accessor :publisher
14
+
15
+ def begin(name, *payload)
16
+ thread_id = Thread.current.object_id
17
+ event = Event.new(name, @stacked_events[thread_id].last, payload)
18
+ @stacked_events[thread_id] << event
19
+ event
20
+ end
21
+
22
+ def end
23
+ thread_id = Thread.current.object_id
24
+ event = @stacked_events[thread_id].pop
25
+ event.finish!
26
+ publisher.push(event)
27
+ event
28
+ end
29
+
30
+ def instrument(*args)
31
+ self.begin(*args)
32
+ yield
33
+ self.end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ module Orchestra
2
+ class Event
3
+ attr_reader :name, :time, :duration, :parent, :thread_id, :payload
4
+
5
+ def initialize(name, parent=nil, payload=nil)
6
+ @name = name
7
+ @time = Time.now.utc
8
+ @thread_id = Thread.current.object_id
9
+ @parent = parent
10
+ @payload = payload || []
11
+ end
12
+
13
+ def finish!
14
+ @duration = Time.now.utc - @time
15
+ end
16
+
17
+ def finished?
18
+ !!@duration
19
+ end
20
+
21
+ def parent?
22
+ !!@parent
23
+ end
24
+
25
+ def payload?
26
+ !@payload.empty?
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ module Orchestra
2
+ class Publisher
3
+ attr_reader :mutex, :signaler
4
+
5
+ def initialize
6
+ @mutex, @signaler = Mutex.new, ConditionVariable.new
7
+ @subscribers = []
8
+ end
9
+
10
+ def push(event)
11
+ @mutex.synchronize do
12
+ @subscribers.each { |s| s.push(event) }
13
+ @signaler.broadcast
14
+ end
15
+ end
16
+
17
+ def register_subscriber(subscriber)
18
+ @subscribers << subscriber
19
+ end
20
+
21
+ def unregister_subscriber(subscriber)
22
+ @subscribers.delete(subscriber)
23
+ end
24
+
25
+ def publishing?
26
+ !@subscribers.empty?
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,34 @@
1
+ module Orchestra
2
+ class Subscriber
3
+ def initialize(publisher)
4
+ @publisher, @stream = publisher, []
5
+ @publisher.register_subscriber(self)
6
+ end
7
+
8
+ def disconnect!
9
+ @publisher.unregister_subscriber(self)
10
+ end
11
+
12
+ def wait
13
+ @publisher.mutex.synchronize do
14
+ @publisher.signaler.wait(@publisher.mutex)
15
+ end
16
+ end
17
+
18
+ def push(event)
19
+ @stream.push(event)
20
+ end
21
+
22
+ def next
23
+ @stream.shift
24
+ end
25
+
26
+ def consume
27
+ Thread.new do
28
+ loop {
29
+ (event = self.next) ? (yield event) : wait
30
+ }
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Orchestra
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,50 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Orchestra::Event do
4
+ before(:each) do
5
+ @parent = Orchestra::Event.new("parent")
6
+ end
7
+
8
+ it "is initialized with name, parent and payload" do
9
+ event = Orchestra::Event.new("awesome", @parent, ["orchestra"])
10
+ event.name.should == "awesome"
11
+ event.parent.should == @parent
12
+ event.payload.should == ["orchestra"]
13
+ end
14
+
15
+ it "sets the thread id on initialization" do
16
+ event = Orchestra::Event.new("awesome")
17
+ event.thread_id.should == Thread.current.object_id
18
+ end
19
+
20
+ it "sets the current time on initialization" do
21
+ mock(Time).now { Time.utc(2009) }
22
+ event = Orchestra::Event.new("awesome")
23
+ event.time.should == Time.utc(2009)
24
+ end
25
+
26
+ it "has a parent" do
27
+ event = Orchestra::Event.new("awesome", @parent)
28
+ event.parent?.should be_true
29
+
30
+ event = Orchestra::Event.new("awesome")
31
+ event.parent?.should be_false
32
+ end
33
+
34
+ it "has payload" do
35
+ event = Orchestra::Event.new("awesome", nil, ["orchestra"])
36
+ event.payload?.should be_true
37
+
38
+ event = Orchestra::Event.new("awesome")
39
+ event.payload?.should be_false
40
+ end
41
+
42
+ it "sets the duration when finishes" do
43
+ event = Orchestra::Event.new("awesome")
44
+ sleep(0.1)
45
+ event.should_not be_finished
46
+ event.finish!
47
+ event.should be_finished
48
+ (0.08..0.12).should include(event.duration)
49
+ end
50
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Orchestra do
4
+ before(:each) do
5
+ Orchestra.publisher = []
6
+ end
7
+
8
+ after(:each) do
9
+ Orchestra.publisher = Orchestra::Publisher.new
10
+ end
11
+
12
+ it "allows any action to be instrumented" do
13
+ Orchestra.instrument("awesome", "orchestra") do
14
+ sleep(0.1)
15
+ end
16
+ Orchestra.publisher.should have(1).event
17
+ Orchestra.publisher.last.should be_event(:name => "awesome", :payload => ["orchestra"], :between => 0.08..1.2)
18
+ end
19
+
20
+ it "allows any action to be instrumented with begin/end hooks" do
21
+ Orchestra.begin("awesome", "orchestra")
22
+ sleep(0.1)
23
+ Orchestra.end
24
+ Orchestra.publisher.should have(1).event
25
+ Orchestra.publisher.last.should be_event(:name => "awesome", :payload => ["orchestra"], :between => 0.08..1.2)
26
+ end
27
+
28
+ it "it allows nested action to be instrumented" do
29
+ Orchestra.instrument("awesome", "orchestra") do
30
+ Orchestra.begin("wot", "child")
31
+ sleep(0.1)
32
+ Orchestra.end
33
+
34
+ Orchestra.publisher.first.should be_event(:name => "wot", :payload => ["child"], :between => 0.08..1.2, :parent => "awesome")
35
+ Orchestra.publisher.first.parent.should_not be_finished
36
+ end
37
+
38
+ Orchestra.publisher.should have(2).events
39
+ Orchestra.publisher.last.should be_event(:name => "awesome", :payload => ["orchestra"], :between => 0.08..1.2)
40
+ end
41
+ end
@@ -0,0 +1,64 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe Orchestra::Publisher do
4
+ before(:each) do
5
+ @subscriber = []
6
+ @publisher = Orchestra::Publisher.new
7
+
8
+ @awesome = Orchestra::Event.new("awesome", nil, ["orchestra"])
9
+ @wot = Orchestra::Event.new("wot", nil, ["orchestra"])
10
+ end
11
+
12
+ it "is not publishing initially" do
13
+ @publisher.should_not be_publishing
14
+ end
15
+
16
+ it "allows any subscriber to be added" do
17
+ @publisher.register_subscriber @subscriber
18
+ @publisher.should be_publishing
19
+ end
20
+
21
+ it "allows any subscriber to be removed" do
22
+ @publisher.register_subscriber @subscriber
23
+ @publisher.unregister_subscriber @subscriber
24
+ @publisher.should_not be_publishing
25
+ end
26
+
27
+ it "broadcasts to waiting locks on publishing" do
28
+ mock(@publisher.signaler).broadcast
29
+ @publisher.push @awesome
30
+ end
31
+
32
+ describe "with one subscriber" do
33
+ before(:each) do
34
+ @publisher.register_subscriber @subscriber
35
+ end
36
+
37
+ it "publishes to the subscriber" do
38
+ @publisher.push @awesome
39
+ @subscriber.first.should be_event(:payload => ["orchestra"], :name => "awesome")
40
+ end
41
+
42
+ it "does not publish to removed subscriber" do
43
+ @publisher.push @awesome
44
+ @publisher.unregister_subscriber @subscriber
45
+
46
+ @publisher.push @wot
47
+ @subscriber.last.should be_event(:payload => ["orchestra"], :name => "awesome")
48
+ end
49
+ end
50
+
51
+ describe "with several subscriber" do
52
+ before(:each) do
53
+ @another_subscriber = []
54
+ @publisher.register_subscriber @subscriber
55
+ @publisher.register_subscriber @another_subscriber
56
+ end
57
+
58
+ it "publishes to all subscribers" do
59
+ @publisher.push @awesome
60
+ @subscriber.first.should be_event(:payload => ["orchestra"], :name => "awesome")
61
+ @another_subscriber.first.should be_event(:payload => ["orchestra"], :name => "awesome")
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,45 @@
1
+ $TESTING=true
2
+ $:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'orchestra'
5
+ Thread.abort_on_exception = true
6
+
7
+ Spec::Runner.configure do |config|
8
+ config.mock_with :rr
9
+
10
+ class BeEvent
11
+ def initialize(options)
12
+ options[:thread_id] ||= Thread.current.object_id
13
+ @options = options
14
+ end
15
+
16
+ def matches?(event)
17
+ if !event
18
+ @error = "Expected event to exist"
19
+ elsif event.name != @options[:name]
20
+ @error = "Expected event's name to be #{@options[:name].inspect}, but was #{event.name.inspect}"
21
+ elsif event.thread_id != @options[:thread_id]
22
+ @error = "Expected event's thread id to be #{@options[:thread_id]}, but was #{event.thread_id}"
23
+ elsif event.payload != @options[:payload]
24
+ @error = "Expected event's payload to be #{@options[:payload].inspect}, but was #{event.payload.inspect}"
25
+ elsif !event.time.kind_of?(Time)
26
+ @error = "Expected event's time to be a Time object, but was #{event.time.inspect}"
27
+ elsif @options.key?(:between) && !@options[:between].include?(event.duration)
28
+ @error = "Expected event's duration to be between #{@options[:between]}, but was #{event.duration}"
29
+ elsif @options.key?(:parent)
30
+ if event.parent.nil?
31
+ @error = "Expected event's parent to be #{@options[:parent]}, but was nil"
32
+ elsif event.parent.name != @options[:parent]
33
+ @error = "Expected event's parent to be #{@options[:parent]}, but was #{event.parent.name}"
34
+ end
35
+ end
36
+ !@error
37
+ end
38
+
39
+ def failure_message() @error end
40
+ end
41
+
42
+ def be_event(options)
43
+ BeEvent.new(options)
44
+ end
45
+ end
@@ -0,0 +1,149 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
2
+
3
+ describe "a subscriber with events on the queue", :shared => true do
4
+ it "is on the queue when Subscriber#next is called" do
5
+ event = @subscriber.next
6
+ event.should be_event(:name => (@name || "awesome"), :payload => ["orchestra"])
7
+ end
8
+ end
9
+
10
+ describe Orchestra::Subscriber do
11
+ before(:each) do
12
+ @publisher = Orchestra::Publisher.new
13
+ @subscriber = Orchestra::Subscriber.new(@publisher)
14
+
15
+ @awesome = Orchestra::Event.new("awesome", nil, ["orchestra"])
16
+ @wot = Orchestra::Event.new("wot", nil, ["orchestra"])
17
+ end
18
+
19
+ it "is instantiated with a publisher" do
20
+ Orchestra::Subscriber.new(@publisher)
21
+ end
22
+
23
+ it "returns nil from #next before anything has been pushed" do
24
+ @subscriber.next.should == nil
25
+ end
26
+
27
+ describe "when an event is added to the publisher" do
28
+ before(:each) do
29
+ @publisher.push @awesome
30
+ end
31
+
32
+ it_should_behave_like "a subscriber with events on the queue"
33
+ end
34
+
35
+ describe "when several events are added" do
36
+ before(:each) do
37
+ @publisher.push @awesome
38
+ @publisher.push @wot
39
+ @publisher.push @awesome
40
+ end
41
+
42
+ it_should_behave_like "a subscriber with events on the queue"
43
+
44
+ it "consumes events in order" do
45
+ @subscriber.next
46
+ event = @subscriber.next
47
+ event.should be_event(:payload => ["orchestra"], :name => "wot")
48
+ end
49
+ end
50
+
51
+ describe "when blocking on an event" do
52
+ before(:each) do
53
+ @test = []
54
+ Thread.new { @subscriber.wait; @test << @subscriber.next }
55
+ end
56
+
57
+ it "initially blocks" do
58
+ @test.should be_empty
59
+ end
60
+
61
+ it "unblocks when an event is pushed on the queue" do
62
+ @publisher.push @awesome
63
+ sleep 0.1
64
+ @test.first.should be_event(:payload => ["orchestra"], :name => "awesome")
65
+ end
66
+ end
67
+
68
+ describe "when consuming events" do
69
+ before(:each) do
70
+ @test = []
71
+ @subscriber.consume {|event| @test << event }
72
+ end
73
+
74
+ it "initially blocks" do
75
+ @test.should be_empty
76
+ end
77
+
78
+ it "unblocks when one event is pushed on the queue" do
79
+ @publisher.push @awesome
80
+ sleep 0.1
81
+ @test.first.should be_event(:payload => ["orchestra"], :name => "awesome")
82
+ end
83
+
84
+ it "unblocks when several events are pushed on the queue" do
85
+ @publisher.push @wot
86
+ @publisher.push @awesome
87
+ sleep 0.1
88
+ @test.size.should == 2
89
+ @test.first.should be_event(:payload => ["orchestra"], :name => "wot")
90
+ @test.last.should be_event(:payload => ["orchestra"], :name => "awesome")
91
+ end
92
+ end
93
+
94
+ describe "when several subscribers are consuming events" do
95
+ before(:each) do
96
+ @test1, @test2 = [], []
97
+ subscriber = Orchestra::Subscriber.new(@publisher)
98
+ subscriber.consume {|event| @test1 << event}
99
+
100
+ subscriber = Orchestra::Subscriber.new(@publisher)
101
+ subscriber.consume {|event| @test2 << event}
102
+ end
103
+
104
+ it "initially blocks" do
105
+ @test1.should be_empty
106
+ @test2.should be_empty
107
+ end
108
+
109
+ it "unblocks all queues when events are pushed on" do
110
+ @publisher.push @wot
111
+ @publisher.push @awesome
112
+ sleep 0.1
113
+
114
+ @test1.size.should == 2
115
+ @test1.first.should be_event(:payload => ["orchestra"], :name => "wot")
116
+ @test1.last.should be_event(:payload => ["orchestra"], :name => "awesome")
117
+
118
+ @test2.size.should == 2
119
+ @test2.first.should be_event(:payload => ["orchestra"], :name => "wot")
120
+ @test2.last.should be_event(:payload => ["orchestra"], :name => "awesome")
121
+ end
122
+ end
123
+
124
+ describe "with multiple subscribers and many events" do
125
+ before(:each) do
126
+ @publisher = Orchestra::Publisher.new
127
+
128
+ @subscribers = [
129
+ Orchestra::Subscriber.new(@publisher),
130
+ Orchestra::Subscriber.new(@publisher)
131
+ ]
132
+
133
+ 1000.times do |i|
134
+ @publisher.push Orchestra::Event.new("wot", nil, ["payload#{i}"])
135
+ end
136
+ end
137
+
138
+ it "is safe when many events are added" do
139
+ @subscribers[0].next.should be_event(:name => "wot", :payload => ["payload0"])
140
+ @subscribers[1].next.should be_event(:name => "wot", :payload => ["payload0"])
141
+ end
142
+
143
+ it "is safe when only one subscriber reads through the queue" do
144
+ @subscribers[0].consume {}
145
+ sleep 0.1
146
+ @subscribers[1].next.should be_event(:name => "wot", :payload => ["payload0"])
147
+ end
148
+ end
149
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: josevalim-orchestra
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Yehuda Katz
8
+ - "Jos\xC3\xA9 Valim"
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2009-09-10 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Simple API to standardize instrumenting Ruby code
18
+ email:
19
+ executables: []
20
+
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - CHANGELOG.rdoc
25
+ - LICENSE
26
+ - README.rdoc
27
+ files:
28
+ - CHANGELOG.rdoc
29
+ - LICENSE
30
+ - README.rdoc
31
+ - lib/orchestra.rb
32
+ - lib/orchestra/event.rb
33
+ - lib/orchestra/publisher.rb
34
+ - lib/orchestra/subscriber.rb
35
+ - lib/orchestra/version.rb
36
+ has_rdoc: false
37
+ homepage:
38
+ licenses:
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: "0"
49
+ version:
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: "0"
55
+ version:
56
+ requirements: []
57
+
58
+ rubyforge_project: orchestra
59
+ rubygems_version: 1.3.5
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: Simple API to standardize instrumenting Ruby code
63
+ test_files:
64
+ - spec/event_spec.rb
65
+ - spec/spec_helper.rb
66
+ - spec/publisher_spec.rb
67
+ - spec/subscriber_spec.rb
68
+ - spec/orchestra_spec.rb
69
+ - examples/bench.rb