appsignal 3.0.6-java → 3.0.10-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -43,6 +43,14 @@ describe Appsignal::System do
43
43
  end
44
44
  end
45
45
 
46
+ context "when using the APPSIGNAL_BUILD_FOR_LINUX_ARM env var" do
47
+ it "returns the linux build" do
48
+ ENV["APPSIGNAL_BUILD_FOR_LINUX_ARM"] = "1"
49
+ is_expected.to eq("linux")
50
+ ENV.delete("APPSIGNAL_BUILD_FOR_LINUX_ARM")
51
+ end
52
+ end
53
+
46
54
  context "when on a musl system" do
47
55
  let(:ldd_output) { "musl libc (x86_64)\nVersion 1.1.16" }
48
56
 
@@ -93,4 +101,26 @@ describe Appsignal::System do
93
101
  end
94
102
  end
95
103
  end
104
+
105
+ describe ".agent_architecture" do
106
+ let(:architecture) { "x86_64" }
107
+ let(:ldd_output) { "" }
108
+ before do
109
+ allow(RbConfig::CONFIG).to receive(:[])
110
+ allow(RbConfig::CONFIG).to receive(:[]).with("host_cpu").and_return(architecture)
111
+ end
112
+ subject { described_class.agent_architecture }
113
+
114
+ it "returns the host CPU value" do
115
+ is_expected.to eq(architecture)
116
+ end
117
+
118
+ context "when using the APPSIGNAL_BUILD_FOR_LINUX_ARM env var" do
119
+ it "returns ARM 64 bit" do
120
+ ENV["APPSIGNAL_BUILD_FOR_LINUX_ARM"] = "1"
121
+ is_expected.to eq("aarch64")
122
+ ENV.delete("APPSIGNAL_BUILD_FOR_LINUX_ARM")
123
+ end
124
+ end
125
+ end
96
126
  end
@@ -1321,6 +1321,13 @@ describe Appsignal::Transaction do
1321
1321
  expect(subject).to eq ["line 1", "line 2"]
1322
1322
  end
1323
1323
 
1324
+ context "with Rails module but without backtrace_cleaner method" do
1325
+ it "returns the backtrace uncleaned" do
1326
+ stub_const("Rails", Module.new)
1327
+ expect(subject).to eq ["line 1", "line 2"]
1328
+ end
1329
+ end
1330
+
1324
1331
  if rails_present?
1325
1332
  context "with rails" do
1326
1333
  it "cleans the backtrace with the Rails backtrace cleaner" do
@@ -10,110 +10,290 @@ RSpec.describe "Puma plugin" do
10
10
  end
11
11
 
12
12
  class MockPumaEvents
13
- def on_booted(&block)
14
- @on_booted = block if block_given?
15
- @on_booted if defined?(@on_booted)
13
+ attr_reader :logs
14
+
15
+ def initialize
16
+ @logs = []
17
+ end
18
+
19
+ def log(message)
20
+ @logs << [:log, message]
21
+ end
22
+
23
+ def debug(message)
24
+ @logs << [:debug, message]
25
+ end
26
+
27
+ def error(message)
28
+ @logs << [:error, message]
29
+ end
30
+ end
31
+
32
+ # StatsD server used for these tests.
33
+ # Open a UDPSocket and listen for messages sent by the AppSignal Puma plugin.
34
+ class StatsdServer
35
+ def start
36
+ stop
37
+ @socket = UDPSocket.new
38
+ @socket.bind("127.0.0.1", 8125)
39
+
40
+ loop do
41
+ begin
42
+ # Listen for messages and track them on the messages Array.
43
+ packet = @socket.recvfrom(1024)
44
+ track_message packet.first
45
+ rescue Errno::EBADF # rubocop:disable Lint/HandleExceptions
46
+ # Ignore error for JRuby 9.1.17.0 specifically, it doesn't appear to
47
+ # happen on 9.2.18.0. It doesn't break the tests themselves, ignoring
48
+ # this error. It's probably a timing issue where it tries to read
49
+ # from the socket after it's closed.
50
+ end
51
+ end
52
+ end
53
+
54
+ def stop
55
+ defined?(@socket) && @socket && @socket.close
56
+ ensure
57
+ @socket = nil
58
+ end
59
+
60
+ def messages
61
+ @messages ||= []
62
+ end
63
+
64
+ private
65
+
66
+ def track_message(message)
67
+ @messages_mutex ||= Mutex.new
68
+ @messages_mutex.synchronize { messages << message }
16
69
  end
