appsignal 2.8.4.beta.1 → 2.9.18.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
  3. data/.github/ISSUE_TEMPLATE/chore.md +14 -0
  4. data/.gitignore +2 -3
  5. data/.rubocop.yml +3 -0
  6. data/.rubocop_todo.yml +7 -16
  7. data/.travis.yml +28 -27
  8. data/CHANGELOG.md +657 -533
  9. data/README.md +31 -3
  10. data/Rakefile +128 -129
  11. data/SUPPORT.md +16 -0
  12. data/appsignal.gemspec +17 -4
  13. data/build_matrix.yml +21 -9
  14. data/ext/Rakefile +23 -17
  15. data/ext/agent.yml +40 -37
  16. data/ext/base.rb +116 -31
  17. data/ext/extconf.rb +34 -28
  18. data/gemfiles/capistrano2.gemfile +5 -0
  19. data/gemfiles/capistrano3.gemfile +5 -0
  20. data/gemfiles/grape.gemfile +5 -0
  21. data/gemfiles/no_dependencies.gemfile +5 -0
  22. data/gemfiles/padrino.gemfile +5 -0
  23. data/gemfiles/que.gemfile +5 -0
  24. data/gemfiles/que_beta.gemfile +10 -0
  25. data/gemfiles/rails-3.2.gemfile +5 -0
  26. data/gemfiles/rails-4.0.gemfile +5 -0
  27. data/gemfiles/rails-4.1.gemfile +5 -0
  28. data/gemfiles/rails-4.2.gemfile +5 -0
  29. data/gemfiles/rails-6.0.gemfile +5 -0
  30. data/gemfiles/resque.gemfile +5 -0
  31. data/lib/appsignal.rb +14 -492
  32. data/lib/appsignal/cli/demo.rb +5 -2
  33. data/lib/appsignal/cli/diagnose.rb +84 -4
  34. data/lib/appsignal/cli/diagnose/paths.rb +0 -5
  35. data/lib/appsignal/cli/diagnose/utils.rb +19 -0
  36. data/lib/appsignal/cli/helpers.rb +6 -0
  37. data/lib/appsignal/cli/install.rb +45 -15
  38. data/lib/appsignal/cli/notify_of_deploy.rb +10 -0
  39. data/lib/appsignal/config.rb +1 -2
  40. data/lib/appsignal/event_formatter.rb +4 -5
  41. data/lib/appsignal/event_formatter/action_view/render_formatter.rb +10 -8
  42. data/lib/appsignal/event_formatter/moped/query_formatter.rb +60 -59
  43. data/lib/appsignal/extension.rb +2 -2
  44. data/lib/appsignal/helpers/instrumentation.rb +494 -0
  45. data/lib/appsignal/helpers/metrics.rb +54 -0
  46. data/lib/appsignal/hooks.rb +11 -8
  47. data/lib/appsignal/hooks/active_support_notifications.rb +2 -5
  48. data/lib/appsignal/hooks/puma.rb +74 -11
  49. data/lib/appsignal/hooks/sequel.rb +1 -1
  50. data/lib/appsignal/hooks/sidekiq.rb +115 -0
  51. data/lib/appsignal/integrations/mongo_ruby_driver.rb +7 -0
  52. data/lib/appsignal/integrations/que.rb +9 -8
  53. data/lib/appsignal/integrations/railtie.rb +2 -1
  54. data/lib/appsignal/marker.rb +2 -3
  55. data/lib/appsignal/minutely.rb +188 -19
  56. data/lib/appsignal/rack/sinatra_instrumentation.rb +1 -1
  57. data/lib/appsignal/system.rb +16 -18
  58. data/lib/appsignal/transaction.rb +8 -0
  59. data/lib/appsignal/utils/rails_helper.rb +20 -0
  60. data/lib/appsignal/version.rb +1 -1
  61. data/lib/puma/plugin/appsignal.rb +26 -0
  62. data/spec/lib/appsignal/cli/diagnose/utils_spec.rb +40 -0
  63. data/spec/lib/appsignal/cli/diagnose_spec.rb +129 -22
  64. data/spec/lib/appsignal/cli/install_spec.rb +57 -8
  65. data/spec/lib/appsignal/cli/notify_of_deploy_spec.rb +10 -0
  66. data/spec/lib/appsignal/config_spec.rb +13 -11
  67. data/spec/lib/appsignal/event_formatter/action_view/render_formatter_spec.rb +38 -28
  68. data/spec/lib/appsignal/event_formatter/moped/query_formatter_spec.rb +6 -0
  69. data/spec/lib/appsignal/event_formatter_spec.rb +168 -69
  70. data/spec/lib/appsignal/hooks/active_support_notifications_spec.rb +104 -25
  71. data/spec/lib/appsignal/hooks/puma_spec.rb +251 -34
  72. data/spec/lib/appsignal/hooks/sidekiq_spec.rb +209 -0
  73. data/spec/lib/appsignal/hooks_spec.rb +4 -0
  74. data/spec/lib/appsignal/integrations/mongo_ruby_driver_spec.rb +24 -1
  75. data/spec/lib/appsignal/minutely_spec.rb +318 -26
  76. data/spec/lib/appsignal/system_spec.rb +0 -35
  77. data/spec/lib/appsignal/transaction_spec.rb +68 -10
  78. data/spec/lib/appsignal/utils/hash_sanitizer_spec.rb +39 -31
  79. data/spec/lib/appsignal/utils/json_spec.rb +7 -3
  80. data/spec/lib/appsignal_spec.rb +98 -22
  81. data/spec/lib/puma/appsignal_spec.rb +91 -0
  82. data/spec/spec_helper.rb +13 -0
  83. data/spec/support/{project_fixture → fixtures/projects/valid}/config/application.rb +0 -0
  84. data/spec/support/{project_fixture → fixtures/projects/valid}/config/appsignal.yml +1 -0
  85. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/development.rb +0 -0
  86. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/production.rb +0 -0
  87. data/spec/support/{project_fixture → fixtures/projects/valid}/config/environments/test.rb +0 -0
  88. data/spec/support/{project_fixture → fixtures/projects/valid}/log/.gitkeep +0 -0
  89. data/spec/support/helpers/config_helpers.rb +1 -1
  90. data/spec/support/helpers/log_helpers.rb +6 -0
  91. data/spec/support/helpers/wait_for_helper.rb +28 -0
  92. data/spec/support/mocks/mock_probe.rb +11 -0
  93. data/spec/support/stubs/sidekiq/api.rb +4 -0
  94. metadata +43 -31
  95. data/spec/support/fixtures/containers/cgroups/docker +0 -14
  96. data/spec/support/fixtures/containers/cgroups/docker_systemd +0 -8
  97. data/spec/support/fixtures/containers/cgroups/lxc +0 -10
  98. data/spec/support/fixtures/containers/cgroups/no_permission +0 -0
  99. data/spec/support/fixtures/containers/cgroups/none +0 -1
