appsignal 3.0.3-java → 3.0.7-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.
- checksums.yaml +4 -4
 - data/.semaphore/semaphore.yml +64 -199
 - data/CHANGELOG.md +47 -19
 - data/README.md +9 -3
 - data/appsignal.gemspec +16 -3
 - data/build_matrix.yml +16 -24
 - data/ext/agent.yml +17 -17
 - data/ext/base.rb +12 -1
 - data/gemfiles/capistrano2.gemfile +0 -1
 - data/gemfiles/capistrano3.gemfile +0 -1
 - data/gemfiles/grape.gemfile +0 -1
 - data/gemfiles/no_dependencies.gemfile +4 -1
 - data/gemfiles/rails-3.2.gemfile +2 -0
 - data/gemfiles/rails-4.2.gemfile +6 -0
 - data/gemfiles/resque-2.gemfile +0 -4
 - data/gemfiles/sequel-435.gemfile +0 -1
 - data/gemfiles/sequel.gemfile +0 -1
 - data/gemfiles/sinatra.gemfile +0 -1
 - data/lib/appsignal/config.rb +1 -0
 - data/lib/appsignal/hooks.rb +2 -1
 - data/lib/appsignal/hooks/excon.rb +19 -0
 - data/lib/appsignal/hooks/puma.rb +1 -16
 - data/lib/appsignal/integrations/excon.rb +20 -0
 - data/lib/appsignal/integrations/padrino.rb +1 -1
 - data/lib/appsignal/integrations/railtie.rb +1 -1
 - data/lib/appsignal/integrations/redis.rb +8 -5
 - data/lib/appsignal/integrations/sinatra.rb +1 -1
 - data/lib/appsignal/probes.rb +0 -1
 - data/lib/appsignal/version.rb +1 -1
 - data/lib/puma/plugin/appsignal.rb +146 -17
 - data/mono.yml +16 -0
 - data/spec/lib/appsignal/cli/diagnose_spec.rb +1 -0
 - data/spec/lib/appsignal/hooks/excon_spec.rb +74 -0
 - data/spec/lib/appsignal/hooks/puma_spec.rb +0 -46
 - data/spec/lib/appsignal/hooks/redis_spec.rb +34 -10
 - data/spec/lib/appsignal/hooks_spec.rb +4 -1
 - data/spec/lib/puma/appsignal_spec.rb +244 -68
 - data/support/install_deps +9 -8
 - metadata +8 -6
 - data/lib/appsignal/probes/puma.rb +0 -61
 - data/spec/lib/appsignal/probes/puma_spec.rb +0 -180
 
    
        data/gemfiles/resque-2.gemfile
    CHANGED
    
    
    
        data/gemfiles/sequel-435.gemfile
    CHANGED
    
    
    
        data/gemfiles/sequel.gemfile
    CHANGED
    
    
    
        data/gemfiles/sinatra.gemfile
    CHANGED
    
    
    
        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.rb
    CHANGED
    
    | 
         @@ -32,7 +32,7 @@ module Appsignal 
     | 
|
| 
       32 
32 
     | 
    
         
             
                    return unless dependencies_present?
         
     | 
| 
       33 
33 
     | 
    
         
             
                    return if installed?
         
     | 
| 
       34 
34 
     | 
    
         | 
| 
       35 
     | 
    
         
            -
                    Appsignal.logger. 
     | 
| 
      
 35 
     | 
    
         
            +
                    Appsignal.logger.debug("Installing #{name} hook")
         
     | 
| 
       36 
36 
     | 
    
         
             
                    begin
         
     | 
| 
       37 
37 
     | 
    
         
             
                      install
         
     | 
| 
       38 
38 
     | 
    
         
             
                      @installed = true
         
     | 
| 
         @@ -108,3 +108,4 @@ require "appsignal/hooks/mongo_ruby_driver" 
     | 
|
| 
       108 
108 
     | 
    
         
             
            require "appsignal/hooks/webmachine"
         
     | 
| 
       109 
109 
     | 
    
         
             
            require "appsignal/hooks/data_mapper"
         
     | 
| 
       110 
110 
     | 
    
         
             
            require "appsignal/hooks/que"
         
     | 
| 
      
 111 
     | 
    
         
            +
            require "appsignal/hooks/excon"
         
     | 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Appsignal
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Hooks
         
     | 
| 
      
 5 
     | 
    
         
            +
                # @api private
         
     | 
| 
      
 6 
     | 
    
         
            +
                class ExconHook < Appsignal::Hooks::Hook
         
     | 
| 
      
 7 
     | 
    
         
            +
                  register :excon
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  def dependencies_present?
         
     | 
| 
      
 10 
     | 
    
         
            +
                    Appsignal.config && defined?(::Excon)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                  def install
         
     | 
| 
      
 14 
     | 
    
         
            +
                    require "appsignal/integrations/excon"
         
     | 
| 
      
 15 
     | 
    
         
            +
                    ::Excon.defaults[:instrumentor] = Appsignal::Integrations::ExconIntegration
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
              end
         
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
    
        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
         
     | 
| 
         @@ -0,0 +1,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module Appsignal
         
     | 
| 
      
 4 
     | 
    
         
            +
              module Integrations
         
     | 
| 
      
 5 
     | 
    
         
            +
                module ExconIntegration
         
     | 
| 
      
 6 
     | 
    
         
            +
                  def self.instrument(name, data, &block)
         
     | 
| 
      
 7 
     | 
    
         
            +
                    namespace, *event = name.split(".")
         
     | 
| 
      
 8 
     | 
    
         
            +
                    rails_name = [event, namespace].flatten.join(".")
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                    title =
         
     | 
| 
      
 11 
     | 
    
         
            +
                      if rails_name == "response.excon"
         
     | 
| 
      
 12 
     | 
    
         
            +
                        data[:host]
         
     | 
| 
      
 13 
     | 
    
         
            +
                      else
         
     | 
| 
      
 14 
     | 
    
         
            +
                        "#{data[:method].to_s.upcase} #{data[:scheme]}://#{data[:host]}"
         
     | 
| 
      
 15 
     | 
    
         
            +
                      end
         
     | 
| 
      
 16 
     | 
    
         
            +
                    Appsignal.instrument(rails_name, title, &block)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  end
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -7,7 +7,7 @@ module Appsignal 
     | 
|
| 
       7 
7 
     | 
    
         
             
                # @api private
         
     | 
| 
       8 
8 
     | 
    
         
             
                module PadrinoPlugin
         
     | 
| 
       9 
9 
     | 
    
         
             
                  def self.init
         
     | 
| 
       10 
     | 
    
         
            -
                    Appsignal.logger. 
     | 
| 
      
 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. 
     | 
| 
      
 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  
     | 
| 
       7 
     | 
    
         
            -
                     
     | 
| 
       8 
     | 
    
         
            -
                       
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
      
 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,  
     | 
| 
      
 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. 
     | 
| 
      
 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(
         
     | 
    
        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
         
     | 
    
        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 
     | 
    
         | 
| 
         @@ -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
         
     |