resque-metrics 0.0.6 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fa7c797cdfa6560213452323b8342776c660999d
4
- data.tar.gz: fb437d501c22eb5092ba201460ab8bcce56d91a5
3
+ metadata.gz: 4b37d0748facc9e0f08c5dc0b3998df4c1463ff4
4
+ data.tar.gz: 1e6970ac0a11ddc2eb25afabec738cbce63c5696
5
5
  SHA512:
6
- metadata.gz: 60ec3b407be33f70cf96737e5334d1ce0c707f8c6c5e82314be33e66f56de8231ff905c93c23000ac91478e9d99660e4c22d5e0652310c1e76f69a68ed77feb3
7
- data.tar.gz: d26228715c7c892779c3a09b59b126046f3743f955db5243af381aa68812393395c51b222ff7a66085179ea44652b9e1fce0aed3ff0b14522ff7aba16170d7cb
6
+ metadata.gz: 6c4f87485e96a5bbf1c9fbbb608d8d5c2dd7478e2600d5c0dd84cbb1601ef79d916848e4efd21c16b17c1bdec7990db986f851a4d6b66160e5deadf538fd9913
7
+ data.tar.gz: 807ec5bad744cd89f1e92bdf2fbbd5f69bfa8af4b4fd19687d70e2d4c906af338d44c598da2fc071736b77b40b36222fbd54c13cf493c81e84d95d459a8e4f0c
data/README.rdoc CHANGED
@@ -8,6 +8,14 @@ mechanism for recording/sending the metrics to your favorite tool (AKA statsd/gr
8
8
 
9
9
  gem install resque-metrics
10
10
 
11
+ If you are using bundler add this to your Gemfile
12
+
13
+ gem "resque-metrics"
14
+
15
+ And if you want the web-ui extensions
16
+
17
+ gem "resque-metrics", :require => "resque/metrics/server"
18
+
11
19
  == Usage
12
20
 
13
21
  Given a job, extend the job class with Resque::Metrics.
@@ -23,8 +31,8 @@ Given a job, extend the job class with Resque::Metrics.
23
31
 
24
32
  end
25
33
 
26
- By default this will record the total job count, the total count of jobs enqueued, the total time the jobs took, the avg time the jobs took. It
27
- will also record each of these per-queue and per-bob class. So for the job above it will record values and you will be able to fetch them
34
+ By default this will record the total job count, the total count of jobs enqueued, the total time the jobs took, the avg time the jobs took. It will also record the total number of job failures.
35
+ These metrics are also tracked by queue and job class. So for the job above, it will record values and you will be able to fetch them
28
36
  with module methods:
29
37
 
30
38
  Resque::Metrics.total_job_count #=> 1
@@ -36,12 +44,15 @@ with module methods:
36
44
  Resque::Metrics.avg_job_time #=> 1000
37
45
  Resque::Metrics.avg_job_time_by_job(SomeJob) #=> 1000
38
46
  Resque::Metrics.avg_job_time_by_queue(:jobs) #=> 1000
47
+ Resque::Metrics.failed_job_count #=> 1
48
+ Resque::Metrics.failed_job_count_by_job(SomeJob) #=> 0
49
+ Resque::Metrics.failed_job_count_by_queue(:jobs) #=> 0
39
50
 
40
51
  All values are recorded and returned as integers. For times, values are in milliseconds.
41
52
 
42
53
  === Forking Metrics
43
54
 
44
- Resque::Metrics can also record forking metrics but these are not on by default as `before_fork` and `after_fork` are singluar hooks.
55
+ Resque::Metrics can also record forking metrics but these are not on by default as `before_fork` and `after_fork` are singluar hooks.
45
56
  If you don't need to define your own fork hooks you can simply add a line to an initializer:
46
57
 
47
58
  Resque::Metrics.watch_fork
@@ -52,28 +63,101 @@ If you do define you're own fork hooks:
52
63
  # my own fork code
53
64
  Resque::Metrics.before_fork.call(job)
54
65
  end
55
-
66
+
56
67
  # Resque::Metrics.(before/after)_fork just returns a lambda so just assign it if you like
57
68
  Resque.after_fork = Resque::Metrics.after_fork
58
69
 
59
- Once enabled this will add `.*_fork_*` methods like `avg_fork_time`, etc.
70
+ Once enabled this will add `.*_fork_*` methods like `avg_fork_time`, etc.
60
71
  Latest Resque is required for fork recording to work.
61
72
 
73
+ === Queue Depth Metrics
74
+
75
+ Resque::Metrics can also record queue depth metrics. These are not on by default,
76
+ as they need to run on an interval to be useful. You can record them manually
77
+ by running in a console:
78
+
79
+ Resque::Metrics.record_depth
80
+
81
+ You can imagine placing this in a small script, and using cron to run it. Once you'll have access to:
82
+
83
+ Resque::Metrics.failed_depth #=> 1
84
+ Resque::Metrics.pending_depth #=> 1
85
+ Resque::Metrics.depth_by_queue(:jobs) #=> 1
86
+
87
+ === Metric Backends
88
+
89
+ By default, Resque::Metrics keeps all it's metrics in Resque's redis instance, but supports plugging in other backends. Resque::Metrics itself supports redis and statsd. Here's how you would enable statsd:
90
+
91
+ # list current backends
92
+ Resque::Metrics.backends
93
+ # build your statsd instance
94
+ statsd = Statsd.new 'localhost', 8125
95
+ # add a Resque::Metrics::Backend
96
+ Resque::Metrics.backends.append Resque::Metrics::Backends::Statsd.new(statsd)
97
+
98
+ ==== Statsd
99
+
100
+ If you have already have a statsd object for you application, just pass it to Resque::Metrics::Backends::Statsd. The statsd client already supports namespacing, and in addition, Resque::Metrics all its metrics under 'resque' under that namespace.
101
+
102
+ Here's a list of metrics emitted:
103
+
104
+ resque.job.<job>.complete.count
105
+ resque.job.<job>.complete.time
106
+ resque.queue.<queue>.complete.count
107
+ resque.queue.<queue>.complete.time
108
+ resque.complete.count
109
+ resque.complete.time
110
+
111
+ resque.job.<job>.enqueue.count
112
+ resque.job.<job>.enqueue.time
113
+ resque.queue.<queue>.enqueue.count
114
+ resque.queue.<queue>.enqueue.time
115
+ resque.enqueue.count
116
+ resque.enqueue.time
117
+
118
+ resque.job.<job>.fork.count
119
+ resque.job.<job>.fork.time
120
+ resque.queue.<queue>.fork.count
121
+ resque.queue.<queue>.fork.time
122
+ resque.fork.count
123
+ resque.fork.time
124
+
125
+ resque.job.<job>.failure.count
126
+ resque.queue.<queue>.failure.count
127
+ resque.failure.count
128
+
129
+ resque.depth.failed
130
+ resque.depth.pending
131
+ resque.depth.queue.<queue>
132
+
133
+ ==== Writing your own
134
+
135
+ To write your own, you create your own class, and then implmement the following that you care about:
136
+
137
+ * increment_metric(metric, by = 1)
138
+ * set_metric(metric, val)
139
+ * set_avg(metric, num, total)
140
+ * get_metric(metric)
141
+
142
+ Resque::Metrics will in turn call each of these methods for each of it's backend
143
+ if it responds_to? it. For get_metric, since it returns a value, only will use
144
+ the first backend that responds_to? it.
145
+
62
146
  === Callbacks/Hooks
63
147
 
64
148
  Resque::Metrics also has a simple callback/hook system so you can send data to your favorite agent. All hooks are passed the job class,
65
- the queue, and the time of the metric.
149
+ the queue, and the time of the metric.
66
150
 
67
- # Also `on_job_fork` and `on_job_enqueue`
68
- Resque::Metric.on_job_complete do |job_class, queue, time|
151
+ # Also `on_job_fork`, `on_job_enqueue`, and `on_job_failure` (`on_job_failure does not include `time`)
152
+ Resque::Metrics.on_job_complete do |job_class, queue, time|
69
153
  # send to your metrics agent
70
154
  Statsd.timing "resque.#{job_class}.complete_time", time
71
155
  Statsd.increment "resque.#{job_class}.complete"
72
156
  # etc
73
157
  end
74
-
158
+
75
159
  == Contributing to resque-metrics
76
-
160
+
77
161
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
78
162
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
79
163
  * Fork the project
data/Rakefile CHANGED
@@ -24,7 +24,7 @@ you could build some simple auto-scaling mechanism based on the speed and ETA of
24
24
  mechanism for recording/sending the metrics to your favorite tool (AKA statsd/graphite).
25
25
  desc
26
26
  gem.email = "aaron@quirkey.com"
27
- gem.authors = ["Aaron Quint"]
27
+ gem.authors = ["Aaron Quint", "Josh Nichols", "Michael Smith", "Tomas Varaneckas"]
28
28
  # dependencies defined in Gemfile
29
29
  end
30
30
  Jeweler::RubygemsDotOrgTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.6
1
+ 0.1.0
@@ -0,0 +1,38 @@
1
+ module Resque
2
+ module Metrics
3
+ module Backends
4
+ class Redis
5
+ attr_accessor :redis
6
+
7
+ def initialize(redis)
8
+ @redis = redis
9
+ end
10
+
11
+ def increment_metric(metric, by = 1)
12
+ redis.incrby("_metrics_:#{metric}", by)
13
+ end
14
+
15
+ def set_metric(metric, val)
16
+ redis.set("_metrics_:#{metric}", val)
17
+ end
18
+
19
+ def set_avg(metric, num, total)
20
+ val = total < 1 ? 0 : num / total
21
+ set_metric(metric, val)
22
+ end
23
+
24
+ def get_metric(metric)
25
+ redis.get("_metrics_:#{metric}").to_i
26
+ end
27
+
28
+ def register_job(job)
29
+ redis.sadd('_metrics_:known_jobs', job)
30
+ end
31
+
32
+ def known_jobs
33
+ redis.smembers('_metrics_:known_jobs')
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,66 @@
1
+ module Resque
2
+ module Metrics
3
+ module Backends
4
+ class Statsd
5
+ attr_accessor :statsd, :metric_prefix
6
+
7
+ def initialize(statsd, metric_prefix = 'resque')
8
+ @statsd = statsd
9
+ @metric_prefix = metric_prefix
10
+ end
11
+
12
+ def increment_metric(metric, by = 1)
13
+ if metric =~ /^(.+)(?:_job)?_(time|count)(?::(queue|job):(.*))?$/
14
+ event = $1
15
+ event = 'complete' if event == 'job'
16
+
17
+ time_or_count = $2
18
+ queue_or_job = $3
19
+ queue_or_job_name = $4
20
+ key = if queue_or_job && queue_or_job_name
21
+ # ie resque.complete.queue.high.count, resque.failed.job.Index.timing
22
+ "#{metric_prefix}.#{event}.#{queue_or_job}.#{queue_or_job_name}.#{time_or_count}"
23
+ else
24
+
25
+ # ie resque.complete.time
26
+ "#{metric_prefix}.#{event}.#{time_or_count}"
27
+ end
28
+ case time_or_count
29
+ when 'time'
30
+ statsd.timing key, by
31
+ when 'count'
32
+ statsd.increment key, by
33
+ else
34
+ raise "Not sure how to increment_metric for a #{time_or_count} metric (#{metric})"
35
+ end
36
+ elsif metric =~ /^payload_size(?::(queue|job):(.*))?$/
37
+ queue_or_job = $1
38
+ queue_or_job_name = $2
39
+ key = if queue_or_job && queue_or_job_name
40
+ # ie resque.complete.queue.high.count, resque.failed.job.Index.timing
41
+ "#{metric_prefix}.payload_size.#{queue_or_job}.#{queue_or_job_name}.#{time_or_count}"
42
+ else
43
+ "#{metric_prefix}.payload_size.#{time_or_count}"
44
+ end
45
+ statsd.increment key, by
46
+ else
47
+ raise "Not sure how to increment_metric #{metric}"
48
+ end
49
+ end
50
+
51
+ def set_metric(metric, val)
52
+ if metric =~ /^depth(?::(failed|pending|queue)(?::(.+))?)?$/
53
+ key = "#{metric_prefix}.#{metric.gsub(':', '.')}"
54
+ statsd.gauge key, val
55
+ else
56
+ raise "Not sure how to set_metric #{metric}"
57
+ end
58
+ end
59
+
60
+ # set_avg: let statsd & graphite handle that
61
+ # get_metric: would have to talk to graphite. but man, complicated
62
+ end
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,2 @@
1
+ require 'resque/metrics/backends/redis'
2
+ require 'resque/metrics/backends/statsd'
@@ -0,0 +1,88 @@
1
+ <h1>All your metrics are belong to Resque</h1>
2
+
3
+ <table class="queues">
4
+ <tbody>
5
+ <tr>
6
+ <th>Queue</th>
7
+ <th>Jobs completed</th>
8
+ </tr>
9
+
10
+ <% resque.queues.each do |queue| %>
11
+ <tr>
12
+ <td class="queue"><a href="<%=u "queues/#{queue}" %>"><%= queue %></a></td>
13
+ <td class="size"><%= Resque::Metrics.total_job_count_by_queue(queue) %></td>
14
+ </tr>
15
+ <% end %>
16
+
17
+ <tr class="failed">
18
+ <td class="queue failed"></td>
19
+ <td class="size"><%= Resque::Metrics.total_job_count %></td>
20
+ </tr>
21
+ </tbody>
22
+ </table>
23
+
24
+ <table class="queues">
25
+ <tbody>
26
+ <tr>
27
+ <th>Queue</th>
28
+ <th>Average job time</th>
29
+ </tr>
30
+
31
+ <% resque.queues.each do |queue| %>
32
+ <tr>
33
+ <td class="queue"><a href="<%=u "queues/#{queue}" %>"><%= queue %></a></td>
34
+ <td class="size"><%= metrics_formatted_ms(Resque::Metrics.avg_job_time_by_queue(queue)) %></td>
35
+ </tr>
36
+ <% end %>
37
+
38
+ <tr class="failed">
39
+ <td class="queue failed"></td>
40
+ <td class="size"><%= metrics_formatted_ms(Resque::Metrics.avg_job_time) %></td>
41
+ </tr>
42
+ </tbody>
43
+ </table>
44
+
45
+ <table class="queues">
46
+ <tbody>
47
+ <tr>
48
+ <th>Queue</th>
49
+ <th>Total job time</th>
50
+ </tr>
51
+
52
+ <% resque.queues.each do |queue| %>
53
+ <tr>
54
+ <td class="queue"><a href="<%=u "queues/#{queue}" %>"><%= queue %></a></td>
55
+ <td class="size"><%= metrics_formatted_ms(Resque::Metrics.total_job_time_by_queue(queue)) %></td>
56
+ </tr>
57
+ <% end %>
58
+
59
+ <tr class="failed">
60
+ <td class="queue failed"></td>
61
+ <td class="size"><%= metrics_formatted_ms(Resque::Metrics.total_job_time) %></td>
62
+ </tr>
63
+ </tbody>
64
+ </table>
65
+
66
+ <h2>Individual job metrics</h2>
67
+
68
+ <table class="workers">
69
+ <tbody>
70
+ <tr>
71
+ <th>Job</th>
72
+ <th>Total count</th>
73
+ <th>Failed count</th>
74
+ <th>Average duration</th>
75
+ <th>Total duration</th>
76
+ </tr>
77
+
78
+ <% Resque::Metrics.known_jobs.each do |job| %>
79
+ <tr>
80
+ <td class="queues queue"><%= job %></td>
81
+ <td class="queues queue"><%= Resque::Metrics.total_job_count_by_job(job) %></td>
82
+ <td class="queues queue"><%= Resque::Metrics.failed_job_count_by_job(job) %></td>
83
+ <td class="queues queue"><%= metrics_formatted_ms(Resque::Metrics.avg_job_time_by_job(job)) %></td>
84
+ <td class="process"><%= metrics_formatted_ms(Resque::Metrics.total_job_time_by_job(job)) %></td>
85
+ </tr>
86
+ <% end %>
87
+ </tbody>
88
+ </table>
@@ -0,0 +1,47 @@
1
+ require 'resque/server'
2
+ require 'resque/metrics'
3
+
4
+ # Extend Resque::Server to add tabs
5
+ module Resque
6
+ module Metrics
7
+ module Server
8
+ def self.included(base)
9
+ base.class_eval do
10
+ helpers do
11
+ # reads a 'local' template file.
12
+ def local_template(path)
13
+ # Is there a better way to specify alternate template locations with sinatra?
14
+ File.read(File.join(File.dirname(__FILE__), "server/views/#{path}"))
15
+ end
16
+
17
+ def metrics_formatted_ms(milliseconds)
18
+ seconds = milliseconds / 1000
19
+ hours = (seconds / 3600).floor
20
+ minutes = (seconds % 3600) / 60
21
+ seconds = seconds % 60
22
+ millis = (milliseconds - (milliseconds / 1000) * 1000)
23
+
24
+ str = []
25
+ str << "#{hours} hours" if hours > 0
26
+ str << "#{minutes} min" if minutes > 0
27
+ str << "#{seconds} sec" if seconds > 0
28
+ str << "#{millis} ms" if millis > 0
29
+
30
+ str.join(" ")
31
+ end
32
+ end
33
+
34
+ get "/metrics" do
35
+ erb local_template("metrics.erb")
36
+ end
37
+ end
38
+ end
39
+
40
+ Resque::Server.tabs << 'Metrics'
41
+ end
42
+ end
43
+ end
44
+
45
+ Resque::Server.class_eval do
46
+ include Resque::Metrics::Server
47
+ end
@@ -1,4 +1,5 @@
1
1
  require 'resque'
2
+ require 'resque/metrics/backends'
2
3
 
3
4
  module Resque
4
5
  module Metrics
@@ -8,7 +9,11 @@ module Resque
8
9
  end
9
10
 
10
11
  def self.redis
11
- ::Resque.redis
12
+ @_redis ||= ::Resque.redis
13
+ end
14
+
15
+ def self.redis=(redis)
16
+ @_redis = redis
12
17
  end
13
18
 
14
19
  def self.use_multi=(multi)
@@ -19,6 +24,36 @@ module Resque
19
24
  @_use_multi
20
25
  end
21
26
 
27
+ def self.backends
28
+ @_backends ||= begin
29
+ self.backends = [Resque::Metrics::Backends::Redis.new(redis)]
30
+ end
31
+ end
32
+
33
+ def self.backends=(new_backends)
34
+ @_backends = new_backends
35
+ end
36
+
37
+ def self.run_backends(method, *args)
38
+ ran_any = false
39
+
40
+ backends.each do |backend|
41
+ if backend.respond_to?(method)
42
+ ran_any = true
43
+ backend.send method, *args
44
+ end
45
+ end
46
+
47
+ raise "No backend responded to #{method}: #{backends.inspect}" unless ran_any
48
+ end
49
+
50
+ def self.run_first_backend(method, *args)
51
+ backend = backends.detect {|backend| backend.respond_to?(method)}
52
+ raise "No backend responds to #{method}: #{backends.inspect}" unless backend
53
+
54
+ backend.send method, *args
55
+ end
56
+
22
57
  def self.watch_fork
23
58
  ::Resque.before_fork = before_fork
24
59
  ::Resque.after_fork = after_fork
@@ -36,6 +71,10 @@ module Resque
36
71
  set_callback(:on_job_enqueue, &block)
37
72
  end
38
73
 
74
+ def self.on_job_failure(&block)
75
+ set_callback(:on_job_failure, &block)
76
+ end
77
+
39
78
  def self.set_callback(callback_name, &block)
40
79
  @callbacks ||= {}
41
80
  @callbacks[callback_name] ||= []
@@ -71,6 +110,17 @@ module Resque
71
110
  end
72
111
  end
73
112
 
113
+ def self.record_depth
114
+ set_metric 'depth:failed', Resque::Failure.count
115
+ set_metric 'depth:pending', Resque.info[:pending]
116
+
117
+ Resque.queues.each do |queue|
118
+ set_metric "depth:queue:#{queue}", Resque.size(queue)
119
+ end
120
+
121
+ true
122
+ end
123
+
74
124
  def self.record_job_fork(job, time)
75
125
  job_class = job.payload_class
76
126
  queue = job.queue
@@ -93,6 +143,7 @@ module Resque
93
143
  increment_metric "enqueue_count"
94
144
  increment_metric "enqueue_count:job:#{job_class}"
95
145
  increment_metric "enqueue_count:queue:#{queue}"
146
+ run_first_backend(:register_job, job_class)
96
147
 
97
148
  size = Resque.encode(args).length
98
149
  multi do
@@ -123,25 +174,40 @@ module Resque
123
174
  run_callback(:on_job_complete, job_class, queue, time)
124
175
  end
125
176
 
177
+ def self.record_job_failure(job_class, e)
178
+ queue = Resque.queue_from_class(job_class)
179
+
180
+ multi do
181
+ increment_metric "failed_job_count"
182
+ increment_metric "failed_job_count:queue:#{queue}"
183
+ increment_metric "failed_job_count:job:#{job_class}"
184
+ end
185
+
186
+ run_callback(:on_job_failure, job_class, queue)
187
+ end
188
+
126
189
  def self.multi(&block)
127
190
  use_multi? ? redis.multi(&block) : yield
128
191
  end
129
192
 
130
193
  def self.increment_metric(metric, by = 1)
131
- redis.incrby("_metrics_:#{metric}", by)
194
+ run_backends(:increment_metric, metric, by)
132
195
  end
133
196
 
134
197
  def self.set_metric(metric, val)
135
- redis.set("_metrics_:#{metric}", val)
198
+ run_backends(:set_metric, metric, val)
136
199
  end
137
200
 
138
201
  def self.set_avg(metric, num, total)
139
- val = total < 1 ? 0 : num / total
140
- set_metric(metric, val)
202
+ run_backends(:set_avg, metric, num, total)
203
+ end
204
+
205
+ def self.known_jobs
206
+ run_first_backend(:known_jobs)
141
207
  end
142
208
 
143
209
  def self.get_metric(metric)
144
- redis.get("_metrics_:#{metric}").to_i
210
+ run_first_backend(:get_metric, metric)
145
211
  end
146
212
 
147
213
  def self.total_enqueue_count
@@ -192,6 +258,18 @@ module Resque
192
258
  get_metric "job_count:job:#{job}"
193
259
  end
194
260
 
261
+ def self.failed_job_count
262
+ get_metric "failed_job_count"
263
+ end
264
+
265
+ def self.failed_job_count_by_queue(queue)
266
+ get_metric "failed_job_count:queue:#{queue}"
267
+ end
268
+
269
+ def self.failed_job_count_by_job(job)
270
+ get_metric "failed_job_count:job:#{job}"
271
+ end
272
+
195
273
  def self.total_payload_size
196
274
  get_metric "payload_size"
197
275
  end
@@ -252,6 +330,18 @@ module Resque
252
330
  get_metric "fork_count:job:#{job}"
253
331
  end
254
332
 
333
+ def self.failed_depth
334
+ get_metric "depth:failed"
335
+ end
336
+
337
+ def self.pending_depth
338
+ get_metric "depth:pending"
339
+ end
340
+
341
+ def self.depth_by_queue(queue)
342
+ get_metric "depth:queue:#{queue}"
343
+ end
344
+
255
345
  module Hooks
256
346
 
257
347
  def after_enqueue_metrics(*args)
@@ -265,6 +355,10 @@ module Resque
265
355
  Resque::Metrics.record_job_completion(self, finish)
266
356
  end
267
357
 
358
+ def on_failure_metrics(e, *args)
359
+ Resque::Metrics.record_job_failure(self, e)
360
+ end
361
+
268
362
  end
269
363
 
270
364
  end
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: resque-metrics 0.0.6 ruby lib
5
+ # stub: resque-metrics 0.1.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "resque-metrics"
9
- s.version = "0.0.6"
9
+ s.version = "0.1.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib"]
13
- s.authors = ["Aaron Quint"]
14
- s.date = "2014-02-12"
13
+ s.authors = ["Aaron Quint", "Josh Nichols", "Michael Smith", "Tomas Varaneckas"]
14
+ s.date = "2014-10-19"
15
15
  s.description = "A simple Resque plugin that times and saves some simple metrics for Resque jobs back into redis. Based on this system\nyou could build some simple auto-scaling mechanism based on the speed and ETA of queues. Also includes a hook/callback\nmechanism for recording/sending the metrics to your favorite tool (AKA statsd/graphite).\n"