@@ -2,12 +2,14 @@ describe Appsignal::Hooks::ActiveSupportNotificationsHook do
2
2
  if active_support_present?
3
3
  let(:notifier) { ActiveSupport::Notifications::Fanout.new }
4
4
  let(:as) { ActiveSupport::Notifications }
5
+ let!(:transaction) do
6
+ Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
7
+ end
5
8
  before :context do
6
9
  start_agent
7
10
  end
8
11
  before do
9
12
  as.notifier = notifier
10
- Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
11
13
  end
12
14
 
13
15
  describe "#dependencies_present?" do
@@ -17,27 +19,71 @@ describe Appsignal::Hooks::ActiveSupportNotificationsHook do
17
19
  end
18
20
 
19
21
  it "instruments an ActiveSupport::Notifications.instrument event" do
20
- expect(Appsignal::Transaction.current).to receive(:start_event)
21
- .at_least(:once)
22
- expect(Appsignal::Transaction.current).to receive(:finish_event)
23
- .at_least(:once)
24
- .with("sql.active_record", nil, "SQL", 1)
25
-
26
22
  return_value = as.instrument("sql.active_record", :sql => "SQL") do
27
23
  "value"
28
24
  end
29
25
 
30
26
  expect(return_value).to eq "value"
27
+ expect(transaction.to_h["events"]).to match([
28
+ {
29
+ "allocation_count" => kind_of(Integer),
30
+ "body" => "SQL",
31
+ "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
32
+ "child_allocation_count" => kind_of(Integer),
33
+ "child_duration" => kind_of(Float),
34
+ "child_gc_duration" => kind_of(Float),
35
+ "count" => 1,
36
+ "duration" => kind_of(Float),
37
+ "gc_duration" => kind_of(Float),
38
+ "name" => "sql.active_record",
39
+ "start" => kind_of(Float),
40
+ "title" => ""
41
+ }
42
+ ])
31
43
  end
