droid 1.0.0 → 1.0.1
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/VERSION +1 -1
- data/droid.gemspec +2 -2
- data/lib/droid/heroku/local_stats.rb +141 -141
- data/lib/droid/heroku/logger_client.rb +193 -193
- data/lib/droid/heroku/memcache_cluster.rb +143 -120
- data/lib/droid/heroku/stats.rb +25 -25
- data/lib/droid/utilization.rb +6 -6
- metadata +3 -3
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.
|
1
|
+
1.0.1
|
data/droid.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{droid}
|
8
|
-
s.version = "1.0.
|
8
|
+
s.version = "1.0.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Ricardo Chimal, Jr."]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-11-12}
|
13
13
|
s.default_executable = %q{bleedq}
|
14
14
|
s.description = %q{Easy to use AMQP Library with constructs for typical usage patterns}
|
15
15
|
s.email = %q{ricardo@heroku.com}
|
@@ -1,145 +1,145 @@
|
|
1
1
|
require 'rest_client'
|
2
2
|
|
3
3
|
module LocalStats
|
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
|
-
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def load_avg
|
7
|
+
File.read('/proc/loadavg').split(' ', 2).first.to_f
|
8
|
+
end
|
9
|
+
|
10
|
+
def memory_info
|
11
|
+
info = {}
|
12
|
+
File.read('/proc/meminfo').split("\n").each do |line|
|
13
|
+
name, value = line.split(/:\s+/, 2)
|
14
|
+
info[name] = value.to_i
|
15
|
+
end
|
16
|
+
info
|
17
|
+
end
|
18
|
+
|
19
|
+
def memory_use
|
20
|
+
info = memory_info
|
21
|
+
used = info['MemTotal'] - info['MemFree'] - info['Cached'] - info['Buffers']
|
22
|
+
(100 * used / info['MemTotal']).to_i
|
23
|
+
end
|
24
|
+
|
25
|
+
def swap_use
|
26
|
+
_, total, use, _ = `free | grep Swap:`.strip.split
|
27
|
+
return 0 if total.to_i == 0
|
28
|
+
(100 * use.to_i / total.to_i).to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
def local_ip
|
32
|
+
@local_ip ||= fetch_local_ip
|
33
|
+
end
|
34
|
+
|
35
|
+
def fetch_local_ip
|
36
|
+
`/sbin/ifconfig eth0 | grep inet | awk '{print $2}'`.gsub('addr:', '').strip
|
37
|
+
end
|
38
|
+
|
39
|
+
def public_ip
|
40
|
+
retries = 0
|
41
|
+
@public_ip ||= begin
|
42
|
+
RestClient.get("http://169.254.169.254/latest/meta-data/public-ipv4").to_s.strip
|
43
|
+
rescue RestClient::RequestTimeout => e
|
44
|
+
retries += 1
|
45
|
+
raise if retries > 5
|
46
|
+
sleep 1
|
47
|
+
retry
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def disk_stats
|
52
|
+
raw = `df -P | grep -v tmpfs | grep -v udev | grep -v slugs | awk '{print $1 " ## " $6 " ## " $5}'`.split("\n").collect { |d| d }
|
53
|
+
raw.shift
|
54
|
+
raw.collect do |d|
|
55
|
+
tokens = d.split("##").collect { |t| t.strip }
|
56
|
+
{
|
57
|
+
:device => tokens[0],
|
58
|
+
:path => tokens[1],
|
59
|
+
:usage => tokens[2].gsub('%','').to_i
|
60
|
+
}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def ion_instance_id
|
65
|
+
@ion_instance_id ||= File.read('/etc/heroku/ion_instance_id').strip rescue nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def slot
|
69
|
+
@slot ||= File.read('/etc/heroku/slot').strip.gsub(/64$/,'').split('-').first rescue nil
|
70
|
+
end
|
71
|
+
|
72
|
+
def instance_name
|
73
|
+
return nil if slot.nil? or ion_instance_id.nil?
|
74
|
+
"#{slot}.#{ion_instance_id}"
|
75
|
+
end
|
76
|
+
|
77
|
+
alias :this_instance_name :instance_name
|
78
|
+
|
79
|
+
def stats
|
80
|
+
{
|
81
|
+
:slot => slot,
|
82
|
+
:ion_id => ion_instance_id,
|
83
|
+
:load_avg => load_avg,
|
84
|
+
:memory_use => memory_use,
|
85
|
+
:swap_use => swap_use,
|
86
|
+
:local_ip => local_ip,
|
87
|
+
:instance_name => this_instance_name,
|
88
|
+
:public_ip => public_ip,
|
89
|
+
:net_in_rate => receive_rate,
|
90
|
+
:net_out_rate => transmit_rate
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
def extended_stats
|
95
|
+
{
|
96
|
+
:disk_stats => disk_stats
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
# sample ifstat every 30 seconds
|
101
|
+
IFSTAT_INTERVAL = 30
|
102
|
+
|
103
|
+
# bytes received per second and bytes transmitted per
|
104
|
+
# second as a two tuple
|
105
|
+
def transfer_rates
|
106
|
+
[receive_rate, transmit_rate]
|
107
|
+
end
|
108
|
+
|
109
|
+
# bytes received per second
|
110
|
+
def receive_rate
|
111
|
+
@rxrate || 0
|
112
|
+
end
|
113
|
+
|
114
|
+
# bytes transmitted per second
|
115
|
+
def transmit_rate
|
116
|
+
@txrate || 0
|
117
|
+
end
|
118
|
+
|
119
|
+
# sample the current RX and TX bytes from ifconfig and
|
120
|
+
# return as a two-tuple.
|
121
|
+
def sample
|
122
|
+
data = ifconfig.match(/RX bytes:(\d+).*TX bytes:(\d+)/)
|
123
|
+
[data[1].to_i, data[2].to_i]
|
124
|
+
rescue => boom
|
125
|
+
Log.notice "error sampling network rate: #{boom.class} #{boom.message}"
|
126
|
+
end
|
127
|
+
|
128
|
+
def update_counters
|
129
|
+
rx, tx = sample
|
130
|
+
@rxrate = (rx - @rx) / IFSTAT_INTERVAL if @rx
|
131
|
+
@txrate = (tx - @tx) / IFSTAT_INTERVAL if @tx
|
132
|
+
@rx, @tx = rx, tx
|
133
|
+
end
|
134
|
+
|
135
|
+
# called when a droid starts up - setup timers and whatnot
|
136
|
+
def attach
|
137
|
+
@rx, @tx, @rxrate, @txrate = nil
|
138
|
+
update_counters
|
139
|
+
EM.add_periodic_timer(IFSTAT_INTERVAL) { update_counters }
|
140
|
+
end
|
141
|
+
|
142
|
+
def ifconfig
|
143
|
+
`/sbin/ifconfig eth0`
|
144
|
+
end
|
145
145
|
end
|
@@ -13,206 +13,206 @@ require 'syslog'
|
|
13
13
|
module Log; end
|
14
14
|
|
15
15
|
class Log::InvalidConfiguration < RuntimeError
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
def message
|
17
|
+
"Invalid component. Configure with Log.configure { |c| c.component = 'myComponent' }"
|
18
|
+
end
|
19
19
|
end
|
20
20
|
|
21
21
|
class Log::Config
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
def initialize
|
23
|
+
@contents = { }
|
24
|
+
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
def method_missing(method, value)
|
27
|
+
@contents[method.to_s.gsub(/=$/, '').to_sym] = value
|
28
|
+
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
def to_hash
|
31
|
+
@contents
|
32
|
+
end
|
33
33
|
end
|
34
34
|
|
35
35
|
module Log
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
36
|
+
extend self
|
37
|
+
|
38
|
+
unless defined? SyslogConvertion
|
39
|
+
SyslogConvertion = {
|
40
|
+
'error' => 3,
|
41
|
+
'warning' => 4,
|
42
|
+
'notice' => 5,
|
43
|
+
'debug' => 7,
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def debug(msg, options={})
|
48
|
+
log msg, options.merge(:level => 'debug')
|
49
|
+
end
|
50
|
+
|
51
|
+
def notice(msg, options={})
|
52
|
+
log msg, options.merge(:level => 'notice')
|
53
|
+
end
|
54
|
+
|
55
|
+
alias :info :notice
|
56
|
+
|
57
|
+
def warning(msg, options={})
|
58
|
+
log msg, options.merge(:level => 'warning')
|
59
|
+
end
|
60
|
+
|
61
|
+
def error(msg, options={})
|
62
|
+
# Fake an exception for error messages with no exception object
|
63
|
+
# so that we get a backtrace.
|
64
|
+
if options[:exception].nil?
|
65
|
+
begin
|
66
|
+
raise StandardError, msg
|
67
|
+
rescue => error
|
68
|
+
error.backtrace.shift
|
69
|
+
options[:exception] = error
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
log msg, options.merge(:level => 'error')
|
74
|
+
end
|
75
|
+
|
76
|
+
##########
|
77
|
+
|
78
|
+
def log(msg, options={})
|
79
|
+
console_log(msg)
|
80
|
+
syslog(msg, options)
|
81
|
+
rescue InvalidConfiguration => e
|
82
|
+
raise e
|
83
|
+
end
|
84
|
+
|
85
|
+
def default_error(e)
|
86
|
+
# avoid backtrace in /usr or vendor if possible
|
87
|
+
system, app = e.backtrace.partition { |b| b =~ /(^\/usr\/|vendor)/ }
|
88
|
+
reordered_backtrace = app + system
|
89
|
+
|
90
|
+
# avoid "/" as the method name (we want the controller action)
|
91
|
+
row = 0
|
92
|
+
row = 1 if reordered_backtrace[row].match(/in `\/'$/)
|
93
|
+
|
94
|
+
# get file and method name
|
95
|
+
begin
|
96
|
+
file, method = reordered_backtrace[row].match(/(.*):in `(.*)'$/)[1..2]
|
97
|
+
file.gsub!(/.*\//, '')
|
98
|
+
self.log "#{e.class} in #{file} #{method}: #{e.message}", :exception => e, :level => 'error'
|
99
|
+
rescue
|
100
|
+
self.log "#{e.class} in #{e.backtrace.first}: #{e.message}", :exception => e, :level => 'error'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def web_error(args)
|
105
|
+
e = args[:exception]
|
106
|
+
summary = "#{e.class} processing #{args[:url]}"
|
107
|
+
|
108
|
+
body = []
|
109
|
+
body << "\tURL: #{args[:url]}"
|
110
|
+
body << "\tParams: #{args[:params].inspect}"
|
111
|
+
body << "\tUser: #{args[:user]}" if args[:user]
|
112
|
+
body << "\tBacktrace:"
|
113
|
+
body << "\t\t#{e.class} (#{e.message}):"
|
114
|
+
body += e.backtrace.map { |l| "\t\t#{l}" }
|
115
|
+
body = body.join("\n")
|
116
|
+
|
117
|
+
log "#{summary}\n#{body}", :level => 'error'
|
118
|
+
end
|
119
|
+
|
120
|
+
def console_puts(*args)
|
121
|
+
$stderr.puts(*args)
|
122
|
+
end
|
123
|
+
|
124
|
+
def exception(e)
|
125
|
+
msg = "Exception #{e.class} -> #{e.message}\n"
|
126
|
+
msg += filtered_backtrace(e.backtrace)
|
127
|
+
Log.error e.message, :exception => e
|
128
|
+
end
|
129
|
+
|
130
|
+
def filtered_backtrace(backtrace)
|
131
|
+
backtrace.select do |line|
|
132
|
+
!line.match(/^\/usr/)
|
133
|
+
end.map do |line|
|
134
|
+
" #{line}\n"
|
135
|
+
end.join
|
136
|
+
end
|
137
|
+
|
138
|
+
def event(name, options = {})
|
139
|
+
console_log("EVENT: #{name} begins")
|
140
|
+
result = yield
|
141
|
+
console_log("EVENT: #{name} complete")
|
142
|
+
result
|
143
|
+
end
|
144
|
+
|
145
|
+
def context(options)
|
146
|
+
prev_options = default_options.dup
|
147
|
+
default_options.merge!(options)
|
148
|
+
yield
|
149
|
+
@@default_options = prev_options
|
150
|
+
end
|
151
|
+
|
152
|
+
def configure
|
153
|
+
config = Config.new
|
154
|
+
yield(config)
|
155
|
+
set_default_options(config.to_hash)
|
156
|
+
end
|
157
|
+
|
158
|
+
def set_default_options(options)
|
159
|
+
default_options.merge!(options)
|
160
|
+
end
|
161
|
+
|
162
|
+
def default_options
|
163
|
+
@@default_options ||= { :console_log => true }
|
164
|
+
end
|
165
|
+
|
166
|
+
def failsafe(params)
|
167
|
+
case default_options[:failsafe]
|
168
|
+
when :file
|
169
|
+
dir = defined?(RAILS_ROOT) ? RAILS_ROOT : '.'
|
170
|
+
File.open("#{dir}/failsafe.log", "a") do |f|
|
171
|
+
f.puts "#{Time.now} #{params[:log][:level]} : #{params[:log][:message]}"
|
172
|
+
end
|
173
|
+
when :console, nil
|
174
|
+
console_log(params[:log][:message])
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def console_log(msg)
|
179
|
+
console_puts "#{Time.now.iso8601} #{msg}" if default_options[:console_log]
|
180
|
+
end
|
181
|
+
|
182
|
+
def syslog(msg, opts = {})
|
183
|
+
@retried = false
|
184
|
+
begin
|
185
|
+
level = SyslogConvertion[opts[:level]]
|
186
|
+
if opts[:exception]
|
187
|
+
msg += "\n" + format_syslog_exception(opts[:exception])
|
188
|
+
end
|
189
|
+
syslog_resource.log(level, '%s', trim_syslog_msg(msg))
|
190
|
+
rescue Exception => e
|
191
|
+
failsafe(:log => { :level => 'error', :message => "could not log to syslog: #{e.class.name} #{e.message}"})
|
192
|
+
unless @retried
|
193
|
+
@retried = true
|
194
|
+
@@syslog.close rescue nil
|
195
|
+
@@syslog = Syslog.open(default_options[:component]) rescue nil
|
196
|
+
retry
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def format_syslog_exception(e)
|
202
|
+
if e.respond_to?(:backtrace)
|
203
|
+
"\t#{e.class}: #{e.message}\n" + e.backtrace.map { |t| "\t#{t}" }.join("\n")
|
204
|
+
else
|
205
|
+
"\t#{e.class}: #{e.message}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def trim_syslog_msg(msg)
|
210
|
+
return msg if msg.size < 800
|
211
|
+
msg[0, 796] + " ..."
|
212
|
+
end
|
213
|
+
|
214
|
+
def syslog_resource
|
215
|
+
@@syslog ||= Syslog.open(default_options[:component].to_s, Syslog::LOG_PID | Syslog::LOG_CONS, default_options[:syslog_facility] || Syslog::LOG_USER)
|
216
|
+
end
|
217
217
|
end
|
218
218
|
|
@@ -6,124 +6,147 @@ require 'digest/sha1'
|
|
6
6
|
# outside of the reactor - it does not account for asynchronous access
|
7
7
|
# to the server list.
|
8
8
|
module MemcacheCluster
|
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
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
9
|
+
extend self
|
10
|
+
|
11
|
+
HEROKU_NAMESPACE = '0Xfa15837Z' # heroku's internal memcache namespace
|
12
|
+
|
13
|
+
class Proxy
|
14
|
+
def initialize(name, options={})
|
15
|
+
@name = name
|
16
|
+
@options = options
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(*args)
|
20
|
+
MemcacheCluster.cache_internal(@name, @options).set(*args)
|
21
|
+
end
|
22
|
+
|
23
|
+
def get(*args)
|
24
|
+
MemcacheCluster.cache_internal(@name, @options).get(*args)
|
25
|
+
end
|
26
|
+
|
27
|
+
def method_missing(method_name, *args)
|
28
|
+
MemcacheCluster.cache_internal(@name, @options).send(method_name, *args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# A MemCache object configured with heroku's internal memcache namespace.
|
33
|
+
def heroku
|
34
|
+
cache(HEROKU_NAMESPACE)
|
35
|
+
end
|
36
|
+
|
37
|
+
def cache_retry(prefix, opts={})
|
38
|
+
opts[:retries] ||= 5
|
39
|
+
opts[:delay] ||= 0.5
|
40
|
+
|
41
|
+
retried = 0
|
42
|
+
begin
|
43
|
+
c = cache_internal(prefix)
|
44
|
+
yield c if block_given?
|
45
|
+
rescue MemCache::MemCacheError => e
|
46
|
+
Log.error "#{e.class} -> #{e.message}", :exception => e
|
47
|
+
raise if retried > opts[:retries]
|
48
|
+
retried += 1
|
49
|
+
sleep opts[:delay]
|
50
|
+
@caches = { }
|
51
|
+
retry
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def set(prefix, *args)
|
56
|
+
res = nil
|
57
|
+
cache_retry(prefix) do |c|
|
58
|
+
res = c.set(*args)
|
59
|
+
end
|
60
|
+
res
|
61
|
+
end
|
62
|
+
|
63
|
+
def get(prefix, *args)
|
64
|
+
res = nil
|
65
|
+
cache_retry(prefix) do |c|
|
66
|
+
res = c.get(*args)
|
67
|
+
end
|
68
|
+
res
|
69
|
+
end
|
70
|
+
|
71
|
+
# Create listeners for standard memcache cluster related topics.
|
72
|
+
def attach(droid, file='memcached.yml')
|
73
|
+
load_from_file(file)
|
74
|
+
|
75
|
+
droid.listen4('memcache.up', :queue => "memcache.up.#{LocalStats.this_instance_name}.#$$") { |msg| add(msg['address'], msg['port']) }
|
76
|
+
droid.listen4('instance.down', :queue => "instance.down.#{LocalStats.this_instance_name}.#$$") { |msg| remove(msg['local_ip']) if msg['slot'] == 'memcache' }
|
77
|
+
EM.add_timer(1) { droid.publish('memcache.needed', {}) }
|
78
|
+
end
|
79
|
+
|
80
|
+
# A MemCache object configured with the given prefix.
|
81
|
+
def cache_internal(prefix, options={})
|
82
|
+
caches[prefix] ||=
|
83
|
+
MemCache.new(servers, options.merge(:namespace => prefix))
|
84
|
+
end
|
85
|
+
|
86
|
+
def cache(prefix, options={})
|
87
|
+
Proxy.new(prefix, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
alias_method :[], :cache
|
91
|
+
|
92
|
+
def caches
|
93
|
+
reload_if_stale
|
94
|
+
@caches ||= {}
|
95
|
+
end
|
96
|
+
|
97
|
+
def servers
|
98
|
+
reload_if_stale
|
99
|
+
@servers ||= []
|
100
|
+
end
|
101
|
+
|
102
|
+
def add(ip, port)
|
103
|
+
host = [ip, port].join(':')
|
104
|
+
return if servers.include?(host)
|
105
|
+
|
106
|
+
log { "action=added server=#{host}" }
|
107
|
+
@servers.push host
|
108
|
+
@servers.sort!
|
109
|
+
@caches = {}
|
110
|
+
write_to_file
|
111
|
+
@last_read = Time.now.to_i
|
112
|
+
end
|
113
|
+
|
114
|
+
def remove(host)
|
115
|
+
if servers.reject!{ |s| s =~ /^#{host}/ }
|
116
|
+
log { "action=remove server=#{host}" }
|
117
|
+
caches.clear
|
118
|
+
write_to_file
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def reload_if_stale
|
123
|
+
if @last_read &&
|
124
|
+
(Time.now.to_i - @last_read) > 20 &&
|
125
|
+
File.mtime(@file).to_i > @last_read
|
126
|
+
load_from_file(@file)
|
127
|
+
end
|
128
|
+
rescue => e
|
129
|
+
log { "action=error file=#{@file} error_class='#{e.class}' error_message='#{e.message}'" }
|
130
|
+
end
|
131
|
+
|
132
|
+
def load_from_file(file)
|
133
|
+
@file = file
|
134
|
+
@last_read = Time.now.to_i
|
135
|
+
@servers = YAML.load(File.read(file)) rescue []
|
136
|
+
@caches = {}
|
137
|
+
log { "action=load file=#{@file} servers=#{@servers.join(',')}" }
|
138
|
+
end
|
139
|
+
|
140
|
+
def write_to_file
|
141
|
+
log { "action=write file=#{@file} servers=#{@servers.join(',')}" }
|
142
|
+
File.open(@file, 'w') do |f|
|
143
|
+
f.flock(File::LOCK_EX)
|
144
|
+
f.write YAML.dump(@servers)
|
145
|
+
f.flock(File::LOCK_UN)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def log(type=:notice)
|
150
|
+
Log.send(type, "memcache_cluster #{yield}")
|
151
|
+
end
|
129
152
|
end
|
data/lib/droid/heroku/stats.rb
CHANGED
@@ -1,30 +1,30 @@
|
|
1
1
|
module Stats
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
2
|
+
# The MemCache instance used to manipulate stats.
|
3
|
+
def cache
|
4
|
+
@@cache ||= MemcacheCluster.cache("heroku:stats")
|
5
|
+
end
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
28
|
|
29
|
-
|
29
|
+
extend self
|
30
30
|
end
|
data/lib/droid/utilization.rb
CHANGED
@@ -6,7 +6,7 @@ class Droid
|
|
6
6
|
def latency=(val); @latency = val; end
|
7
7
|
def latency; @latency; end
|
8
8
|
|
9
|
-
@@start = Time.now
|
9
|
+
@@start = Time.now.getutc
|
10
10
|
@@data = { }
|
11
11
|
|
12
12
|
def start
|
@@ -14,7 +14,7 @@ class Droid
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def reinit
|
17
|
-
@@start = Time.now
|
17
|
+
@@start = Time.now.getutc
|
18
18
|
@@data = { }
|
19
19
|
end
|
20
20
|
|
@@ -39,7 +39,7 @@ class Droid
|
|
39
39
|
def calc_utilization(topic, t2=nil)
|
40
40
|
d = data(topic)
|
41
41
|
t1 = @@start
|
42
|
-
t2 ||= Time.now
|
42
|
+
t2 ||= Time.now.getutc.to_i
|
43
43
|
secs = (t2 - t1)
|
44
44
|
secs = 1 if secs <= 0.0
|
45
45
|
if d['msgs'] == 0
|
@@ -60,18 +60,18 @@ class Droid
|
|
60
60
|
def monitor(topic, opts={})
|
61
61
|
topic = 'temporary' if opts[:temp]
|
62
62
|
|
63
|
-
t1 = Time.now
|
63
|
+
t1 = Time.now.getutc
|
64
64
|
begin
|
65
65
|
yield if block_given?
|
66
66
|
ensure
|
67
|
-
t2 = Time.now
|
67
|
+
t2 = Time.now.getutc
|
68
68
|
record(topic, t2 - t1)
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
72
|
def report_data
|
73
73
|
data = {}
|
74
|
-
t2 = Time.now
|
74
|
+
t2 = Time.now.getutc
|
75
75
|
topics.each do |topic|
|
76
76
|
data[topic] = calc_utilization(topic, t2)
|
77
77
|
end
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 1
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 1.0.
|
8
|
+
- 1
|
9
|
+
version: 1.0.1
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Ricardo Chimal, Jr.
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-11-12 00:00:00 -08:00
|
18
18
|
default_executable: bleedq
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|