appsignal 3.0.4.alpha.1-java → 3.0.8-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.
@@ -7,7 +7,7 @@ module Appsignal
7
7
  # @api private
8
8
  module PadrinoPlugin
9
9
  def self.init
10
- Appsignal.logger.info("Loading Padrino (#{Padrino::VERSION}) integration")
10
+ Appsignal.logger.debug("Loading Padrino (#{Padrino::VERSION}) integration")
11
11
 
12
12
  root = Padrino.mounted_root
13
13
  Appsignal.config = Appsignal::Config.new(root, Padrino.env)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- Appsignal.logger.info("Loading Rails (#{Rails.version}) integration")
3
+ Appsignal.logger.debug("Loading Rails (#{Rails.version}) integration")
4
4
 
5
5
  require "appsignal/utils/rails_helper"
6
6
  require "appsignal/rack/rails_instrumentation"
@@ -3,12 +3,15 @@
3
3
  module Appsignal
4
4
  module Integrations
5
5
  module RedisIntegration
6
- def process(commands, &block)
7
- sanitized_commands = commands.map do |command, *args|
8
- "#{command}#{" ?" * args.size}"
9
- end.join("\n")
6
+ def write(command)
7
+ sanitized_command =
8
+ if command[0] == :eval
9
+ "#{command[1]}#{" ?" * (command.size - 3)}"
10
+ else
11
+ "#{command[0]}#{" ?" * (command.size - 1)}"
12
+ end
10
13
 
11
- Appsignal.instrument "query.redis", id, sanitized_commands do
14
+ Appsignal.instrument "query.redis", id, sanitized_command do
12
15
  super
13
16
  end
14
17
  end
@@ -3,7 +3,7 @@
3
3
  require "appsignal"
4
4
  require "appsignal/rack/sinatra_instrumentation"
5
5
 
6
- Appsignal.logger.info("Loading Sinatra (#{Sinatra::VERSION}) integration")
6
+ Appsignal.logger.debug("Loading Sinatra (#{Sinatra::VERSION}) integration")
7
7
 
8
8
  app_settings = ::Sinatra::Application.settings