32
44
 
33
- it "should convert non-string names to strings" do
34
- expect(Appsignal::Transaction.current).to receive(:start_event)
35
- .at_least(:once)
36
- expect(Appsignal::Transaction.current).to receive(:finish_event)
37
- .at_least(:once)
38
- .with("not_a_string", nil, nil, nil)
45
+ it "instruments an ActiveSupport::Notifications.instrument event with no registered formatter" do
46
+ return_value = as.instrument("no-registered.formatter", :key => "something") do
47
+ "value"
48
+ end
49
+
50
+ expect(return_value).to eq "value"
51
+ expect(transaction.to_h["events"]).to match([
52
+ {
53
+ "allocation_count" => kind_of(Integer),
54
+ "body" => "",
55
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
56
+ "child_allocation_count" => kind_of(Integer),
57
+ "child_duration" => kind_of(Float),
58
+ "child_gc_duration" => kind_of(Float),
59
+ "count" => 1,
60
+ "duration" => kind_of(Float),
61
+ "gc_duration" => kind_of(Float),
62
+ "name" => "no-registered.formatter",
63
+ "start" => kind_of(Float),
64
+ "title" => ""
65
+ }
66
+ ])
67
+ end
39
68
 
69
+ it "converts non-string names to strings" do
40
70
  as.instrument(:not_a_string) {}
71
+ expect(transaction.to_h["events"]).to match([
72
+ {
73
+ "allocation_count" => kind_of(Integer),
74
+ "body" => "",
75
+ "body_format" => Appsignal::EventFormatter::DEFAULT,
76
+ "child_allocation_count" => kind_of(Integer),
77
+ "child_duration" => kind_of(Float),
78
+ "child_gc_duration" => kind_of(Float),
79
+ "count" => 1,
80
+ "duration" => kind_of(Float),
81
+ "gc_duration" => kind_of(Float),
82
+ "name" => "not_a_string",
83
+ "start" => kind_of(Float),
84
+ "title" => ""
85
+ }
86
+ ])
41
87
  end
42
88
 
43
89
  it "does not instrument events whose name starts with a bang" do
@@ -53,33 +99,66 @@ describe Appsignal::Hooks::ActiveSupportNotificationsHook do
53
99
 
54
100
  context "when an error is raised in an instrumented block" do
55
101
  it "instruments an ActiveSupport::Notifications.instrument event" do
56
- expect(Appsignal::Transaction.current).to receive(:start_event)
57
- .at_least(:once)
58
- expect(Appsignal::Transaction.current).to receive(:finish_event)
59
- .at_least(:once)
60
- .with("sql.active_record", nil, "SQL", 1)
61
-
62
102
  expect do
63
103
  as.instrument("sql.active_record", :sql => "SQL") do
64
104
  raise ExampleException, "foo"
65
105
  end
66
106
  end.to raise_error(ExampleException, "foo")
107
+
108
+ expect(transaction.to_h["events"]).to match([
109
+ {
110
+ "allocation_count" => kind_of(Integer),
111
+ "body" => "SQL",
112
+ "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
113
+ "child_allocation_count" => kind_of(Integer),
114
+ "child_duration" => kind_of(Float),
115
+ "child_gc_duration" => kind_of(Float),
116
+ "count" => 1,
117
+ "duration" => kind_of(Float),
118
+ "gc_duration" => kind_of(Float),
119
+ "name" => "sql.active_record",
120
+ "start" => kind_of(Float),
121
+ "title" => ""
122
+ }
123
+ ])
67
124
  end
68
125
  end
69
126
 
70
127
  context "when a message is thrown in an instrumented block" do
71
128
  it "instruments an ActiveSupport::Notifications.instrument event" do
72
- expect(Appsignal::Transaction.current).to receive(:start_event)
73
- .at_least(:once)
74
- expect(Appsignal::Transaction.current).to receive(:finish_event)
75
- .at_least(:once)
76
- .with("sql.active_record", nil, "SQL", 1)
77
-
78
129
  expect do
79
130
  as.instrument("sql.active_record", :sql => "SQL") do
80
131
  throw :foo
81
132
  end
