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 +4 -4
- data/README.rdoc +94 -10
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/lib/resque/metrics/backends/redis.rb +38 -0
- data/lib/resque/metrics/backends/statsd.rb +66 -0
- data/lib/resque/metrics/backends.rb +2 -0
- data/lib/resque/metrics/server/views/metrics.erb +88 -0
- data/lib/resque/metrics/server.rb +47 -0
- data/lib/resque/metrics.rb +100 -6
- data/resque-metrics.gemspec +10 -5
- data/test/helper.rb +40 -24
- data/test/test_resque-metrics.rb +47 -7
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4b37d0748facc9e0f08c5dc0b3998df4c1463ff4
|
4
|
+
data.tar.gz: 1e6970ac0a11ddc2eb25afabec738cbce63c5696
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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 `
|
68
|
-
Resque::
|
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
|
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,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
|
data/lib/resque/metrics.rb
CHANGED
@@ -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
|
-
|
194
|
+
run_backends(:increment_metric, metric, by)
|
132
195
|
end
|
133
196
|
|
134
197
|
def self.set_metric(metric, val)
|
135
|
-
|
198
|
+
run_backends(:set_metric, metric, val)
|
136
199
|
end
|
137
200
|
|
138
201
|
def self.set_avg(metric, num, total)
|
139
|
-
|
140
|
-
|
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
|
-
|
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
|
data/resque-metrics.gemspec
CHANGED
@@ -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
|
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
|
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-
|
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.
|
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
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
37
|
+
at_exit do
|
38
|
+
next if $!
|
36
39
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
44
|
-
|
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
|
-
|
51
|
-
|
52
|
-
Resque.redis =
|
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
|
data/test/test_resque-metrics.rb
CHANGED
@@ -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
|
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 <<
|
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][
|
69
|
-
assert_equal :jobs, recorded[0][
|
70
|
-
assert recorded[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
|
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-
|
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.
|
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
|