# encoding: utf-8 require 'set' require 'one_apm/version' module OneApm # This class is responsible for determining the 'dispatcher' in use by the # current process. The dispatcher might be a recognized web server such as # unicorn or passenger, a background job processor such as resque or sidekiq, # or nil for unknown. # # Dispatcher detection is best-effort, and serves two purposes: # # 1. For some dispatchers, we need to apply specific workarounds in order for # the agent to work correctly. # 2. When reading logs, since multiple processes on a given host might write # into the same log, it's useful to be able to identify what kind of # process a given PID mapped to. # # Overriding the dispatcher is possible via the ONEAPM_DISPATCHER # environment variable, but this should not generally be necessary unless # you're on a dispatcher that falls into category 1 above, and our detection # logic isn't working correctly. # # If the environment can't be determined, it will be set to nil. # # OneApm::LocalEnvironment should be accessed through OneApm::Probe#env (via the OneApm::Probe singleton). class LocalEnvironment def discovered_dispatcher discover_dispatcher unless @discovered_dispatcher @discovered_dispatcher end def initialize # Extend self with any any submodules of LocalEnvironment. These can override # the discover methods to discover new framworks and dispatchers. OneApm::LocalEnvironment.constants.each do | const | mod = OneApm::LocalEnvironment.const_get const self.extend mod if mod.instance_of? Module end discover_dispatcher end # Runs through all the objects in ObjectSpace to find the first one that # match the provided class def find_class_in_object_space(klass) if OneApm::LanguageSupport.object_space_usable? ObjectSpace.each_object(klass) do |x| return x end end return nil end private def discover_dispatcher dispatchers = %w[ passenger torquebox trinidad glassfish resque sidekiq delayed_job puma thin mongrel fastcgi rainbows unicorn goliath webrick event_machine ] while dispatchers.any? && @discovered_dispatcher.nil? send 'check_for_'+(dispatchers.shift) end end def check_for_torquebox return unless defined?(::JRuby) && ( org.torquebox::TorqueBox rescue nil) @discovered_dispatcher = :torquebox end def check_for_glassfish return unless defined?(::JRuby) && (((com.sun.grizzly.jruby.rack.DefaultRackApplicationFactory rescue nil) && defined?(com::sun::grizzly::jruby::rack::DefaultRackApplicationFactory)) || (jruby_rack? && defined?(::GlassFish::Server))) @discovered_dispatcher = :glassfish end def check_for_trinidad return unless defined?(::JRuby) && jruby_rack? && defined?(::Trinidad::Server) @discovered_dispatcher = :trinidad end def jruby_rack? defined?(JRuby::Rack::VERSION) end def check_for_webrick return unless defined?(::WEBrick) && defined?(::WEBrick::VERSION) @discovered_dispatcher = :webrick end def check_for_fastcgi return unless defined?(::FCGI) @discovered_dispatcher = :fastcgi end # this case covers starting by mongrel_rails def check_for_mongrel return unless defined?(::Mongrel) && defined?(::Mongrel::HttpServer) @discovered_dispatcher = :mongrel end def check_for_unicorn if (defined?(::Unicorn) && defined?(::Unicorn::HttpServer)) && OneApm::LanguageSupport.object_space_usable? v = find_class_in_object_space(::Unicorn::HttpServer) @discovered_dispatcher = :unicorn if v end end def check_for_rainbows if (defined?(::Rainbows) && defined?(::Rainbows::HttpServer)) && OneApm::LanguageSupport.object_space_usable? v = find_class_in_object_space(::Rainbows::HttpServer) @discovered_dispatcher = :rainbows if v end end def check_for_puma if defined?(::Puma) && File.basename($0) == 'puma' @discovered_dispatcher = :puma end end def check_for_delayed_job if $0 =~ /delayed_job$/ || (File.basename($0) == 'rake' && ARGV.include?('jobs:work')) @discovered_dispatcher = :delayed_job end end def check_for_resque has_queue = ENV['QUEUE'] || ENV['QUEUES'] resque_rake = executable == 'rake' && ARGV.include?('resque:work') resque_pool_rake = executable == 'rake' && ARGV.include?('resque:pool') resque_pool_executable = executable == 'resque-pool' && defined?(::Resque::Pool) using_resque = defined?(::Resque) && (has_queue && resque_rake) || (resque_pool_executable || resque_pool_rake) @discovered_dispatcher = :resque if using_resque end def check_for_sidekiq if defined?(::Sidekiq) && File.basename($0) == 'sidekiq' @discovered_dispatcher = :sidekiq end end def check_for_thin if defined?(::Thin) && defined?(::Thin::Server) @discovered_dispatcher = :thin end end def check_for_passenger if defined?(::PhusionPassenger) @discovered_dispatcher = :passenger end end def check_for_goliath if defined?(::Goliath) && defined?(::Goliath::API) @discovered_dispatcher = :goliath end end def check_for_event_machine if defined?(::EventMachine) @discovered_dispatcher = :event_machine end end public # outputs a human-readable description def to_s s = "LocalEnvironment[" s << ";dispatcher=#{@discovered_dispatcher}" if @discovered_dispatcher s << "]" end def executable File.basename($0) end end end