82
133
  end.to throw_symbol(:foo)
134
+
135
+ expect(transaction.to_h["events"]).to match([
136
+ {
137
+ "allocation_count" => kind_of(Integer),
138
+ "body" => "SQL",
139
+ "body_format" => Appsignal::EventFormatter::SQL_BODY_FORMAT,
140
+ "child_allocation_count" => kind_of(Integer),
141
+ "child_duration" => kind_of(Float),
142
+ "child_gc_duration" => kind_of(Float),
143
+ "count" => 1,
144
+ "duration" => kind_of(Float),
145
+ "gc_duration" => kind_of(Float),
146
+ "name" => "sql.active_record",
147
+ "start" => kind_of(Float),
148
+ "title" => ""
149
+ }
150
+ ])
151
+ end
152
+ end
153
+
154
+ context "when a transaction is completed in an instrumented block" do
155
+ it "does not complete the ActiveSupport::Notifications.instrument event" do
156
+ expect(transaction).to receive(:complete)
157
+ as.instrument("sql.active_record", :sql => "SQL") do
158
+ Appsignal::Transaction.complete_current!
159
+ end
160
+
161
+ expect(transaction.to_h["events"]).to match([])
83
162
  end
84
163
  end
85
164
  else
@@ -2,13 +2,11 @@ describe Appsignal::Hooks::PumaHook do
2
2
  context "with puma" do
3
3
  before(:context) do
4
4
  class Puma
5
- def self.cli_config
6
- @cli_config ||= CliConfig.new
5
+ def self.stats
7
6
  end
8
7
 
9
- class Cluster
10
- def stop_workers
11
- end
8
+ def self.cli_config
9
+ @cli_config ||= CliConfig.new
12
10
  end
13
11
  end
14
12
 
@@ -28,53 +26,272 @@ describe Appsignal::Hooks::PumaHook do
28
26
  it { is_expected.to be_truthy }
29
27
  end
30
28
 
31
- context "when installed" do
32
- before do
33
- Appsignal::Hooks::PumaHook.new.install
29
+ describe "installation" do
30
+ before { Appsignal::Minutely.probes.clear }
31
+
32
+ context "when not clustered mode" do
33
+ it "does not add AppSignal stop behavior Puma::Cluster" do
34
+ expect(defined?(::Puma::Cluster)).to be_falsy
35
+ # Does not error on call
36
+ Appsignal::Hooks::PumaHook.new.install
37
+ end
38
+
39
+ context "with APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
40
+ before do
41
+ # Set in lib/puma/appsignal.rb
42
+ APPSIGNAL_PUMA_PLUGIN_LOADED = true
43
+ end
44
+ after { Object.send :remove_const, :APPSIGNAL_PUMA_PLUGIN_LOADED }
45
+
46
+ it "does not add the Puma minutely probe" do
47
+ Appsignal::Hooks::PumaHook.new.install
48
+ expect(Appsignal::Minutely.probes[:puma]).to be_nil
49
+ end
50
+ end
51
+
52
+ context "without APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
53
+ it "adds the Puma minutely probe" do
54
+ expect(defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)).to be_nil
55
+
56
+ Appsignal::Hooks::PumaHook.new.install
57
+ probe = Appsignal::Minutely.probes[:puma]
58
+ expect(probe).to eql(Appsignal::Hooks::PumaProbe)
59
+ end
60
+ end
34
61
  end
35
62
 
36
- it "adds behavior to Unicorn::Worker#close" do
37
- cluster = Puma::Cluster.new
63
+ context "when in clustered mode" do
64
+ before do
65
+ class Puma
66
+ class Cluster
67
+ def stop_workers
68
+ @called = true
69
+ end
70
+ end
71
+ end
72
+ end
73
+ after { Puma.send(:remove_const, :Cluster) }
74
+
75
+ it "adds behavior to Puma::Cluster.stop_workers" do
76
+ Appsignal::Hooks::PumaHook.new.install
77
+ cluster = Puma::Cluster.new
38
78
 
