appsignal 2.11.0.alpha.1-java → 2.11.0.alpha.2-java

Sign up to get free protection for your applications and to get access to all the features.
@@ -142,7 +142,7 @@ if DependencyHelper.padrino_present?
142
142
  expect_a_transaction_to_be_created
143
143
  # Uses path for action name
144
144
  expect(transaction).to receive(:set_action_if_nil).with("PadrinoTestApp#unknown")
145
- expect(response).to match_response(404, "<h1>Not Found</h1>")
145
+ expect(response).to match_response(404, "GET /404")
146
146
  end
147
147
  end
148
148
 
@@ -0,0 +1,180 @@
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
@@ -0,0 +1,201 @@
1
+ require "appsignal/probes/sidekiq"
2
+
3
+ describe Appsignal::Probes::SidekiqProbe do
4
+ describe "#call" do
5
+ let(:probe) { described_class.new }
6
+ let(:redis_hostname) { "localhost" }
7
+ let(:expected_default_tags) { { :hostname => "localhost" } }
8
+ before do
9
+ Appsignal.config = project_fixture_config
10
+ module Sidekiq
11
+ def self.redis_info
12
+ {
13
+ "connected_clients" => 2,
14
+ "used_memory" => 1024,
15
+ "used_memory_rss" => 512
16
+ }
17
+ end
18
+
19
+ def self.redis
20
+ yield Client.new
21
+ end
22
+
23
+ class Client
24
+ def connection
25
+ { :host => "localhost" }
26
+ end
27
+ end
28
+
29
+ class Stats
30
+ class << self
31
+ attr_reader :calls
32
+
33
+ def count_call
34
+ @calls ||= -1
35
+ @calls += 1
36
+ end
37
+ end
38
+
39
+ def workers_size
40
+ # First method called, so count it towards a call
41
+ self.class.count_call
42
+ 24
43
+ end
44
+
45
+ def processes_size
46
+ 25
47
+ end
48
+
49
+ # Return two different values for two separate calls.
50
+ # This allows us to test the delta of the value send as a gauge.
51
+ def processed
52
+ [10, 15][self.class.calls]
53
+ end
54
+
55
+ # Return two different values for two separate calls.
56
+ # This allows us to test the delta of the value send as a gauge.
57
+ def failed
58
+ [10, 13][self.class.calls]
59
+ end
60
+
61
+ def retry_size
62
+ 12
63
+ end
64
+
65
+ # Return two different values for two separate calls.
66
+ # This allows us to test the delta of the value send as a gauge.
67
+ def dead_size
68
+ [10, 12][self.class.calls]
69
+ end
70
+
71
+ def scheduled_size
72
+ 14
73
+ end
74
+
75
+ def enqueued
76
+ 15
77
+ end
78
+ end
79
+
80
+ class Queue
81
+ Queue = Struct.new(:name, :size, :latency)
82
+
83
+ def self.all
84
+ [
85
+ Queue.new("default", 10, 12),
86
+ Queue.new("critical", 1, 2)
87
+ ]
88
+ end
89
+ end
90
+ end
91
+ end
92
+ after { Object.send(:remove_const, "Sidekiq") }
93
+
94
+ describe ".dependencies_present?" do
95
+ before do
96
+ class Redis; end
97
+ Redis.const_set(:VERSION, version)
98
+ end
99
+ after { Object.send(:remove_const, "Redis") }
100
+
101
+ context "when Redis version is < 3.3.5" do
102
+ let(:version) { "3.3.4" }
103
+
104
+ it "does not start probe" do
105
+ expect(described_class.dependencies_present?).to be_falsy
106
+ end
107
+ end
108
+
109
+ context "when Redis version is >= 3.3.5" do
110
+ let(:version) { "3.3.5" }
111
+
112
+ it "does not start probe" do
113
+ expect(described_class.dependencies_present?).to be_truthy
114
+ end
115
+ end
116
+ end
117
+
118
+ it "loads Sidekiq::API" do
119
+ expect(defined?(Sidekiq::API)).to be_falsy
120
+ probe
121
+ expect(defined?(Sidekiq::API)).to be_truthy
122
+ end
123
+
124
+ it "logs config on initialize" do
125
+ log = capture_logs { probe }
126
+ expect(log).to contains_log(:debug, "Initializing Sidekiq probe\n")
127
+ end
128
+
129
+ it "logs used hostname on call once" do
130
+ log = capture_logs { probe.call }
131
+ expect(log).to contains_log(
132
+ :debug,
133
+ %(Sidekiq probe: Using Redis server hostname "localhost" as hostname)
134
+ )
135
+ log = capture_logs { probe.call }
136
+ # Match more logs with incompelete message
137
+ expect(log).to_not contains_log(:debug, %(Sidekiq probe: ))
138
+ end
139
+
140
+ it "collects custom metrics" do
141
+ expect_gauge("worker_count", 24).twice
142
+ expect_gauge("process_count", 25).twice
143
+ expect_gauge("connection_count", 2).twice
144
+ expect_gauge("memory_usage", 1024).twice
145
+ expect_gauge("memory_usage_rss", 512).twice
146
+ expect_gauge("job_count", 5, :status => :processed) # Gauge delta
147
+ expect_gauge("job_count", 3, :status => :failed) # Gauge delta
148
+ expect_gauge("job_count", 12, :status => :retry_queue).twice
149
+ expect_gauge("job_count", 2, :status => :died) # Gauge delta
150
+ expect_gauge("job_count", 14, :status => :scheduled).twice
151
+ expect_gauge("job_count", 15, :status => :enqueued).twice
152
+ expect_gauge("queue_length", 10, :queue => "default").twice
153
+ expect_gauge("queue_latency", 12_000, :queue => "default").twice
154
+ expect_gauge("queue_length", 1, :queue => "critical").twice
155
+ expect_gauge("queue_latency", 2_000, :queue => "critical").twice
156
+ # Call probe twice so we can calculate the delta for some gauge values
157
+ probe.call
158
+ probe.call
159
+ end
160
+
161
+ context "when `redis_info` is not defined" do
162
+ before do
163
+ allow(Sidekiq).to receive(:respond_to?).with(:redis_info).and_return(false)
164
+ end
165
+
166
+ it "does not collect redis metrics" do
167
+ expect_gauge("connection_count", 2).never
168
+ expect_gauge("memory_usage", 1024).never
169
+ expect_gauge("memory_usage_rss", 512).never
170
+ probe.call
171
+ end
172
+ end
173
+
174
+ context "when hostname is configured for probe" do
175
+ let(:redis_hostname) { "my_redis_server" }
176
+ let(:probe) { described_class.new(:hostname => redis_hostname) }
177
+
178
+ it "uses the redis hostname for the hostname tag" do
179
+ allow(Appsignal).to receive(:set_gauge).and_call_original
180
+ log = capture_logs { probe }
181
+ expect(log).to contains_log(
182
+ :debug,
183
+ %(Initializing Sidekiq probe with config: {:hostname=>"#{redis_hostname}"})
184
+ )
185
+ log = capture_logs { probe.call }
186
+ expect(log).to contains_log(
187
+ :debug,
188
+ "Sidekiq probe: Using hostname config option #{redis_hostname.inspect} as hostname"
189
+ )
190
+ expect(Appsignal).to have_received(:set_gauge)
191
+ .with(anything, anything, :hostname => redis_hostname).at_least(:once)
192
+ end
193
+ end
194
+
195
+ def expect_gauge(key, value, tags = {})
196
+ expect(Appsignal).to receive(:set_gauge)
197
+ .with("sidekiq_#{key}", value, expected_default_tags.merge(tags))
198
+ .and_call_original
199
+ end
200
+ end
201
+ end
@@ -4,9 +4,12 @@ describe Appsignal::Rack::JSExceptionCatcher do
4
4
  let(:config_options) { { :enable_frontend_error_catching => true } }