17
70
  end
18
71
 
19
72
  let(:probe) { MockProbe.new }
20
73
  let(:launcher) { MockPumaLauncher.new }
74
+ let(:hostname) { Socket.gethostname }
75
+ let(:expected_default_tags) { { "hostname" => hostname } }
76
+ let(:stats_data) { { :backlog => 1 } }
77
+ before :context do
78
+ Appsignal.stop
79
+ end
21
80
  before do
22
81
  module Puma
23
82
  def self.stats
83
+ JSON.dump(@_stats_data)
84
+ end
85
+
86
+ def self.stats_hash
87
+ @_stats_data
24
88
  end
25
89
 
26
- def self.run
27
- # Capture threads running before application is preloaded
28
- before = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
29
-
30
- # An abbreviated version of what happens in Puma::Cluster#run
31
- launcher = MockPumaLauncher.new
32
- plugin = Plugin.plugin.new
33
- plugin.start(launcher)
34
- launcher.events.on_booted.call
35
-
36
- # Wait for minutely probe thread to finish starting
37
- sleep 0.005
38
-
39
- # Capture any new threads running after application is preloaded.
40
- # Any threads created during the preloading phase will not be
41
- # carried over into the forked workers. Puma warns about these
42
- # but the minutely probe thread should only exist in the main process.
43
- after = Thread.list.reject { |t| t.thread_variable_get(:fork_safe) }
44
- $stdout.puts "! WARNING: Detected #{after.size - before.size} Thread(s) started in app boot" if after.size > before.size
90
+ def self._set_stats=(data)
91
+ @_stats_data = data
45
92
  end
46
93
 
47
94
  class Plugin
48
95
  class << self
49
- attr_reader :plugin
96
+ attr_reader :appsignal_plugin
50
97
 
51
98
  def create(&block)
52
- @plugin = Class.new(::Puma::Plugin)
53
- @plugin.class_eval(&block)
99
+ @appsignal_plugin = Class.new(::Puma::Plugin)
100
+ @appsignal_plugin.class_eval(&block)
54
101
  end
55
102
  end
56
- end
57
- end
58
103
 
59
- Appsignal::Minutely.probes.clear
60
- ENV["APPSIGNAL_ENABLE_MINUTELY_PROBES"] = "true"
61
- Appsignal.config = project_fixture_config
62
- # Speed up test time
63
- allow(Appsignal::Minutely).to receive(:initial_wait_time).and_return(0.001)
64
- allow(Appsignal::Minutely).to receive(:wait_time).and_return(0.001)
104
+ attr_reader :in_background_block
65
105
 
66
- Appsignal::Minutely.probes.register :my_probe, probe
106
+ def in_background(&block)
107
+ @in_background_block = block
108
+ end
109
+ end
110
+ end
111
+ Puma._set_stats = stats_data
67
112
  load File.expand_path("../lib/puma/plugin/appsignal.rb", APPSIGNAL_SPEC_DIR)
113
+
114
+ @statsd = StatsdServer.new
115
+ @server_thread = Thread.new { @statsd.start }
116
+ @server_thread.abort_on_exception = true
68
117
  end
69
118
  after do
