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 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