5
5
  let(:config) { project_fixture_config("production", config_options) }
6
6
  let(:deprecation_message) do
7
- "The Appsignal::Rack::JSExceptionCatcher is deprecated. " \
8
- "Please use the official AppSignal JavaScript integration instead. " \
9
- "https://docs.appsignal.com/front-end/"
7
+ "The Appsignal::Rack::JSExceptionCatcher is " \
8
+ "deprecated and will be removed in a future version. Please use " \
9
+ "the official AppSignal JavaScript integration by disabling " \
10
+ "`enable_frontend_error_catching` in your configuration and " \
11
+ "installing AppSignal for JavaScript instead. " \
12
+ "(https://docs.appsignal.com/front-end/)"
10
13
  end
11
14
  before { Appsignal.config = config }
12
15
 
@@ -32,7 +35,9 @@ describe Appsignal::Rack::JSExceptionCatcher do
32
35
 
33
36
  describe "#call" do
34
37
  let(:catcher) do
35
- silence { Appsignal::Rack::JSExceptionCatcher.new(app, options) }
38
+ silence :allowed => ["enable_frontend_error_catching"] do
39
+ Appsignal::Rack::JSExceptionCatcher.new(app, options)
40
+ end
36
41
  end
37
42
  after { catcher.call(env) }
