appsignal 3.0.6 → 3.0.10
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitmodules +3 -0
- data/.rubocop.yml +2 -2
- data/.semaphore/semaphore.yml +460 -39
- data/CHANGELOG.md +23 -0
- data/README.md +22 -16
- data/Rakefile +11 -10
- data/build_matrix.yml +106 -32
- data/ext/agent.yml +24 -17
- data/ext/base.rb +7 -5
- data/lib/appsignal/cli/diagnose.rb +10 -5
- 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/system.rb +30 -3
- data/lib/appsignal/transaction.rb +1 -1
- data/lib/appsignal/version.rb +1 -1
- data/lib/puma/plugin/appsignal.rb +146 -17
- data/script/install_lintje +18 -0
- data/spec/lib/appsignal/cli/diagnose_spec.rb +4 -2
- data/spec/lib/appsignal/hooks/puma_spec.rb +0 -46
- data/spec/lib/appsignal/system_spec.rb +30 -0
- data/spec/lib/appsignal/transaction_spec.rb +7 -0
- data/spec/lib/puma/appsignal_spec.rb +246 -66
- data/spec/support/helpers/wait_for_helper.rb +16 -4
- metadata +4 -5
- data/lib/appsignal/probes/puma.rb +0 -61
- data/spec/lib/appsignal/probes/puma_spec.rb +0 -180
@@ -105,7 +105,6 @@ module Appsignal
|
|
105
105
|
paths_report = Paths.new
|
106
106
|
data[:paths] = paths_report.report
|
107
107
|
print_paths_section(paths_report)
|
108
|
-
print_empty_line
|
109
108
|
|
110
109
|
transmit_report_to_appsignal if send_report_to_appsignal?(options)
|
111
110
|
end
|
@@ -327,6 +326,7 @@ module Appsignal
|
|
327
326
|
puts "AppSignal library"
|
328
327
|
data_section :library do
|
329
328
|
save :language, "ruby"
|
329
|
+
puts_value "Language", "Ruby"
|
330
330
|
puts_and_save :package_version, "Gem version", Appsignal::VERSION
|
331
331
|
puts_and_save :agent_version, "Agent version", Appsignal::Extension.agent_version
|
332
332
|
puts_and_save :extension_loaded, "Extension loaded", Appsignal.extension_loaded
|
@@ -399,6 +399,7 @@ module Appsignal
|
|
399
399
|
puts " Architecture: #{report["architecture"]}"
|
400
400
|
puts " Target: #{report["target"]}"
|
401
401
|
puts " Musl override: #{report["musl_override"]}"
|
402
|
+
puts " Linux ARM override: #{report["linux_arm_override"]}"
|
402
403
|
puts " Library type: #{report["library_type"]}"
|
403
404
|
puts " Source: #{report["source"]}" if report["source"] != "remote"
|
404
405
|
puts " Dependencies: #{report["dependencies"]}"
|
@@ -416,7 +417,7 @@ module Appsignal
|
|
416
417
|
rbconfig = RbConfig::CONFIG
|
417
418
|
puts "Host information"
|
418
419
|
data_section :host do
|
419
|
-
puts_and_save :architecture, "Architecture",
|
420
|
+
puts_and_save :architecture, "Architecture", Appsignal::System.agent_architecture
|
420
421
|
|
421
422
|
os_label = os = rbconfig["host_os"]
|
422
423
|
os_label = "#{os} (Microsoft Windows is not supported.)" if Gem.win_platform?
|
@@ -573,9 +574,13 @@ module Appsignal
|
|
573
574
|
"(file: #{ownership[:user]}:#{ownership[:uid]}, " \
|
574
575
|
"process: #{process_user[:user]}:#{process_user[:uid]})"
|
575
576
|
puts_value "Ownership?", owner, :level => 2
|
576
|
-
|
577
|
-
|
578
|
-
|
577
|
+
|
578
|
+
if path.key?(:content)
|
579
|
+
puts " Contents (last 10 lines):"
|
580
|
+
puts path[:content].last(10)
|
581
|
+
else
|
582
|
+
print_empty_line
|
583
|
+
end
|
579
584
|
end
|
580
585
|
|
581
586
|
def print_empty_line
|
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/system.rb
CHANGED
@@ -8,6 +8,7 @@ module Appsignal
|
|
8
8
|
# @api private
|
9
9
|
module System
|
10
10
|
LINUX_TARGET = "linux".freeze
|
11
|
+
LINUX_ARM_ARCHITECTURE = "aarch64".freeze
|
11
12
|
MUSL_TARGET = "linux-musl".freeze
|
12
13
|
FREEBSD_TARGET = "freebsd".freeze
|
13
14
|
GEM_EXT_PATH = File.expand_path("../../../ext", __FILE__).freeze
|
@@ -18,15 +19,18 @@ module Appsignal
|
|
18
19
|
|
19
20
|
# Detect agent and extension platform build
|
20
21
|
#
|
21
|
-
# Used by `ext
|
22
|
+
# Used by `ext/*` to select which build it should download and
|
22
23
|
# install.
|
23
24
|
#
|
24
|
-
# Use `export APPSIGNAL_BUILD_FOR_MUSL=1` if the detection doesn't work
|
25
|
-
#
|
25
|
+
# - Use `export APPSIGNAL_BUILD_FOR_MUSL=1` if the detection doesn't work
|
26
|
+
# and to force selection of the musl build.
|
27
|
+
# - Use `export APPSIGNAL_BUILD_FOR_LINUX_ARM=1` to enable the experimental
|
28
|
+
# Linux ARM build.
|
26
29
|
#
|
27
30
|
# @api private
|
28
31
|
# @return [String]
|
29
32
|
def self.agent_platform
|
33
|
+
return LINUX_TARGET if force_linux_arm_build?
|
30
34
|
return MUSL_TARGET if force_musl_build?
|
31
35
|
|
32
36
|
host_os = RbConfig::CONFIG["host_os"].downcase
|
@@ -53,6 +57,22 @@ module Appsignal
|
|
53
57
|
local_os
|
54
58
|
end
|
55
59
|
|
60
|
+
# Detect agent and extension architecture build
|
61
|
+
#
|
62
|
+
# Used by the `ext/*` tasks to select which architecture build it should download and install.
|
63
|
+
#
|
64
|
+
# - Use `export APPSIGNAL_BUILD_FOR_LINUX_ARM=1` to enable the experimental
|
65
|
+
# Linux ARM build.
|
66
|
+
#
|
67
|
+
# @api private
|
68
|
+
# @return [String]
|
69
|
+
def self.agent_architecture
|
70
|
+
return LINUX_ARM_ARCHITECTURE if force_linux_arm_build?
|
71
|
+
|
72
|
+
# Fallback on the Ruby
|
73
|
+
RbConfig::CONFIG["host_cpu"]
|
74
|
+
end
|
75
|
+
|
56
76
|
# Returns whether or not the musl build was forced by the user.
|
57
77
|
#
|
58
78
|
# @api private
|
@@ -60,6 +80,13 @@ module Appsignal
|
|
60
80
|
%w[true 1].include?(ENV["APPSIGNAL_BUILD_FOR_MUSL"])
|
61
81
|
end
|
62
82
|
|
83
|
+
# Returns whether or not the linux ARM build was selected by the user.
|
84
|
+
#
|
85
|
+
# @api private
|
86
|
+
def self.force_linux_arm_build?
|
87
|
+
%w[true 1].include?(ENV["APPSIGNAL_BUILD_FOR_LINUX_ARM"])
|
88
|
+
end
|
89
|
+
|
63
90
|
# @api private
|
64
91
|
def self.versionify(version)
|
65
92
|
Gem::Version.new(version)
|
@@ -526,7 +526,7 @@ module Appsignal
|
|
526
526
|
end
|
527
527
|
|
528
528
|
def cleaned_backtrace(backtrace)
|
529
|
-
if defined?(::Rails) && backtrace
|
529
|
+
if defined?(::Rails) && Rails.respond_to?(:backtrace_cleaner) && backtrace
|
530
530
|
::Rails.backtrace_cleaner.clean(backtrace, nil)
|
531
531
|
else
|
532
532
|
backtrace
|
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
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
set -eu
|
4
|
+
|
5
|
+
mkdir -p $HOME/bin
|
6
|
+
cache_key=v1-lintje-$LINTJE_VERSION
|
7
|
+
cache restore $cache_key
|
8
|
+
|
9
|
+
# File exists and is executable
|
10
|
+
if [ -x "$HOME/bin/lintje" ]; then
|
11
|
+
echo "Restored Lintje $LINTJE_VERSION from cache"
|
12
|
+
else
|
13
|
+
echo "Downloading Lintje $LINTJE_VERSION"
|
14
|
+
curl -L \
|
15
|
+
https://github.com/tombruijn/lintje/releases/download/v$LINTJE_VERSION/x86_64-unknown-linux-gnu.tar.gz | \
|
16
|
+
tar -xz --directory $HOME/bin
|
17
|
+
cache store $cache_key $HOME/bin/lintje
|
18
|
+
fi
|
@@ -270,9 +270,10 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
|
|
270
270
|
"build" => {
|
271
271
|
"time" => kind_of(String),
|
272
272
|
"package_path" => File.expand_path("../../../../../", __FILE__),
|
273
|
-
"architecture" =>
|
273
|
+
"architecture" => Appsignal::System.agent_architecture,
|
274
274
|
"target" => Appsignal::System.agent_platform,
|
275
275
|
"musl_override" => false,
|
276
|
+
"linux_arm_override" => false,
|
276
277
|
"library_type" => jruby ? "dynamic" : "static",
|
277
278
|
"source" => "remote",
|
278
279
|
"dependencies" => kind_of(Hash),
|
@@ -301,9 +302,10 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
|
|
301
302
|
" Checksum: verified",
|
302
303
|
"Build details",
|
303
304
|
" Install time: 20",
|
304
|
-
" Architecture: #{
|
305
|
+
" Architecture: #{Appsignal::System.agent_architecture}",
|
305
306
|
" Target: #{Appsignal::System.agent_platform}",
|
306
307
|
" Musl override: false",
|
308
|
+
" Linux ARM override: false",
|
307
309
|
" Library type: #{jruby ? "dynamic" : "static"}",
|
308
310
|
" Dependencies: {",
|
309
311
|
" Flags: {",
|
@@ -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
|