# frozen_string_literal: true module Appsignal class Minutely class ProbeCollection include Appsignal::Utils::DeprecationMessage def initialize @probes = {} end # @return [Integer] Number of probes that are registered. def count probes.count end # Clears all probes from the list. # @return [void] def clear probes.clear end # Fetch a probe using its name. # @param key [Symbol/String] The name of the probe to fetch. # @return [Object] Returns the registered probe. def [](key) probes[key] end # @param probe [Object] Any object that listens to the `call` method will # be used as a probe. # @deprecated Use {#register} instead. # @return [void] def <<(probe) deprecation_message "Deprecated `Appsignal::Minute.probes <<` " \ "call. Please use `Appsignal::Minutely.probes.register` instead.", logger register probe.object_id, probe end # Register a new minutely probe. # # Supported probe types are: # # - Lambda - A lambda is an object that listens to a `call` method call. # This `call` method is called every minute. # - Class - A class object is an object that listens to a `new` and # `call` method call. The `new` method is called when the Minutely # probe thread is started to initialize all probes. This allows probes # to load dependencies once beforehand. Their `call` method is called # every minute. # - Class instance - A class instance object is an object that listens to # a `call` method call. The `call` method is called every minute. # # @example Register a new probe # Appsignal::Minutely.probes.register :my_probe, lambda {} # # @example Overwrite an existing registered probe # Appsignal::Minutely.probes.register :my_probe, lambda {} # Appsignal::Minutely.probes.register :my_probe, lambda { puts "hello" } # # @example Add a lambda as a probe # Appsignal::Minutely.probes.register :my_probe, lambda { puts "hello" } # # "hello" # printed every minute # # @example Add a probe instance # class MyProbe # def initialize # puts "started" # end # # def call # puts "called" # end # end # # Appsignal::Minutely.probes.register :my_probe, MyProbe.new # # "started" # printed immediately # # "called" # printed every minute # # @example Add a probe class # class MyProbe # def initialize # # Add things that only need to be done on start up for this probe # require "some/library/dependency" # @cache = {} # initialize a local cache variable # puts "started" # end # # def call # puts "called" # end # end # # Appsignal::Minutely.probes.register :my_probe, MyProbe # Appsignal::Minutely.start # This is called for you # # "started" # Printed on Appsignal::Minutely.start # # "called" # Repeated every minute # # @param name [Symbol/String] Name of the probe. Can be used with {[]}. # This name will be used in errors in the log and allows overwriting of # probes by registering new ones with the same name. # @param probe [Object] Any object that listens to the `call` method will # 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 attr_reader :probes def logger Appsignal.logger end end class << self # @see ProbeCollection # @return [ProbeCollection] Returns list of probes. def probes @@probes ||= ProbeCollection.new end # @api private def start stop initialize_probes @@thread = Thread.new do sleep initial_wait_time loop do logger = Appsignal.logger logger.debug("Gathering minutely metrics with #{probes.count} probes") probe_instances.each do |name, probe| begin logger.debug("Gathering minutely metrics with '#{name}' probe") probe.call rescue => ex logger.error "Error in minutely probe '#{name}': #{ex}\n" logger.debug ex.backtrace.join("\n") end end sleep wait_time end end end # @api private def stop defined?(@@thread) && @@thread.kill probe_instances.clear end # @api private def wait_time 60 - Time.now.sec end private def initial_wait_time remaining_seconds = 60 - Time.now.sec return remaining_seconds if remaining_seconds > 30 remaining_seconds + 60 end def initialize_probes probes.each do |name, probe| instance = probe.respond_to?(:new) ? probe.new : probe probe_instances[name] = instance end end def probe_instances @@probe_instances ||= {} end end end end