16
16
  s.email = "aaron@quirkey.com"
17
17
  s.extra_rdoc_files = [
@@ -28,6 +28,11 @@ Gem::Specification.new do |s|
28
28
  "VERSION",
29
29
  "lib/resque-metrics.rb",
30
30
  "lib/resque/metrics.rb",
31
+ "lib/resque/metrics/backends.rb",
32
+ "lib/resque/metrics/backends/redis.rb",
33
+ "lib/resque/metrics/backends/statsd.rb",
34
+ "lib/resque/metrics/server.rb",
35
+ "lib/resque/metrics/server/views/metrics.erb",
31
36
  "resque-metrics.gemspec",
32
37
  "test/helper.rb",
33
38
  "test/redis-test.conf",
@@ -35,7 +40,7 @@ Gem::Specification.new do |s|
35
40
  ]
36
41
  s.homepage = "http://github.com/quirkey/resque-metrics"
37
42
  s.licenses = ["MIT"]
38
- s.rubygems_version = "2.2.1"
43
+ s.rubygems_version = "2.2.2"
39
44
  s.summary = "A Resque plugin for recording simple metrics for your jobs"
40
45
 
41
46
  if s.respond_to? :specification_version then
data/test/helper.rb CHANGED
@@ -20,36 +20,42 @@ require 'resque/metrics'
20
20
  # make sure we can run redis
