instrumental_agent 0.1.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 +5 -0
- data/Gemfile +3 -0
- data/Guardfile +5 -0
- data/README.rdoc +35 -0
- data/Rakefile +1 -0
- data/bin/watch_server.rb +151 -0
- data/instrumental_agent.gemspec +24 -0
- data/lib/instrumental/agent.rb +170 -0
- data/lib/instrumental/rack/middleware.rb +85 -0
- data/lib/instrumental/rack/rails23.rb +27 -0
- data/lib/instrumental/rack/rails3.rb +27 -0
- data/lib/instrumental/rack/rails3/middleware_bootstrap.rb +6 -0
- data/lib/instrumental/setup.rb +3 -0
- data/lib/instrumental/version.rb +4 -0
- data/lib/instrumental_agent.rb +1 -0
- data/spec/agent_spec.rb +61 -0
- data/spec/spec_helper.rb +63 -0
- data/spec/test_server.rb +85 -0
- metadata +173 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
= Instrumental Agent
|
2
|
+
|
3
|
+
Instrument anything.
|
4
|
+
|
5
|
+
== Setup
|
6
|
+
|
7
|
+
Add the gem to your Gemfile.
|
8
|
+
|
9
|
+
gem 'instrumental-agent'
|
10
|
+
|
11
|
+
Head over to instrumentalapp.com and setup an account, then
|
12
|
+
initialize the agent.
|
13
|
+
|
14
|
+
I = Instrumental::Agent.new('YOUR_API_KEY')
|
15
|
+
|
16
|
+
# or, if you're using eventmachine already
|
17
|
+
|
18
|
+
I = Instrumental::Agent.new('YOUR_API_KEY', :start_reactor => false)
|
19
|
+
|
20
|
+
Now you can begin to use instrumental to track your application
|
21
|
+
|
22
|
+
I.gauge('load', 1.23)
|
23
|
+
I.increment('signups')
|
24
|
+
|
25
|
+
Data without historical context sucks, so Instrumental lets you
|
26
|
+
backfill data to. Letting you see deep into your past.
|
27
|
+
|
28
|
+
User.find_each do |user|
|
29
|
+
I.increment('signups', 1, user.created_at)
|
30
|
+
end
|
31
|
+
|
32
|
+
Running under Rails? You can also give our experimental rack middleware
|
33
|
+
a shot by initializing it with:
|
34
|
+
|
35
|
+
Instrumental::Middleware.boot
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/watch_server.rb
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
require 'instrumental_agent'
|
6
|
+
|
7
|
+
|
8
|
+
class SystemInspector
|
9
|
+
TYPES = [:gauges, :incrementors]
|
10
|
+
attr_accessor *TYPES
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@gauges = {}
|
14
|
+
@incrementors = {}
|
15
|
+
@platform =
|
16
|
+
case RUBY_PLATFORM
|
17
|
+
when /linux/
|
18
|
+
Linux
|
19
|
+
when /darwin/
|
20
|
+
OSX
|
21
|
+
else
|
22
|
+
raise "unsupported OS"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def load_all
|
27
|
+
load @platform.load_cpu
|
28
|
+
load @platform.load_memory
|
29
|
+
load @platform.load_disks
|
30
|
+
end
|
31
|
+
|
32
|
+
def load(stats)
|
33
|
+
@gauges.merge!(stats[:gauges] || {})
|
34
|
+
end
|
35
|
+
|
36
|
+
module OSX
|
37
|
+
def self.load_cpu
|
38
|
+
{ :gauges => top }
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.top
|
42
|
+
lines = []
|
43
|
+
processes = date = load = cpu = nil
|
44
|
+
IO.popen('top -l 1 -n 0') do |top|
|
45
|
+
processes = top.gets.split(': ')[1]
|
46
|
+
date = top.gets
|
47
|
+
load = top.gets.split(': ')[1]
|
48
|
+
cpu = top.gets.split(': ')[1]
|
49
|
+
end
|
50
|
+
|
51
|
+
user, system, idle = cpu.split(", ").map { |v| v.to_f }
|
52
|
+
load1, load5, load15 = load.split(", ").map { |v| v.to_f }
|
53
|
+
total, running, stuck, sleeping, threads = processes.split(", ").map { |v| v.to_i }
|
54
|
+
|
55
|
+
{
|
56
|
+
'cpu.user' => user,
|
57
|
+
'cpu.system' => system,
|
58
|
+
'cpu.idle' => idle,
|
59
|
+
'load.1min' => load1,
|
60
|
+
'load.5min' => load5,
|
61
|
+
'load.15min' => load15,
|
62
|
+
'processes.total' => total,
|
63
|
+
'processes.running' => running,
|
64
|
+
'processes.stuck' => stuck,
|
65
|
+
'processes.sleeping' => sleeping,
|
66
|
+
'threads' => threads,
|
67
|
+
}
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.load_memory
|
71
|
+
# TODO: swap
|
72
|
+
{ :gauges => vm_stat }
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.vm_stat
|
76
|
+
header, *rows = `vm_stat`.split("\n")
|
77
|
+
page_size = header.match(/page size of (\d+) bytes/)[1].to_i
|
78
|
+
sections = ["free", "active", "inactive", "wired", "speculative", "wired down"]
|
79
|
+
output = {}
|
80
|
+
total = 0.0
|
81
|
+
rows.each do |row|
|
82
|
+
if match = row.match(/Pages (.*):\s+(\d+)\./)
|
83
|
+
section, value = match[1, 2]
|
84
|
+
if sections.include?(section)
|
85
|
+
value = value.to_f * page_size / 1024 / 1024
|
86
|
+
output["memory.#{section.gsub(' ', '_')}_mb"] = value
|
87
|
+
total += value
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
output["memory.free_percent"] = output["memory.free_mb"] / total * 100 # TODO: verify
|
92
|
+
output
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.load_disks
|
96
|
+
{ :gauges => df }
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.df
|
100
|
+
output = {}
|
101
|
+
`df -k`.split("\n").grep(%r{^/dev/}).each do |line|
|
102
|
+
device, total, used, available, capacity, mount = line.split(/\s+/)
|
103
|
+
names = [File.basename(device)]
|
104
|
+
names << 'root' if mount == '/'
|
105
|
+
names.each do |name|
|
106
|
+
output["disk.#{name}.total_mb"] = total.to_f / 1024
|
107
|
+
output["disk.#{name}.used_mb"] = used.to_f / 1024
|
108
|
+
output["disk.#{name}.available_mb"] = available.to_f / 1024
|
109
|
+
output["disk.#{name}.available_percent"] = available.to_f / total.to_f * 100
|
110
|
+
end
|
111
|
+
end
|
112
|
+
output
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.netstat(interface = 'en1')
|
116
|
+
# mostly functional network io stats
|
117
|
+
headers, *lines = `netstat -ibI #{interface}`.split("\n").map { |l| l.split(/\s+/) } # FIXME: vulnerability?
|
118
|
+
headers = headers.map { |h| h.downcase }
|
119
|
+
lines.each do |line|
|
120
|
+
if !line[3].include?(':')
|
121
|
+
return Hash[headers.zip(line)]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# TODO: swap
|
129
|
+
# TODO: utilization
|
130
|
+
|
131
|
+
|
132
|
+
token, collector = *ARGV
|
133
|
+
unless token
|
134
|
+
puts "Usage: #{$0} <token> [collector]"
|
135
|
+
exit 1
|
136
|
+
end
|
137
|
+
I = Instrumental::Agent.new(token, :collector => collector)
|
138
|
+
|
139
|
+
host = `hostname`.chomp
|
140
|
+
|
141
|
+
puts "Collecting stats under the hostname: #{host}"
|
142
|
+
|
143
|
+
loop do
|
144
|
+
inspector = SystemInspector.new
|
145
|
+
inspector.load_all
|
146
|
+
inspector.gauges.each do |stat, value|
|
147
|
+
I.gauge("#{host}.#{stat}", value)
|
148
|
+
end
|
149
|
+
# I.increment("#{host}.#{stat}", delta)
|
150
|
+
sleep 1
|
151
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "instrumental/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "instrumental_agent"
|
6
|
+
s.version = Instrumental::VERSION
|
7
|
+
s.authors = ["Elijah Miller", "Christopher Zelenak"]
|
8
|
+
s.email = ["elijah.miller@gmail.com", "netshade@gmail.com"]
|
9
|
+
s.homepage = "http://github.com/fastestforward/instrumental_agent"
|
10
|
+
s.summary = %q{Agent for reporting data to instrumentalapp.com}
|
11
|
+
s.description = %q{Keep track of anything.}
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
15
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
16
|
+
s.require_paths = ["lib"]
|
17
|
+
|
18
|
+
s.add_runtime_dependency(%q<eventmachine>, [">= 0"])
|
19
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.0"])
|
20
|
+
s.add_development_dependency(%q<guard>, [">= 0"])
|
21
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
22
|
+
s.add_development_dependency(%q<growl_notify>, [">= 0"])
|
23
|
+
s.add_development_dependency(%q<rb-fsevent>, [">= 0"])
|
24
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'instrumental/setup'
|
2
|
+
require 'instrumental/version'
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'logger'
|
5
|
+
|
6
|
+
# Sets up a connection to the collector.
|
7
|
+
#
|
8
|
+
# Instrumental::Agent.new(API_KEY)
|
9
|
+
module Instrumental
|
10
|
+
class Agent
|
11
|
+
attr_accessor :host, :port
|
12
|
+
attr_reader :connection
|
13
|
+
|
14
|
+
def self.start_reactor
|
15
|
+
unless EM.reactor_running?
|
16
|
+
logger.debug 'Starting EventMachine reactor'
|
17
|
+
Thread.new { EM.run }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.logger=(l)
|
22
|
+
@logger = l
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.logger
|
26
|
+
@logger ||= Logger.new('/dev/null')
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.all
|
30
|
+
@agents ||= []
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.new(*args)
|
34
|
+
inst = super
|
35
|
+
all << inst
|
36
|
+
inst
|
37
|
+
end
|
38
|
+
|
39
|
+
module ServerConnection
|
40
|
+
BACKOFF = 2
|
41
|
+
MAX_RECONNECT_DELAY = 5
|
42
|
+
MAX_BUFFER = 10
|
43
|
+
|
44
|
+
attr_accessor :agent
|
45
|
+
attr_reader :connected, :failures, :buffer
|
46
|
+
|
47
|
+
def initialize(agent, api_key)
|
48
|
+
@agent = agent
|
49
|
+
@buffer = []
|
50
|
+
@api_key = api_key
|
51
|
+
end
|
52
|
+
|
53
|
+
def logger
|
54
|
+
agent.logger
|
55
|
+
end
|
56
|
+
|
57
|
+
def connection_completed
|
58
|
+
logger.info "connected to collector"
|
59
|
+
@connected = true
|
60
|
+
@failures = 0
|
61
|
+
send_data("hello version #{Instrumental::VERSION}\n")
|
62
|
+
send_data("authenticate #{@api_key}\n") if @api_key
|
63
|
+
dropped = @buffer.dup
|
64
|
+
@buffer = []
|
65
|
+
dropped.each do |msg|
|
66
|
+
send_data(msg)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def receive_data(data)
|
71
|
+
logger.debug "Received: #{data.chomp}"
|
72
|
+
end
|
73
|
+
|
74
|
+
def send_data(data)
|
75
|
+
if @connected
|
76
|
+
super
|
77
|
+
else
|
78
|
+
if @buffer.size < MAX_BUFFER
|
79
|
+
@buffer << data
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def unbind
|
85
|
+
@connected = false
|
86
|
+
@failures = @failures.to_i + 1
|
87
|
+
delay = [@failures ** BACKOFF / 10.to_f, MAX_RECONNECT_DELAY].min
|
88
|
+
logger.info "disconnected, reconnect in #{delay}..."
|
89
|
+
EM::Timer.new(delay) do
|
90
|
+
reconnect(agent.host, agent.port)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
# Sets up a connection to the collector.
|
97
|
+
#
|
98
|
+
# Instrumental::Agent.new(API_KEY)
|
99
|
+
# Instrumental::Agent.new(API_KEY, :collector => 'hostname:port')
|
100
|
+
def initialize(api_key, options = {})
|
101
|
+
default_options = { :start_reactor => true }
|
102
|
+
options = default_options.merge(options)
|
103
|
+
@api_key = api_key
|
104
|
+
if options[:collector]
|
105
|
+
@host, @port = options[:collector].split(':')
|
106
|
+
@port = (@port || 8000).to_i
|
107
|
+
else
|
108
|
+
@host = 'instrumentalapp.com'
|
109
|
+
@port = 8000
|
110
|
+
end
|
111
|
+
|
112
|
+
self.class.start_reactor if options[:start_reactor]
|
113
|
+
|
114
|
+
EM.next_tick do
|
115
|
+
@connection = EM.connect host, port, ServerConnection, self, api_key
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Store a gauge for a metric, optionally at a specific time.
|
120
|
+
#
|
121
|
+
# agent.gauge('load', 1.23)
|
122
|
+
def gauge(metric, value, time = Time.now)
|
123
|
+
if valid?(metric, value, time)
|
124
|
+
send_command("gauge", metric, value, time.to_i)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Increment a metric, optionally more than one or at a specific time.
|
129
|
+
#
|
130
|
+
# agent.increment('users')
|
131
|
+
def increment(metric, value = 1, time = Time.now)
|
132
|
+
valid?(metric, value, time)
|
133
|
+
send_command("increment", metric, value, time.to_i)
|
134
|
+
end
|
135
|
+
|
136
|
+
def connected?
|
137
|
+
connection && connection.connected
|
138
|
+
end
|
139
|
+
|
140
|
+
def logger
|
141
|
+
self.class.logger
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def valid?(metric, value, time)
|
147
|
+
if metric !~ /^([\d\w\-_]+\.)*[\d\w\-_]+$/i
|
148
|
+
increment 'agent.invalid_metric'
|
149
|
+
logger.warn "Invalid metric #{metric}"
|
150
|
+
return false
|
151
|
+
end
|
152
|
+
if value.to_s !~ /^\d+(\.\d+)?$/
|
153
|
+
increment 'agent.invalid_value'
|
154
|
+
logger.warn "Invalid value #{value.inspect} for #{metric}"
|
155
|
+
return false
|
156
|
+
end
|
157
|
+
true
|
158
|
+
end
|
159
|
+
|
160
|
+
def send_command(cmd, *args)
|
161
|
+
cmd = "%s %s\n" % [cmd, args.collect(&:to_s).join(" ")]
|
162
|
+
logger.debug "Sending: #{cmd.chomp}"
|
163
|
+
EM.next_tick do
|
164
|
+
connection.send_data(cmd)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Instrumental
|
2
|
+
class Middleware
|
3
|
+
def self.boot
|
4
|
+
if @stack = detect_stack
|
5
|
+
@stack.install_middleware
|
6
|
+
@enabled = true
|
7
|
+
else
|
8
|
+
@enabled = false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.stack; @stack; end
|
13
|
+
|
14
|
+
def self.enabled; @enabled; end
|
15
|
+
def self.enabled=(v); @enabled = v; end
|
16
|
+
|
17
|
+
def initialize(app)
|
18
|
+
@app = app
|
19
|
+
end
|
20
|
+
|
21
|
+
def stack
|
22
|
+
Middleware.stack
|
23
|
+
end
|
24
|
+
|
25
|
+
def measure(env, &block)
|
26
|
+
response = nil
|
27
|
+
if Middleware.enabled
|
28
|
+
request = Rack::Request.new(env)
|
29
|
+
key_parts = stack.recognize_uri(request)
|
30
|
+
if key = key_parts.join(".")
|
31
|
+
exc = nil
|
32
|
+
tms = Benchmark.measure do
|
33
|
+
begin
|
34
|
+
response = yield
|
35
|
+
rescue Exception => e
|
36
|
+
exc = e
|
37
|
+
end
|
38
|
+
end
|
39
|
+
begin
|
40
|
+
Agent.all.each do |agent|
|
41
|
+
if exc
|
42
|
+
agent.increment("%s.error.%s" % [key, exc.class])
|
43
|
+
end
|
44
|
+
if response && response.first
|
45
|
+
agent.increment("%s.status.%i" % [key, response.first])
|
46
|
+
else
|
47
|
+
agent.increment(key)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
rescue Exception => e
|
51
|
+
stack.log "Error occurred sending stats: #{e}"
|
52
|
+
stack.log e.backtrace.join("\n")
|
53
|
+
end
|
54
|
+
raise exc if exc
|
55
|
+
end
|
56
|
+
end
|
57
|
+
response ||= yield
|
58
|
+
end
|
59
|
+
|
60
|
+
def call(env)
|
61
|
+
measure(env) do
|
62
|
+
@app.call(env)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Stack
|
67
|
+
def self.default_logger
|
68
|
+
@logger ||= Logger.new('/dev/null')
|
69
|
+
end
|
70
|
+
|
71
|
+
def log(msg)
|
72
|
+
Stack.default_logger.error(msg)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def self.detect_stack
|
79
|
+
[Rails3, Rails23].collect { |klass| klass.create }.detect { |obj| !obj.nil? }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
require 'instrumental/rack/rails3'
|
85
|
+
require 'instrumental/rack/rails23'
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Instrumental
|
2
|
+
class Middleware
|
3
|
+
class Rails23 < Stack
|
4
|
+
def self.create
|
5
|
+
if (defined?(::RAILS_VERSION) && const_get(:RAILS_VERSION).to_s =~ /^2\.3/) ||
|
6
|
+
(defined?(Rails) && Rails.respond_to?(:version) && Rails.version.to_s =~ /^2\.3/)
|
7
|
+
new
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def install_middleware
|
12
|
+
Rails.configuration.middleware.use Instrumental::Middleware
|
13
|
+
end
|
14
|
+
|
15
|
+
def log(msg)
|
16
|
+
Rails.logger.error msg
|
17
|
+
end
|
18
|
+
|
19
|
+
def recognize_uri(request)
|
20
|
+
params = ActionController::Routing::Routes.recognize_path(request.path, request.env.merge(:method => request.env["REQUEST_METHOD"].downcase.to_sym))
|
21
|
+
["controller", params[:controller], params[:action]]
|
22
|
+
rescue ActionController::RoutingError => e
|
23
|
+
["controller", "unknown"]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Instrumental
|
2
|
+
class Middleware
|
3
|
+
class Rails3 < Stack
|
4
|
+
def self.create
|
5
|
+
if defined?(Rails) && Rails.respond_to?(:version) && Rails.version.to_s =~ /^3/
|
6
|
+
new
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def install_middleware
|
11
|
+
require 'instrumental/rack/rails3/middleware_bootstrap'
|
12
|
+
end
|
13
|
+
|
14
|
+
def log(msg)
|
15
|
+
Rails.logger.error msg
|
16
|
+
end
|
17
|
+
|
18
|
+
def recognize_uri(request)
|
19
|
+
Rails.application.routes.finalize!
|
20
|
+
params = Rails.application.routes.recognize_path(request.url, request.env)
|
21
|
+
["controller", params[:controller], params[:action]]
|
22
|
+
rescue ActionController::RoutingError => e
|
23
|
+
["controller", "unknown"]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'instrumental/agent'
|
data/spec/agent_spec.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Instrumental::Agent do
|
4
|
+
before do
|
5
|
+
random_port = Time.now.to_i % rand(2000)
|
6
|
+
base_port = 4000
|
7
|
+
@port = base_port + random_port
|
8
|
+
TestServer.start(@port)
|
9
|
+
end
|
10
|
+
|
11
|
+
after do
|
12
|
+
TestServer.stop
|
13
|
+
end
|
14
|
+
|
15
|
+
subject { Instrumental::Agent.new('test_token', :collector => "127.0.0.1:#{@port}") }
|
16
|
+
|
17
|
+
it 'should announce itself including version' do
|
18
|
+
subject.gauge('gauge_test', 123)
|
19
|
+
EM.next do
|
20
|
+
TestServer.last.buffer.first.should match(/hello.*version/)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should authenticate using the token' do
|
25
|
+
subject.gauge('gauge_test', 123)
|
26
|
+
EM.next do
|
27
|
+
TestServer.last.buffer[1].should == "authenticate test_token"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should report a gauge to the collector' do
|
32
|
+
now = Time.now
|
33
|
+
subject.gauge('gauge_test', 123)
|
34
|
+
EM.next do
|
35
|
+
TestServer.last.buffer.last.should == "gauge gauge_test 123 #{now.to_i}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should report a gauge to the collector with a set time' do
|
40
|
+
subject.gauge('gauge_test', 123, 555)
|
41
|
+
EM.next do
|
42
|
+
TestServer.last.buffer.last.should == 'gauge gauge_test 123 555'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should automatically reconnect" do
|
47
|
+
EM.next do
|
48
|
+
subject.gauge('gauge_test', 123, 555)
|
49
|
+
end
|
50
|
+
EM.next do
|
51
|
+
subject.connection.close_connection(false)
|
52
|
+
end
|
53
|
+
EM.next do
|
54
|
+
subject.gauge('gauge_test', 444, 555)
|
55
|
+
end
|
56
|
+
EM.next do
|
57
|
+
TestServer.last.buffer.last.should == 'gauge gauge_test 444 555'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
|
3
|
+
require 'instrumental_agent'
|
4
|
+
require 'test_server'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
|
8
|
+
config.before(:all) do
|
9
|
+
unless EM.reactor_running?
|
10
|
+
EM.error_handler { |*args|
|
11
|
+
puts "\n"
|
12
|
+
puts "*" * 80
|
13
|
+
puts "EVENTMACHINE ERROR: #{args.inspect}\n"
|
14
|
+
puts args.first.backtrace.join("\n")
|
15
|
+
puts "*" * 80
|
16
|
+
puts "\n"
|
17
|
+
}
|
18
|
+
Thread.new { EM.run }
|
19
|
+
sleep(0.001) while !EM.reactor_running?
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
config.after(:all) do
|
24
|
+
EM.stop
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
module EM
|
31
|
+
|
32
|
+
def self.next(&block)
|
33
|
+
EM.wait_for_events
|
34
|
+
mtx = Mutex.new
|
35
|
+
exc = nil
|
36
|
+
EM.next_tick do
|
37
|
+
mtx.synchronize {
|
38
|
+
begin
|
39
|
+
yield
|
40
|
+
rescue Exception => e
|
41
|
+
exc = e
|
42
|
+
end
|
43
|
+
}
|
44
|
+
end
|
45
|
+
EM.wait_for_events
|
46
|
+
mtx.lock
|
47
|
+
raise exc if exc
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.wait_for_events
|
52
|
+
raise "Reactor is not running" unless EM.reactor_running?
|
53
|
+
while EM.reactor_running? && !(@next_tick_queue && @next_tick_queue.empty? && @timers && @timers.empty?)
|
54
|
+
sleep(0.01) # This value is REALLY important
|
55
|
+
# Too low and it doesn't allow
|
56
|
+
# local socket events to finish
|
57
|
+
# processing
|
58
|
+
# Too high and it makes the tests
|
59
|
+
# slow.
|
60
|
+
end
|
61
|
+
raise @wrapped_exception if @wrapped_exception
|
62
|
+
end
|
63
|
+
end
|
data/spec/test_server.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
module TestServer
|
2
|
+
CMUTEX = Mutex.new
|
3
|
+
|
4
|
+
def self.start_reactor
|
5
|
+
Thread.new { EM.run } unless EM.reactor_running?
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.start(port)
|
9
|
+
start_reactor
|
10
|
+
unless @sig
|
11
|
+
EM.next_tick {
|
12
|
+
@sig = EventMachine.start_server "127.0.0.1", port, TestServer
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.stop
|
18
|
+
if (sig=@sig)
|
19
|
+
EM.next_tick {
|
20
|
+
EventMachine.stop_server sig
|
21
|
+
}
|
22
|
+
@sig = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.connections
|
27
|
+
CMUTEX.synchronize do
|
28
|
+
@connections ||= []
|
29
|
+
@connections.dup
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.add_connection(conn)
|
34
|
+
CMUTEX.synchronize do
|
35
|
+
@connections ||= []
|
36
|
+
@connections << conn
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.remove_connection(conn)
|
41
|
+
CMUTEX.synchronize do
|
42
|
+
@connections ||= []
|
43
|
+
@connections.delete(conn)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.last
|
48
|
+
connections.last
|
49
|
+
end
|
50
|
+
|
51
|
+
def post_init
|
52
|
+
TestServer.add_connection(self)
|
53
|
+
end
|
54
|
+
|
55
|
+
def receive_data data
|
56
|
+
buffer_response(data)
|
57
|
+
end
|
58
|
+
|
59
|
+
def unbind
|
60
|
+
TestServer.remove_connection(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
def last_message
|
64
|
+
buffer.last
|
65
|
+
end
|
66
|
+
|
67
|
+
def buffer
|
68
|
+
@buffer ||= []
|
69
|
+
@buffer.dup
|
70
|
+
end
|
71
|
+
|
72
|
+
def clear_buffer!
|
73
|
+
@buffer = []
|
74
|
+
end
|
75
|
+
|
76
|
+
def buffer_response(dt)
|
77
|
+
@buffer ||= []
|
78
|
+
dt.split(/\n/).each do |measurement|
|
79
|
+
@buffer << measurement
|
80
|
+
end
|
81
|
+
dt
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
|
metadata
ADDED
@@ -0,0 +1,173 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: instrumental_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Elijah Miller
|
14
|
+
- Christopher Zelenak
|
15
|
+
autorequire:
|
16
|
+
bindir: bin
|
17
|
+
cert_chain: []
|
18
|
+
|
19
|
+
date: 2011-10-27 00:00:00 -04:00
|
20
|
+
default_executable:
|
21
|
+
dependencies:
|
22
|
+
- !ruby/object:Gem::Dependency
|
23
|
+
name: eventmachine
|
24
|
+
prerelease: false
|
25
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 3
|
31
|
+
segments:
|
32
|
+
- 0
|
33
|
+
version: "0"
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ~>
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 0
|
48
|
+
version: "2.0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: guard
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: guard-rspec
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :development
|
78
|
+
version_requirements: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: growl_notify
|
81
|
+
prerelease: false
|
82
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
hash: 3
|
88
|
+
segments:
|
89
|
+
- 0
|
90
|
+
version: "0"
|
91
|
+
type: :development
|
92
|
+
version_requirements: *id005
|
93
|
+
- !ruby/object:Gem::Dependency
|
94
|
+
name: rb-fsevent
|
95
|
+
prerelease: false
|
96
|
+
requirement: &id006 !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
hash: 3
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
type: :development
|
106
|
+
version_requirements: *id006
|
107
|
+
description: Keep track of anything.
|
108
|
+
email:
|
109
|
+
- elijah.miller@gmail.com
|
110
|
+
- netshade@gmail.com
|
111
|
+
executables:
|
112
|
+
- watch_server.rb
|
113
|
+
extensions: []
|
114
|
+
|
115
|
+
extra_rdoc_files: []
|
116
|
+
|
117
|
+
files:
|
118
|
+
- .gitignore
|
119
|
+
- Gemfile
|
120
|
+
- Guardfile
|
121
|
+
- README.rdoc
|
122
|
+
- Rakefile
|
123
|
+
- bin/watch_server.rb
|
124
|
+
- instrumental_agent.gemspec
|
125
|
+
- lib/instrumental/agent.rb
|
126
|
+
- lib/instrumental/rack/middleware.rb
|
127
|
+
- lib/instrumental/rack/rails23.rb
|
128
|
+
- lib/instrumental/rack/rails3.rb
|
129
|
+
- lib/instrumental/rack/rails3/middleware_bootstrap.rb
|
130
|
+
- lib/instrumental/setup.rb
|
131
|
+
- lib/instrumental/version.rb
|
132
|
+
- lib/instrumental_agent.rb
|
133
|
+
- spec/agent_spec.rb
|
134
|
+
- spec/spec_helper.rb
|
135
|
+
- spec/test_server.rb
|
136
|
+
has_rdoc: true
|
137
|
+
homepage: http://github.com/fastestforward/instrumental_agent
|
138
|
+
licenses: []
|
139
|
+
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
|
143
|
+
require_paths:
|
144
|
+
- lib
|
145
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
146
|
+
none: false
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
hash: 3
|
151
|
+
segments:
|
152
|
+
- 0
|
153
|
+
version: "0"
|
154
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
155
|
+
none: false
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
hash: 3
|
160
|
+
segments:
|
161
|
+
- 0
|
162
|
+
version: "0"
|
163
|
+
requirements: []
|
164
|
+
|
165
|
+
rubyforge_project:
|
166
|
+
rubygems_version: 1.6.1
|
167
|
+
signing_key:
|
168
|
+
specification_version: 3
|
169
|
+
summary: Agent for reporting data to instrumentalapp.com
|
170
|
+
test_files:
|
171
|
+
- spec/agent_spec.rb
|
172
|
+
- spec/spec_helper.rb
|
173
|
+
- spec/test_server.rb
|