appsignal 3.0.6 → 3.0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +9 -3
- data/ext/agent.yml +17 -17
- data/lib/appsignal/config.rb +1 -0
- data/lib/appsignal/hooks/puma.rb +1 -16
- data/lib/appsignal/probes.rb +0 -1
- data/lib/appsignal/version.rb +1 -1
- data/lib/puma/plugin/appsignal.rb +146 -17
- data/spec/lib/appsignal/hooks/puma_spec.rb +0 -46
- data/spec/lib/puma/appsignal_spec.rb +244 -68
- metadata +2 -5
- data/lib/appsignal/probes/puma.rb +0 -61
- data/spec/lib/appsignal/probes/puma_spec.rb +0 -180
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b30d6aa0276c7bc900b235aa7837d44c11cca8cf4fc81be150c59962636dc42
|
4
|
+
data.tar.gz: 4ea9db25ed39f99052d8e83a83513a8f75363a028895f15e09e23a18774d633c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6ac1d744af5f9674baec2985bbbe9ad46bb1c76a068a35b9902261e3d7ff40b0344796c62b90ce50f02ffaeb5b79b15bb4e238885834cbe9058b0791e340ab87
|
7
|
+
data.tar.gz: f91fcf688355c79d562fabda066473e3942aa1f174f3ce22485a43fe9aac6c530c2200b0f2be0af09a5b2568ca43e9a001d0a0293c5eaea6f989ff83027f709b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,16 @@
|
|
1
1
|
# AppSignal for Ruby gem Changelog
|
2
2
|
|
3
|
+
## 3.0.7
|
4
|
+
|
5
|
+
- [27f9b178](https://github.com/appsignal/appsignal-ruby/commit/27f9b178c20006ee15e69bdf878f3a0c9975b1f4) patch - Bump agent to 6caf6d0. Replaces curl HTTP client and includes various other maintenance updates.
|
6
|
+
- [665d883a](https://github.com/appsignal/appsignal-ruby/commit/665d883a529e5c14b28e73eeb3ae6410deb3e182) patch - Improve Puma plugin stats collection. Instead of starting the AppSignal gem in the main process we send the stats to the AppSignal agent directly using StatsD. This should improve compatibility with phased restarts. If you use `prune_bundler`, you will need to add AppSignal to the extra `extra_runtime_dependencies` list.
|
7
|
+
|
8
|
+
```
|
9
|
+
# config/puma.rb
|
10
|
+
plugin :appsignal
|
11
|
+
extra_runtime_dependencies ["appsignal"]
|
12
|
+
```
|
13
|
+
|
3
14
|
## 3.0.6
|
4
15
|
|
5
16
|
- [d354d79b](https://github.com/appsignal/appsignal-ruby/commit/d354d79b293fd549e66cae60d805d1b1e9e9d2d8) patch - Add Excon integration. Track requests and responses from the Excon gem.
|
data/README.md
CHANGED
@@ -161,9 +161,13 @@ systems][supported-systems] page.
|
|
161
161
|
|
162
162
|
Following the process below to release a new version of the Ruby gem.
|
163
163
|
|
164
|
-
1.
|
165
|
-
|
166
|
-
|
164
|
+
1. Make sure [mono](https://github.com/appsignal/mono/) is installed by
|
165
|
+
following the installation instructions.
|
166
|
+
1. Run: `mono publish`
|
167
|
+
- Mono will automatically bump the version number based on the
|
168
|
+
`.changesets/`.
|
169
|
+
- Mono will automatically update the `CHANGELOG.md` file based on the
|
170
|
+
`.changesets/`.
|
167
171
|
1. Confirm with your two-factor authentication token for Rubygems.org, _twice_.
|
168
172
|
|
169
173
|
## Development
|
@@ -176,6 +180,8 @@ install all possible dependencies.
|
|
176
180
|
```bash
|
177
181
|
# Install Bundler
|
178
182
|
gem install bundler
|
183
|
+
# Bootstrap the project
|
184
|
+
mono bootstrap
|
179
185
|
# Install the AppSignal extension and _all_ gems we support.
|
180
186
|
bundle exec rake install
|
181
187
|
# Only install the AppSignal extension.
|
data/ext/agent.yml
CHANGED
@@ -1,62 +1,62 @@
|
|
1
1
|
---
|
2
|
-
version:
|
2
|
+
version: 3ecd06f
|
3
3
|
mirrors:
|
4
4
|
- https://appsignal-agent-releases.global.ssl.fastly.net
|
5
5
|
- https://d135dj0rjqvssy.cloudfront.net
|
6
6
|
triples:
|
7
7
|
x86_64-darwin:
|
8
8
|
static:
|
9
|
-
checksum:
|
9
|
+
checksum: 39839e64832d7964fad51d9acc95374654446ade04d06cbba4d052e4483a8516
|
10
10
|
filename: appsignal-x86_64-darwin-all-static.tar.gz
|
11
11
|
dynamic:
|
12
|
-
checksum:
|
12
|
+
checksum: 841b82677aaf553219f689b92b6dc6988278420e2e72dcab28a6c5915a0e4158
|
13
13
|
filename: appsignal-x86_64-darwin-all-dynamic.tar.gz
|
14
14
|
universal-darwin:
|
15
15
|
static:
|
16
|
-
checksum:
|
16
|
+
checksum: 39839e64832d7964fad51d9acc95374654446ade04d06cbba4d052e4483a8516
|
17
17
|
filename: appsignal-x86_64-darwin-all-static.tar.gz
|
18
18
|
dynamic:
|
19
|
-
checksum:
|
19
|
+
checksum: 841b82677aaf553219f689b92b6dc6988278420e2e72dcab28a6c5915a0e4158
|
20
20
|
filename: appsignal-x86_64-darwin-all-dynamic.tar.gz
|
21
21
|
i686-linux:
|
22
22
|
static:
|
23
|
-
checksum:
|
23
|
+
checksum: fdf923153992816c813f938f41feadafbf80d8fa10a785123ee23184945047a0
|
24
24
|
filename: appsignal-i686-linux-all-static.tar.gz
|
25
25
|
dynamic:
|
26
|
-
checksum:
|
26
|
+
checksum: d2b8635199a73a1769ee7495acac0cdc69c4f964fef9ec7749fde517d789e1dd
|
27
27
|
filename: appsignal-i686-linux-all-dynamic.tar.gz
|
28
28
|
x86-linux:
|
29
29
|
static:
|
30
|
-
checksum:
|
30
|
+
checksum: fdf923153992816c813f938f41feadafbf80d8fa10a785123ee23184945047a0
|
31
31
|
filename: appsignal-i686-linux-all-static.tar.gz
|
32
32
|
dynamic:
|
33
|
-
checksum:
|
33
|
+
checksum: d2b8635199a73a1769ee7495acac0cdc69c4f964fef9ec7749fde517d789e1dd
|
34
34
|
filename: appsignal-i686-linux-all-dynamic.tar.gz
|
35
35
|
x86_64-linux:
|
36
36
|
static:
|
37
|
-
checksum:
|
37
|
+
checksum: d81383dedb228f484e1d1e28a76ddc017d788fa62270b6e3f5a55765a4fb89d9
|
38
38
|
filename: appsignal-x86_64-linux-all-static.tar.gz
|
39
39
|
dynamic:
|
40
|
-
checksum:
|
40
|
+
checksum: f98948600345ee4726712937b13059fe7145d9b83273fc6efc6e0fd6a255381a
|
41
41
|
filename: appsignal-x86_64-linux-all-dynamic.tar.gz
|
42
42
|
x86_64-linux-musl:
|
43
43
|
static:
|
44
|
-
checksum:
|
44
|
+
checksum: be2c414da7eb0837f23a8791b369e650aea26afbf9d3be9f530bc2de11fc888f
|
45
45
|
filename: appsignal-x86_64-linux-musl-all-static.tar.gz
|
46
46
|
dynamic:
|
47
|
-
checksum:
|
47
|
+
checksum: 3d04b9dcfbfe7696e028f9205dcae08e41bd87d162f7f37d04cc3d4a7442a9ec
|
48
48
|
filename: appsignal-x86_64-linux-musl-all-dynamic.tar.gz
|
49
49
|
x86_64-freebsd:
|
50
50
|
static:
|
51
|
-
checksum:
|
51
|
+
checksum: fcbfabaa51f9004ed697e9000bfc1a7d71f48f40c04f9c9588a5c8f09cddf0ea
|
52
52
|
filename: appsignal-x86_64-freebsd-all-static.tar.gz
|
53
53
|
dynamic:
|
54
|
-
checksum:
|
54
|
+
checksum: ffc261d6d2a799e9bbf1cee3f9134da60cd72cd6b47a72e8c63a5a11e4d0b472
|
55
55
|
filename: appsignal-x86_64-freebsd-all-dynamic.tar.gz
|
56
56
|
amd64-freebsd:
|
57
57
|
static:
|
58
|
-
checksum:
|
58
|
+
checksum: fcbfabaa51f9004ed697e9000bfc1a7d71f48f40c04f9c9588a5c8f09cddf0ea
|
59
59
|
filename: appsignal-x86_64-freebsd-all-static.tar.gz
|
60
60
|
dynamic:
|
61
|
-
checksum:
|
61
|
+
checksum: ffc261d6d2a799e9bbf1cee3f9134da60cd72cd6b47a72e8c63a5a11e4d0b472
|
62
62
|
filename: appsignal-x86_64-freebsd-all-dynamic.tar.gz
|
data/lib/appsignal/config.rb
CHANGED
@@ -290,6 +290,7 @@ module Appsignal
|
|
290
290
|
ENV["_APPSIGNAL_FILES_WORLD_ACCESSIBLE"] = config_hash[:files_world_accessible].to_s
|
291
291
|
ENV["_APPSIGNAL_TRANSACTION_DEBUG_MODE"] = config_hash[:transaction_debug_mode].to_s
|
292
292
|
ENV["_APPSIGNAL_SEND_ENVIRONMENT_METADATA"] = config_hash[:send_environment_metadata].to_s
|
293
|
+
ENV["_APPSIGNAL_ENABLE_STATSD"] = "true"
|
293
294
|
ENV["_APP_REVISION"] = config_hash[:revision].to_s
|
294
295
|
end
|
295
296
|
|
data/lib/appsignal/hooks/puma.rb
CHANGED
@@ -11,23 +11,8 @@ module Appsignal
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def install
|
14
|
-
if ::Puma.respond_to?(:stats) && !defined?(APPSIGNAL_PUMA_PLUGIN_LOADED)
|
15
|
-
# Only install the minutely probe if a user isn't using our Puma
|
16
|
-
# plugin, which lives in `lib/puma/appsignal.rb`. This plugin defines
|
17
|
-
# the {APPSIGNAL_PUMA_PLUGIN_LOADED} constant.
|
18
|
-
#
|
19
|
-
# We prefer people use the AppSignal Puma plugin. This fallback is
|
20
|
-
# only there when users relied on our *magic* integration.
|
21
|
-
#
|
22
|
-
# Using the Puma plugin, the minutely probe thread will still run in
|
23
|
-
# Puma workers, for other non-Puma probes, but the Puma probe only
|
24
|
-
# runs in the Puma main process.
|
25
|
-
# For more information:
|
26
|
-
# https://docs.appsignal.com/ruby/integrations/puma.html
|
27
|
-
Appsignal::Minutely.probes.register :puma, ::Appsignal::Probes::PumaProbe
|
28
|
-
end
|
29
|
-
|
30
14
|
return unless defined?(::Puma::Cluster)
|
15
|
+
|
31
16
|
# For clustered mode with multiple workers
|
32
17
|
::Puma::Cluster.send(:prepend, Module.new do
|
33
18
|
def stop_workers
|
data/lib/appsignal/probes.rb
CHANGED
data/lib/appsignal/version.rb
CHANGED
@@ -1,27 +1,156 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "json"
|
2
4
|
|
3
5
|
# AppSignal Puma plugin
|
4
6
|
#
|
5
|
-
# This plugin ensures
|
6
|
-
# minutely probe in the Puma master process.
|
7
|
-
#
|
8
|
-
# The constant {APPSIGNAL_PUMA_PLUGIN_LOADED} is here to mark the Plugin as
|
9
|
-
# loaded by the rest of the AppSignal gem. This ensures that the Puma minutely
|
10
|
-
# probe is not also started in every Puma workers, which was the old behavior.
|
11
|
-
# See {Appsignal::Hooks::PumaHook#install} for more information.
|
7
|
+
# This plugin ensures Puma metrics are sent to the AppSignal agent using StatsD.
|
12
8
|
#
|
13
9
|
# For even more information:
|
14
10
|
# https://docs.appsignal.com/ruby/integrations/puma.html
|
15
|
-
Puma::Plugin.create do
|
16
|
-
def start(launcher
|
17
|
-
launcher
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
Puma::Plugin.create do # rubocop:disable Metrics/BlockLength
|
12
|
+
def start(launcher)
|
13
|
+
@launcher = launcher
|
14
|
+
@launcher.events.debug "AppSignal: Puma plugin start."
|
15
|
+
in_background do
|
16
|
+
@launcher.events.debug "AppSignal: Start Puma stats collection loop."
|
17
|
+
plugin = AppsignalPumaPlugin.new
|
18
|
+
|
19
|
+
loop do
|
20
|
+
begin
|
21
|
+
# Implement similar behavior to minutely probes.
|
22
|
+
# Initial sleep to wait until the app is fully initalized.
|
23
|
+
# Then loop every 60 seconds and collect the Puma stats as AppSignal
|
24
|
+
# metrics.
|
25
|
+
sleep sleep_time
|
26
|
+
|
27
|
+
@launcher.events.debug "AppSignal: Collecting Puma stats."
|
28
|
+
stats = fetch_puma_stats
|
29
|
+
if stats
|
30
|
+
plugin.call(stats)
|
31
|
+
else
|
32
|
+
@launcher.events.log "AppSignal: No Puma stats to report."
|
33
|
+
end
|
34
|
+
rescue StandardError => error
|
35
|
+
log_error "Error while processing metrics.", error
|
36
|
+
end
|
22
37
|
end
|
23
|
-
|
24
|
-
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def sleep_time
|
44
|
+
60 # seconds
|
45
|
+
end
|
46
|
+
|
47
|
+
def log_error(message, error)
|
48
|
+
@launcher.events.log "AppSignal: #{message}\n" \
|
49
|
+
"#{error.class}: #{error.message}\n#{error.backtrace.join("\n")}"
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_puma_stats
|
53
|
+
if Puma.respond_to? :stats_hash # Puma >= 5.0.0
|
54
|
+
Puma.stats_hash
|
55
|
+
elsif Puma.respond_to? :stats # Puma < 5.0.0
|
56
|
+
# Puma.stats_hash returns symbolized keys as well
|
57
|
+
JSON.parse Puma.stats, :symbolize_names => true
|
58
|
+
end
|
59
|
+
rescue StandardError => error
|
60
|
+
log_error "Error while parsing Puma stats.", error
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# AppsignalPumaPlugin
|
66
|
+
#
|
67
|
+
# Class to handle the logic of translating the Puma stats to AppSignal metrics.
|
68
|
+
#
|
69
|
+
# @api private
|
70
|
+
class AppsignalPumaPlugin
|
71
|
+
def initialize
|
72
|
+
@hostname = fetch_hostname
|
73
|
+
@statsd = Statsd.new
|
74
|
+
end
|
75
|
+
|
76
|
+
def call(stats)
|
77
|
+
counts = {}
|
78
|
+
count_keys = [:backlog, :running, :pool_capacity, :max_threads]
|
79
|
+
|
80
|
+
if stats[:worker_status] # Clustered mode - Multiple workers
|
81
|
+
stats[:worker_status].each do |worker|
|
82
|
+
stat = worker[:last_status]
|
83
|
+
count_keys.each do |key|
|
84
|
+
count_if_present counts, key, stat
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
gauge(:workers, stats[:workers], :type => :count)
|
89
|
+
gauge(:workers, stats[:booted_workers], :type => :booted)
|
90
|
+
gauge(:workers, stats[:old_workers], :type => :old)
|
91
|
+
else # Single mode - Single worker
|
92
|
+
count_keys.each do |key|
|
93
|
+
count_if_present counts, key, stats
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
gauge(:connection_backlog, counts[:backlog]) if counts[:backlog]
|
98
|
+
gauge(:pool_capacity, counts[:pool_capacity]) if counts[:pool_capacity]
|
99
|
+
gauge(:threads, counts[:running], :type => :running) if counts[:running]
|
100
|
+
gauge(:threads, counts[:max_threads], :type => :max) if counts[:max_threads]
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
attr_reader :hostname
|
106
|
+
|
107
|
+
def fetch_hostname
|
108
|
+
# Configure hostname as reported for the Puma metrics with the
|
109
|
+
# APPSIGNAL_HOSTNAME environment variable.
|
110
|
+
env_hostname = ENV["APPSIGNAL_HOSTNAME"]
|
111
|
+
return env_hostname if env_hostname
|
112
|
+
|
113
|
+
# Auto detect hostname as fallback. May be inaccurate.
|
114
|
+
Socket.gethostname
|
115
|
+
end
|
116
|
+
|
117
|
+
def gauge(field, count, tags = {})
|
118
|
+
@statsd.gauge("puma_#{field}", count, tags.merge(:hostname => hostname))
|
119
|
+
end
|
120
|
+
|
121
|
+
def count_if_present(counts, key, stats)
|
122
|
+
stat_value = stats[key]
|
123
|
+
return unless stat_value
|
124
|
+
|
125
|
+
counts[key] ||= 0
|
126
|
+
counts[key] += stat_value
|
127
|
+
end
|
128
|
+
|
129
|
+
class Statsd
|
130
|
+
def initialize
|
131
|
+
# StatsD server location as configured in AppSignal agent StatsD server.
|
132
|
+
@host = "127.0.0.1"
|
133
|
+
@port = 8125
|
134
|
+
end
|
135
|
+
|
136
|
+
def gauge(metric_name, value, tags)
|
137
|
+
send_metric "g", metric_name, value, tags
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
attr_reader :host, :port
|
143
|
+
|
144
|
+
def send_metric(type, metric_name, metric_value, tags_hash)
|
145
|
+
tags = tags_hash.map { |key, value| "#{key}:#{value}" }.join(",")
|
146
|
+
data = "#{metric_name}:#{metric_value}|#{type}|##{tags}"
|
147
|
+
|
148
|
+
# Open (and close) a new socket every time because we don't know when the
|
149
|
+
# plugin will exit and when to cleanly close the socket connection.
|
150
|
+
socket = UDPSocket.new
|
151
|
+
socket.send(data, 0, host, port)
|
152
|
+
ensure
|
153
|
+
socket && socket.close
|
25
154
|
end
|
26
155
|
end
|
27
156
|
end
|
@@ -35,29 +35,6 @@ describe Appsignal::Hooks::PumaHook do
|
|
35
35
|
# Does not error on call
|
36
36
|
Appsignal::Hooks::PumaHook.new.install
|
37
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::Probes::PumaProbe)
|
59
|
-
end
|
60
|
-
end
|
61
38
|
end
|
62
39
|
|
63
40
|
context "when in clustered mode" do
|
@@ -81,29 +58,6 @@ describe Appsignal::Hooks::PumaHook do
|
|
81
58
|
cluster.stop_workers
|
82
59
|
expect(cluster.instance_variable_get(:@called)).to be(true)
|
83
60
|
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 }
|
91
|
-
|
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::Probes::PumaProbe)
|
105
|
-
end
|
106
|
-
end
|
107
61
|
end
|
108
62
|
end
|
109
63
|
end
|
@@ -1,6 +1,4 @@
|
|
1
1
|
RSpec.describe "Puma plugin" do
|
2
|
-
include WaitForHelper
|
3
|
-
|
4
2
|
class MockPumaLauncher
|
5
3
|
def events
|
6
4
|
return @events if defined?(@events)
|
@@ -10,110 +8,288 @@ RSpec.describe "Puma plugin" do
|
|
10
8
|
end
|
11
9
|
|
12
10
|
class MockPumaEvents
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
attr_reader :logs
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@logs = []
|
15
|
+
end
|
16
|
+
|
17
|
+
def log(message)
|
18
|
+
@logs << [:log, message]
|
19
|
+
end
|
20
|
+
|
21
|
+
def debug(message)
|
22
|
+
@logs << [:debug, message]
|
23
|
+
end
|
24
|
+
|
25
|
+
def error(message)
|
26
|
+
@logs << [:error, message]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# StatsD server used for these tests.
|
31
|
+
# Open a UDPSocket and listen for messages sent by the AppSignal Puma plugin.
|
32
|
+
class StatsdServer
|
33
|
+
def start
|
34
|
+
stop
|
35
|
+
@socket = UDPSocket.new
|
36
|
+
@socket.bind("127.0.0.1", 8125)
|
37
|
+
|
38
|
+
loop do
|
39
|
+
begin
|
40
|
+
# Listen for messages and track them on the messages Array.
|
41
|
+
packet = @socket.recvfrom(1024)
|
42
|
+
track_message packet.first
|
43
|
+
rescue Errno::EBADF # rubocop:disable Lint/HandleExceptions
|
44
|
+
# Ignore error for JRuby 9.1.17.0 specifically, it doesn't appear to
|
45
|
+
# happen on 9.2.18.0. It doesn't break the tests themselves, ignoring
|
46
|
+
# this error. It's probably a timing issue where it tries to read
|
47
|
+
# from the socket after it's closed.
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def stop
|
53
|
+
@socket && @socket.close
|
54
|
+
ensure
|
55
|
+
@socket = nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def messages
|
59
|
+
@messages ||= []
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def track_message(message)
|
65
|
+
@messages_mutex ||= Mutex.new
|
66
|
+
@messages_mutex.synchronize { messages << message }
|
16
67
|
end
|
17
68
|
end
|
18
69
|
|
19
70
|
let(:probe) { MockProbe.new }
|
20
71
|
let(:launcher) { MockPumaLauncher.new }
|
72
|
+
let(:hostname) { Socket.gethostname }
|
73
|
+
let(:expected_default_tags) { { "hostname" => hostname } }
|
74
|
+
let(:stats_data) { { :backlog => 1 } }
|
21
75
|
before do
|
22
76
|
module Puma
|
23
77
|
def self.stats
|
78
|
+
JSON.dump(@_stats_data)
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.stats_hash
|
82
|
+
@_stats_data
|
24
83
|
end
|
25
84
|
|
26
|
-
def self.
|
27
|
-
|
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
|
85
|
+
def self._set_stats=(data)
|
86
|
+
@_stats_data = data
|
45
87
|
end
|
46
88
|
|
47
89
|
class Plugin
|
48
90
|
class << self
|
49
|
-
attr_reader :
|
91
|
+
attr_reader :appsignal_plugin
|
50
92
|
|
51
93
|
def create(&block)
|
52
|
-
@
|
53
|
-
@
|
94
|
+
@appsignal_plugin = Class.new(::Puma::Plugin)
|
95
|
+
@appsignal_plugin.class_eval(&block)
|
54
96
|
end
|
55
97
|
end
|
56
|
-
end
|
57
|
-
end
|
58
98
|
|
59
|
-
|
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)
|
99
|
+
attr_reader :in_background_block
|
65
100
|
|
66
|
-
|
101
|
+
def in_background(&block)
|
102
|
+
@in_background_block = block
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
Puma._set_stats = stats_data
|
67
107
|
load File.expand_path("../lib/puma/plugin/appsignal.rb", APPSIGNAL_SPEC_DIR)
|
108
|
+
|
109
|
+
@statsd = StatsdServer.new
|
110
|
+
@server_thread = Thread.new { @statsd.start }
|
111
|
+
@server_thread.abort_on_exception = true
|
68
112
|
end
|
69
113
|
after do
|
70
|
-
|
71
|
-
|
72
|
-
Object.send
|
114
|
+
@statsd = nil
|
115
|
+
|
116
|
+
Object.send(:remove_const, :Puma)
|
117
|
+
Object.send(:remove_const, :AppsignalPumaPlugin)
|
118
|
+
end
|
119
|
+
|
120
|
+
def run(plugin)
|
121
|
+
@client_thread = Thread.new { start_plugin(plugin) }
|
122
|
+
@client_thread.abort_on_exception = true
|
123
|
+
sleep 0.03
|
124
|
+
ensure
|
125
|
+
stop_all
|
73
126
|
end
|
74
127
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
plugin = Puma::Plugin.plugin.new
|
79
|
-
expect(launcher.events.on_booted).to be_nil
|
128
|
+
def appsignal_plugin
|
129
|
+
Puma::Plugin.appsignal_plugin
|
130
|
+
end
|
80
131
|
|
132
|
+
def start_plugin(plugin_class)
|
133
|
+
plugin = plugin_class.new
|
134
|
+
# Speed up test by not waiting for 60 seconds initial wait time and loop
|
135
|
+
# interval.
|
136
|
+
allow(plugin).to receive(:sleep_time).and_return(0.01)
|
81
137
|
plugin.start(launcher)
|
82
|
-
|
83
|
-
|
138
|
+
plugin.in_background_block.call
|
139
|
+
end
|
140
|
+
|
141
|
+
# Stop all threads in test and stop listening on the UDPSocket
|
142
|
+
def stop_all
|
143
|
+
@client_thread.kill if defined?(@client_thread) && @client_thread
|
144
|
+
@server_thread.kill if defined?(@server_thread) && @server_thread
|
145
|
+
@statsd.stop if defined?(@statsd) && @statsd
|
146
|
+
@client_thread = nil
|
147
|
+
@server_thread = nil
|
148
|
+
end
|
149
|
+
|
150
|
+
def logs
|
151
|
+
launcher.events.logs
|
152
|
+
end
|
153
|
+
|
154
|
+
def messages
|
155
|
+
@statsd.messages.map do |message|
|
156
|
+
metric, type, tags_string = message.split("|")
|
157
|
+
metric_name, metric_value = metric.split(":")
|
158
|
+
tags = {}
|
159
|
+
tags_string[1..-1].split(",").each do |tag|
|
160
|
+
key, value = tag.split(":")
|
161
|
+
tags[key] = value
|
162
|
+
end
|
163
|
+
{
|
164
|
+
:name => metric_name,
|
165
|
+
:value => metric_value.to_i,
|
166
|
+
:type => type,
|
167
|
+
:tags => tags
|
168
|
+
}
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def expect_gauge(metric_name, metric_value, tags_hash = {})
|
173
|
+
expect(messages).to include(
|
174
|
+
:name => "puma_#{metric_name}",
|
175
|
+
:value => metric_value,
|
176
|
+
:type => "g",
|
177
|
+
:tags => expected_default_tags.merge(tags_hash)
|
178
|
+
)
|
179
|
+
end
|
180
|
+
|
181
|
+
context "with multiple worker stats" do
|
182
|
+
let(:stats_data) do
|
183
|
+
{
|
184
|
+
:workers => 2,
|
185
|
+
:booted_workers => 2,
|
186
|
+
:old_workers => 0,
|
187
|
+
:worker_status => [
|
188
|
+
{
|
189
|
+
:last_status => {
|
190
|
+
:backlog => 0,
|
191
|
+
:running => 5,
|
192
|
+
:pool_capacity => 5,
|
193
|
+
:max_threads => 5
|
194
|
+
}
|
195
|
+
},
|
196
|
+
{
|
197
|
+
:last_status => {
|
198
|
+
:backlog => 0,
|
199
|
+
:running => 5,
|
200
|
+
:pool_capacity => 5,
|
201
|
+
:max_threads => 5
|
202
|
+
}
|
203
|
+
}
|
204
|
+
]
|
205
|
+
}
|
206
|
+
end
|
207
|
+
|
208
|
+
it "collects puma stats as guage metrics with the (summed) worker metrics" do
|
209
|
+
run(appsignal_plugin)
|
210
|
+
|
211
|
+
expect(logs).to_not include([:error, kind_of(String)])
|
212
|
+
expect_gauge(:workers, 2, "type" => "count")
|
213
|
+
expect_gauge(:workers, 2, "type" => "booted")
|
214
|
+
expect_gauge(:workers, 0, "type" => "old")
|
215
|
+
expect_gauge(:connection_backlog, 0)
|
216
|
+
expect_gauge(:pool_capacity, 10)
|
217
|
+
expect_gauge(:threads, 10, "type" => "running")
|
218
|
+
expect_gauge(:threads, 10, "type" => "max")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
context "with single worker stats" do
|
223
|
+
let(:stats_data) do
|
224
|
+
{
|
225
|
+
:backlog => 0,
|
226
|
+
:running => 5,
|
227
|
+
:pool_capacity => 5,
|
228
|
+
:max_threads => 5
|
229
|
+
}
|
230
|
+
end
|
231
|
+
|
232
|
+
it "calls `puma_gauge` with the (summed) worker metrics" do
|
233
|
+
run(appsignal_plugin)
|
234
|
+
|
235
|
+
expect(logs).to_not include([:error, kind_of(String)])
|
236
|
+
expect_gauge(:connection_backlog, 0)
|
237
|
+
expect_gauge(:pool_capacity, 5)
|
238
|
+
expect_gauge(:threads, 5, "type" => "running")
|
239
|
+
expect_gauge(:threads, 5, "type" => "max")
|
240
|
+
end
|
241
|
+
end
|
84
242
|
|
85
|
-
|
86
|
-
|
243
|
+
context "when using APPSIGNAL_HOSTNAME" do
|
244
|
+
let(:hostname) { "my-host-name" }
|
245
|
+
before { ENV["APPSIGNAL_HOSTNAME"] = hostname }
|
246
|
+
after { ENV.delete("APPSIGNAL_HOSTNAME") }
|
87
247
|
|
88
|
-
|
89
|
-
|
248
|
+
it "reports the APPSIGNAL_HOSTNAME as the hostname tag value" do
|
249
|
+
run(appsignal_plugin)
|
250
|
+
|
251
|
+
expect(logs).to_not include([:error, kind_of(String)])
|
252
|
+
expect_gauge(:connection_backlog, 1)
|
253
|
+
end
|
90
254
|
end
|
91
255
|
|
92
|
-
|
93
|
-
|
94
|
-
|
256
|
+
context "without Puma.stats_hash" do
|
257
|
+
before do
|
258
|
+
Puma.singleton_class.send(:remove_method, :stats_hash)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "fetches metrics from Puma.stats instead" do
|
262
|
+
run(appsignal_plugin)
|
95
263
|
|
96
|
-
|
264
|
+
expect(logs).to_not include([:error, kind_of(String)])
|
265
|
+
expect(logs).to_not include([kind_of(Symbol), "AppSignal: No Puma stats to report."])
|
266
|
+
expect_gauge(:connection_backlog, 1)
|
267
|
+
end
|
97
268
|
end
|
98
269
|
|
99
|
-
context "without Puma.stats" do
|
100
|
-
before
|
270
|
+
context "without Puma.stats and Puma.stats_hash" do
|
271
|
+
before do
|
272
|
+
Puma.singleton_class.send(:remove_method, :stats)
|
273
|
+
Puma.singleton_class.send(:remove_method, :stats_hash)
|
274
|
+
end
|
101
275
|
|
102
|
-
it "does not
|
103
|
-
|
104
|
-
expect(Appsignal::Minutely.probes[:puma]).to be_nil
|
105
|
-
plugin = Puma::Plugin.plugin.new
|
106
|
-
expect(launcher.events.on_booted).to be_nil
|
276
|
+
it "does not fetch metrics" do
|
277
|
+
run(appsignal_plugin)
|
107
278
|
|
108
|
-
|
109
|
-
expect(
|
110
|
-
expect(
|
279
|
+
expect(logs).to_not include([:error, kind_of(String)])
|
280
|
+
expect(logs).to include([:log, "AppSignal: No Puma stats to report."])
|
281
|
+
expect(messages).to be_empty
|
282
|
+
end
|
283
|
+
end
|
111
284
|
|
112
|
-
|
113
|
-
|
285
|
+
context "without running StatsD server" do
|
286
|
+
it "does nothing" do
|
287
|
+
Appsignal.stop
|
288
|
+
stop_all
|
289
|
+
run(appsignal_plugin)
|
114
290
|
|
115
|
-
|
116
|
-
|
291
|
+
expect(logs).to_not include([:error, kind_of(String)])
|
292
|
+
expect(messages).to be_empty
|
117
293
|
end
|
118
294
|
end
|
119
295
|
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.
|
4
|
+
version: 3.0.7
|
5
5
|
platform: ruby
|
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-
|
13
|
+
date: 2021-06-15 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -249,7 +249,6 @@ files:
|
|
249
249
|
- lib/appsignal/marker.rb
|
250
250
|
- lib/appsignal/minutely.rb
|
251
251
|
- lib/appsignal/probes.rb
|
252
|
-
- lib/appsignal/probes/puma.rb
|
253
252
|
- lib/appsignal/probes/sidekiq.rb
|
254
253
|
- lib/appsignal/rack/generic_instrumentation.rb
|
255
254
|
- lib/appsignal/rack/rails_instrumentation.rb
|
@@ -335,7 +334,6 @@ files:
|
|
335
334
|
- spec/lib/appsignal/logger_spec.rb
|
336
335
|
- spec/lib/appsignal/marker_spec.rb
|
337
336
|
- spec/lib/appsignal/minutely_spec.rb
|
338
|
-
- spec/lib/appsignal/probes/puma_spec.rb
|
339
337
|
- spec/lib/appsignal/probes/sidekiq_spec.rb
|
340
338
|
- spec/lib/appsignal/rack/generic_instrumentation_spec.rb
|
341
339
|
- spec/lib/appsignal/rack/rails_instrumentation_spec.rb
|
@@ -484,7 +482,6 @@ test_files:
|
|
484
482
|
- spec/lib/appsignal/logger_spec.rb
|
485
483
|
- spec/lib/appsignal/marker_spec.rb
|
486
484
|
- spec/lib/appsignal/minutely_spec.rb
|
487
|
-
- spec/lib/appsignal/probes/puma_spec.rb
|
488
485
|
- spec/lib/appsignal/probes/sidekiq_spec.rb
|
489
486
|
- spec/lib/appsignal/rack/generic_instrumentation_spec.rb
|
490
487
|
- spec/lib/appsignal/rack/rails_instrumentation_spec.rb
|
@@ -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
|