39
- expect(Appsignal).to receive(:stop)
40
- expect(cluster).to receive(:stop_workers_without_appsignal)
79
+ expect(cluster.instance_variable_defined?(:@called)).to be_falsy
80
+ expect(Appsignal).to receive(:stop).and_call_original
81
+ cluster.stop_workers
82
+ expect(cluster.instance_variable_get(:@called)).to be(true)
83
+ end
84
+
85
+ context "with APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
86
+ before do
87
+ # Set in lib/puma/appsignal.rb
88
+ APPSIGNAL_PUMA_PLUGIN_LOADED = true
89
+ end
90
+ after { Object.send :remove_const, :APPSIGNAL_PUMA_PLUGIN_LOADED }
41
91
 
42
- cluster.stop_workers
92
+ it "does not add the Puma minutely probe" do
93
+ Appsignal::Hooks::PumaHook.new.install
94
+ expect(Appsignal::Minutely.probes[:puma]).to be_nil
95
+ end
96
+ end
97
+
98
+ context "without APPSIGNAL_PUMA_PLUGIN_LOADED defined" do
99
+ it "adds the Puma minutely probe" do
100
+ expect(defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)).to be_nil
101
+
102
+ Appsignal::Hooks::PumaHook.new.install
103
+ probe = Appsignal::Minutely.probes[:puma]
104
+ expect(probe).to eql(Appsignal::Hooks::PumaProbe)
105
+ end
106
+ end
43
107
  end
44
108
  end
109
+ end
110
+
111
+ context "without puma" do
112
+ describe "#dependencies_present?" do
113
+ subject { described_class.new.dependencies_present? }
45
114
 
46
- context "with nil hooks" do
47
- before do
48
- Puma.cli_config.options.delete(:before_worker_boot)
49
- Puma.cli_config.options.delete(:before_worker_shutdown)
50
- Appsignal::Hooks::PumaHook.new.install
115
+ it { is_expected.to be_falsy }
116
+ end
117
+ end
118
+ end
119
+
120
+ describe Appsignal::Hooks::PumaProbe do
121
+ before(:context) do
122
+ Appsignal.config = project_fixture_config
123
+ end
124
+ after(:context) do
125
+ Appsignal.config = nil
126
+ end
127
+
128
+ let(:probe) { Appsignal::Hooks::PumaProbe.new }
129
+
130
+ describe "hostname" do
131
+ it "returns the socket hostname" do
132
+ expect(probe.send(:hostname)).to eql(Socket.gethostname)
133
+ end
134
+
135
+ context "with overridden hostname" do
136
+ around do |sample|
137
+ Appsignal.config[:hostname] = "frontend1"
138
+ sample.run
139
+ Appsignal.config[:hostname] = nil
140
+ end
141
+ it "returns the configured host" do
142
+ expect(probe.send(:hostname)).to eql("frontend1")
51
143
  end
144
+ end
145
+ end
146
+
147
+ describe "#call" do
148
+ let(:expected_default_tags) { { :hostname => Socket.gethostname } }
149
+
150
+ context "with multiple worker stats" do
151
+ before(:context) do
152
+ class Puma
153
+ def self.stats
154
+ {
155
+ "workers" => 2,
156
+ "booted_workers" => 2,
157
+ "old_workers" => 0,
158
+ "worker_status" => [
159
+ {
160
+ "last_status" => {
161
+ "backlog" => 0,
162
+ "running" => 5,
163
+ "pool_capacity" => 5,
164
+ "max_threads" => 5
165
+ }
166
+ },
167
+ {
168
+ "last_status" => {
169
+ "backlog" => 0,
170
+ "running" => 5,
171
+ "pool_capacity" => 5,
172
+ "max_threads" => 5
173
+ }
174
+ }
175
+ ]
176
+ }.to_json
177
+ end
178
+ end
179
+ end
180
+ after(:context) { Object.send(:remove_const, :Puma) }
181
+
182
+ it "calls `puma_gauge` with the (summed) worker metrics" do
183
+ expect_gauge(:workers, 2, :type => :count)
184
+ expect_gauge(:workers, 2, :type => :booted)
185
+ expect_gauge(:workers, 0, :type => :old)
186
+
187
+ expect_gauge(:connection_backlog, 0)
188
+ expect_gauge(:pool_capacity, 10)
189
+ expect_gauge(:threads, 10, :type => :running)
190
+ expect_gauge(:threads, 10, :type => :max)
52
191
 
53
- it "should add a before shutdown worker callback" do
54
- expect(Puma.cli_config.options[:before_worker_boot].first).to be_a(Proc)
55
- expect(Puma.cli_config.options[:before_worker_shutdown].first).to be_a(Proc)
192
+ probe.call
56
193
  end
