require 'consul/async/utilities' require 'consul/async/consul_endpoint' require 'consul/async/json_endpoint' require 'consul/async/vault_endpoint' require 'consul/async/consul_template' require 'consul/async/consul_template_render' require 'em-http' require 'erb' module Consul module Async # The Engine keeps tracks of all templates, handle hot-reload of files if needed # as well as ticking the clock to compute state of templates periodically. class ConsulTemplateEngine attr_reader :template_manager, :hot_reload_failure, :template_frequency, :debug_memory, :result, :templates attr_writer :hot_reload_failure, :template_frequency, :debug_memory def initialize @templates = [] @template_callbacks = [] @hot_reload_failure = 'die' @all_templates_rendered = false @template_frequency = 1 @periodic_started = false @debug_memory = false @result = 0 @last_memory_state = build_memory_info @start = Time.now end def build_memory_info s = GC.stat { pages: s[:total_allocated_pages] - s[:total_freed_pages], objects: s[:total_allocated_objects] - s[:total_freed_objects], time: Time.now.utc } end def add_template_callback(&block) @template_callbacks << block end def add_template(source, dest, params = {}) @templates.push([source, dest, params]) end # Run templating engine once def do_run(template_manager, template_renders) unless template_manager.running ::Consul::Async::Debug.puts_info '[FATAL] TemplateManager has been stopped, stopping everything' @result = 3 EventMachine.stop return end results = template_renders.map(&:run) all_ready = results.all?(&:ready?) if !@all_templates_rendered && all_ready @all_templates_rendered = true cur_time = Time.now ::Consul::Async::Debug.puts_info "First rendering of #{results.count} templates completed in #{cur_time - @start}s at #{cur_time}. " end begin @template_callbacks.each do |c| c.call([all_ready, template_manager, results]) end rescue StandardError => e ::Consul::Async::Debug.puts_error "callback error: #{e.inspect}" raise e end rescue Consul::Async::InvalidTemplateException => e warn "[FATAL]#{e}" template_manager.terminate @result = 1 EventMachine.stop rescue StandardError => e warn "[FATAL] Error occured: #{e.inspect} - #{e.backtrace.join("\n\t")}" template_manager.terminate @result = 2 EventMachine.stop end # Run template engine as fast as possible until first rendering occurs def do_run_fast(template_manager, template_renders) do_run(template_manager, template_renders) return if @all_templates_rendered || @periodic_started # We continue if rendering not done and periodic not started EventMachine.next_tick do do_run_fast(template_manager, template_renders) end end def run(template_manager) @template_manager = template_manager EventMachine.run do template_renders = [] @templates.each do |template_file, output_file, params| template_renders << Consul::Async::ConsulTemplateRender.new(template_manager, template_file, output_file, hot_reload_failure: hot_reload_failure, params: params) end # Initiate first run immediately to speed up rendering EventMachine.next_tick do do_run_fast(template_manager, template_renders) end EventMachine.add_periodic_timer(template_frequency) do @periodic_started = true do_run(template_manager, template_renders) if debug_memory GC.start new_memory_state = build_memory_info diff_allocated = new_memory_state[:pages] - @last_memory_state[:pages] diff_num_objects = new_memory_state[:objects] - @last_memory_state[:objects] if diff_allocated != 0 || diff_num_objects.abs > (@last_memory_state[:pages] / 3) timediff = new_memory_state[:time] - @last_memory_state[:time] warn "[MEMORY] #{new_memory_state[:time]} significant RAM Usage detected\n" \ "[MEMORY] #{new_memory_state[:time]} Pages : #{new_memory_state[:pages]}" \ " (diff #{diff_allocated} aka #{(diff_allocated / timediff).round(0)}/s) \n" \ "[MEMORY] #{new_memory_state[:time]} Objects: #{new_memory_state[:objects]}"\ " (diff #{diff_num_objects} aka #{(diff_num_objects / timediff).round(0)}/s)" @last_memory_state = new_memory_state end end end end @result end end end end