# frozen_string_literal: true require "singleton" module PlainApm class Agent include Singleton def self.collect(event) instance.collect(event) end def self.start instance.start end def collect(event) return unless @config.enabled publisher_start if @pid != $$ @events << event end def start if !defined?(@started) @started = true else return end configure return unless @config.enabled warn("PlainAPM agent enabled.") setup_at_exit_hooks publisher_start install_hooks end private def stop uninstall_hooks publisher_shutdown end def publisher_start # Store PID for fork detection @pid = $$ # Already running return if @publisher&.alive? # TODO: sized queue w/ a timeout. @events = Thread::Queue.new # TODO: Multiple threads @publisher = Thread.new { publisher_loop } end def setup_at_exit_hooks at_exit { stop } end def publisher_shutdown return if @publisher.nil? # FIXME: raise in / kill the threads after a pre-determined timeout not # to block @events << nil @publisher.join @publisher = nil end def configure @config = Config.new end def install_hooks hooks.each(&:install) end def uninstall_hooks hooks.each(&:uninstall) end def hooks @hooks ||= [ Hooks::ActionMailer, Hooks::ActionPack, Hooks::ActionView, Hooks::ActiveJob, Hooks::ActiveRecord, Hooks::ActiveSupport, Hooks::ErrorReporter, Hooks::Manual ].map(&:new) end ## # Run a background thread that pops events from the queue and posts them to # the target server. def publisher_loop # Have the thread keep it's own connection. transport = Transport.new( endpoint: @config.endpoint, app_key: @config.app_key ) loop do event = @events.pop Thread.exit if event.nil? meta = { queue: @events.size, pid: $$, thread: Thread.current.object_id } # TODO: event serialization, batching _response, _error, _retriable = transport.deliver(event, meta) # TODO: retries / drops end end end end