Commit 73f48c32 authored by Igor Wiedler's avatar Igor Wiedler

Defer stackprof signal trap when running in sidekiq

Sidekiq currently overrides the trap. This change
defers the definition of the signal handler until
after sidekiq's setup code.

This way, we get to keep our signal handler, and
stackprof can be invoked by sending a SIGUSR2 to
sidekiq processes.

Because sidekiq-cluster forwards SIGUSR2 signals,
we can also send the signal to the sidekiq-cluster
process.
parent f9ef6bc7
...@@ -8,12 +8,35 @@ ...@@ -8,12 +8,35 @@
# * timeout profile after 30 seconds # * timeout profile after 30 seconds
# * write to $TMPDIR/stackprof.$PID.$RAND.profile # * write to $TMPDIR/stackprof.$PID.$RAND.profile
if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s) module Gitlab
Gitlab::Cluster::LifecycleEvents.on_worker_start do class StackProf
# this is a workaround for sidekiq, which defines its own SIGUSR2 handler.
# by defering to the sidekiq startup event, we get to set up our own
# handler late enough.
# see also: https://github.com/mperham/sidekiq/pull/4653
def self.install
require 'stackprof' require 'stackprof'
require 'tmpdir' require 'tmpdir'
Gitlab::AppJsonLogger.info "stackprof: listening on SIGUSR2 signal" if Gitlab::Runtime.sidekiq?
Sidekiq.configure_server do |config|
config.on :startup do
on_worker_start
end
end
else
Gitlab::Cluster::LifecycleEvents.on_worker_start do
on_worker_start
end
end
end
def self.on_worker_start
Gitlab::AppJsonLogger.info(
event: "stackprof",
message: "listening on SIGUSR2 signal",
pid: Process.pid
)
# create a pipe in order to propagate signal out of the signal handler # create a pipe in order to propagate signal out of the signal handler
# see also: https://cr.yp.to/docs/selfpipe.html # see also: https://cr.yp.to/docs/selfpipe.html
...@@ -38,7 +61,7 @@ if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s) ...@@ -38,7 +61,7 @@ if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s)
got_value = IO.select([read], nil, nil, current_timeout_s) got_value = IO.select([read], nil, nil, current_timeout_s)
read.getbyte if got_value read.getbyte if got_value
if StackProf.running? if ::StackProf.running?
stackprof_file_prefix = ENV['STACKPROF_FILE_PREFIX'] || Dir.tmpdir stackprof_file_prefix = ENV['STACKPROF_FILE_PREFIX'] || Dir.tmpdir
stackprof_out_file = "#{stackprof_file_prefix}/stackprof.#{Process.pid}.#{SecureRandom.hex(6)}.profile" stackprof_out_file = "#{stackprof_file_prefix}/stackprof.#{Process.pid}.#{SecureRandom.hex(6)}.profile"
...@@ -51,8 +74,8 @@ if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s) ...@@ -51,8 +74,8 @@ if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s)
timed_out: got_value.nil? timed_out: got_value.nil?
) )
StackProf.stop ::StackProf.stop
StackProf.results(stackprof_out_file) ::StackProf.results(stackprof_out_file)
current_timeout_s = nil current_timeout_s = nil
else else
Gitlab::AppJsonLogger.info( Gitlab::AppJsonLogger.info(
...@@ -61,7 +84,7 @@ if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s) ...@@ -61,7 +84,7 @@ if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s)
pid: Process.pid pid: Process.pid
) )
StackProf.start( ::StackProf.start(
mode: :cpu, mode: :cpu,
raw: Gitlab::Utils.to_boolean(ENV['STACKPROF_RAW'] || 'true'), raw: Gitlab::Utils.to_boolean(ENV['STACKPROF_RAW'] || 'true'),
interval: ENV['STACKPROF_INTERVAL_US']&.to_i || 10_000 interval: ENV['STACKPROF_INTERVAL_US']&.to_i || 10_000
...@@ -98,4 +121,9 @@ if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s) ...@@ -98,4 +121,9 @@ if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s)
write.write('.') write.write('.')
end end
end end
end
end
if Gitlab::Utils.to_boolean(ENV['STACKPROF_ENABLED'].to_s)
Gitlab::StackProf.install
end end
...@@ -281,6 +281,10 @@ This can be done via `pkill -USR2 puma:`. The `:` disambiguates between `puma ...@@ -281,6 +281,10 @@ This can be done via `pkill -USR2 puma:`. The `:` disambiguates between `puma
4.3.3.gitlab.2 ...` (the master process) from `puma: cluster worker 0: ...` (the 4.3.3.gitlab.2 ...` (the master process) from `puma: cluster worker 0: ...` (the
worker processes), selecting the latter. worker processes), selecting the latter.
For Sidekiq, the signal can be sent to the `sidekiq-cluster` process via `pkill
-USR2 bin/sidekiq-cluster` -- this will forward the signal to all Sidekiq
children. Alternatively, you can also select a specific pid of interest.
Production profiles can be especially noisy. It can be helpful to visualize them Production profiles can be especially noisy. It can be helpful to visualize them
as a [flamegraph](https://github.com/brendangregg/FlameGraph). This can be done as a [flamegraph](https://github.com/brendangregg/FlameGraph). This can be done
via: via:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment