Skip to content

Commit

Permalink
Merge pull request #1067 from appsignal/probes-late-start
Browse files Browse the repository at this point in the history
Start probes registered after thread is started
  • Loading branch information
tombruijn committed Apr 26, 2024
2 parents cbd3384 + 126132c commit f4695e7
Show file tree
Hide file tree
Showing 4 changed files with 183 additions and 62 deletions.
6 changes: 6 additions & 0 deletions .changesets/add-probes-register-method.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: "patch"
type: "change"
---

Add `Appsignal::Probes.register` method as the preferred method to register probes. The `Appsignal::Probes.probes.register` is now deprecated.
6 changes: 6 additions & 0 deletions .changesets/automatically-start-new-probes-on-register.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
bump: "patch"
type: "change"
---

Automatically start new probes when registered with `Appsignal::Probes.register` when the gem has already started the probes thread. Previously, the late registered probes would not be run.
113 changes: 74 additions & 39 deletions lib/appsignal/probes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,50 @@ def [](key)
probes[key]
end

# @deprecated Use {Appsignal::Probes.register} instead.
def register(name, probe)
Appsignal::Utils::StdoutAndLoggerMessage.warning(
"The method 'Appsignal::Probes.probes.register' is deprecated. " \
"Use 'Appsignal::Probes.register' instead."
)
Appsignal::Probes.register(name, probe)
end

# @api private
def internal_register(name, probe)
if probes.key?(name)
logger.debug "A probe with the name `#{name}` is already " \
"registered. Overwriting the entry with the new probe."
end
probes[name] = probe
end

# @api private
def each(&block)
probes.each(&block)
end

private

attr_reader :probes

def logger
Appsignal.internal_logger
end
end

class << self
# @api private
def mutex
@mutex ||= Thread::Mutex.new
end

# @see ProbeCollection
# @return [ProbeCollection] Returns list of probes.
def probes
@probes ||= ProbeCollection.new
end

# Register a new minutely probe.
#
# Supported probe types are:
Expand All @@ -40,14 +84,14 @@ def [](key)
# a `call` method call. The `call` method is called every minute.
#
# @example Register a new probe
# Appsignal::Probes.probes.register :my_probe, lambda {}
# Appsignal::Probes.register :my_probe, lambda {}
#
# @example Overwrite an existing registered probe
# Appsignal::Probes.probes.register :my_probe, lambda {}
# Appsignal::Probes.probes.register :my_probe, lambda { puts "hello" }
# Appsignal::Probes.register :my_probe, lambda {}
# Appsignal::Probes.register :my_probe, lambda { puts "hello" }
#
# @example Add a lambda as a probe
# Appsignal::Probes.probes.register :my_probe, lambda { puts "hello" }
# Appsignal::Probes.register :my_probe, lambda { puts "hello" }
# # "hello" # printed every minute
#
# @example Add a probe instance
Expand All @@ -61,7 +105,7 @@ def [](key)
# end
# end
#
# Appsignal::Probes.probes.register :my_probe, MyProbe.new
# Appsignal::Probes.register :my_probe, MyProbe.new
# # "started" # printed immediately
# # "called" # printed every minute
#
Expand All @@ -79,7 +123,7 @@ def [](key)
# end
# end
#
# Appsignal::Probes.probes.register :my_probe, MyProbe
# Appsignal::Probes.register :my_probe, MyProbe
# Appsignal::Probes.start # This is called for you
# # "started" # Printed on Appsignal::Probes.start
# # "called" # Repeated every minute
Expand All @@ -91,37 +135,15 @@ def [](key)
# be used as a probe.
# @return [void]
def register(name, probe)
if probes.key?(name)
logger.debug "A probe with the name `#{name}` is already " \
"registered. Overwriting the entry with the new probe."
end
probes[name] = probe
end

# @api private
def each(&block)
probes.each(&block)
end

private
probes.internal_register(name, probe)

attr_reader :probes

def logger
Appsignal.internal_logger
end
end

class << self
# @see ProbeCollection
# @return [ProbeCollection] Returns list of probes.
def probes
@probes ||= ProbeCollection.new
initialize_probe(name, probe) if started?
end

# @api private
def start
stop
@started = true
@thread = Thread.new do
# Advise multi-threaded app servers to ignore this thread
# for the purposes of fork safety warnings
Expand All @@ -133,22 +155,33 @@ def start
initialize_probes
loop do
logger = Appsignal.internal_logger
logger.debug("Gathering minutely metrics with #{probe_instances.count} probes")
probe_instances.each do |name, probe|
logger.debug("Gathering minutely metrics with '#{name}' probe")
probe.call
rescue => ex
logger.error "Error in minutely probe '#{name}': #{ex}"
logger.debug ex.backtrace.join("\n")
mutex.synchronize do
logger.debug("Gathering minutely metrics with #{probe_instances.count} probes")
probe_instances.each do |name, probe|
logger.debug("Gathering minutely metrics with '#{name}' probe")
probe.call
rescue => ex
logger.error "Error in minutely probe '#{name}': #{ex}"
logger.debug ex.backtrace.join("\n")
end
end
sleep wait_time
end
end
end

# Returns if the probes thread has been started. If the value is false or
# nil, it has not been started yet.
#
# @return [Boolean, nil]
def started?
@started
end

# @api private
def stop
defined?(@thread) && @thread.kill
@started = false
probe_instances.clear
end

Expand Down Expand Up @@ -185,7 +218,9 @@ def initialize_probe(name, probe)
"#{klass}.dependency_present? returned falsy"
return
end
probe_instances[name] = instance
mutex.synchronize do
probe_instances[name] = instance
end
rescue => error
logger = Appsignal.internal_logger
logger.error "Error while initializing minutely probe '#{name}': #{error}"
Expand Down

0 comments on commit f4695e7

Please sign in to comment.