21
21
  #
22
22
 
23
- if !system("which redis-server")
24
- puts '', "** can't find `redis-server` in your path"
25
- puts "** try running `sudo rake install`"
26
- abort ''
27
- end
23
+ if ENV['BOXEN_REDIS_PORT']
24
+ redis_port = ENV['BOXEN_REDIS_PORT']
25
+ else
26
+ if !system("which redis-server")
27
+ puts '', "** can't find `redis-server` in your path"
28
+ puts "** try running `sudo rake install`"
29
+ abort ''
30
+ end
28
31
 
29
- #
30
- # start our own redis when the tests start,
31
- # kill it when they end
32
- #
32
+ #
33
+ # start our own redis when the tests start,
34
+ # kill it when they end
35
+ #
33
36
 
34
- at_exit do
35
- next if $!
37
+ at_exit do
38
+ next if $!
36
39
 
37
- if defined?(MiniTest)
38
- exit_code = MiniTest::Unit.new.run(ARGV)
39
- else
40
- exit_code = Test::Unit::AutoRunner.run
40
+ if defined?(MiniTest)
41
+ exit_code = MiniTest::Unit.new.run(ARGV)
42
+ else
43
+ exit_code = Test::Unit::AutoRunner.run
44
+ end
45
+
46
+ pid = `ps -e -o pid,command | grep [r]edis-test`.split(" ")[0]
47
+ puts "Killing test redis server..."
48
+ Process.kill("KILL", pid.to_i)
49
+ FileUtils.rm_rf("#{dir}/dump.rdb")
50
+ exit exit_code
41
51
  end
