pirate_metrics_agent 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +9 -0
- data/Guardfile +5 -0
- data/LICENSE +21 -0
- data/README.md +59 -0
- data/Rakefile +9 -0
- data/lib/pirate_metrics/agent.rb +266 -0
- data/lib/pirate_metrics/system_timer.rb +31 -0
- data/lib/pirate_metrics/version.rb +3 -0
- data/lib/pirate_metrics_agent.rb +1 -0
- data/pirate_metrics_agent.gemspec +28 -0
- data/spec/agent_spec.rb +436 -0
- data/spec/spec_helper.rb +72 -0
- data/spec/test_server.rb +97 -0
- metadata +212 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour --format Fuubar
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Initial software Copyright (c) 2011 Fastest Forward
|
2
|
+
Pirate Metrics portions Copyright (c) 2012 Expected Behavior
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
a copy of this software and associated documentation files (the
|
6
|
+
"Software"), to deal in the Software without restriction, including
|
7
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Pirate Metrics Agent
|
2
|
+
|
3
|
+
Get to know your customers
|
4
|
+
|
5
|
+
## Setup & Usage
|
6
|
+
|
7
|
+
Add the gem to your Gemfile.
|
8
|
+
|
9
|
+
```sh
|
10
|
+
gem 'pirate_metrics_agent'
|
11
|
+
```
|
12
|
+
|
13
|
+
Visit [piratemetrics.com](https://piratemetrics.com) and create an account, then initialize the agent with your API key, found on the main project page.
|
14
|
+
|
15
|
+
```sh
|
16
|
+
PM = PirateMetrics::Agent.new('YOUR_API_KEY', :enabled => Rails.env.production?)
|
17
|
+
```
|
18
|
+
|
19
|
+
You'll probably want something like the above, only enabling the agent in production mode so you don't have development and production data writing to the same value. Or you can setup two projects, so that you can verify stats in one, and release them to production in another.
|
20
|
+
|
21
|
+
Now you can begin to use Pirate Metrics to track your application.
|
22
|
+
|
23
|
+
```sh
|
24
|
+
PM.acquisition({ :email => 'joe@example.com'}) # new user acquisition
|
25
|
+
```
|
26
|
+
|
27
|
+
**Note**: For your app's safety, the agent is meant to isolate your app from any problems our service might suffer. If it is unable to connect to the service, it will discard data after reaching a low memory threshold.
|
28
|
+
|
29
|
+
## Backfilling
|
30
|
+
|
31
|
+
You almost certainly have events that occurred before you signed up for Pirate Metrics. To get all of your users into the proper context, Pirate Metrics allows you to backfill data.
|
32
|
+
|
33
|
+
When backfilling, you may send tens of thousands of metrics per second, and the command buffer may start discarding data it isn't able to send fast enough. Using the ! form of the various API calls will force them to be synchronous.
|
34
|
+
|
35
|
+
**Warning**: You should only use synchronous mode for backfilling data as any issues with the Pirate Metrics service issues will cause this code to halt until it can reconnect.
|
36
|
+
|
37
|
+
```sh
|
38
|
+
acquisition_data = []
|
39
|
+
User.find_in_batches(:batch_size => 100) do |users|
|
40
|
+
users.each do |user|
|
41
|
+
acquisition_data << {:email => user.email, :occurred_at => user.created_at}
|
42
|
+
end
|
43
|
+
PM.acquisition!(acquisition_data)
|
44
|
+
acquisition_data.clear
|
45
|
+
end
|
46
|
+
```
|
47
|
+
## Agent Control
|
48
|
+
|
49
|
+
Need to quickly disable the agent? set :enabled to false on initialization and you don't need to change any application code.
|
50
|
+
|
51
|
+
## Tracking metrics in Resque jobs (and Resque-like scenarios)
|
52
|
+
|
53
|
+
If you plan on tracking metrics in Resque jobs, you will need to explicitly cleanup after the agent when the jobs are finished. You can accomplish this by adding `after_perform` and `on_failure` hooks to your Resque jobs. See the Resque [hooks documentation](https://github.com/defunkt/resque/blob/master/docs/HOOKS.md) for more information.
|
54
|
+
|
55
|
+
You're required to do this because Resque calls `exit!` when a worker has finished processing, which bypasses Ruby's `at_exit` hooks. The Pirate Metrics Agent installs an `at_exit` hook to flush any pending metrics to the servers, but this hook is bypassed by the `exit!` call; any other code you rely that uses `exit!` should call `PM.cleanup` to ensure any pending metrics are correctly sent to the server before exiting the process.
|
56
|
+
|
57
|
+
## Troubleshooting & Help
|
58
|
+
|
59
|
+
We are here to help. Email us at [support@piratemetrics.com](mailto:support@piratemetrics.com).
|
data/Rakefile
ADDED
@@ -0,0 +1,266 @@
|
|
1
|
+
require 'pirate_metrics/version'
|
2
|
+
require 'pirate_metrics/system_timer'
|
3
|
+
require 'faraday'
|
4
|
+
require 'logger'
|
5
|
+
require 'thread'
|
6
|
+
require 'socket'
|
7
|
+
|
8
|
+
|
9
|
+
module PirateMetrics
|
10
|
+
class Agent
|
11
|
+
BACKOFF = 2.0
|
12
|
+
MAX_RECONNECT_DELAY = 15
|
13
|
+
MAX_BUFFER = 5000
|
14
|
+
REPLY_TIMEOUT = 10
|
15
|
+
CONNECT_TIMEOUT = 20
|
16
|
+
EXIT_FLUSH_TIMEOUT = 5
|
17
|
+
|
18
|
+
attr_accessor :host, :port, :synchronous, :queue
|
19
|
+
attr_reader :connection, :enabled
|
20
|
+
|
21
|
+
def self.logger=(l)
|
22
|
+
@logger = l
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.logger
|
26
|
+
if !@logger
|
27
|
+
@logger = Logger.new(STDERR)
|
28
|
+
@logger.level = Logger::WARN
|
29
|
+
end
|
30
|
+
@logger
|
31
|
+
end
|
32
|
+
|
33
|
+
# Sets up a connection to the collector.
|
34
|
+
#
|
35
|
+
# PirateMetrics::Agent.new(API_KEY)
|
36
|
+
# PirateMetrics::Agent.new(API_KEY, :collector => 'hostname:port')
|
37
|
+
def initialize(api_key, options = {})
|
38
|
+
# symbolize options keys
|
39
|
+
options.replace(
|
40
|
+
options.inject({}) { |m, (k, v)| m[(k.to_sym rescue k) || k] = v; m }
|
41
|
+
)
|
42
|
+
|
43
|
+
# defaults
|
44
|
+
# host: piratemetrics.com
|
45
|
+
# port: 80
|
46
|
+
# enabled: true
|
47
|
+
# synchronous: false
|
48
|
+
@api_key = api_key
|
49
|
+
@host, @port = options[:collector].to_s.split(':')
|
50
|
+
@host = options[:host] || 'https://piratemetrics.com'
|
51
|
+
@port = (options[:port] || 443).to_i
|
52
|
+
@enabled = options.has_key?(:enabled) ? !!options[:enabled] : true
|
53
|
+
@synchronous = !!options[:synchronous]
|
54
|
+
@pid = Process.pid
|
55
|
+
@allow_reconnect = true
|
56
|
+
|
57
|
+
setup_cleanup_at_exit if @enabled
|
58
|
+
end
|
59
|
+
|
60
|
+
# Store a customer metric
|
61
|
+
#
|
62
|
+
# agent.acquisition!({ :email => 'test@example.com',
|
63
|
+
# :occurred_at => user.created_at,
|
64
|
+
# :level => 'Double Uranium'})
|
65
|
+
[:acquisition, :activation, :retention, :revenue, :referral].each do |metric|
|
66
|
+
define_method metric do |customer|
|
67
|
+
begin
|
68
|
+
payload = customer.is_a?(Array) ? customer : [customer]
|
69
|
+
send_metric(metric, payload, @synchronous)
|
70
|
+
rescue Exception => ex
|
71
|
+
report_exception ex
|
72
|
+
return nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
define_method "#{metric}!" do |customer|
|
76
|
+
begin
|
77
|
+
payload = customer.is_a?(Array) ? customer : [customer]
|
78
|
+
send_metric(metric, payload, true)
|
79
|
+
rescue Exception => ex
|
80
|
+
report_exception ex
|
81
|
+
return nil
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Synchronously flush all pending metrics out to the server
|
87
|
+
# By default will not try to reconnect to the server if a
|
88
|
+
# connection failure happens during the flush, though you
|
89
|
+
# may optionally override this behavior by passing true.
|
90
|
+
#
|
91
|
+
# agent.flush
|
92
|
+
def flush(allow_reconnect = false)
|
93
|
+
queue_metric('flush', nil, {
|
94
|
+
:synchronous => true,
|
95
|
+
:allow_reconnect => allow_reconnect
|
96
|
+
}) if running?
|
97
|
+
end
|
98
|
+
|
99
|
+
def enabled?
|
100
|
+
@enabled
|
101
|
+
end
|
102
|
+
|
103
|
+
def logger=(logger)
|
104
|
+
@logger = logger
|
105
|
+
end
|
106
|
+
|
107
|
+
def logger
|
108
|
+
@logger || self.class.logger
|
109
|
+
end
|
110
|
+
|
111
|
+
# Stopping the agent will immediately stop all communication
|
112
|
+
# to PirateMetrics. If you call this and submit another metric,
|
113
|
+
# the agent will start again.
|
114
|
+
#
|
115
|
+
# Calling stop will cause all metrics waiting to be sent to be
|
116
|
+
# discarded. Don't call it unless you are expecting this behavior.
|
117
|
+
#
|
118
|
+
# agent.stop
|
119
|
+
#
|
120
|
+
def stop
|
121
|
+
if @thread
|
122
|
+
@thread.kill
|
123
|
+
@thread = nil
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Called when a process is exiting to give it some extra time to
|
128
|
+
# push events to the service. An at_exit handler is automatically
|
129
|
+
# registered for this method, but can be called manually in cases
|
130
|
+
# where at_exit is bypassed like Resque workers.
|
131
|
+
def cleanup
|
132
|
+
if running?
|
133
|
+
logger.info "Cleaning up agent, queue size: #{@queue.size}, thread running: #{@thread.alive?}"
|
134
|
+
@allow_reconnect = false
|
135
|
+
if @queue.size > 0
|
136
|
+
queue_metric('exit')
|
137
|
+
begin
|
138
|
+
with_timeout(EXIT_FLUSH_TIMEOUT) { @thread.join }
|
139
|
+
rescue Timeout::Error
|
140
|
+
if @queue.size > 0
|
141
|
+
logger.error "Timed out working agent thread on exit, dropping #{@queue.size} metrics"
|
142
|
+
else
|
143
|
+
logger.error "Timed out PirateMetrics Agent, exiting"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def with_timeout(time, &block)
|
153
|
+
PirateMetricsTimeout.timeout(time) { yield }
|
154
|
+
end
|
155
|
+
|
156
|
+
def report_exception(e)
|
157
|
+
logger.error "Exception occurred: #{e.message}\n#{e.backtrace.join("\n")}"
|
158
|
+
end
|
159
|
+
|
160
|
+
def send_metric(metric, payload, synchronous = false)
|
161
|
+
if enabled?
|
162
|
+
start_connection_worker if !running?
|
163
|
+
if @queue.size < MAX_BUFFER
|
164
|
+
@queue_full_warning = false
|
165
|
+
logger.debug "Queueing: #{metric} -> #{payload.inspect}"
|
166
|
+
queue_metric(metric, payload, { :synchronous => synchronous })
|
167
|
+
else
|
168
|
+
if !@queue_full_warning
|
169
|
+
@queue_full_warning = true
|
170
|
+
logger.warn "Queue full(#{@queue.size}), dropping commands..."
|
171
|
+
end
|
172
|
+
logger.debug "Dropping command, queue full(#{@queue.size}): #{metric}"
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def queue_metric(metric, payload = nil, options = {})
|
179
|
+
if @enabled
|
180
|
+
options ||= {}
|
181
|
+
if options[:allow_reconnect].nil?
|
182
|
+
options[:allow_reconnect] = @allow_reconnect
|
183
|
+
end
|
184
|
+
synchronous = options.delete(:synchronous)
|
185
|
+
if synchronous
|
186
|
+
options[:sync_resource] ||= ConditionVariable.new
|
187
|
+
@sync_mutex.synchronize {
|
188
|
+
@queue << [metric, payload, options]
|
189
|
+
options[:sync_resource].wait(@sync_mutex)
|
190
|
+
}
|
191
|
+
else
|
192
|
+
@queue << [metric, payload, options]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
metric
|
196
|
+
end
|
197
|
+
|
198
|
+
def start_connection_worker
|
199
|
+
if enabled?
|
200
|
+
@pid = Process.pid
|
201
|
+
@queue = Queue.new
|
202
|
+
@sync_mutex = Mutex.new
|
203
|
+
@failures = 0
|
204
|
+
logger.info "Starting thread"
|
205
|
+
@thread = Thread.new do
|
206
|
+
run_worker_loop
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def run_worker_loop
|
212
|
+
command_and_args = nil
|
213
|
+
command_options = nil
|
214
|
+
@piratemetrics = Faraday.new(:url => "#{@host}:#{@port}") do |faraday|
|
215
|
+
faraday.request :url_encoded
|
216
|
+
faraday.adapter Faraday.default_adapter
|
217
|
+
end
|
218
|
+
logger.info "connected to collector at #{host}:#{port}"
|
219
|
+
@failures = 0
|
220
|
+
loop do
|
221
|
+
begin
|
222
|
+
metric, payload, options = @queue.pop
|
223
|
+
sync_resource = options && options[:sync_resource]
|
224
|
+
case metric
|
225
|
+
when 'exit'
|
226
|
+
logger.info "Exiting, #{@queue.size} commands remain"
|
227
|
+
return true
|
228
|
+
when 'flush'
|
229
|
+
release_resource = true
|
230
|
+
else
|
231
|
+
logger.debug "Sending: #{metric} -> #{payload.inspect}"
|
232
|
+
result = @piratemetrics.post("/api/v1/#{metric}s", { :api_key => @api_key, :data => payload})
|
233
|
+
end
|
234
|
+
metric = payload = options = nil
|
235
|
+
rescue Exception => err
|
236
|
+
queue_metric(metric, payload, options) if metric
|
237
|
+
sleep MAX_RECONNECT_DELAY
|
238
|
+
end
|
239
|
+
if sync_resource
|
240
|
+
@sync_mutex.synchronize do
|
241
|
+
sync_resource.signal
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
rescue Exception => err
|
246
|
+
if err.is_a?(EOFError)
|
247
|
+
# nop
|
248
|
+
elsif err.is_a?(Errno::ECONNREFUSED)
|
249
|
+
logger.error "unable to connect to PirateMetrics."
|
250
|
+
else
|
251
|
+
report_exception(err)
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def setup_cleanup_at_exit
|
256
|
+
at_exit do
|
257
|
+
cleanup
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def running?
|
262
|
+
!@thread.nil? && @pid == Process.pid
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
if RUBY_VERSION < "1.9" && RUBY_PLATFORM != "java"
|
2
|
+
timeout_lib = nil
|
3
|
+
["SystemTimer", "system_timer"].each do |lib|
|
4
|
+
begin
|
5
|
+
unless timeout_lib
|
6
|
+
gem lib
|
7
|
+
require "system_timer"
|
8
|
+
timeout_lib = SystemTimer
|
9
|
+
end
|
10
|
+
rescue Exception => e
|
11
|
+
end
|
12
|
+
end
|
13
|
+
if !timeout_lib
|
14
|
+
puts <<-EOMSG
|
15
|
+
WARNING:: You do not currently have system_timer installed.
|
16
|
+
It is strongly advised that you install this gem when using
|
17
|
+
pirate_metrics_agent with Ruby 1.8.x. You can install it in
|
18
|
+
your Gemfile via:
|
19
|
+
gem 'system_timer'
|
20
|
+
or manually via:
|
21
|
+
gem install system_timer
|
22
|
+
EOMSG
|
23
|
+
require 'timeout'
|
24
|
+
PirateMetricsTimeout = Timeout
|
25
|
+
else
|
26
|
+
PirateMetricsTimeout = timeout_lib
|
27
|
+
end
|
28
|
+
else
|
29
|
+
require 'timeout'
|
30
|
+
PirateMetricsTimeout = Timeout
|
31
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'pirate_metrics/agent'
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require "pirate_metrics/version"
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "pirate_metrics_agent"
|
6
|
+
s.version = PirateMetrics::VERSION
|
7
|
+
s.authors = ["Elijah Miller", "Christopher Zelenak", "Kristopher Chambers", "Matthew Hassfurder", "Expected Behavior"]
|
8
|
+
s.email = ["support@piratemetrics.com"]
|
9
|
+
s.homepage = "http://github.com/expectedbehavior/pirate_metrics_agent"
|
10
|
+
s.summary = %q{Agent for reporting data to piratemetrics.com}
|
11
|
+
s.description = %q{Get to know your customers.}
|
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
|
+
s.add_development_dependency(%q<rake>, [">= 0"])
|
18
|
+
s.add_development_dependency(%q<rack>, [">= 0"])
|
19
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.0"])
|
20
|
+
s.add_development_dependency(%q<fuubar>, [">= 0"])
|
21
|
+
if RUBY_VERSION >= "1.8.7"
|
22
|
+
s.add_development_dependency(%q<pry>, ["~> 0.9"])
|
23
|
+
s.add_development_dependency(%q<guard>, [">= 0"])
|
24
|
+
s.add_development_dependency(%q<guard-rspec>, [">= 0"])
|
25
|
+
s.add_development_dependency(%q<growl>, [">= 0"])
|
26
|
+
s.add_development_dependency(%q<rb-fsevent>, [">= 0"])
|
27
|
+
end
|
28
|
+
end
|
data/spec/agent_spec.rb
ADDED
@@ -0,0 +1,436 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
def wait
|
4
|
+
sleep 0.1 # FIXME: hack
|
5
|
+
end
|
6
|
+
|
7
|
+
PirateMetrics::Agent.logger.level = Logger::FATAL
|
8
|
+
describe PirateMetrics::Agent, "disabled" do
|
9
|
+
before do
|
10
|
+
@server = TestServer.new
|
11
|
+
@agent = @server.fresh_agent(:enabled => false)
|
12
|
+
end
|
13
|
+
|
14
|
+
after do
|
15
|
+
@agent.stop
|
16
|
+
@agent = nil
|
17
|
+
@server.stop
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should not connect to the server" do
|
21
|
+
wait
|
22
|
+
@server.connect_count.should == 0
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should not connect to the server after receiving a metric" do
|
26
|
+
wait
|
27
|
+
@agent.acquisition({:email => 'test@example.com'})
|
28
|
+
wait
|
29
|
+
@server.connect_count.should == 0
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should no op on flush without reconnect" do
|
33
|
+
1.upto(100) { |i| @agent.acquisition({:email => "test#{i}@example.com"}) }
|
34
|
+
@agent.flush(false)
|
35
|
+
wait
|
36
|
+
@server.metrics.should be_empty
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should no op on flush with reconnect" do
|
40
|
+
1.upto(100) { |i| @agent.acquisition({:email => "test#{i}@example.com"}) }
|
41
|
+
@agent.flush(true)
|
42
|
+
wait
|
43
|
+
@server.metrics.should be_empty
|
44
|
+
end
|
45
|
+
|
46
|
+
it "should no op on an empty flush" do
|
47
|
+
@agent.flush(true)
|
48
|
+
wait
|
49
|
+
@server.metrics.should be_empty
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe PirateMetrics::Agent, "enabled" do
|
54
|
+
before do
|
55
|
+
@server = TestServer.new
|
56
|
+
@agent = @server.fresh_agent
|
57
|
+
end
|
58
|
+
|
59
|
+
after do
|
60
|
+
@agent.stop
|
61
|
+
@agent = nil
|
62
|
+
@server.stop
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should send an api key" do
|
66
|
+
@agent.acquisition({:email => "test@example.com"})
|
67
|
+
wait
|
68
|
+
@server.last_api_key == "test_token"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe PirateMetrics::Agent, "acquisitions" do
|
73
|
+
before do
|
74
|
+
@server = TestServer.new
|
75
|
+
@agent = @server.fresh_agent
|
76
|
+
end
|
77
|
+
|
78
|
+
after do
|
79
|
+
@agent.stop
|
80
|
+
@agent = nil
|
81
|
+
@server.stop
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should not connect to the server" do
|
85
|
+
wait
|
86
|
+
@server.connect_count.should == 0
|
87
|
+
end
|
88
|
+
|
89
|
+
it "should report an acquisition using the hash form" do
|
90
|
+
@agent.acquisition({:email => "test@example.com"})
|
91
|
+
wait
|
92
|
+
@server.acquisitions.last.should == { "email" => "test@example.com"}
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should report an acquisition using the array form" do
|
96
|
+
@agent.acquisition([{:email => "test1@example.com"}, { :email => "test2@example.com"}])
|
97
|
+
wait
|
98
|
+
@server.acquisitions.first.should == { "email" => "test1@example.com"}
|
99
|
+
@server.acquisitions.last.should == { "email" => "test2@example.com"}
|
100
|
+
end
|
101
|
+
|
102
|
+
it "should be able to report acquisitions synchronously" do
|
103
|
+
@agent.acquisition!({:email => "test@example.com"})
|
104
|
+
wait
|
105
|
+
@server.acquisitions.last.should == { "email" => "test@example.com"}
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
describe PirateMetrics::Agent, "activations" do
|
110
|
+
before do
|
111
|
+
@server = TestServer.new
|
112
|
+
@agent = @server.fresh_agent
|
113
|
+
end
|
114
|
+
|
115
|
+
after do
|
116
|
+
@agent.stop
|
117
|
+
@agent = nil
|
118
|
+
@server.stop
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should not connect to the server" do
|
122
|
+
wait
|
123
|
+
@server.connect_count.should == 0
|
124
|
+
end
|
125
|
+
|
126
|
+
it "should report an activation using the hash form" do
|
127
|
+
@agent.activation({:email => "test@example.com"})
|
128
|
+
wait
|
129
|
+
@server.activations.last.should == { "email" => "test@example.com"}
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should report an activation using the array form" do
|
133
|
+
@agent.activation([{:email => "test1@example.com"}, { :email => "test2@example.com"}])
|
134
|
+
wait
|
135
|
+
@server.activations.first.should == { "email" => "test1@example.com"}
|
136
|
+
@server.activations.last.should == { "email" => "test2@example.com"}
|
137
|
+
end
|
138
|
+
|
139
|
+
it "should be able to report activations synchronously" do
|
140
|
+
@agent.activation!({:email => "test@example.com"})
|
141
|
+
wait
|
142
|
+
@server.activations.last.should == { "email" => "test@example.com"}
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
describe PirateMetrics::Agent, "retentions" do
|
147
|
+
before do
|
148
|
+
@server = TestServer.new
|
149
|
+
@agent = @server.fresh_agent
|
150
|
+
end
|
151
|
+
|
152
|
+
after do
|
153
|
+
@agent.stop
|
154
|
+
@agent = nil
|
155
|
+
@server.stop
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should not connect to the server" do
|
159
|
+
wait
|
160
|
+
@server.connect_count.should == 0
|
161
|
+
end
|
162
|
+
|
163
|
+
it "should report an retention using the hash form" do
|
164
|
+
@agent.retention({:email => "test@example.com"})
|
165
|
+
wait
|
166
|
+
@server.retentions.last.should == { "email" => "test@example.com"}
|
167
|
+
end
|
168
|
+
|
169
|
+
it "should report an retention using the array form" do
|
170
|
+
@agent.retention([{:email => "test1@example.com"}, { :email => "test2@example.com"}])
|
171
|
+
wait
|
172
|
+
@server.retentions.first.should == { "email" => "test1@example.com"}
|
173
|
+
@server.retentions.last.should == { "email" => "test2@example.com"}
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should be able to report retentions synchronously" do
|
177
|
+
@agent.retention!({:email => "test@example.com"})
|
178
|
+
wait
|
179
|
+
@server.retentions.last.should == { "email" => "test@example.com"}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe PirateMetrics::Agent, "revenues" do
|
184
|
+
before do
|
185
|
+
@server = TestServer.new
|
186
|
+
@agent = @server.fresh_agent
|
187
|
+
end
|
188
|
+
|
189
|
+
after do
|
190
|
+
@agent.stop
|
191
|
+
@agent = nil
|
192
|
+
@server.stop
|
193
|
+
end
|
194
|
+
|
195
|
+
it "should not connect to the server" do
|
196
|
+
wait
|
197
|
+
@server.connect_count.should == 0
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should report an revenue using the hash form" do
|
201
|
+
@agent.revenue({:email => "test@example.com", :amount_in_cents => "1000"})
|
202
|
+
wait
|
203
|
+
@server.revenues.last.should == { "email" => "test@example.com", "amount_in_cents" => "1000"}
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should report an revenue using the array form" do
|
207
|
+
@agent.revenue([{:email => "test1@example.com", :amount_in_cents => "1000"}, { :email => "test2@example.com", :amount_in_cents => "2000"}])
|
208
|
+
wait
|
209
|
+
@server.revenues.first.should == { "email" => "test1@example.com", "amount_in_cents" => "1000"}
|
210
|
+
@server.revenues.last.should == { "email" => "test2@example.com", "amount_in_cents" => "2000"}
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should be able to report revenues synchronously" do
|
214
|
+
@agent.revenue!({:email => "test@example.com", :amount_in_cents => "1000"})
|
215
|
+
wait
|
216
|
+
@server.revenues.last.should == { "email" => "test@example.com", "amount_in_cents" => "1000"}
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
|
221
|
+
describe PirateMetrics::Agent, "referrals" do
|
222
|
+
before do
|
223
|
+
@server = TestServer.new
|
224
|
+
@agent = @server.fresh_agent
|
225
|
+
end
|
226
|
+
|
227
|
+
after do
|
228
|
+
@agent.stop
|
229
|
+
@agent = nil
|
230
|
+
@server.stop
|
231
|
+
end
|
232
|
+
|
233
|
+
it "should not connect to the server" do
|
234
|
+
wait
|
235
|
+
@server.connect_count.should == 0
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should report an referral using the hash form" do
|
239
|
+
@agent.referral({:customer_email => "test@example.com", :referree_email => "ref@example.com"})
|
240
|
+
wait
|
241
|
+
@server.referrals.last.should == { "customer_email" => "test@example.com", "referree_email" => "ref@example.com"}
|
242
|
+
end
|
243
|
+
|
244
|
+
it "should report an referral using the array form" do
|
245
|
+
@agent.referral([{:customer_email => "test1@example.com", :referree_email => "ref@example.com"}, { :customer_email => "test2@example.com", :referree_email => "ref2@example.com"}])
|
246
|
+
wait
|
247
|
+
@server.referrals.first.should == { "customer_email" => "test1@example.com", "referree_email" => "ref@example.com"}
|
248
|
+
@server.referrals.last.should == { "customer_email" => "test2@example.com", "referree_email" => "ref2@example.com"}
|
249
|
+
end
|
250
|
+
|
251
|
+
it "should be able to report referrals synchronously" do
|
252
|
+
@agent.referral!({:customer_email => "test@example.com", :referree_email => "ref@example.com"})
|
253
|
+
wait
|
254
|
+
@server.referrals.last.should == { "customer_email" => "test@example.com", "referree_email" => "ref@example.com"}
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
describe PirateMetrics::Agent, "agent queueing, synchronicity, reliability" do
|
259
|
+
before do
|
260
|
+
@server = TestServer.new
|
261
|
+
@agent = @server.fresh_agent
|
262
|
+
end
|
263
|
+
|
264
|
+
after do
|
265
|
+
@agent.stop
|
266
|
+
@agent = nil
|
267
|
+
@server.stop
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should discard data that overflows the buffer" do
|
271
|
+
with_constants('PirateMetrics::Agent::MAX_BUFFER' => 3) do
|
272
|
+
5.times do |i|
|
273
|
+
@agent.acquisition({ :email => "test#{i}@example.com"})
|
274
|
+
end
|
275
|
+
wait
|
276
|
+
@server.acquisitions.should include({ "email" => "test0@example.com"})
|
277
|
+
@server.acquisitions.should include({ "email" => "test1@example.com"})
|
278
|
+
@server.acquisitions.should include({ "email" => "test2@example.com"})
|
279
|
+
@server.acquisitions.should_not include({ "email" => "test3@example.com"})
|
280
|
+
@server.acquisitions.should_not include({ "email" => "test4@example.com"})
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should send all data in synchronous mode" do
|
285
|
+
with_constants('PirateMetrics::Agent::MAX_BUFFER' => 3) do
|
286
|
+
5.times do |i|
|
287
|
+
@agent.acquisition!({ :email => "test#{i}@example.com"})
|
288
|
+
end
|
289
|
+
@agent.instance_variable_get(:@queue).size.should == 0
|
290
|
+
wait
|
291
|
+
@server.acquisitions.should include({ "email" => "test0@example.com"})
|
292
|
+
@server.acquisitions.should include({ "email" => "test1@example.com"})
|
293
|
+
@server.acquisitions.should include({ "email" => "test2@example.com"})
|
294
|
+
@server.acquisitions.should include({ "email" => "test3@example.com"})
|
295
|
+
@server.acquisitions.should include({ "email" => "test4@example.com"})
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
it "should send all data in synchronous mode (agent-level)" do
|
300
|
+
with_constants('PirateMetrics::Agent::MAX_BUFFER' => 3) do
|
301
|
+
@agent.synchronous = true
|
302
|
+
5.times do |i|
|
303
|
+
@agent.acquisition({ :email => "test#{i}@example.com"})
|
304
|
+
end
|
305
|
+
@agent.instance_variable_get(:@queue).size.should == 0
|
306
|
+
wait
|
307
|
+
@server.acquisitions.should include({ "email" => "test0@example.com"})
|
308
|
+
@server.acquisitions.should include({ "email" => "test1@example.com"})
|
309
|
+
@server.acquisitions.should include({ "email" => "test2@example.com"})
|
310
|
+
@server.acquisitions.should include({ "email" => "test3@example.com"})
|
311
|
+
@server.acquisitions.should include({ "email" => "test4@example.com"})
|
312
|
+
end
|
313
|
+
end
|
314
|
+
|
315
|
+
it "should automatically reconnect when forked" do
|
316
|
+
wait
|
317
|
+
@agent.acquisition({ :email => "test0@example.com"})
|
318
|
+
fork do
|
319
|
+
@agent.acquisition({ :email => "test1@example.com"})
|
320
|
+
end
|
321
|
+
wait
|
322
|
+
@agent.acquisition({ :email => "test2@example.com"})
|
323
|
+
wait
|
324
|
+
@server.acquisitions.should include({ "email" => "test0@example.com"})
|
325
|
+
@server.acquisitions.should include({ "email" => "test1@example.com"})
|
326
|
+
@server.acquisitions.should include({ "email" => "test2@example.com"})
|
327
|
+
end
|
328
|
+
|
329
|
+
it "should never let an exception reach the user" do
|
330
|
+
@agent.stub!(:send_metric).and_raise(Exception.new("Test Exception"))
|
331
|
+
@agent.acquisition({ :email => "test@example.com"}).should be_nil
|
332
|
+
wait
|
333
|
+
@agent.activation({ :email => "test@example.com"}).should be_nil
|
334
|
+
wait
|
335
|
+
end
|
336
|
+
|
337
|
+
it "should allow outgoing metrics to be stopped" do
|
338
|
+
tm = Time.now
|
339
|
+
@agent.acquisition({ :email => "testbad@example.com"})
|
340
|
+
@agent.stop
|
341
|
+
wait
|
342
|
+
@agent.acquisition({ :email => "testgood@example.com"})
|
343
|
+
wait
|
344
|
+
@server.acquisitions.should_not include({ "email" => "testbad@example.com"})
|
345
|
+
@server.acquisitions.should include({ "email" => "testgood@example.com"})
|
346
|
+
end
|
347
|
+
|
348
|
+
it "should allow flushing pending values to the server" do
|
349
|
+
1.upto(100) { |i| @agent.acquisition({ :email => "test#{i}@example.com"}) }
|
350
|
+
@agent.instance_variable_get(:@queue).size.should >= 100
|
351
|
+
@agent.flush
|
352
|
+
@agent.instance_variable_get(:@queue).size.should == 0
|
353
|
+
wait
|
354
|
+
@server.acquisitions.size.should == 100
|
355
|
+
end
|
356
|
+
|
357
|
+
it "should no op on an empty flush" do
|
358
|
+
@agent.flush(true)
|
359
|
+
wait
|
360
|
+
@server.connect_count.should == 0
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
describe PirateMetrics::Agent, "connection problems" do
|
365
|
+
after do
|
366
|
+
@agent.stop
|
367
|
+
@server.stop
|
368
|
+
end
|
369
|
+
|
370
|
+
it "should automatically reconnect on disconnect" do
|
371
|
+
@server = TestServer.new
|
372
|
+
@agent = @server.fresh_agent
|
373
|
+
@agent.acquisition({ :email => "test1@example.com"})
|
374
|
+
wait
|
375
|
+
@server.disconnect_all
|
376
|
+
wait
|
377
|
+
@agent.acquisition({ :email => "test2@example.com"})
|
378
|
+
wait
|
379
|
+
@server.connect_count.should == 2
|
380
|
+
@server.acquisitions.last.should == { "email" => "test2@example.com"}
|
381
|
+
end
|
382
|
+
|
383
|
+
it "should buffer commands when server is down" do
|
384
|
+
@server = TestServer.new(:listen => false)
|
385
|
+
@agent = @server.fresh_agent
|
386
|
+
wait
|
387
|
+
@agent.retention({ :email => "test@example.com"})
|
388
|
+
wait
|
389
|
+
@agent.queue.size.should == 1
|
390
|
+
end
|
391
|
+
|
392
|
+
it "should send commands in a short-lived process" do
|
393
|
+
@server = TestServer.new
|
394
|
+
@agent = @server.fresh_agent
|
395
|
+
if pid = fork { @agent.acquisition({ :email => "test@example.com"}) }
|
396
|
+
Process.wait(pid)
|
397
|
+
@server.acquisitions.size.should == 1
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
it "should send commands in a process that bypasses at_exit when using #cleanup" do
|
402
|
+
@server = TestServer.new
|
403
|
+
@agent = @server.fresh_agent
|
404
|
+
if pid = fork { @agent.acquisition({ :email => "test@example.com"}); @agent.cleanup; exit! }
|
405
|
+
Process.wait(pid)
|
406
|
+
@server.acquisitions.size.should == 1
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
describe PirateMetrics::Agent, "enabled with sync option" do
|
412
|
+
before do
|
413
|
+
@server = TestServer.new
|
414
|
+
@agent = @server.fresh_agent({ :synchronous => true})
|
415
|
+
end
|
416
|
+
|
417
|
+
after do
|
418
|
+
@agent.stop
|
419
|
+
@server.stop
|
420
|
+
end
|
421
|
+
|
422
|
+
it "should send all data in synchronous mode" do
|
423
|
+
with_constants('PirateMetrics::Agent::MAX_BUFFER' => 3) do
|
424
|
+
5.times do |i|
|
425
|
+
@agent.acquisition({ :email => "test#{i}@example.com"})
|
426
|
+
end
|
427
|
+
wait # let the server receive the commands
|
428
|
+
@server.acquisitions.should include({ "email" => "test0@example.com"})
|
429
|
+
@server.acquisitions.should include({ "email" => "test1@example.com"})
|
430
|
+
@server.acquisitions.should include({ "email" => "test2@example.com"})
|
431
|
+
@server.acquisitions.should include({ "email" => "test3@example.com"})
|
432
|
+
@server.acquisitions.should include({ "email" => "test4@example.com"})
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
$: << File.join(File.dirname(__FILE__), "..", "lib")
|
2
|
+
|
3
|
+
require 'pirate_metrics_agent'
|
4
|
+
require 'test_server'
|
5
|
+
|
6
|
+
RSpec.configure do |config|
|
7
|
+
|
8
|
+
config.before(:all) do
|
9
|
+
end
|
10
|
+
|
11
|
+
config.after(:all) do
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def parse_constant(constant)
|
18
|
+
constant = constant.to_s
|
19
|
+
parts = constant.split("::")
|
20
|
+
constant_name = parts.pop
|
21
|
+
source = parts.join("::")
|
22
|
+
[source.constantize, constant_name]
|
23
|
+
end
|
24
|
+
|
25
|
+
def with_constants(constants, &block)
|
26
|
+
saved_constants = {}
|
27
|
+
constants.each do |constant, val|
|
28
|
+
source_object, const_name = parse_constant(constant)
|
29
|
+
|
30
|
+
saved_constants[constant] = source_object.const_get(const_name)
|
31
|
+
Kernel::silence_warnings { source_object.const_set(const_name, val) }
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
block.call
|
36
|
+
ensure
|
37
|
+
constants.each do |constant, val|
|
38
|
+
source_object, const_name = parse_constant(constant)
|
39
|
+
|
40
|
+
Kernel::silence_warnings { source_object.const_set(const_name, saved_constants[constant]) }
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
alias :with_constant :with_constants
|
45
|
+
|
46
|
+
class String
|
47
|
+
# From Rails
|
48
|
+
def constantize
|
49
|
+
names = split('::')
|
50
|
+
names.shift if names.empty? || names.first.empty?
|
51
|
+
|
52
|
+
constant = Object
|
53
|
+
names.each do |name|
|
54
|
+
constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
|
55
|
+
end
|
56
|
+
constant
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
module Kernel
|
61
|
+
# File activesupport/lib/active_support/core_ext/kernel/reporting.rb, line 10
|
62
|
+
def silence_warnings
|
63
|
+
with_warnings(nil) { yield }
|
64
|
+
end
|
65
|
+
|
66
|
+
def with_warnings(flag)
|
67
|
+
old_verbose, $VERBOSE = $VERBOSE, flag
|
68
|
+
yield
|
69
|
+
ensure
|
70
|
+
$VERBOSE = old_verbose
|
71
|
+
end
|
72
|
+
end
|
data/spec/test_server.rb
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
class TestServer
|
4
|
+
attr_accessor :host, :port, :connect_count, :metrics, :last_api_key
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
default_options = {
|
8
|
+
:listen => true,
|
9
|
+
:authenticate => true,
|
10
|
+
:response => true,
|
11
|
+
}
|
12
|
+
@options = default_options.merge(options)
|
13
|
+
|
14
|
+
@connect_count = 0
|
15
|
+
@connections = []
|
16
|
+
@metrics = Hash.new{ |h,k| h[k] = Array.new}
|
17
|
+
@last_api_key = ""
|
18
|
+
@host = 'http://localhost'
|
19
|
+
@main_thread = nil
|
20
|
+
@response = options[:response]
|
21
|
+
listen if @options[:listen]
|
22
|
+
end
|
23
|
+
|
24
|
+
def listen
|
25
|
+
@port ||= 10001
|
26
|
+
@server = TCPServer.new(@port)
|
27
|
+
@main_thread = Thread.new do
|
28
|
+
begin
|
29
|
+
loop do
|
30
|
+
begin
|
31
|
+
client = @server.accept
|
32
|
+
@connections << client
|
33
|
+
@connect_count += 1
|
34
|
+
|
35
|
+
while command = client.readline
|
36
|
+
if command.start_with? 'POST'
|
37
|
+
metric = command[/v1\/(.*)\sHTTP/, 1]
|
38
|
+
end
|
39
|
+
if command.start_with? "Content-Length: "
|
40
|
+
content_length = command.gsub("Content-Length: ", "").to_i
|
41
|
+
end
|
42
|
+
break if command == "\r\n"
|
43
|
+
end
|
44
|
+
content = client.read(content_length)
|
45
|
+
payload = Rack::Utils.parse_nested_query(content)
|
46
|
+
@metrics[metric] += payload["data"]
|
47
|
+
@last_api_key = payload[:api_key]
|
48
|
+
|
49
|
+
headers = ["HTTP/1.1 200 OK"].join("\r\n")
|
50
|
+
client.puts headers
|
51
|
+
client.close
|
52
|
+
rescue Exception => e
|
53
|
+
puts "Error in test server: #{e.inspect}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
rescue Errno::EADDRINUSE => err
|
60
|
+
puts "#{err.inspect} failed to get port #{@port}"
|
61
|
+
puts err.message
|
62
|
+
@port += 1
|
63
|
+
retry
|
64
|
+
end
|
65
|
+
|
66
|
+
def host_and_port
|
67
|
+
"#{host}:#{port}"
|
68
|
+
end
|
69
|
+
|
70
|
+
def stop
|
71
|
+
@stopping = true
|
72
|
+
disconnect_all
|
73
|
+
@main_thread.kill if @main_thread
|
74
|
+
@main_thread = nil
|
75
|
+
begin
|
76
|
+
@server.close if @server
|
77
|
+
rescue Exception => e
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def disconnect_all
|
82
|
+
@connections.each { |c|
|
83
|
+
c.close rescue false
|
84
|
+
}
|
85
|
+
@connections = []
|
86
|
+
end
|
87
|
+
|
88
|
+
def fresh_agent(options = { })
|
89
|
+
PirateMetrics::Agent.new('test_token', { :host => host, :port => port, :enabled => true}.merge(options))
|
90
|
+
end
|
91
|
+
|
92
|
+
[:acquisitions, :activations, :retentions, :referrals, :revenues].each do |metric|
|
93
|
+
define_method metric do
|
94
|
+
@metrics[metric.to_s]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
metadata
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pirate_metrics_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Elijah Miller
|
9
|
+
- Christopher Zelenak
|
10
|
+
- Kristopher Chambers
|
11
|
+
- Matthew Hassfurder
|
12
|
+
- Expected Behavior
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
date: 2012-10-31 00:00:00.000000000 Z
|
17
|
+
dependencies:
|
18
|
+
- !ruby/object:Gem::Dependency
|
19
|
+
name: rake
|
20
|
+
requirement: !ruby/object:Gem::Requirement
|
21
|
+
none: false
|
22
|
+
requirements:
|
23
|
+
- - ! '>='
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
type: :development
|
27
|
+
prerelease: false
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: rack
|
36
|
+
requirement: !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ! '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '0'
|
42
|
+
type: :development
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: rspec
|
52
|
+
requirement: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ~>
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '2.0'
|
58
|
+
type: :development
|
59
|
+
prerelease: false
|
60
|
+
version_requirements: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '2.0'
|
66
|
+
- !ruby/object:Gem::Dependency
|
67
|
+
name: fuubar
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :development
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ! '>='
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: pry
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.9'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ~>
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0.9'
|
98
|
+
- !ruby/object:Gem::Dependency
|
99
|
+
name: guard
|
100
|
+
requirement: !ruby/object:Gem::Requirement
|
101
|
+
none: false
|
102
|
+
requirements:
|
103
|
+
- - ! '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '0'
|
106
|
+
type: :development
|
107
|
+
prerelease: false
|
108
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
|
+
none: false
|
110
|
+
requirements:
|
111
|
+
- - ! '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: guard-rspec
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
type: :development
|
123
|
+
prerelease: false
|
124
|
+
version_requirements: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
- !ruby/object:Gem::Dependency
|
131
|
+
name: growl
|
132
|
+
requirement: !ruby/object:Gem::Requirement
|
133
|
+
none: false
|
134
|
+
requirements:
|
135
|
+
- - ! '>='
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0'
|
138
|
+
type: :development
|
139
|
+
prerelease: false
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ! '>='
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
- !ruby/object:Gem::Dependency
|
147
|
+
name: rb-fsevent
|
148
|
+
requirement: !ruby/object:Gem::Requirement
|
149
|
+
none: false
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
type: :development
|
155
|
+
prerelease: false
|
156
|
+
version_requirements: !ruby/object:Gem::Requirement
|
157
|
+
none: false
|
158
|
+
requirements:
|
159
|
+
- - ! '>='
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
description: Get to know your customers.
|
163
|
+
email:
|
164
|
+
- support@piratemetrics.com
|
165
|
+
executables: []
|
166
|
+
extensions: []
|
167
|
+
extra_rdoc_files: []
|
168
|
+
files:
|
169
|
+
- .gitignore
|
170
|
+
- .rspec
|
171
|
+
- CHANGELOG.md
|
172
|
+
- Gemfile
|
173
|
+
- Guardfile
|
174
|
+
- LICENSE
|
175
|
+
- README.md
|
176
|
+
- Rakefile
|
177
|
+
- lib/pirate_metrics/agent.rb
|
178
|
+
- lib/pirate_metrics/system_timer.rb
|
179
|
+
- lib/pirate_metrics/version.rb
|
180
|
+
- lib/pirate_metrics_agent.rb
|
181
|
+
- pirate_metrics_agent.gemspec
|
182
|
+
- spec/agent_spec.rb
|
183
|
+
- spec/spec_helper.rb
|
184
|
+
- spec/test_server.rb
|
185
|
+
homepage: http://github.com/expectedbehavior/pirate_metrics_agent
|
186
|
+
licenses: []
|
187
|
+
post_install_message:
|
188
|
+
rdoc_options: []
|
189
|
+
require_paths:
|
190
|
+
- lib
|
191
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
192
|
+
none: false
|
193
|
+
requirements:
|
194
|
+
- - ! '>='
|
195
|
+
- !ruby/object:Gem::Version
|
196
|
+
version: '0'
|
197
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
|
+
none: false
|
199
|
+
requirements:
|
200
|
+
- - ! '>='
|
201
|
+
- !ruby/object:Gem::Version
|
202
|
+
version: '0'
|
203
|
+
requirements: []
|
204
|
+
rubyforge_project:
|
205
|
+
rubygems_version: 1.8.24
|
206
|
+
signing_key:
|
207
|
+
specification_version: 3
|
208
|
+
summary: Agent for reporting data to piratemetrics.com
|
209
|
+
test_files:
|
210
|
+
- spec/agent_spec.rb
|
211
|
+
- spec/spec_helper.rb
|
212
|
+
- spec/test_server.rb
|