Module: NewRelic::Agent::Instrumentation::PumaStats

Defined in:
lib/new_relic/agent/instrumentation/puma.rb

Defined Under Namespace

Modules: StatsObjectRegistration

Class Method Summary collapse

Class Method Details

.arm_late_installObject

Prepends a one-shot interceptor on Puma.stats_object= so the agent installs when the launcher registers its runner (+rails server+ boot order). The prepend is permanent, so @late_install_armed does the real gating: disarming it on the first registration keeps a Puma hot restart from starting a second sampler.



64
65
66
67
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 64

def arm_late_install # :nodoc:
  @late_install_armed = true
  ::Puma.singleton_class.prepend(StatsObjectRegistration)
end

.install(runner) ⇒ Object

Started in the master before workers fork, so forked workers do not inherit the sampling thread.



95
96
97
98
99
100
101
102
103
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 95

def install(runner) # :nodoc:
  sampler = NewRelic::Agent::PumaStatsSampler.new(runner)
  register_stop(runner, sampler)
  Thread.new { sampler.start }
  sampler
rescue => e
  NewRelic::Agent.logger.error('Failed to start the New Relic Puma stats sampler', e)
  nil
end

.install_in_master(runner) ⇒ Object

:nodoc:



36
37
38
39
40
41
42
43
44
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 36

def install_in_master(runner) # :nodoc:
  unless master?(runner)
    log_puma_6x_preload_warning(runner)
    return
  end

  NewRelic::Agent.logger.info('Installing Puma stats instrumentation')
  install(runner)
end

.install_on_register(runner) ⇒ Object

Invoked by the interceptor when Puma registers a runner. One-shot: disarm before installing so the registration is handled exactly once.



71
72
73
74
75
76
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 71

def install_on_register(runner) # :nodoc:
  return unless @late_install_armed

  @late_install_armed = false
  install_in_master(runner)
end

.install_or_armObject

:nodoc:



31
32
33
34
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 31

def install_or_arm # :nodoc:
  current = runner
  current ? install_in_master(current) : arm_late_install
end

.log_puma_6x_preload_warning(runner) ⇒ Object

Puma 6.x doesn't apply preload_app defaults from the config block, so a clustered 6.x app with workers 2 but no explicit preload_app!(true) silently gets no metrics.



49
50
51
52
53
54
55
56
57
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 49

def log_puma_6x_preload_warning(runner) # :nodoc:
  return unless runner&.respond_to?(:options)
  return unless runner.options[:workers].to_i > 1 && !runner.options[:preload_app]
  return unless defined?(::Puma::Const::VERSION) && ::Puma::Const::VERSION.start_with?('6.')

  NewRelic::Agent.logger.warn(
    'Did not install Puma instrumentation. Set preload_app!(true) explicitly in your puma.rb to enable Puma metrics.'
  )
end

.master?(runner) ⇒ Boolean

True only where the agent should sample: single mode (one process) or clustered with preload_app! (where the agent is loaded in the master process).

Returns:

  • (Boolean)


23
24
25
26
27
28
29
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 23

def master?(runner) # :nodoc:
  return false if runner.nil?
  return true if defined?(::Puma::Single) && runner.is_a?(::Puma::Single)
  return false unless runner.respond_to?(:options)

  !!runner.options[:preload_app]
end

.register_stop(runner, sampler) ⇒ Object

Stops the sampler on Puma shutdown/restart. Registers both Puma 7+ events (+:before_restart+/+:after_stopped+) and Puma 6.x events (+:on_restart+/+:on_stopped+), plus :state for single mode (the clustered master runs no Puma::Server). stop is idempotent, so overlapping events are harmless.



110
111
112
113
114
115
116
117
118
119
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 110

def register_stop(runner, sampler) # :nodoc:
  launcher = runner.instance_variable_get(:@launcher)
  return unless launcher.respond_to?(:events)

  events = launcher.events
  events.register(:state) { |state| sampler.stop if %i[halt restart stop].include?(state) }
  %i[before_restart after_stopped on_restart on_stopped].each do |event|
    events.register(event) { sampler.stop }
  end
end

.runnerObject

Puma stores the runner via Puma.stats_object=. No public accessor, so read the ivar that the setter writes.



15
16
17
18
19
# File 'lib/new_relic/agent/instrumentation/puma.rb', line 15

def runner # :nodoc:
  return nil unless defined?(::Puma) && ::Puma.instance_variable_defined?(:@get_stats)

  ::Puma.instance_variable_get(:@get_stats)
end