38
43
 
@@ -62,7 +62,7 @@ RSpec.describe "Puma plugin" do
62
62
  expect(launcher.events.on_booted).to_not be_nil
63
63
 
64
64
  launcher.events.on_booted.call
65
- expect(Appsignal::Minutely.probes[:puma]).to eql(Appsignal::Hooks::PumaProbe)
65
+ expect(Appsignal::Minutely.probes[:puma]).to eql(Appsignal::Probes::PumaProbe)
66
66
 
67
67
  # Minutely probes started and called
68
68
  wait_for("enough probe calls") { probe.calls >= 2 }
@@ -1,4 +1,4 @@
1
- class Sidekiq
1
+ module Sidekiq
2
2
  class API
3
3
  end
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: appsignal
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.11.0.alpha.1
4
+ version: 2.11.0.alpha.2
5
5
  platform: java
6
6
  authors:
7
7
  - Robert Beekman
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-06-29 00:00:00.000000000 Z
13
+ date: 2020-07-02 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -255,6 +255,8 @@ files:
255
255
  - lib/appsignal/logger.rb
256
256
  - lib/appsignal/marker.rb
257
257
  - lib/appsignal/minutely.rb
258
+ - lib/appsignal/probes/puma.rb
259
+ - lib/appsignal/probes/sidekiq.rb
258
260
  - lib/appsignal/rack/generic_instrumentation.rb
259
261
  - lib/appsignal/rack/js_exception_catcher.rb
260
262
  - lib/appsignal/rack/rails_instrumentation.rb
@@ -334,6 +336,8 @@ files:
334
336
  - spec/lib/appsignal/logger_spec.rb
335
337
  - spec/lib/appsignal/marker_spec.rb
336
338
  - spec/lib/appsignal/minutely_spec.rb
339
+ - spec/lib/appsignal/probes/puma_spec.rb
340
+ - spec/lib/appsignal/probes/sidekiq_spec.rb
337
341
  - spec/lib/appsignal/rack/generic_instrumentation_spec.rb
338
342
  - spec/lib/appsignal/rack/js_exception_catcher_spec.rb
339
343
  - spec/lib/appsignal/rack/rails_instrumentation_spec.rb
@@ -476,6 +480,8 @@ test_files:
476
480
  - spec/lib/appsignal/logger_spec.rb
477
481
  - spec/lib/appsignal/marker_spec.rb
478
482
  - spec/lib/appsignal/minutely_spec.rb
483
+ - spec/lib/appsignal/probes/puma_spec.rb
484
+ - spec/lib/appsignal/probes/sidekiq_spec.rb
479
485
  - spec/lib/appsignal/rack/generic_instrumentation_spec.rb
480
486
  - spec/lib/appsignal/rack/js_exception_catcher_spec.rb
481
487
  - spec/lib/appsignal/rack/rails_instrumentation_spec.rb