42
52
 
43
- pid = `ps -e -o pid,command | grep [r]edis-test`.split(" ")[0]
44
- puts "Killing test redis server..."
45
- Process.kill("KILL", pid.to_i)
46
- FileUtils.rm_rf("#{dir}/dump.rdb")
47
- exit exit_code
48
- end
53
+ puts "Starting redis for testing at localhost:9736..."
54
+ `redis-server #{dir}/redis-test.conf`
49
55
 
50
- puts "Starting redis for testing at localhost:9736..."
51
- `redis-server #{dir}/redis-test.conf`
52
- Resque.redis = 'localhost:9736:2'
56
+ redis_port = 9736
57
+ end
58
+ Resque.redis = "localhost:#{redis_port}:2"
53
59
 
54
60
  class SomeJob
55
61
  extend Resque::Metrics
@@ -61,3 +67,13 @@ class SomeJob
61
67
  end
62
68
 
63
69
  end
70
+
71
+ class FailureJob
72
+ extend Resque::Metrics
73
+
74
+ @queue = :jobs
75
+
76
+ def self.perform
77
+ raise "failing lol"
78
+ end
79
+ end
@@ -13,6 +13,11 @@ class TestResqueMetrics < MiniTest::Unit::TestCase
13
13
  @num_jobs.times do
