Skip to content

Commit 8d77c53

Browse files
authored
FEATURE: Added basic GoodJob collectors (#280)
1 parent 9ea664f commit 8d77c53

File tree

6 files changed

+186
-0
lines changed

6 files changed

+186
-0
lines changed

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ To learn more see [Instrumenting Rails with Prometheus](https://samsaffron.com/a
2121
* [Puma metrics](#puma-metrics)
2222
* [Unicorn metrics](#unicorn-process-metrics)
2323
* [Resque metrics](#resque-metrics)
24+
* [GoodJob metrics](#goodjob-metrics)
2425
* [Custom type collectors](#custom-type-collectors)
2526
* [Multi process mode with custom collector](#multi-process-mode-with-custom-collector)
2627
* [GraphQL support](#graphql-support)
@@ -627,6 +628,29 @@ PrometheusExporter::Instrumentation::Resque.start
627628
| Gauge | `resque_workers` | Total number of Resque workers running |
628629
| Gauge | `resque_working` | Total number of Resque workers working |
629630

631+
### GoodJob metrics
632+
633+
The metrics are generated from the database using the relevant scopes. To start monitoring your GoodJob
634+
installation, you'll need to start the instrumentation:
635+
636+
```ruby
637+
# e.g. config/initializers/good_job.rb
638+
require 'prometheus_exporter/instrumentation'
639+
PrometheusExporter::Instrumentation::GoodJob.start
640+
```
641+
642+
#### Metrics collected by GoodJob Instrumentation
643+
644+
| Type | Name | Description |
645+
| --- |----------------------|-----------------------------------------|
646+
| Gauge | `good_job_scheduled` | Total number of scheduled GoodJob jobs. |
647+
| Gauge | `good_job_retried` | Total number of retried GoodJob jobs. |
648+
| Gauge | `good_job_queued` | Total number of queued GoodJob jobs. |
649+
| Gauge | `good_job_running` | Total number of running GoodJob jobs. |
650+
| Gauge | `good_job_finished` | Total number of finished GoodJob jobs. |
651+
| Gauge | `good_job_succeeded` | Total number of succeeded GoodJob jobs. |
652+
| Gauge | `good_job_discarded` | Total number of discarded GoodJob jobs |
653+
630654
### Unicorn process metrics
631655

632656
In order to gather metrics from unicorn processes, we use `rainbows`, which exposes `Rainbows::Linux.tcp_listener_stats` to gather information about active workers and queued requests. To start monitoring your unicorn processes, you'll need to know both the path to unicorn PID file and the listen address (`pid_file` and `listen` in your unicorn config file)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# frozen_string_literal: true
2+
3+
# collects stats from GoodJob
4+
module PrometheusExporter::Instrumentation
5+
class GoodJob < PeriodicStats
6+
def self.start(client: nil, frequency: 30)
7+
good_job_collector = new
8+
client ||= PrometheusExporter::Client.default
9+
10+
worker_loop do
11+
client.send_json(good_job_collector.collect)
12+
end
13+
14+
super
15+
end
16+
17+
def collect
18+
{
19+
type: "good_job",
20+
scheduled: ::GoodJob::Job.scheduled.size,
21+
retried: ::GoodJob::Job.retried.size,
22+
queued: ::GoodJob::Job.queued.size,
23+
running: ::GoodJob::Job.running.size,
24+
finished: ::GoodJob::Job.finished.size,
25+
succeeded: ::GoodJob::Job.succeeded.size,
26+
discarded: ::GoodJob::Job.discarded.size
27+
}
28+
end
29+
end
30+
end

lib/prometheus_exporter/server.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@
1919
require_relative "server/active_record_collector"
2020
require_relative "server/shoryuken_collector"
2121
require_relative "server/resque_collector"
22+
require_relative "server/good_job_collector"

lib/prometheus_exporter/server/collector.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def initialize(json_serializer: nil)
2323
register_collector(ActiveRecordCollector.new)
2424
register_collector(ShoryukenCollector.new)
2525
register_collector(ResqueCollector.new)
26+
register_collector(GoodJobCollector.new)
2627
end
2728

2829
def register_collector(collector)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# frozen_string_literal: true
2+
3+
module PrometheusExporter::Server
4+
class GoodJobCollector < TypeCollector
5+
MAX_METRIC_AGE = 30
6+
GOOD_JOB_GAUGES = {
7+
scheduled: "Total number of scheduled GoodJob jobs.",
8+
retried: "Total number of retried GoodJob jobs.",
9+
queued: "Total number of queued GoodJob jobs.",
10+
running: "Total number of running GoodJob jobs.",
11+
finished: "Total number of finished GoodJob jobs.",
12+
succeeded: "Total number of succeeded GoodJob jobs.",
13+
discarded: "Total number of discarded GoodJob jobs."
14+
}
15+
16+
def initialize
17+
@good_job_metrics = MetricsContainer.new(ttl: MAX_METRIC_AGE)
18+
@gauges = {}
19+
end
20+
21+
def type
22+
"good_job"
23+
end
24+
25+
def metrics
26+
return [] if good_job_metrics.length == 0
27+
28+
good_job_metrics.map do |metric|
29+
labels = metric.fetch("custom_labels", {})
30+
31+
GOOD_JOB_GAUGES.map do |name, help|
32+
value = metric[name.to_s]
33+
34+
if value
35+
gauge = gauges[name] ||= PrometheusExporter::Metric::Gauge.new("good_job_#{name}", help)
36+
gauge.observe(value, labels)
37+
end
38+
end
39+
end
40+
41+
gauges.values
42+
end
43+
44+
def collect(object)
45+
@good_job_metrics << object
46+
end
47+
48+
private
49+
50+
attr_reader :good_job_metrics, :gauges
51+
end
52+
end
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# frozen_string_literal: true
2+
3+
require_relative '../test_helper'
4+
require 'prometheus_exporter/server'
5+
require 'prometheus_exporter/instrumentation'
6+
7+
class PrometheusGoodJobCollectorTest < Minitest::Test
8+
include CollectorHelper
9+
10+
def collector
11+
@collector ||= PrometheusExporter::Server::GoodJobCollector.new
12+
end
13+
14+
def test_collecting_metrics
15+
collector.collect(
16+
{
17+
"scheduled" => 3,
18+
"retried" => 4,
19+
"queued" => 0,
20+
"running" => 5,
21+
"finished" => 100,
22+
"succeeded" => 2000,
23+
"discarded" => 9
24+
}
25+
)
26+
27+
metrics = collector.metrics
28+
29+
expected = [
30+
"good_job_scheduled 3",
31+
"good_job_retried 4",
32+
"good_job_queued 0",
33+
"good_job_running 5",
34+
"good_job_finished 100",
35+
"good_job_succeeded 2000",
36+
"good_job_discarded 9"
37+
]
38+
assert_equal expected, metrics.map(&:metric_text)
39+
end
40+
41+
def test_collecting_metrics_with_custom_labels
42+
collector.collect(
43+
"type" => "good_job",
44+
"scheduled" => 3,
45+
"retried" => 4,
46+
"queued" => 0,
47+
"running" => 5,
48+
"finished" => 100,
49+
"succeeded" => 2000,
50+
"discarded" => 9,
51+
'custom_labels' => {
52+
'hostname' => 'good_job_host'
53+
}
54+
)
55+
56+
metrics = collector.metrics
57+
58+
assert(metrics.first.metric_text.include?('good_job_scheduled{hostname="good_job_host"}'))
59+
end
60+
61+
def test_metrics_expiration
62+
data = {
63+
"type" => "good_job",
64+
"scheduled" => 3,
65+
"retried" => 4,
66+
"queued" => 0
67+
}
68+
69+
stub_monotonic_clock(0) do
70+
collector.collect(data)
71+
assert_equal 3, collector.metrics.size
72+
end
73+
74+
stub_monotonic_clock(max_metric_age + 1) do
75+
assert_equal 0, collector.metrics.size
76+
end
77+
end
78+
end

0 commit comments

Comments
 (0)