70
- Appsignal.config = nil
71
- Object.send :remove_const, :Puma
72
- Object.send :remove_const, :APPSIGNAL_PUMA_PLUGIN_LOADED
119
+ @statsd = nil
120
+
121
+ Object.send(:remove_const, :Puma)
122
+ Object.send(:remove_const, :AppsignalPumaPlugin)
123
+ end
124
+
125
+ def run_plugin(plugin, &block)
126
+ @client_thread = Thread.new { start_plugin(plugin) }
127
+ @client_thread.abort_on_exception = true
128
+ wait_for(:puma_client_wait, &block)
129
+ ensure
130
+ stop_all
73
131
  end
74
132
 
75
- it "registers the PumaProbe" do
76
- expect(Appsignal::Minutely.probes[:my_probe]).to eql(probe)
77
- expect(Appsignal::Minutely.probes[:puma]).to be_nil
78
- plugin = Puma::Plugin.plugin.new
79
- expect(launcher.events.on_booted).to be_nil
133
+ def appsignal_plugin
134
+ Puma::Plugin.appsignal_plugin
135
+ end
80
136
 
137
+ def start_plugin(plugin_class)
138
+ plugin = plugin_class.new
139
+ # Speed up test by not waiting for 60 seconds initial wait time and loop
140
+ # interval.
141
+ allow(plugin).to receive(:sleep_time).and_return(0.01)
81
142
  plugin.start(launcher)
82
- expect(Appsignal::Minutely.probes[:puma]).to be_nil
83
- expect(launcher.events.on_booted).to_not be_nil
143
+ plugin.in_background_block.call
144
+ end
145
+
146
+ # Stop all threads in test and stop listening on the UDPSocket
147
+ def stop_all
148
+ @client_thread.kill if defined?(@client_thread) && @client_thread
149
+ @server_thread.kill if defined?(@server_thread) && @server_thread
150
+ @statsd.stop if defined?(@statsd) && @statsd
151
+ @client_thread = nil
152
+ @server_thread = nil
153
+ end
154
+
155
+ def logs
156
+ launcher.events.logs
157
+ end
158
+
159
+ def messages
160
+ @statsd.messages.map do |message|
161
+ metric, type, tags_string = message.split("|")
162
+ metric_name, metric_value = metric.split(":")
163
+ tags = {}
164
+ tags_string[1..-1].split(",").each do |tag|
165
+ key, value = tag.split(":")
166
+ tags[key] = value
167
+ end
168
+ {
169
+ :name => metric_name,
170
+ :value => metric_value.to_i,
171
+ :type => type,
172
+ :tags => tags
173
+ }
174
+ end
175
+ end
176
+
177
+ def expect_gauge(metric_name, metric_value, tags_hash = {})
178
+ expect(messages).to include(
179
+ :name => "puma_#{metric_name}",
180
+ :value => metric_value,
181
+ :type => "g",
182
+ :tags => expected_default_tags.merge(tags_hash)
183
+ )
184
+ end
185
+
186
+ context "with multiple worker stats" do
187
+ let(:stats_data) do
188
+ {
189
+ :workers => 2,
190
+ :booted_workers => 2,
191
+ :old_workers => 0,
192
+ :worker_status => [
193
+ {
194
+ :last_status => {
195
+ :backlog => 0,
196
+ :running => 5,
197
+ :pool_capacity => 5,
198
+ :max_threads => 5
199
+ }
200
+ },
201
+ {
202
+ :last_status => {
203
+ :backlog => 0,
204
+ :running => 5,
205
+ :pool_capacity => 5,
206
+ :max_threads => 5
207
+ }
208
+ }
209
+ ]
210
+ }
211
+ end
212
+
213
+ it "collects puma stats as guage metrics with the (summed) worker metrics" do
214
+ run_plugin(appsignal_plugin) do
215
+ expect(logs).to_not include([:error, kind_of(String)])
216
+ expect_gauge(:workers, 2, "type" => "count")
217
+ expect_gauge(:workers, 2, "type" => "booted")
218
+ expect_gauge(:workers, 0, "type" => "old")
219
+ expect_gauge(:connection_backlog, 0)
220
+ expect_gauge(:pool_capacity, 10)
221
+ expect_gauge(:threads, 10, "type" => "running")
222
+ expect_gauge(:threads, 10, "type" => "max")
223
+ end
224
+ end
225
+ end
84
226
 
