appsignal 3.0.4 → 3.0.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,61 +0,0 @@
1
- module Appsignal
2
- module Probes
3
- class PumaProbe
4
- def initialize
5
- @hostname = Appsignal.config[:hostname] || Socket.gethostname
6
- end
7
-
8
- # @api private
9
- def call
10
- puma_stats = fetch_puma_stats
11
- return unless puma_stats
12
-
13
- stats = JSON.parse puma_stats, :symbolize_names => true
14
- counts = {}
15
- count_keys = [:backlog, :running, :pool_capacity, :max_threads]
16
-
17
- if stats[:worker_status] # Multiple workers
18
- stats[:worker_status].each do |worker|
19
- stat = worker[:last_status]
20
- count_keys.each do |key|
21
- count_if_present counts, key, stat
22
- end
23
- end
24
-
25
- gauge(:workers, stats[:workers], :type => :count)
26
- gauge(:workers, stats[:booted_workers], :type => :booted)
27
- gauge(:workers, stats[:old_workers], :type => :old)
28
- else # Single worker
29
- count_keys.each do |key|
30
- count_if_present counts, key, stats
31
- end
32
- end
33
-
34
- gauge(:connection_backlog, counts[:backlog]) if counts[:backlog]
35
- gauge(:pool_capacity, counts[:pool_capacity]) if counts[:pool_capacity]
36
- gauge(:threads, counts[:running], :type => :running) if counts[:running]
37
- gauge(:threads, counts[:max_threads], :type => :max) if counts[:max_threads]
38
- end
39
-
40
- private
41
-
42
- attr_reader :hostname
43
-
44
- def gauge(field, count, tags = {})
45
- Appsignal.set_gauge("puma_#{field}", count, tags.merge(:hostname => hostname))
46
- end
47
-
48
- def count_if_present(counts, key, stats)
49
- stat_value = stats[key]
50
- return unless stat_value
51
- counts[key] ||= 0
52
- counts[key] += stat_value
53
- end
54
-
55
- def fetch_puma_stats
56
- ::Puma.stats
57
- rescue NoMethodError # rubocop:disable Lint/HandleExceptions
58
- end
59
- end
60
- end
61
- end
@@ -1,180 +0,0 @@
1
- require "appsignal/probes/puma"
2
-
3
- describe Appsignal::Probes::PumaProbe do
4
- before(:context) do
5
- Appsignal.config = project_fixture_config
6
- end
7
- after(:context) do
8
- Appsignal.config = nil
9
- end
10
-
11
- let(:probe) { described_class.new }
12
-
13
- describe "hostname" do
14
- it "returns the socket hostname" do
15
- expect(probe.send(:hostname)).to eql(Socket.gethostname)
16
- end
17
-
18
- context "with overridden hostname" do
19
- around do |sample|
20
- Appsignal.config[:hostname] = "frontend1"
21
- sample.run
22
- Appsignal.config[:hostname] = nil
23
- end
24
- it "returns the configured host" do
25
- expect(probe.send(:hostname)).to eql("frontend1")
26
- end
27
- end
28
- end
29
-
30
- describe "#call" do
31
- let(:expected_default_tags) { { :hostname => Socket.gethostname } }
32
-
33
- context "with multiple worker stats" do
34
- before(:context) do
35
- class Puma
36
- def self.stats
37
- {
38
- "workers" => 2,
39
- "booted_workers" => 2,
40
- "old_workers" => 0,
41
- "worker_status" => [
42
- {
43
- "last_status" => {
44
- "backlog" => 0,
45
- "running" => 5,
46
- "pool_capacity" => 5,
47
- "max_threads" => 5
48
- }
49
- },
50
- {
51
- "last_status" => {
52
- "backlog" => 0,
53
- "running" => 5,
54
- "pool_capacity" => 5,
55
- "max_threads" => 5
56
- }
57
- }
58
- ]
59
- }.to_json
60
- end
61
- end
62
- end
63
- after(:context) { Object.send(:remove_const, :Puma) }
64
-
65
- it "calls `puma_gauge` with the (summed) worker metrics" do
66
- expect_gauge(:workers, 2, :type => :count)
67
- expect_gauge(:workers, 2, :type => :booted)
68
- expect_gauge(:workers, 0, :type => :old)
69
-
70
- expect_gauge(:connection_backlog, 0)
71
- expect_gauge(:pool_capacity, 10)
72
- expect_gauge(:threads, 10, :type => :running)
73
- expect_gauge(:threads, 10, :type => :max)
74
-
75
- probe.call
76
- end
77
- end
78
-
79
- context "with single worker stats" do
80
- before(:context) do
81
- class Puma
82
- def self.stats
83
- {
84
- "backlog" => 0,
85
- "running" => 5,
86
- "pool_capacity" => 5,
87
- "max_threads" => 5
88
- }.to_json
89
- end
90
- end
91
- end
92
- after(:context) { Object.send(:remove_const, :Puma) }
93
-
94
- it "calls `puma_gauge` with the (summed) worker metrics" do
95
- expect_gauge(:connection_backlog, 0)
96
- expect_gauge(:pool_capacity, 5)
97
- expect_gauge(:threads, 5, :type => :running)
98
- expect_gauge(:threads, 5, :type => :max)
99
- probe.call
100
- end
101
- end
102
-
103
- context "without stats" do
104
- before(:context) do
105
- class Puma
106
- def self.stats
107
- end
108
- end
109
- end
110
- after(:context) { Object.send(:remove_const, :Puma) }
111
-
112
- context "when it returns nil" do
113
- it "does not track metrics" do
114
- expect(probe).to_not receive(:puma_gauge)
115
- probe.call
116
- end
117
- end
118
-
119
- # Puma.stats raises a NoMethodError on a nil object on the first call.
120
- context "when it returns a NoMethodError on the first call" do
121
- let(:log) { StringIO.new }
122
-
123
- it "ignores the first call and tracks the second call" do
124
- use_logger_with log do
125
- expect(Puma).to receive(:stats)
126
- .and_raise(NoMethodError.new("undefined method `stats' for nil:NilClass"))
127
- probe.call
128
-
129
- expect(Puma).to receive(:stats).and_return({
130
- "backlog" => 1,
131
- "running" => 5,
132
- "pool_capacity" => 4,
133
- "max_threads" => 6
134
- }.to_json)
135
-
136
- expect_gauge(:connection_backlog, 1)
137
- expect_gauge(:pool_capacity, 4)
138
- expect_gauge(:threads, 5, :type => :running)
139
- expect_gauge(:threads, 6, :type => :max)
140
- probe.call
141
- end
142
-
143
- expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
144
- end
145
- end
146
-
147
- context "when it does not have a complete stats payload" do
148
- let(:log) { StringIO.new }
149
-
150
- it "tracks whatever metrics we do have" do
151
- use_logger_with log do
152
- expect(Puma).to receive(:stats).and_return({
153
- "backlog" => 1,
154
- "running" => 5
155
- }.to_json)
156
-
157
- expect_gauge(:connection_backlog, 1)
158
- expect_no_gauge(:pool_capacity)
159
- expect_gauge(:threads, 5, :type => :running)
160
- expect_no_gauge(:threads, :type => :max)
161
- probe.call
162
- end
163
-
164
- expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
165
- end
166
- end
167
- end
168
-
169
- def expect_gauge(key, value, tags = {})
170
- expect(Appsignal).to receive(:set_gauge)
171
- .with("puma_#{key}", value, expected_default_tags.merge(tags))
172
- .and_call_original
173
- end
174
-
175
- def expect_no_gauge(key, tags = {})
176
- expect(Appsignal).to_not receive(:set_gauge)
177
- .with("puma_#{key}", anything, tags)
178
- end
179
- end
180
- end