57
194
  end
58
195
 
59
- context "with existing hooks" do
60
- before do
61
- Puma.cli_config.options[:before_worker_boot] = []
62
- Puma.cli_config.options[:before_worker_shutdown] = []
63
- Appsignal::Hooks::PumaHook.new.install
196
+ context "with single worker stats" do
197
+ before(:context) do
198
+ class Puma
199
+ def self.stats
200
+ {
201
+ "backlog" => 0,
202
+ "running" => 5,
203
+ "pool_capacity" => 5,
204
+ "max_threads" => 5
205
+ }.to_json
206
+ end
207
+ end
64
208
  end
209
+ after(:context) { Object.send(:remove_const, :Puma) }
65
210
 
66
- it "should add a before shutdown worker callback" do
67
- expect(Puma.cli_config.options[:before_worker_boot].first).to be_a(Proc)
68
- expect(Puma.cli_config.options[:before_worker_shutdown].first).to be_a(Proc)
211
+ it "calls `puma_gauge` with the (summed) worker metrics" do
212
+ expect_gauge(:connection_backlog, 0)
213
+ expect_gauge(:pool_capacity, 5)
214
+ expect_gauge(:threads, 5, :type => :running)
215
+ expect_gauge(:threads, 5, :type => :max)
216
+ probe.call
69
217
  end
70
218
  end
71
- end
72
219
 
73
- context "without puma" do
74
- describe "#dependencies_present?" do
75
- subject { described_class.new.dependencies_present? }
220
+ context "without stats" do
221
+ before(:context) do
222
+ class Puma
223
+ def self.stats
224
+ end
225
+ end
226
+ end
227
+ after(:context) { Object.send(:remove_const, :Puma) }
76
228
 
77
- it { is_expected.to be_falsy }
229
+ context "when it returns nil" do
230
+ it "does not track metrics" do
231
+ expect(probe).to_not receive(:puma_gauge)
232
+ probe.call
233
+ end
234
+ end
235
+
236
+ # Puma.stats raises a NoMethodError on a nil object on the first call.
237
+ context "when it returns a NoMethodError on the first call" do
238
+ let(:log) { StringIO.new }
239
+
240
+ it "ignores the first call and tracks the second call" do
241
+ use_logger_with log do
242
+ expect(Puma).to receive(:stats)
243
+ .and_raise(NoMethodError.new("undefined method `stats' for nil:NilClass"))
244
+ probe.call
245
+
246
+ expect(Puma).to receive(:stats).and_return({
247
+ "backlog" => 1,
248
+ "running" => 5,
249
+ "pool_capacity" => 4,
250
+ "max_threads" => 6
251
+ }.to_json)
252
+
253
+ expect_gauge(:connection_backlog, 1)
254
+ expect_gauge(:pool_capacity, 4)
255
+ expect_gauge(:threads, 5, :type => :running)
256
+ expect_gauge(:threads, 6, :type => :max)
257
+ probe.call
258
+ end
259
+
260
+ expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
261
+ end
262
+ end
263
+
264
+ context "when it does not have a complete stats payload" do
265
+ let(:log) { StringIO.new }
266
+
267
+ it "tracks whatever metrics we do have" do
268
+ use_logger_with log do
269
+ expect(Puma).to receive(:stats).and_return({
270
+ "backlog" => 1,
271
+ "running" => 5
272
+ }.to_json)
273
+
274
+ expect_gauge(:connection_backlog, 1)
275
+ expect_no_gauge(:pool_capacity)
276
+ expect_gauge(:threads, 5, :type => :running)
277
+ expect_no_gauge(:threads, :type => :max)
278
+ probe.call
279
+ end
280
+
281
+ expect(log_contents(log)).to_not contains_log(:error, "Error in minutely probe 'puma'")
282
+ end
283
+ end
284
+ end
285
+
286
+ def expect_gauge(key, value, tags = {})
287
+ expect(Appsignal).to receive(:set_gauge)
288
+ .with("puma_#{key}", value, expected_default_tags.merge(tags))
289
+ .and_call_original
290
+ end
291
+
292
+ def expect_no_gauge(key, tags = {})
293
+ expect(Appsignal).to_not receive(:set_gauge)
294
+ .with("puma_#{key}", anything, tags)
78
295
  end
79
296
  end
80
297
  end