instrumental_agent 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .DS_Store
2
+ Thumbs.db
3
+ Gemfile.lock
4
+ pkg/
5
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source :rubygems
2
+
3
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', :version => 2 do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
4
+ watch(%r{spec/(spec_helper|test_server).rb}) { "spec/" }
5
+ end
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'
@@ -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,6 @@
1
+ module Instrumental
2
+ class MiddlewareBootstrap < Rails::Railtie
3
+ config.app_middleware.use Instrumental::Middleware
4
+
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ require 'instrumental/rack/middleware'
2
+
3
+ Instrumental::Middleware.boot
@@ -0,0 +1,4 @@
1
+ module Instrumental
2
+ VERSION = "0.1.0"
3
+ end
4
+
@@ -0,0 +1 @@
1
+ require 'instrumental/agent'
@@ -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
@@ -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
@@ -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