14
14
  work_job
15
15
  end
16
+
17
+ @num_failed_jobs = 2
18
+ @num_failed_jobs.times do
19
+ fail_job
20
+ end
16
21
  end
17
22
 
18
23
  def test_should_pass_resque_plugin_lint
@@ -30,9 +35,10 @@ class TestResqueMetrics < MiniTest::Unit::TestCase
30
35
  end
31
36
 
32
37
  def test_should_record_enqueue_count
33
- assert_equal @num_jobs, Resque::Metrics.total_enqueue_count
34
- assert_equal @num_jobs, Resque::Metrics.total_enqueue_count_by_queue(:jobs)
38
+ assert_equal @num_jobs + @num_failed_jobs, Resque::Metrics.total_enqueue_count
39
+ assert_equal @num_jobs + @num_failed_jobs, Resque::Metrics.total_enqueue_count_by_queue(:jobs)
35
40
  assert_equal @num_jobs, Resque::Metrics.total_enqueue_count_by_job(SomeJob)
41
+ assert_equal @num_failed_jobs, Resque::Metrics.total_enqueue_count_by_job(FailureJob)
36
42
  end
37
43
 
38
44
  def test_should_record_job_count
@@ -41,6 +47,12 @@ class TestResqueMetrics < MiniTest::Unit::TestCase
41
47
  assert Resque::Metrics.total_job_count_by_job(SomeJob) > 0
