Skip to content

Commit ad0f37e

Browse files
authored
Expand usage of MetricsContainer in other collectors (#268)
* Update activerecord collector * Add extra AR collector test * Add expiration test for resque collector * Add process collector test file * Adds the basic process collection tests (individually for the collector class) * Fixes hostname collection (hostname was previously ignored, see #272 for details) * Add unicorn collector test
1 parent 8db6dc1 commit ad0f37e

19 files changed

+281
-120
lines changed

lib/prometheus_exporter/server/active_record_collector.rb

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
module PrometheusExporter::Server
44
class ActiveRecordCollector < TypeCollector
5-
MAX_ACTIVERECORD_METRIC_AGE = 60
5+
MAX_METRIC_AGE = 60
6+
67
ACTIVE_RECORD_GAUGES = {
78
connections: "Total connections in pool",
89
busy: "Connections in use in pool",
@@ -13,7 +14,12 @@ class ActiveRecordCollector < TypeCollector
1314
}
1415

1516
def initialize
16-
@active_record_metrics = []
17+
@active_record_metrics = MetricsContainer.new(ttl: MAX_METRIC_AGE)
18+
@active_record_metrics.filter = -> (new_metric, old_metric) do
19+
new_metric["pid"] == old_metric["pid"] &&
20+
new_metric["hostname"] == old_metric["hostname"] &&
21+
new_metric["metric_labels"]["pool_name"] == old_metric["metric_labels"]["pool_name"]
22+
end
1723
end
1824

1925
def type
@@ -42,16 +48,6 @@ def metrics
4248
end
4349

4450
def collect(obj)
45-
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
46-
47-
obj["created_at"] = now
48-
49-
@active_record_metrics.delete_if do |current|
50-
(obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"] &&
51-
obj["metric_labels"]["pool_name"] == current["metric_labels"]["pool_name"]) ||
52-
(current["created_at"] + MAX_ACTIVERECORD_METRIC_AGE < now)
53-
end
54-
5551
@active_record_metrics << obj
5652
end
5753
end

lib/prometheus_exporter/server/process_collector.rb

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
module PrometheusExporter::Server
44

55
class ProcessCollector < TypeCollector
6-
MAX_PROCESS_METRIC_AGE = 60
6+
MAX_METRIC_AGE = 60
7+
78
PROCESS_GAUGES = {
89
heap_free_slots: "Free ruby heap slots.",
910
heap_live_slots: "Used ruby heap slots.",
@@ -21,7 +22,10 @@ class ProcessCollector < TypeCollector
2122
}
2223

2324
def initialize
24-
@process_metrics = []
25+
@process_metrics = MetricsContainer.new(ttl: MAX_METRIC_AGE)
26+
@process_metrics.filter = -> (new_metric, old_metric) do
27+
new_metric["pid"] == old_metric["pid"] && new_metric["hostname"] == old_metric["hostname"]
28+
end
2529
end
2630

2731
def type
@@ -34,8 +38,8 @@ def metrics
3438
metrics = {}
3539

3640
@process_metrics.map do |m|
37-
metric_key = m["metric_labels"].merge("pid" => m["pid"])
38-
metric_key.merge!(m["custom_labels"] || {})
41+
metric_key = (m["metric_labels"] || {}).merge("pid" => m["pid"], "hostname" => m["hostname"])
42+
metric_key.merge!(m["custom_labels"]) if m["custom_labels"]
3943

4044
PROCESS_GAUGES.map do |k, help|
4145
k = k.to_s
@@ -58,15 +62,6 @@ def metrics
5862
end
5963

6064
def collect(obj)
61-
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
62-
63-
obj["created_at"] = now
64-
65-
@process_metrics.delete_if do |current|
66-
(obj["pid"] == current["pid"] && obj["hostname"] == current["hostname"]) ||
67-
(current["created_at"] + MAX_PROCESS_METRIC_AGE < now)
68-
end
69-
7065
@process_metrics << obj
7166
end
7267
end

lib/prometheus_exporter/server/puma_collector.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ class PumaCollector < TypeCollector
1414
}
1515

1616
def initialize
17-
@puma_metrics = MetricsContainer.new
17+
@puma_metrics = MetricsContainer.new(ttl: MAX_PUMA_METRIC_AGE)
1818
@puma_metrics.filter = -> (new_metric, old_metric) do
1919
new_metric["pid"] == old_metric["pid"] && new_metric["hostname"] == old_metric["hostname"]
2020
end

lib/prometheus_exporter/server/resque_collector.rb

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module PrometheusExporter::Server
44
class ResqueCollector < TypeCollector
5-
MAX_RESQUE_METRIC_AGE = 30
5+
MAX_METRIC_AGE = 30
66
RESQUE_GAUGES = {
77
processed_jobs: "Total number of processed Resque jobs.",
88
failed_jobs: "Total number of failed Resque jobs.",
@@ -13,7 +13,7 @@ class ResqueCollector < TypeCollector
1313
}
1414

1515
def initialize
16-
@resque_metrics = []
16+
@resque_metrics = MetricsContainer.new(ttl: MAX_METRIC_AGE)
1717
@gauges = {}
1818
end
1919

@@ -40,11 +40,7 @@ def metrics
4040
end
4141

4242
def collect(object)
43-
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
44-
45-
object["created_at"] = now
46-
resque_metrics.delete_if { |metric| metric["created_at"] + MAX_RESQUE_METRIC_AGE < now }
47-
resque_metrics << object
43+
@resque_metrics << object
4844
end
4945

5046
private

lib/prometheus_exporter/server/sidekiq_process_collector.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module PrometheusExporter::Server
44
class SidekiqProcessCollector < PrometheusExporter::Server::TypeCollector
5-
MAX_SIDEKIQ_METRIC_AGE = 60
5+
MAX_METRIC_AGE = 60
66

77
SIDEKIQ_PROCESS_GAUGES = {
88
'busy' => 'Number of running jobs',
@@ -12,7 +12,7 @@ class SidekiqProcessCollector < PrometheusExporter::Server::TypeCollector
1212
attr_reader :sidekiq_metrics, :gauges
1313

1414
def initialize
15-
@sidekiq_metrics = MetricsContainer.new(ttl: MAX_SIDEKIQ_METRIC_AGE)
15+
@sidekiq_metrics = MetricsContainer.new(ttl: MAX_METRIC_AGE)
1616
@gauges = {}
1717
end
1818

lib/prometheus_exporter/server/sidekiq_queue_collector.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# frozen_string_literal: true
22
module PrometheusExporter::Server
33
class SidekiqQueueCollector < TypeCollector
4-
MAX_SIDEKIQ_METRIC_AGE = 60
4+
MAX_METRIC_AGE = 60
55

66
SIDEKIQ_QUEUE_GAUGES = {
77
'backlog' => 'Size of the sidekiq queue.',
@@ -11,7 +11,7 @@ class SidekiqQueueCollector < TypeCollector
1111
attr_reader :sidekiq_metrics, :gauges
1212

1313
def initialize
14-
@sidekiq_metrics = MetricsContainer.new
14+
@sidekiq_metrics = MetricsContainer.new(ttl: MAX_METRIC_AGE)
1515
@gauges = {}
1616
end
1717

lib/prometheus_exporter/server/sidekiq_stats_collector.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module PrometheusExporter::Server
44
class SidekiqStatsCollector < TypeCollector
5-
MAX_SIDEKIQ_METRIC_AGE = 60
5+
MAX_METRIC_AGE = 60
66

77
SIDEKIQ_STATS_GAUGES = {
88
'dead_size' => 'Size of dead the queue',
@@ -18,7 +18,7 @@ class SidekiqStatsCollector < TypeCollector
1818
attr_reader :sidekiq_metrics, :gauges
1919

2020
def initialize
21-
@sidekiq_metrics = MetricsContainer.new(ttl: MAX_SIDEKIQ_METRIC_AGE)
21+
@sidekiq_metrics = MetricsContainer.new(ttl: MAX_METRIC_AGE)
2222
@gauges = {}
2323
end
2424

lib/prometheus_exporter/server/unicorn_collector.rb

Lines changed: 32 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,46 @@
22

33
# custom type collector for prometheus_exporter for handling the metrics sent from
44
# PrometheusExporter::Instrumentation::Unicorn
5-
class PrometheusExporter::Server::UnicornCollector < PrometheusExporter::Server::TypeCollector
6-
MAX_UNICORN_METRIC_AGE = 60
7-
8-
UNICORN_GAUGES = {
9-
workers: 'Number of unicorn workers.',
10-
active_workers: 'Number of active unicorn workers',
11-
request_backlog: 'Number of requests waiting to be processed by a unicorn worker.'
12-
}.freeze
13-
14-
def initialize
15-
@unicorn_metrics = []
16-
end
5+
module PrometheusExporter::Server
6+
class UnicornCollector < PrometheusExporter::Server::TypeCollector
7+
MAX_METRIC_AGE = 60
8+
9+
UNICORN_GAUGES = {
10+
workers: 'Number of unicorn workers.',
11+
active_workers: 'Number of active unicorn workers',
12+
request_backlog: 'Number of requests waiting to be processed by a unicorn worker.'
13+
}.freeze
14+
15+
def initialize
16+
@unicorn_metrics = MetricsContainer.new(ttl: MAX_METRIC_AGE)
17+
end
1718

18-
def type
19-
'unicorn'
20-
end
19+
def type
20+
'unicorn'
21+
end
2122

22-
def metrics
23-
return [] if @unicorn_metrics.length.zero?
23+
def metrics
24+
return [] if @unicorn_metrics.length.zero?
2425

25-
metrics = {}
26+
metrics = {}
2627

27-
@unicorn_metrics.map do |m|
28-
labels = m["custom_labels"] || {}
28+
@unicorn_metrics.map do |m|
29+
labels = m["custom_labels"] || {}
2930

30-
UNICORN_GAUGES.map do |k, help|
31-
k = k.to_s
32-
if (v = m[k])
33-
g = metrics[k] ||= PrometheusExporter::Metric::Gauge.new("unicorn_#{k}", help)
34-
g.observe(v, labels)
31+
UNICORN_GAUGES.map do |k, help|
32+
k = k.to_s
33+
if (v = m[k])
34+
g = metrics[k] ||= PrometheusExporter::Metric::Gauge.new("unicorn_#{k}", help)
35+
g.observe(v, labels)
36+
end
3537
end
3638
end
37-
end
3839

39-
metrics.values
40-
end
40+
metrics.values
41+
end
4142

42-
def collect(obj)
43-
now = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
44-
obj["created_at"] = now
45-
@unicorn_metrics.delete_if { |m| m['created_at'] + MAX_UNICORN_METRIC_AGE < now }
46-
@unicorn_metrics << obj
43+
def collect(obj)
44+
@unicorn_metrics << obj
45+
end
4746
end
4847
end

test/server/active_record_collector_test.rb

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
require 'prometheus_exporter/instrumentation'
77

88
class PrometheusActiveRecordCollectorTest < Minitest::Test
9+
include CollectorHelper
10+
911
def collector
1012
@collector ||= PrometheusExporter::Server::ActiveRecordCollector.new
1113
end
@@ -27,7 +29,6 @@ def test_collecting_metrics
2729
end
2830

2931
def test_collecting_metrics_with_custom_labels
30-
3132
collector.collect(
3233
"type" => "active_record",
3334
"pid" => "1000",
@@ -49,7 +50,6 @@ def test_collecting_metrics_with_custom_labels
4950
end
5051

5152
def test_collecting_metrics_with_client_default_labels
52-
5353
collector.collect(
5454
"type" => "active_record",
5555
"pid" => "1000",
@@ -108,4 +108,56 @@ def test_collecting_metrics_for_multiple_pools
108108
assert(metrics.first.metric_text.include?('active_record_connection_pool_connections{pool_name="primary",pid="1000",hostname="localhost"} 50'))
109109
assert(metrics.first.metric_text.include?('active_record_connection_pool_connections{pool_name="other",pid="1000",hostname="localhost"} 5'))
110110
end
111+
112+
def test_metrics_deduplication
113+
data = {
114+
"pid" => "1000",
115+
"hostname" => "localhost",
116+
"metric_labels" => { "pool_name" => "primary" },
117+
"connections" => 100
118+
}
119+
120+
collector.collect(data)
121+
collector.collect(data.merge("connections" => 200))
122+
collector.collect(data.merge("pid" => "2000", "connections" => 300))
123+
collector.collect(data.merge("pid" => "3000", "connections" => 400))
124+
collector.collect(data.merge("hostname" => "localhost2", "pid" => "2000", "connections" => 500))
125+
126+
metrics = collector.metrics
127+
metrics_lines = metrics.map(&:metric_text).join.split("\n")
128+
129+
assert_equal 1, metrics.size
130+
assert_equal [
131+
'active_record_connection_pool_connections{pool_name="primary",pid="1000",hostname="localhost"} 200',
132+
'active_record_connection_pool_connections{pool_name="primary",pid="2000",hostname="localhost"} 300',
133+
'active_record_connection_pool_connections{pool_name="primary",pid="3000",hostname="localhost"} 400',
134+
'active_record_connection_pool_connections{pool_name="primary",pid="2000",hostname="localhost2"} 500'
135+
], metrics_lines
136+
end
137+
138+
def test_metrics_expiration
139+
data = {
140+
"pid" => "1000",
141+
"hostname" => "localhost",
142+
"connections" => 50,
143+
"busy" => 20,
144+
"dead" => 10,
145+
"idle" => 20,
146+
"waiting" => 0,
147+
"size" => 120,
148+
"metric_labels" => {
149+
"pool_name" => "primary"
150+
}
151+
}
152+
153+
stub_monotonic_clock(0) do
154+
collector.collect(data)
155+
collector.collect(data.merge("pid" => "1001", "hostname" => "localhost2"))
156+
assert_equal 6, collector.metrics.size
157+
end
158+
159+
stub_monotonic_clock(max_metric_age + 1) do
160+
assert_equal 0, collector.metrics.size
161+
end
162+
end
111163
end

test/server/collector_test.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,7 +727,7 @@ def test_it_can_collect_process_metrics
727727

728728
text = collector.prometheus_metrics_text
729729

730-
v8_str = "v8_heap_count{type=\"web\",pid=\"#{collected[:pid]}\"} #{collected[:v8_heap_count]}"
730+
v8_str = "v8_heap_count{type=\"web\",pid=\"#{collected[:pid]}\",hostname=\"#{PrometheusExporter.hostname}\"} #{collected[:v8_heap_count]}"
731731

732732
assert(text.include?(v8_str), "must include v8 metric")
733733
assert(text.include?("minor_gc_ops_total"), "must include counters")

0 commit comments

Comments
 (0)