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.
- data/.gitignore +2 -0
- data/Rakefile +38 -0
- data/VERSION +1 -0
- data/bin/bleedq +19 -0
- data/droid.gemspec +100 -0
- data/examples/async_reply.rb +25 -0
- data/examples/heroku_async_reply.rb +22 -0
- data/examples/sync.rb +32 -0
- data/examples/worker.rb +58 -0
- data/lib/droid.rb +88 -476
- data/lib/droid/em.rb +55 -0
- data/lib/droid/heroku.rb +102 -0
- data/lib/droid/heroku/local_stats.rb +145 -0
- data/{vendor/logger_client/lib → lib/droid/heroku}/logger_client.rb +9 -5
- data/lib/droid/heroku/memcache_cluster.rb +129 -0
- data/lib/droid/heroku/stats.rb +30 -0
- data/lib/droid/json_server.rb +109 -0
- data/lib/droid/monkey.rb +8 -0
- data/lib/droid/publish.rb +24 -0
- data/lib/droid/queue.rb +196 -0
- data/lib/droid/request.rb +110 -0
- data/lib/droid/sync.rb +88 -0
- data/lib/droid/utilization.rb +113 -0
- data/lib/droid/utils.rb +113 -0
- data/lib/heroku_droid.rb +1 -86
- data/lib/local_stats.rb +1 -143
- data/lib/memcache_cluster.rb +1 -129
- data/lib/stats.rb +1 -30
- data/spec/publish_spec.rb +27 -0
- data/spec/response_spec.rb +47 -0
- data/spec/spec_helper.rb +11 -0
- data/spec/utils_spec.rb +43 -0
- data/{test/wait_for_port_test.rb → spec/wait_for_port_spec.rb} +2 -9
- metadata +109 -35
- data/README.md +0 -34
- data/lib/utilization.rb +0 -90
- data/test/base.rb +0 -43
- data/test/droid_test.rb +0 -53
- data/test/heroku_droid_test.rb +0 -42
- data/vendor/logger_client/Rakefile +0 -53
- data/vendor/logger_client/init.rb +0 -1
- data/vendor/logger_client/test.rb +0 -18
data/lib/droid/sync.rb
CHANGED
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'droid'
|
2
|
+
require 'bunny'
|
3
|
+
require 'system_timer'
|
4
|
+
|
5
|
+
class Droid
|
6
|
+
class SyncException < RuntimeError; end
|
7
|
+
|
8
|
+
def self.reset_bunny
|
9
|
+
@@bunny = nil
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.bunny
|
13
|
+
@@bunny ||= begin
|
14
|
+
b = Bunny.new(default_config)
|
15
|
+
b.start
|
16
|
+
b
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.start(name, opts={})
|
21
|
+
raise SyncException, "start block is not allowed under sync operation"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.reconnect_on_error
|
25
|
+
SystemTimer::timeout(20) do
|
26
|
+
begin
|
27
|
+
yield if block_given?
|
28
|
+
rescue Bunny::ProtocolError
|
29
|
+
sleep 0.5
|
30
|
+
retry
|
31
|
+
rescue Bunny::ConnectionError
|
32
|
+
sleep 0.5
|
33
|
+
reset_bunny
|
34
|
+
retry
|
35
|
+
rescue Bunny::ServerDownError
|
36
|
+
sleep 0.5
|
37
|
+
reset_bunny
|
38
|
+
retry
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.pop(q)
|
44
|
+
begin
|
45
|
+
loop do
|
46
|
+
result = q.pop
|
47
|
+
result = result[:payload] if result.is_a?(Hash)
|
48
|
+
return JSON.parse(result) unless result == :queue_empty
|
49
|
+
sleep 0.1
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.call(queue_name, data, opts={}, popts={})
|
55
|
+
opts[:reply_to] ||= Droid::Utils.generate_reply_to(queue_name)
|
56
|
+
q = nil
|
57
|
+
begin
|
58
|
+
reconnect_on_error do
|
59
|
+
q = bunny.queue(opts[:reply_to], :auto_delete => true)
|
60
|
+
publish_to_ex(queue_name, data, opts, popts)
|
61
|
+
pop(q)
|
62
|
+
end
|
63
|
+
ensure
|
64
|
+
if q
|
65
|
+
q.delete rescue nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# override publish methods
|
71
|
+
def self.publish_to_q(queue_name, data, opts={}, popts={})
|
72
|
+
reconnect_on_error do
|
73
|
+
q = bunny.queue(queue_name)
|
74
|
+
json, popts = Droid::Utils.format_publish(data, opts, popts)
|
75
|
+
q.publish(json, popts)
|
76
|
+
end
|
77
|
+
log.info "amqp_publish queue=#{queue_name} #{Droid::Utils.format_data_summary(data, popts[:headers])}" unless opts[:log] == false
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.publish_to_ex(ex_name, data, opts={}, popts={})
|
81
|
+
reconnect_on_error do
|
82
|
+
ex = bunny.exchange(ex_name)
|
83
|
+
json, popts = Droid::Utils.format_publish(data, opts, popts)
|
84
|
+
ex.publish(json, popts)
|
85
|
+
end
|
86
|
+
log.info "amqp_publish exchange=#{ex_name} #{Droid::Utils.format_data_summary(data, popts[:headers])}" unless opts[:log] == false
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
class Droid
|
2
|
+
module Utilization
|
3
|
+
extend self
|
4
|
+
|
5
|
+
@latency = 0.0
|
6
|
+
def latency=(val); @latency = val; end
|
7
|
+
def latency; @latency; end
|
8
|
+
|
9
|
+
@@start = Time.now
|
10
|
+
@@data = { }
|
11
|
+
|
12
|
+
def start
|
13
|
+
@@start
|
14
|
+
end
|
15
|
+
|
16
|
+
def reinit
|
17
|
+
@@start = Time.now
|
18
|
+
@@data = { }
|
19
|
+
end
|
20
|
+
|
21
|
+
def data(topic)
|
22
|
+
@@data[topic] ||= {
|
23
|
+
'msgs' => 0,
|
24
|
+
'time' => 0.0
|
25
|
+
}
|
26
|
+
@@data[topic]
|
27
|
+
end
|
28
|
+
|
29
|
+
def topics
|
30
|
+
@@data.keys
|
31
|
+
end
|
32
|
+
|
33
|
+
def record(topic, secs)
|
34
|
+
d = data(topic)
|
35
|
+
d['msgs'] += 1
|
36
|
+
d['time'] += secs
|
37
|
+
end
|
38
|
+
|
39
|
+
def calc_utilization(topic, t2=nil)
|
40
|
+
d = data(topic)
|
41
|
+
t1 = @@start
|
42
|
+
t2 ||= Time.now
|
43
|
+
secs = (t2 - t1)
|
44
|
+
secs = 1 if secs <= 0.0
|
45
|
+
if d['msgs'] == 0
|
46
|
+
avg = 0.0
|
47
|
+
else
|
48
|
+
avg = d['time'] / d['msgs']
|
49
|
+
end
|
50
|
+
utilization = d['time'] / secs
|
51
|
+
{
|
52
|
+
'avg' => avg,
|
53
|
+
'secs' => secs,
|
54
|
+
'utilization' => utilization,
|
55
|
+
'msgs' => d['msgs'],
|
56
|
+
'msgs_per_sec' => d['msgs'] / secs
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def monitor(topic, opts={})
|
61
|
+
topic = 'temporary' if opts[:temp]
|
62
|
+
|
63
|
+
t1 = Time.now
|
64
|
+
begin
|
65
|
+
yield if block_given?
|
66
|
+
ensure
|
67
|
+
t2 = Time.now
|
68
|
+
record(topic, t2 - t1)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def report_data
|
73
|
+
data = {}
|
74
|
+
t2 = Time.now
|
75
|
+
topics.each do |topic|
|
76
|
+
data[topic] = calc_utilization(topic, t2)
|
77
|
+
end
|
78
|
+
data
|
79
|
+
end
|
80
|
+
|
81
|
+
def report_summary(data=nil)
|
82
|
+
data ||= report_data
|
83
|
+
|
84
|
+
summary = {
|
85
|
+
'avg' => 0.0,
|
86
|
+
'utilization' => 0.0,
|
87
|
+
'msgs' => 0,
|
88
|
+
'msgs_per_sec' => 0.0,
|
89
|
+
'secs' => 0.0
|
90
|
+
}
|
91
|
+
data.each do |topic, d|
|
92
|
+
summary['utilization'] += d['utilization']
|
93
|
+
summary['msgs'] += d['msgs']
|
94
|
+
summary['msgs_per_sec'] += d['msgs_per_sec']
|
95
|
+
summary['avg'] += d['avg']
|
96
|
+
summary['secs'] += d['secs']
|
97
|
+
end
|
98
|
+
if data.size < 1
|
99
|
+
summary['avg'] = 0.0
|
100
|
+
else
|
101
|
+
summary['avg'] /= data.size
|
102
|
+
end
|
103
|
+
|
104
|
+
summary
|
105
|
+
end
|
106
|
+
|
107
|
+
def report
|
108
|
+
data = report_data
|
109
|
+
summary = report_summary(data)
|
110
|
+
{ 'data' => data, 'summary' => summary }
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/droid/utils.rb
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'digest/md5'
|
3
|
+
|
4
|
+
class Droid
|
5
|
+
DEFAULT_TTL = 300
|
6
|
+
|
7
|
+
class BadPayload < RuntimeError; end
|
8
|
+
|
9
|
+
module Utils
|
10
|
+
def self.parse_message(raw)
|
11
|
+
return { } unless raw
|
12
|
+
JSON.parse(raw)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.parse_custom_headers(headers)
|
16
|
+
return { } unless headers
|
17
|
+
|
18
|
+
h = headers.dup
|
19
|
+
|
20
|
+
h[:published_on] = h[:published_on].to_i
|
21
|
+
|
22
|
+
h[:ttl] = h[:ttl].to_i rescue -1
|
23
|
+
h[:ttl] = -1 if h[:ttl] == 0
|
24
|
+
|
25
|
+
h
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.create_event_hash
|
29
|
+
s = Time.now.to_s + self.object_id.to_s + rand(100).to_s
|
30
|
+
'd' + Digest::MD5.hexdigest(s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.extract_custom_headers(hash, opts={}, popts={})
|
34
|
+
popts[:headers] ||= {}
|
35
|
+
headers = popts[:headers]
|
36
|
+
|
37
|
+
headers[:published_on] ||= hash.delete('published_on') || opts[:published_on] || Time.now.getgm.to_i
|
38
|
+
headers[:ttl] ||= hash.delete('ttl') || (opts[:ttl] || Droid::DEFAULT_TTL).to_i
|
39
|
+
headers[:reply_to] ||= opts[:reply_to] if opts[:reply_to]
|
40
|
+
|
41
|
+
# this is the event hash that gets transferred through various publish/reply actions
|
42
|
+
headers[:event_hash] ||= hash.delete('event_hash') || opts[:event_hash] || create_event_hash
|
43
|
+
|
44
|
+
# this value should be unique for each published/received message pair
|
45
|
+
headers[:message_id] ||= create_event_hash
|
46
|
+
|
47
|
+
# some strange behavior with integers makes it better to
|
48
|
+
# convert all amqp headers to strings to avoid any problems
|
49
|
+
headers.each { |k,v| headers[k] = v.to_s }
|
50
|
+
|
51
|
+
[hash, headers]
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.format_publish(data, opts={}, popts={})
|
55
|
+
raise Droid::BadPayload unless data.is_a?(Hash)
|
56
|
+
|
57
|
+
hash, headers = extract_custom_headers(data, opts, popts)
|
58
|
+
|
59
|
+
popts[:content_type] ||= 'application/json'
|
60
|
+
|
61
|
+
[hash.to_json, popts]
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.generate_queue(exchange_name, second_name=nil)
|
65
|
+
second_name ||= $$
|
66
|
+
"#{generate_name_for_instance(exchange_name)}.#{second_name}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.generate_name_for_instance(name)
|
70
|
+
"#{name}.#{Socket.gethostname}"
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.generate_reply_to(name)
|
74
|
+
"temp.reply.#{name}.#{self.generate_sym}"
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.generate_sym
|
78
|
+
values = [
|
79
|
+
rand(0x0010000),
|
80
|
+
rand(0x0010000),
|
81
|
+
rand(0x0010000),
|
82
|
+
rand(0x0010000),
|
83
|
+
rand(0x0010000),
|
84
|
+
rand(0x1000000),
|
85
|
+
rand(0x1000000),
|
86
|
+
]
|
87
|
+
"%04x%04x%04x%04x%04x%06x%06x" % values
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.data_summary(json)
|
91
|
+
return '-> (empty)' if json.empty?
|
92
|
+
summary = json.map do |k,v|
|
93
|
+
v = v.to_s
|
94
|
+
v = v[0..37] + '...' if v.size > 40
|
95
|
+
"#{k}=#{v}"
|
96
|
+
end.join(', ')
|
97
|
+
"-> #{summary}"
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.format_data_summary(data, headers)
|
101
|
+
"(data) " + Droid::Utils.data_summary(data) + " (headers) " + Droid::Utils.data_summary(headers)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Need this to be backwards compatible
|
106
|
+
def self.gen_queue(droid, name)
|
107
|
+
dn = droid
|
108
|
+
dn = dn.name if dn.respond_to?(:name)
|
109
|
+
dn ||= "d"
|
110
|
+
dn = dn.gsub(" ", "")
|
111
|
+
Droid::Utils.generate_queue(name, droid)
|
112
|
+
end
|
113
|
+
end
|
data/lib/heroku_droid.rb
CHANGED
@@ -1,86 +1 @@
|
|
1
|
-
require '
|
2
|
-
require 'time'
|
3
|
-
require 'uri'
|
4
|
-
|
5
|
-
require File.dirname(__FILE__) + '/droid'
|
6
|
-
require File.dirname(__FILE__) + '/local_stats'
|
7
|
-
|
8
|
-
require File.dirname(__FILE__) + '/../vendor/logger_client/init'
|
9
|
-
Log.configure do |c|
|
10
|
-
slot = LocalStats.slot
|
11
|
-
slot = 'railgun' if slot == 'userapps'
|
12
|
-
c.component = slot
|
13
|
-
c.instance_name = LocalStats.this_instance_name
|
14
|
-
end
|
15
|
-
|
16
|
-
class HerokuDroid < Droid
|
17
|
-
attr_reader :extended_stats
|
18
|
-
attr_reader :force_syslog
|
19
|
-
|
20
|
-
def initialize(name, options = {}, &block)
|
21
|
-
@name = name
|
22
|
-
@force_syslog = (options[:force_syslog] == true)
|
23
|
-
@extended_stats = !(options[:extended_stats] == false)
|
24
|
-
Droid.new(name, self.class.default_options) do |d|
|
25
|
-
standard_topics(d) unless options[:standard_topics] == false
|
26
|
-
LocalStats.attach
|
27
|
-
block.call(d)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def ruby_path
|
32
|
-
if File.exists?("/usr/ruby1.8.6/bin/ruby")
|
33
|
-
"/usr/ruby1.8.6/bin"
|
34
|
-
else
|
35
|
-
"/usr/local/bin"
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
def standard_topics(droid)
|
40
|
-
queue_name = Droid.gen_queue(droid, 'ping') + ".pid#{Process.pid}"
|
41
|
-
droid.listen4('ping', :queue => queue_name) do |msg|
|
42
|
-
Utilization.latency = (Time.now.to_f - msg['departed_at']).abs
|
43
|
-
end
|
44
|
-
|
45
|
-
blk = Proc.new do |d|
|
46
|
-
begin
|
47
|
-
t1 = Time.now
|
48
|
-
response = {}.merge(LocalStats.stats)
|
49
|
-
|
50
|
-
estats = nil
|
51
|
-
if self.extended_stats
|
52
|
-
estats = droid.call_stats
|
53
|
-
estats = { :notes => estats } unless estats.kind_of?(Hash)
|
54
|
-
estats[:notes] ||= estats.map { |k, v| "#{v} #{k}" }.join(", ")
|
55
|
-
estats.merge!(LocalStats.extended_stats)
|
56
|
-
end
|
57
|
-
|
58
|
-
response.merge!({
|
59
|
-
:extended_stats => estats,
|
60
|
-
:droid_name => name,
|
61
|
-
:utilization => Utilization.report,
|
62
|
-
:latency => Utilization.latency,
|
63
|
-
})
|
64
|
-
|
65
|
-
d.publish('pong', response.merge(:stat_collection => (Time.now - t1)))
|
66
|
-
rescue Object => e
|
67
|
-
Log.error "Ping Block Error: #{e.class} -> #{e.message}", :exception => e
|
68
|
-
STDERR.puts e.backtrace.join("\n") + "\n"
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
blk.call(droid)
|
73
|
-
EM.add_periodic_timer(50 + (rand*15).to_i) { blk.call(droid) }
|
74
|
-
EM.add_periodic_timer(360) { Utilization.reinit }
|
75
|
-
|
76
|
-
return unless LocalStats.slot == self.name.downcase or self.force_syslog
|
77
|
-
end
|
78
|
-
|
79
|
-
def self.publish_oneshot(topic, params={})
|
80
|
-
HerokuDroid.new('One-shot publish') do |droid|
|
81
|
-
droid.publish(topic, params)
|
82
|
-
HerokuDroid.stop_safe
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
|
1
|
+
require 'droid/heroku'
|
data/lib/local_stats.rb
CHANGED
@@ -1,143 +1 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
module LocalStats
|
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").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_instnace_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 this_instance_name
|
73
|
-
return nil if slot.nil? or ion_instance_id.nil?
|
74
|
-
"#{slot}.#{ion_instance_id}"
|
75
|
-
end
|
76
|
-
|
77
|
-
def stats
|
78
|
-
{
|
79
|
-
:slot => slot,
|
80
|
-
:ion_id => ion_instance_id,
|
81
|
-
:load_avg => load_avg,
|
82
|
-
:memory_use => memory_use,
|
83
|
-
:swap_use => swap_use,
|
84
|
-
:local_ip => local_ip,
|
85
|
-
:instance_name => this_instance_name,
|
86
|
-
:public_ip => public_ip,
|
87
|
-
:net_in_rate => receive_rate,
|
88
|
-
:net_out_rate => transmit_rate
|
89
|
-
}
|
90
|
-
end
|
91
|
-
|
92
|
-
def extended_stats
|
93
|
-
{
|
94
|
-
:disk_stats => disk_stats
|
95
|
-
}
|
96
|
-
end
|
97
|
-
|
98
|
-
# sample ifstat every 30 seconds
|
99
|
-
IFSTAT_INTERVAL = 30
|
100
|
-
|
101
|
-
# bytes received per second and bytes transmitted per
|
102
|
-
# second as a two tuple
|
103
|
-
def transfer_rates
|
104
|
-
[receive_rate, transmit_rate]
|
105
|
-
end
|
106
|
-
|
107
|
-
# bytes received per second
|
108
|
-
def receive_rate
|
109
|
-
@rxrate || 0
|
110
|
-
end
|
111
|
-
|
112
|
-
# bytes transmitted per second
|
113
|
-
def transmit_rate
|
114
|
-
@txrate || 0
|
115
|
-
end
|
116
|
-
|
117
|
-
# sample the current RX and TX bytes from ifconfig and
|
118
|
-
# return as a two-tuple.
|
119
|
-
def sample
|
120
|
-
data = ifconfig.match(/RX bytes:(\d+).*TX bytes:(\d+)/)
|
121
|
-
[data[1].to_i, data[2].to_i]
|
122
|
-
rescue => boom
|
123
|
-
Log.notice "error sampling network rate: #{boom.class} #{boom.message}"
|
124
|
-
end
|
125
|
-
|
126
|
-
def update_counters
|
127
|
-
rx, tx = sample
|
128
|
-
@rxrate = (rx - @rx) / IFSTAT_INTERVAL if @rx
|
129
|
-
@txrate = (tx - @tx) / IFSTAT_INTERVAL if @tx
|
130
|
-
@rx, @tx = rx, tx
|
131
|
-
end
|
132
|
-
|
133
|
-
# called when a droid starts up - setup timers and whatnot
|
134
|
-
def attach
|
135
|
-
@rx, @tx, @rxrate, @txrate = nil
|
136
|
-
update_counters
|
137
|
-
EM.add_periodic_timer(IFSTAT_INTERVAL) { update_counters }
|
138
|
-
end
|
139
|
-
|
140
|
-
def ifconfig
|
141
|
-
`/sbin/ifconfig eth0`
|
142
|
-
end
|
143
|
-
end
|
1
|
+
require 'droid/heroku/local_stats'
|