42
48
  end
43
49
 
50
+ def test_should_record_failed_job_count
51
+ assert Resque::Metrics.failed_job_count > 0, "Expected #{Resque::Metrics.failed_job_count} to be > 0, but wasn't"
52
+ assert Resque::Metrics.failed_job_count_by_queue(:jobs) > 0
53
+ assert Resque::Metrics.failed_job_count_by_job(FailureJob) > 0
54
+ end
55
+
44
56
  def test_should_record_payload_size
45
57
  assert Resque::Metrics.total_payload_size > 0
46
58
  assert Resque::Metrics.total_payload_size_by_queue(:jobs) > 0
@@ -53,28 +65,56 @@ class TestResqueMetrics < MiniTest::Unit::TestCase
53
65
  assert Resque::Metrics.avg_job_time_by_job(SomeJob) > 0
54
66
  end
55
67
 
56
- def test_should_call_callbacks
68
+ def test_should_call_job_complete_callbacks
57
69
  recorded = []
58
70
  recorded_count = 0
59
71
  Resque::Metrics.on_job_complete do |klass, queue, time|
60
- recorded << [klass, queue, time]
72
+ recorded << {:klass => klass, :queue => queue, :time => time }
61
73
  end
62
74
  Resque::Metrics.on_job_complete do |klass, queue, time|