85
- launcher.events.on_booted.call
86
- expect(Appsignal::Minutely.probes[:puma]).to eql(Appsignal::Probes::PumaProbe)
227
+ context "with single worker stats" do
228
+ let(:stats_data) do
229
+ {
230
+ :backlog => 0,
231
+ :running => 5,
232
+ :pool_capacity => 5,
233
+ :max_threads => 5
234
+ }
235
+ end
87
236
 
88
- # Minutely probes started and called
89
- wait_for("enough probe calls") { probe.calls >= 2 }
237
+ it "calls `puma_gauge` with the (summed) worker metrics" do
238
+ run_plugin(appsignal_plugin) do
239
+ expect(logs).to_not include([:error, kind_of(String)])
240
+ expect_gauge(:connection_backlog, 0)
241
+ expect_gauge(:pool_capacity, 5)
242
+ expect_gauge(:threads, 5, "type" => "running")
243
+ expect_gauge(:threads, 5, "type" => "max")
244
+ end
245
+ end
90
246
  end
91
247
 
92
- it "marks the PumaProbe thread as fork-safe" do
93
- out_stream = std_stream
94
- capture_stdout(out_stream) { Puma.run }
248
+ context "when using APPSIGNAL_HOSTNAME" do
249
+ let(:hostname) { "my-host-name" }
250
+ before { ENV["APPSIGNAL_HOSTNAME"] = hostname }
251
+ after { ENV.delete("APPSIGNAL_HOSTNAME") }
95
252
 
96
- expect(out_stream.read).not_to include("WARNING: Detected 1 Thread")
253
+ it "reports the APPSIGNAL_HOSTNAME as the hostname tag value" do
254
+ run_plugin(appsignal_plugin) do
255
+ expect(logs).to_not include([:error, kind_of(String)])
256
+ expect_gauge(:connection_backlog, 1)
257
+ end
258
+ end
97
259
  end
98
260
 
99
- context "without Puma.stats" do
100
- before { Puma.singleton_class.send(:remove_method, :stats) }
261
+ context "without Puma.stats_hash" do
262
+ before do
263
+ Puma.singleton_class.send(:remove_method, :stats_hash)
264
+ end
101
265
 
102
- it "does not register the PumaProbe" do
103
- expect(Appsignal::Minutely.probes[:my_probe]).to eql(probe)
104
- expect(Appsignal::Minutely.probes[:puma]).to be_nil
105
- plugin = Puma::Plugin.plugin.new
106
- expect(launcher.events.on_booted).to be_nil
266
+ it "fetches metrics from Puma.stats instead" do
267
+ run_plugin(appsignal_plugin) do
268
+ expect(logs).to_not include([:error, kind_of(String)])
269
+ expect(logs).to_not include([kind_of(Symbol), "AppSignal: No Puma stats to report."])
270
+ expect_gauge(:connection_backlog, 1)
271
+ end
272
+ end
273
+ end
107
274
 
108
- plugin.start(launcher)
109
- expect(Appsignal::Minutely.probes[:puma]).to be_nil
110
- expect(launcher.events.on_booted).to_not be_nil
275
+ context "without Puma.stats and Puma.stats_hash" do
276
+ before do
277
+ Puma.singleton_class.send(:remove_method, :stats)
278
+ Puma.singleton_class.send(:remove_method, :stats_hash)
279
+ end
111
280
 
112
- launcher.events.on_booted.call
113
- expect(Appsignal::Minutely.probes[:puma]).to be_nil
281
+ it "does not fetch metrics" do
282
+ run_plugin(appsignal_plugin) do
283
+ expect(logs).to_not include([:error, kind_of(String)])
284
+ expect(logs).to include([:log, "AppSignal: No Puma stats to report."])
285
+ expect(messages).to be_empty
286
+ end
287
+ end
288
+ end
114
289
 
