class Watchdog

Prepends
ThreadLocalProfiler: #start, #stop

Samples stacks while a fiber runs without switching back to the event loop.

Definitions

def self.default(env = ENV)

Build a watchdog using environment configuration.

Signature

parameter env Hash

The environment to read configuration from.

returns Watchdog

The configured watchdog.

Implementation

def self.default(env = ENV)
	new(
		stall_threshold: Float(env.fetch("FIBER_PROFILER_WATCHDOG_STALL_THRESHOLD", "0.5")),
		sample_interval: Float(env.fetch("FIBER_PROFILER_WATCHDOG_SAMPLE_INTERVAL", "0.1"))
	)
end

def initialize(stall_threshold: 0.5, sample_interval: 0.1, max_samples: 5, output: $stderr)

Initialize a watchdog. Monitoring begins when Fiber::Profiler::Watchdog#start is called.

Signature

parameter stall_threshold Float

Seconds without a fiber switch before reporting, and the minimum interval between reports for the same execution.

parameter sample_interval Float

Seconds between stack samples.

parameter max_samples Integer

Maximum number of recent stacks retained per execution.

parameter output IO

The destination for stall reports.

Implementation

def initialize(stall_threshold: 0.5, sample_interval: 0.1, max_samples: 5, output: $stderr)
	@stall_threshold = Float(stall_threshold)
	@sample_interval = Float(sample_interval)
	
	unless @stall_threshold.finite? && @stall_threshold.positive? && @sample_interval.finite? && @sample_interval.positive?
		raise ArgumentError, "Watchdog intervals must be finite and positive"
	end
	unless max_samples.is_a?(Integer) && max_samples.positive?
		raise ArgumentError, "max_samples must be a positive integer"
	end
	
	@max_samples = max_samples
	@output = output
	@running = false
	@stalls = 0
	@error = nil
end

attr_reader :stall_threshold

Signature

attribute Float

The minimum execution duration before reporting a stall.

attr_reader :sample_interval

Signature

attribute Float

The delay between samples.

attr_reader :stalls

Signature

attribute Integer

The number of reports written.

attr_reader :error

Signature

attribute StandardError | Nil

The sampling or reporting failure, retained until monitoring starts again.

def start

Start monitoring application fibers on the calling thread.

Signature

returns Watchdog | false

Self, or false if already running.

Implementation

def start
	return false if @running
	
	@thread = Thread.current
	@pid = Process.pid
	@mutex = Mutex.new
	@condition = ConditionVariable.new
	@running = true
	@execution = nil
	@error = nil
	
	# When started on the event loop, distinguish it from blocking application fibers:
	@loop = Fiber.current if Fiber.current.blocking?
	@tracepoint = TracePoint.new(:fiber_switch){record_execution}
	@tracepoint.enable(target_thread: @thread)
	record_execution
	
	@watchdog = Thread.new{watch}
	self
rescue Exception
	stop
	raise
end

def stop

Stop monitoring and wait for the sampling thread to finish.

Signature

returns Watchdog | false

Self, or false if already stopped.

Implementation

def stop
	return false unless @running
	
	@tracepoint&.disable
	
	if @pid == Process.pid
		@mutex.synchronize do
			@running = false
			@condition.broadcast
		end
		@watchdog&.join
	else
		# The sampling thread does not survive fork; avoid inherited synchronization:
		@running = false
	end
	
	self
ensure
	@watchdog = @thread = @execution = @loop = @tracepoint = nil
end