droid 1.0.2pre → 1.0.2
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.
- data/Rakefile +22 -22
- data/VERSION +1 -1
- data/bin/bleedq +1 -1
- data/droid.gemspec +2 -2
- data/examples/async_reply.rb +15 -15
- data/examples/heroku_async_reply.rb +12 -12
- data/examples/sync.rb +13 -13
- data/examples/worker.rb +46 -46
- data/lib/droid.rb +122 -122
- data/lib/droid/em.rb +53 -53
- data/lib/droid/heroku.rb +83 -83
- data/lib/droid/json_server.rb +104 -104
- data/lib/droid/monkey.rb +6 -6
- data/lib/droid/publish.rb +22 -25
- data/lib/droid/queue.rb +194 -194
- data/lib/droid/request.rb +106 -106
- data/lib/droid/sync.rb +74 -74
- data/lib/droid/utilization.rb +96 -96
- data/lib/droid/utils.rb +107 -107
- data/spec/publish_spec.rb +20 -20
- data/spec/response_spec.rb +43 -43
- data/spec/utils_spec.rb +39 -39
- data/spec/wait_for_port_spec.rb +9 -9
- metadata +5 -24
data/lib/droid/em.rb
CHANGED
@@ -1,55 +1,55 @@
|
|
1
1
|
class Droid
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
2
|
+
module EMTimerUtils
|
3
|
+
def self.included(base)
|
4
|
+
base.extend(ClassMethods)
|
5
|
+
end
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
# Trap exceptions leaving the block and log them. Do not re-raise
|
9
|
+
def trap_exceptions
|
10
|
+
yield
|
11
|
+
rescue => e
|
12
|
+
em_exception(e)
|
13
|
+
end
|
14
|
+
|
15
|
+
def em_exception(e)
|
16
|
+
msg = format_em_exception(e)
|
17
|
+
log.error "[EM.timer] #{msg}", :exception => e
|
18
|
+
end
|
19
|
+
|
20
|
+
def format_em_exception(e)
|
21
|
+
# avoid backtrace in /usr or vendor if possible
|
22
|
+
system, app = e.backtrace.partition { |b| b =~ /(^\/usr\/|vendor)/ }
|
23
|
+
reordered_backtrace = app + system
|
24
|
+
|
25
|
+
# avoid "/" as the method name (we want the controller action)
|
26
|
+
row = 0
|
27
|
+
row = 1 if reordered_backtrace[row].match(/in `\/'$/)
|
28
|
+
|
29
|
+
# get file and method name
|
30
|
+
begin
|
31
|
+
file, method = reordered_backtrace[row].match(/(.*):in `(.*)'$/)[1..2]
|
32
|
+
file.gsub!(/.*\//, '')
|
33
|
+
"#{e.class} in #{file} #{method}: #{e.message}"
|
34
|
+
rescue
|
35
|
+
"#{e.class} in #{e.backtrace.first}: #{e.message}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# One-shot timer
|
40
|
+
def timer(duration, &blk)
|
41
|
+
EM.add_timer(duration) { trap_exceptions(&blk) }
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add a periodic timer. If the now argument is true, run the block
|
45
|
+
# immediately in addition to scheduling the periodic timer.
|
46
|
+
def periodic_timer(duration, now=false, &blk)
|
47
|
+
timer(1, &blk) if now
|
48
|
+
EM.add_periodic_timer(duration) { trap_exceptions(&blk) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def timer(*args, &blk); self.class.timer(*args, &blk); end
|
53
|
+
def periodic_timer(*args, &blk); self.class.periodic_timer(*args, &blk); end
|
54
|
+
end
|
55
55
|
end
|
data/lib/droid/heroku.rb
CHANGED
@@ -3,100 +3,100 @@ require 'droid/heroku/local_stats'
|
|
3
3
|
require 'droid/heroku/logger_client'
|
4
4
|
|
5
5
|
Log.configure do |c|
|
6
|
-
|
7
|
-
|
6
|
+
c.component = LocalStats.slot
|
7
|
+
c.instance_name = LocalStats.instance_name
|
8
8
|
end
|
9
9
|
|
10
10
|
Droid.log = Log
|
11
11
|
|
12
12
|
class Droid
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
module Utils
|
14
|
+
def self.generate_name_for_instance(name)
|
15
|
+
"#{name}.#{LocalStats.slot}.#{LocalStats.ion_instance_id}"
|
16
|
+
end
|
17
|
+
end
|
18
18
|
end
|
19
19
|
|
20
20
|
begin
|
21
|
-
|
21
|
+
require 'droid/json_server'
|
22
22
|
rescue LoadError => e
|
23
|
-
|
23
|
+
Droid.log.error "Could not load JSONServer", :exception => e
|
24
24
|
end
|
25
25
|
|
26
26
|
class HerokuDroid < Droid
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
27
|
+
attr_reader :extended_stats
|
28
|
+
|
29
|
+
def initialize(name, opts={}, &blk)
|
30
|
+
@extended_stats = !(opts[:extended_stats] == false)
|
31
|
+
|
32
|
+
super(name, opts) do |droid|
|
33
|
+
setup_standard_topics(self) unless opts[:standard_topics] == false
|
34
|
+
LocalStats.attach
|
35
|
+
blk.call(self)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def stats(&blk)
|
40
|
+
@stats = blk
|
41
|
+
out = call_stats
|
42
|
+
out = out.inspect unless out.kind_of?(String)
|
43
|
+
Log.notice out
|
44
|
+
end
|
45
|
+
|
46
|
+
def call_stats
|
47
|
+
@stats ? @stats.call : nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def name
|
51
|
+
Droid.name
|
52
|
+
end
|
53
|
+
|
54
|
+
def ruby_path
|
55
|
+
if File.exists?("/usr/ruby1.8.6/bin/ruby")
|
56
|
+
"/usr/ruby1.8.6/bin"
|
57
|
+
else
|
58
|
+
"/usr/local/bin"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def setup_standard_topics(droid)
|
63
|
+
setup_ping_topic(droid)
|
64
|
+
end
|
65
|
+
|
66
|
+
def setup_ping_topic(droid)
|
67
|
+
require 'time'
|
68
|
+
|
69
|
+
droid.listener('ping').subscribe do |req|
|
70
|
+
Droid::Utilization.latency = (Time.now.to_f - req['departed_at']).abs
|
71
|
+
end
|
72
|
+
|
73
|
+
blk = Proc.new do |d|
|
74
|
+
begin
|
75
|
+
t1 = Time.now
|
76
|
+
response = {}.merge(LocalStats.stats)
|
77
|
+
|
78
|
+
estats = nil
|
79
|
+
if self.extended_stats
|
80
|
+
estats = droid.call_stats
|
81
|
+
estats = { :notes => estats } unless estats.kind_of?(Hash)
|
82
|
+
estats[:notes] ||= estats.map { |k, v| "#{v} #{k}" }.join(", ")
|
83
|
+
estats.merge!(LocalStats.extended_stats)
|
84
|
+
end
|
85
|
+
|
86
|
+
response.merge!({
|
87
|
+
:extended_stats => estats,
|
88
|
+
:droid_name => Droid.name,
|
89
|
+
:latency => Droid::Utilization.latency,
|
90
|
+
})
|
91
|
+
|
92
|
+
d.publish('pong', response.merge(:stat_collection => (Time.now - t1)))
|
93
|
+
rescue Object => e
|
94
|
+
log.error "Ping Block Error: #{e.class} -> #{e.message}", :exception => e
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
EM.add_timer(2) { blk.call(droid) }
|
99
|
+
EM.add_periodic_timer(50 + (rand*15).to_i) { blk.call(droid) }
|
100
|
+
end
|
101
101
|
end
|
102
102
|
|
data/lib/droid/json_server.rb
CHANGED
@@ -2,108 +2,108 @@ require 'eventmachine'
|
|
2
2
|
require 'evma_httpserver'
|
3
3
|
|
4
4
|
class Droid
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
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
109
|
end
|