63
75
  recorded_count += 1
64
76
  end
77
+
65
78
  work_job
66
79
  work_job
80
+
67
81
  assert_equal 2, recorded.length
68
- assert_equal SomeJob, recorded[0][0]
69
- assert_equal :jobs, recorded[0][1]
70
- assert recorded[0][2] > 0
82
+ assert_equal SomeJob, recorded[0][:klass]
83
+ assert_equal :jobs, recorded[0][:queue]
84
+ assert recorded[0][:time] > 0, "Expected #{recorded[0][:time]} to be > 0, but wasn't"
71
85
  assert_equal 2, recorded_count
72
86
  end
73
87
 
88
+ def test_should_call_job_failure_callbacks
89
+ recorded = []
90
+ recorded_count = 0
91
+ Resque::Metrics.on_job_failure do |klass, queue|
92
+ recorded << {:klass => klass, :queue => queue}
93
+ end
94
+ Resque::Metrics.on_job_failure do |klass, queue|
95
+ recorded_count += 1
96
+ end
97
+
98
+ fail_job
99
+ fail_job
100
+ fail_job
101
+
102
+ assert_equal 3, recorded.length
103
+ assert_equal FailureJob, recorded[0][:klass]
104
+ assert_equal :jobs, recorded[0][:queue]
105
+ assert_equal 3, recorded_count
106
+ end
107
+
74
108
  private