9
9
  Appsignal.config = Appsignal::Config.new(
@@ -3,5 +3,4 @@ module Appsignal
3
3
  end
4
4
  end
5
5
 
6
- require "appsignal/probes/puma"
7
6
  require "appsignal/probes/sidekiq"
@@ -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/extconf.rb` to select which build it should download and
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
- # and to force selection of the musl build.
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)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Appsignal
4
- VERSION = "3.0.4.alpha.1".freeze
4
+ VERSION = "3.0.8".freeze
5
5
  end
@@ -1,27 +1,156 @@
1
- APPSIGNAL_PUMA_PLUGIN_LOADED = true
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
2
4
 
3
5
  # AppSignal Puma plugin
4
6
  #
5
- # This plugin ensures the minutely probe thread is started with the Puma
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 = nil)
17
- launcher.events.on_booted do
18
- require "appsignal"
19
- if ::Puma.respond_to?(:stats)
20
- require "appsignal/probes/puma"
21
- Appsignal::Minutely.probes.register :puma, Appsignal::Probes::PumaProbe
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
- Appsignal.start
24
- Appsignal.start_logger
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
data/mono.yml ADDED
@@ -0,0 +1,16 @@
1
+ ---
2
+ language: ruby
3
+ repo: "https://github.com/appsignal/appsignal-ruby"
4
+ bootstrap:
5
+ post:
6
+ - "rake extension:install"
7
+ clean:
8
+ post:
9
+ - "bundle exec rake extension:clean"
10
+ - "rm -rf pkg"
11
+ build:
12
+ command: "bundle exec rake build:all"
13
+ publish:
14
+ gem_files_dir: pkg/
15
+ test:
16
+ command: "bundle exec rake test"
@@ -115,6 +115,7 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
115
115
  it "logs to the log file" do
116
116
  run
117
117
  log_contents = File.read(config.log_file_path)
118
+ p log_contents
118
119
  expect(log_contents).to contains_log :info, "Starting AppSignal diagnose"
119
120
  end
120
121
 
@@ -269,9 +270,10 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
269
270
  "build" => {
270
271
  "time" => kind_of(String),
271
272
  "package_path" => File.expand_path("../../../../../", __FILE__),
272
- "architecture" => rbconfig["host_cpu"],
273
+ "architecture" => Appsignal::System.agent_architecture,
273
274
  "target" => Appsignal::System.agent_platform,
274
275
  "musl_override" => false,
276
+ "linux_arm_override" => false,
275
277
  "library_type" => jruby ? "dynamic" : "static",
276
278
  "source" => "remote",
277
279
  "dependencies" => kind_of(Hash),
@@ -300,9 +302,10 @@ describe Appsignal::CLI::Diagnose, :api_stub => true, :send_report => :yes_cli_i
300
302
  " Checksum: verified",
301
303
  "Build details",
302
304
  " Install time: 20",
303
- " Architecture: #{rbconfig["host_cpu"]}",
305
+ " Architecture: #{Appsignal::System.agent_architecture}",
304
306
  " Target: #{Appsignal::System.agent_platform}",
305
307
  " Musl override: false",
308
+ " Linux ARM override: false",
306
309
  " Library type: #{jruby ? "dynamic" : "static"}",
307
310
  " Dependencies: {",
308
311
  " Flags: {",
@@ -0,0 +1,74 @@
1
+ describe Appsignal::Hooks::ExconHook do
2
+ before :context do
3
+ start_agent
4
+ end
5
+
6
+ context "with Excon" do
7
+ before(:context) do
8
+ class Excon
9
+ def self.defaults
10
+ @defaults ||= {}
11
+ end
12
+ end
13
+ Appsignal::Hooks::ExconHook.new.install
14
+ end
15
+ after(:context) { Object.send(:remove_const, :Excon) }
16
+
17
+ describe "#dependencies_present?" do
18
+ subject { described_class.new.dependencies_present? }
19
+
20
+ it { is_expected.to be_truthy }
21
+ end
22
+
23
+ describe "#install" do
24
+ it "adds the AppSignal instrumentor to Excon" do
25
+ expect(Excon.defaults[:instrumentor]).to eql(Appsignal::Integrations::ExconIntegration)
26
+ end
27
+ end
28
+
29
+ describe "instrumentation" do
30
+ let!(:transaction) do
31
+ Appsignal::Transaction.create("uuid", Appsignal::Transaction::HTTP_REQUEST, "test")
32
+ end
33
+ around { |example| keep_transactions { example.run } }
34
+
35
+ it "instruments a http request" do
36
+ data = {
37
+ :host => "www.google.com",
38
+ :method => :get,
39
+ :scheme => "http"
40
+ }
41
+ Excon.defaults[:instrumentor].instrument("excon.request", data) {}
42
+
43
+ expect(transaction.to_h["events"]).to include(
44
+ hash_including(
45
+ "name" => "request.excon",
46
+ "title" => "GET http://www.google.com",
47
+ "body" => ""
48
+ )
49
+ )
50
+ end
51
+
52
+ it "instruments a http response" do
53
+ data = { :host => "www.google.com" }
54
+ Excon.defaults[:instrumentor].instrument("excon.response", data) {}
55
+
56
+ expect(transaction.to_h["events"]).to include(
57
+ hash_including(
58
+ "name" => "response.excon",
59
+ "title" => "www.google.com",
60
+ "body" => ""
61
+ )
62
+ )
63
+ end
64
+ end
65
+ end
66
+
67
+ context "without Excon" do
68
+ describe "#dependencies_present?" do
69
+ subject { described_class.new.dependencies_present? }
70
+
71
+ it { is_expected.to be_falsy }
72
+ end
73
+ end
74
+ 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