115
- # Minutely probes started and called
116
- wait_for("enough probe calls") { probe.calls >= 2 }
290
+ context "without running StatsD server" do
291
+ it "does nothing" do
292
+ stop_all
293
+ run_plugin(appsignal_plugin) do
294
+ expect(logs).to_not include([:error, kind_of(String)])
295
+ expect(messages).to be_empty
296
+ end
117
297
  end
118
298
  end
119
299
  end
@@ -16,13 +16,25 @@ module WaitForHelper
16
16
  def wait_for(name)
17
17
  max_wait = 5_000
18
18
  i = 0
19
+ error = nil
19
20
  while i < max_wait
20
- break if yield
21
- i += 1
22
- sleep 0.001
21
+ begin
22
+ result = yield
23
+ break if result
24
+ rescue Exception => e # rubocop:disable Lint/RescueException
25
+ # Capture error so we know if it exited with an error
26
+ error = e
27
+ ensure
28
+ i += 1
29
+ sleep 0.001
30
+ end
23
31
  end
24
32
 
25
33
  return unless i >= max_wait
26
- raise "Waited 5 seconds for #{name} condition, but was not met."
34
+ error_message =
35
+ if error
36
+ "\nError: #{error.class}: #{error.message}\n#{error.backtrace.join("\n")}"
37
+ end
38
+ raise "Waited 5 seconds for #{name} condition, but was not met.#{error_message}"
27
39
  end
28
40
  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: 3.0.6
4
+ version: 3.0.10
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: 2021-05-31 00:00:00.000000000 Z
13
+ date: 2021-07-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -150,6 +150,7 @@ files:
150
150
  - ".github/ISSUE_TEMPLATE/bug_report.md"
151
151
  - ".github/ISSUE_TEMPLATE/chore.md"
152
152
  - ".gitignore"
153
+ - ".gitmodules"
153
154
  - ".rspec"
154
155
  - ".rubocop.yml"
155
156
  - ".rubocop_todo.yml"
@@ -263,7 +264,6 @@ files:
263
264
  - lib/appsignal/marker.rb
264
265
  - lib/appsignal/minutely.rb
265
266
  - lib/appsignal/probes.rb
266
- - lib/appsignal/probes/puma.rb
267
267
  - lib/appsignal/probes/sidekiq.rb
268
268
  - lib/appsignal/rack/generic_instrumentation.rb
269
269
  - lib/appsignal/rack/rails_instrumentation.rb
@@ -285,6 +285,7 @@ files:
285
285
  - mono.yml
286
286
  - resources/appsignal.yml.erb
287
287
  - resources/cacert.pem
288
+ - script/install_lintje
288
289
  - spec/.rubocop.yml
289
290
  - spec/lib/appsignal/auth_check_spec.rb
290
291
  - spec/lib/appsignal/capistrano2_spec.rb
@@ -349,7 +350,6 @@ files:
349
350
  - spec/lib/appsignal/logger_spec.rb
350
351
  - spec/lib/appsignal/marker_spec.rb
351
352
  - spec/lib/appsignal/minutely_spec.rb
352
- - spec/lib/appsignal/probes/puma_spec.rb
353
353
  - spec/lib/appsignal/probes/sidekiq_spec.rb
354
354
  - spec/lib/appsignal/rack/generic_instrumentation_spec.rb
355
355
  - spec/lib/appsignal/rack/rails_instrumentation_spec.rb
@@ -498,7 +498,6 @@ test_files:
498
498
  - spec/lib/appsignal/logger_spec.rb
499
499
  - spec/lib/appsignal/marker_spec.rb
500
500
  - spec/lib/appsignal/minutely_spec.rb
501
- - spec/lib/appsignal/probes/puma_spec.rb
502
501
  - spec/lib/appsignal/probes/sidekiq_spec.rb
503
502
  - spec/lib/appsignal/rack/generic_instrumentation_spec.rb
504
503
  - spec/lib/appsignal/rack/rails_instrumentation_spec.rb