109
+
75
110
  def work_job
76
111
  Resque.enqueue(SomeJob, 20, '/tmp')
77
112
  @worker.work(0)
78
113
  end
79
114
 
115
+ def fail_job
116
+ Resque.enqueue(FailureJob)
117
+ @worker.work(0)
118
+ end
119
+
80
120
  end
metadata CHANGED
@@ -1,14 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-metrics
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.6
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aaron Quint
8
+ - Josh Nichols
9
+ - Michael Smith
10
+ - Tomas Varaneckas
8
11
  autorequire:
9
12
  bindir: bin
10
13
  cert_chain: []
11
- date: 2014-02-12 00:00:00.000000000 Z
14
+ date: 2014-10-19 00:00:00.000000000 Z
12
15
  dependencies:
13
16
  - !ruby/object:Gem::Dependency
14
17
  name: resque
@@ -100,6 +103,11 @@ files:
100
103
  - VERSION
101
104
  - lib/resque-metrics.rb
102
105
  - lib/resque/metrics.rb
106
+ - lib/resque/metrics/backends.rb
107
+ - lib/resque/metrics/backends/redis.rb
108
+ - lib/resque/metrics/backends/statsd.rb
109
+ - lib/resque/metrics/server.rb
110
+ - lib/resque/metrics/server/views/metrics.erb
103
111
  - resque-metrics.gemspec
104
112
  - test/helper.rb
105
113
  - test/redis-test.conf
@@ -124,7 +132,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
124
132
  version: '0'
125
133
  requirements: []
126
134
  rubyforge_project:
127
- rubygems_version: 2.2.1
135
+ rubygems_version: 2.2.2
128
136
  signing_key:
129
137
  specification_version: 4
130
138
  summary: A Resque plugin for recording simple metrics for your jobs