droid 0.9.5 → 1.0.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,30 @@
1
+ module Stats
2
+ # The MemCache instance used to manipulate stats.
3
+ def cache
4
+ MemcacheCluster.cache("heroku:stats")
5
+ end
6
+
7
+ # Increment a stat counter. If the counter does not exist,
8
+ # yield to the block and use the result as the current counter
9
+ # value. With no block, the counter will be started at zero.
10
+ def increment(key, amount=1)
11
+ if (value = cache.incr(key, amount)).nil?
12
+ value = yield if block_given?
13
+ value = (value || 0) + amount
14
+ cache.add(key, value.to_s, 0, true)
15
+ end
16
+ rescue => boom
17
+ Log.default_error(boom)
18
+ nil
19
+ end
20
+
21
+ # Set the stat counter to a specific value.
22
+ def sample(key, value)
23
+ cache.set(key, value.to_s, 0, true)
24
+ rescue => boom
25
+ Log.default_error(boom)
26
+ nil
27
+ end
28
+
29
+ extend self
30
+ end
@@ -0,0 +1,109 @@
1
+ require 'eventmachine'
2
+ require 'evma_httpserver'
3
+
4
+ class Droid
5
+ class JSONServer < ::EM::Connection
6
+ include ::EM::HttpServer
7
+
8
+ def post_init
9
+ super
10
+ no_environment_strings
11
+ end
12
+
13
+ def process_http_request
14
+ return not_found_response if @http_request_method != "GET"
15
+
16
+ if @http_request_uri == "/"
17
+ default_response
18
+ else
19
+ method_name = "get_#{@http_request_uri.split("/")[1]}".gsub(/[^\d\w_]/,'').downcase
20
+ if public_methods.include?(method_name)
21
+ generate_response(method_name)
22
+ else
23
+ not_found_response
24
+ end
25
+ end
26
+ end
27
+
28
+ def generate_response(method_name)
29
+ status, data, content_type = self.send(method_name)
30
+
31
+ response = ::EM::DelegatedHttpResponse.new(self)
32
+ response.status = status
33
+ response.content_type content_type
34
+ response.content = data
35
+ response.send_response
36
+ end
37
+
38
+ def default_response
39
+ response = ::EM::DelegatedHttpResponse.new(self)
40
+ response.status = 200
41
+ response.content_type 'application/json'
42
+ response.content = {"status" => "OK"}.to_json
43
+ response.send_response
44
+ end
45
+
46
+ def not_found_response
47
+ response = ::EM::DelegatedHttpResponse.new(self)
48
+ response.status = 404
49
+ response.content_type 'text/plain'
50
+ response.content = "Not Found"
51
+ response.send_response
52
+ end
53
+
54
+ def get_droid
55
+ report_data = Droid::Utilization.report_data
56
+
57
+ metrics = {}
58
+ report_data.each do |topic, data|
59
+ metrics[topic] = data['msgs']
60
+ end
61
+ metrics['latency'] = Droid::Utilization.latency
62
+
63
+ summary = Droid::Utilization.report_summary(report_data)
64
+
65
+ metrics['total_msgs'] = summary['msgs']
66
+
67
+ status = "AMQP: #{summary['msgs']} msgs processed since #{Droid::Utilization.start.utc}"
68
+
69
+ # reset metrics data
70
+ Droid::Utilization.reinit
71
+
72
+ data = {
73
+ 'status' => status,
74
+ 'state' => 'ok',
75
+ 'metrics' => hash_to_metrics(metrics)
76
+ }
77
+ [200, data.to_json, "application/json"]
78
+ end
79
+
80
+ def hash_to_metrics(hash); self.class.hash_to_metrics(hash); end
81
+
82
+ # utility method to convert a ruby hash to a metrics format
83
+ # that can be consumed by cloudkick
84
+ def self.hash_to_metrics(hash)
85
+ hash.collect do |k,v|
86
+ name = k.to_s
87
+ value = v
88
+ type = if v.kind_of?(Integer)
89
+ 'int'
90
+ elsif v.kind_of?(Float)
91
+ 'float'
92
+ else
93
+ 'string'
94
+ end
95
+
96
+ # bool -> int conversion
97
+ if [TrueClass, FalseClass].include?(v.class)
98
+ value = v ? 1 : 0
99
+ type = 'int'
100
+ end
101
+
102
+ # if type is really string then it should respond to .to_s
103
+ value = value.to_s if type == 'string'
104
+
105
+ { 'name' => name, 'type' => type, 'value' => value }
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,8 @@
1
+ module AMQP
2
+ module Client
3
+ def reconnect(*args)
4
+ sleep 10
5
+ exit 1
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ require 'droid/utils'
2
+
3
+ class Droid
4
+ # publish to queue directly
5
+ def self.publish_to_q(queue_name, data, opts={}, popts={})
6
+ q = ::MQ.queue(queue_name)
7
+ json, popts = Droid::Utils.format_publish(data, opts, popts)
8
+ q.publish(json, popts)
9
+ log.info "amqp_publish queue=#{queue_name} #{Droid::Utils.format_data_summary(data, popts[:headers])}" unless opts[:log] == false
10
+ end
11
+
12
+ # publish to exchange directly
13
+ def self.publish_to_ex(ex_name, data, opts={}, popts={})
14
+ ex = ::MQ.direct(ex_name)
15
+ json, popts = Droid::Utils.format_publish(data, opts, popts)
16
+ ex.publish(json, popts)
17
+ log.info "amqp_publish exchange=#{ex_name} #{Droid::Utils.format_data_summary(data, popts[:headers])}" unless opts[:log] == false
18
+ end
19
+
20
+ # default is publish to exchange
21
+ def self.publish(ex_name, data, opts={}, popts={})
22
+ publish_to_ex(ex_name, data, opts, popts)
23
+ end
24
+ end
@@ -0,0 +1,196 @@
1
+ class Droid
2
+ class ExpiredMessage < RuntimeError; end
3
+
4
+ class BaseQueue
5
+ attr_reader :queue_name, :opts
6
+ attr_reader :q, :ex, :mq
7
+
8
+ def initialize(queue_name, opts={})
9
+ opts[:auto_delete] = true unless opts.has_key?(:auto_delete) and opts[:auto_delete] === false
10
+
11
+ @queue_name, @opts = queue_name, opts
12
+ end
13
+
14
+ def setup
15
+ @mq = MQ.new
16
+ @q = @mq.queue(queue_name, opts)
17
+ # if we don't specify an exchange name it defaults to the queue_name
18
+ @ex = @mq.direct(opts[:exchange_name] || queue_name)
19
+ end
20
+
21
+ def temp?
22
+ false
23
+ end
24
+
25
+ def log
26
+ Droid.log
27
+ end
28
+
29
+ def tag
30
+ s = "queue=#{q.name}"
31
+ s += " exchange=#{ex.name}" if ex
32
+ end
33
+
34
+ def subscribe(amqp_opts={}, opts={})
35
+ setup
36
+
37
+ q.bind(ex) if ex
38
+ q.subscribe(amqp_opts) do |header, message|
39
+ Droid::Utilization.monitor(q.name, :temp => temp?) do
40
+ request = Droid::Request.new(self, header, message)
41
+ log.info "amqp_message #{tag} action=received ttl=#{request.ttl} age=#{request.age} #{request.data_summary}"
42
+ begin
43
+ raise Droid::ExpiredMessage if request.expired?
44
+ yield request if block_given?
45
+ finished = Time.now.getgm.to_i
46
+ log.info "amqp_message action=processed #{tag} elapsed=#{finished-request.start} ttl=#{request.ttl} age=#{request.age} #{request.data_summary}"
47
+ rescue Droid::ExpiredMessage
48
+ log.info "amqp_message action=timeout #{tag} ttl=#{request.ttl} age=#{request.age} #{request.data_summary}"
49
+ request.ack if amqp_opts[:ack]
50
+ rescue => e
51
+ request.ack if amqp_opts[:ack]
52
+ Droid.handle_error(e)
53
+ end
54
+ end
55
+ end
56
+ log.info "amqp_subscribe #{tag}"
57
+ self
58
+ end
59
+
60
+ def teardown
61
+ @q.unsubscribe
62
+ @mq.close
63
+ log.info "amqp_unsubscribe #{tag}"
64
+ end
65
+
66
+ def destroy
67
+ teardown
68
+ end
69
+ end
70
+
71
+ class WorkerQueue < BaseQueue
72
+ attr_reader :prefetch
73
+
74
+ def initialize(queue_name, opts={})
75
+ @prefetch = opts.delete(:prefetch) || 1
76
+ opts[:auto_delete] = false
77
+
78
+ super(queue_name, opts)
79
+ end
80
+
81
+ def setup
82
+ super
83
+ @mq.prefetch(self.prefetch)
84
+ end
85
+
86
+ def subscribe(amqp_opts={}, opts={})
87
+ amqp_opts[:ack] = true
88
+ super(amqp_opts, opts) do |request|
89
+ begin
90
+ yield request if block_given?
91
+ ensure
92
+ request.ack unless amqp_opts[:auto_ack] == false
93
+ end
94
+ end
95
+ end
96
+ end
97
+
98
+ class ListenQueue < BaseQueue
99
+ def initialize(exchange_name, opts={})
100
+ opts[:exchange_name] = exchange_name
101
+ queue_name = opts.delete(:queue) || Droid::Utils.generate_queue(exchange_name)
102
+ super(queue_name, opts)
103
+ end
104
+ end
105
+
106
+ class ReplyQueue < BaseQueue
107
+ def initialize(queue_name, opts={})
108
+ opts[:auto_delete] = true
109
+ super
110
+ end
111
+
112
+ def setup
113
+ @mq = MQ.new
114
+ @q = @mq.queue(queue_name, opts)
115
+ @ex = nil
116
+ end
117
+
118
+ def temp?
119
+ true
120
+ end
121
+
122
+ def subscribe(amqp_opts={}, opts={})
123
+ super(amqp_opts, opts) do |request|
124
+ yield request if block_given?
125
+ self.destroy
126
+ end
127
+ end
128
+
129
+ def teardown
130
+ @q.delete
131
+ super
132
+ end
133
+ end
134
+
135
+ module QueueMethods
136
+ def worker(queue_name, opts={})
137
+ WorkerQueue.new(queue_name, opts)
138
+ end
139
+
140
+ def listener(exchange_name, opts={})
141
+ ListenQueue.new(exchange_name, opts)
142
+ end
143
+ end
144
+
145
+ class BackwardsCompatibleQueue < BaseQueue
146
+ def initialize(exchange_name, opts={})
147
+ opts[:auto_delete] = true unless opts.has_key?(:auto_delete) and opts[:auto_delete] === false
148
+ opts[:exchange_name] = exchange_name
149
+ queue_name = opts.delete(:queue) || Droid::Utils.generate_queue(exchange_name)
150
+ @queue_name, @opts = queue_name, opts
151
+ end
152
+
153
+ def setup
154
+ @mq = MQ.new
155
+ @q = @mq.queue(queue_name, opts)
156
+ @ex = @mq.direct(opts[:exchange_name])
157
+
158
+ @mq.prefetch(opts[:prefetch]) if opts[:prefetch]
159
+ end
160
+
161
+ def subscribe(amqp_opts={}, opts={})
162
+ super(amqp_opts, opts) do |request|
163
+ if block_given?
164
+ if opts[:detail]
165
+ yield request, request.header, request.raw_message if block_given?
166
+ else
167
+ yield request if block_given?
168
+ end
169
+ end
170
+ end
171
+
172
+ self
173
+ end
174
+ end
175
+
176
+ module BackwardsCompatibleMethods
177
+ def listen4(key, orig_opts={}, &block)
178
+ opts = {}
179
+ amqp_opts = {}
180
+ subscribe_opts = {}
181
+
182
+ if orig_opts[:prefetch] || orig_opts[:ack]
183
+ opts[:prefetch] = orig_opts[:prefetch] || 1
184
+ opts[:ack] = true
185
+ end
186
+ if orig_opts[:queue]
187
+ opts[:queue] = orig_opts[:queue]
188
+ end
189
+ if orig_opts[:detail]
190
+ subscribe_opts[:detail] = true
191
+ end
192
+
193
+ BackwardsCompatibleQueue.new(key, opts).subscribe(amqp_opts, subscribe_opts, &block)
194
+ end
195
+ end
196
+ end
@@ -0,0 +1,110 @@
1
+ require 'droid/utils'
2
+
3
+ class Droid
4
+ class UnknownReplyTo < RuntimeError; end
5
+
6
+ class Request
7
+ attr_reader :qobj, :header, :raw_message
8
+ attr_reader :droid_headers, :msg, :start
9
+
10
+ def initialize(qobj, header, raw_message)
11
+ @qobj = qobj
12
+ @header = header
13
+ @raw_message = raw_message
14
+
15
+ @droid_headers = Droid::Utils.parse_custom_headers(header.headers)
16
+ @msg = Droid::Utils.parse_message(raw_message)
17
+
18
+ @start = Time.now.getgm.to_i
19
+
20
+ @acked = false
21
+ end
22
+
23
+ def q
24
+ qobj.q
25
+ end
26
+
27
+ def ex
28
+ qobj.ex
29
+ end
30
+
31
+ def mq
32
+ qobj.mq
33
+ end
34
+
35
+ def [](field)
36
+ msg[field.to_s]
37
+ end
38
+
39
+ alias :params :msg
40
+
41
+ def age
42
+ return -1 unless droid_headers[:published_on]
43
+ start - droid_headers[:published_on]
44
+ end
45
+
46
+ def ttl
47
+ droid_headers[:ttl] || -1
48
+ end
49
+
50
+ def expired?
51
+ return false if ttl == -1
52
+ age > ttl
53
+ end
54
+
55
+ def acked?
56
+ @acked == true
57
+ end
58
+
59
+ def ack
60
+ return if acked?
61
+ header.ack
62
+ @acked = true
63
+ end
64
+
65
+ def reply(data, opts={}, popts={})
66
+ opts.merge!(default_publish_opts)
67
+ reply_to = droid_headers[:reply_to] || self.msg['reply_to']
68
+ raise UnknownReplyTo unless reply_to
69
+ Droid.publish_to_q(reply_to, data, opts, popts)
70
+ end
71
+
72
+ def publish(name, data, opts={}, popts={}, &block)
73
+ opts = default_publish_opts.merge(opts)
74
+ if block
75
+ opts[:reply_to] ||= Droid::Utils.generate_reply_to(name)
76
+ Droid::ReplyQueue.new(opts[:reply_to]).subscribe(&block)
77
+ end
78
+ Droid.publish(name, data, opts, popts)
79
+ end
80
+
81
+ def requeue(ropts={})
82
+ h = droid_headers.dup
83
+ h[:requeued] = true
84
+ h.delete(:ttl)
85
+ popts = { :headers => h }
86
+ ropts[:ttl] ||= 10
87
+ Droid.publish_to_q(q.name, msg, ropts, popts)
88
+ end
89
+
90
+ def defer(&blk)
91
+ EM.defer(lambda do
92
+ begin
93
+ blk.call
94
+ rescue => e
95
+ Droid.handle_error(e)
96
+ end
97
+ end)
98
+ end
99
+
100
+ def default_publish_opts
101
+ {
102
+ :event_hash => droid_headers[:event_hash]
103
+ }
104
+ end
105
+
106
+ def data_summary
107
+ Droid::Utils.format_data_summary(msg, droid_headers)
108
+ end
109
+ end
110
+ end