# frozen_string_literal: true # TODO: Add Bench::Error and better errors processing. # TODO: Add Bench::Report and extract all metrics to it. module Bench module Matching class AMQP # include Helpers include ::Bench::Helpers def initialize(config) @config = config @rmq_http_client = RabbitMQHTTP.default_client @injector = Injectors.initialize_injector(@config[:orders]) @currencies = Currency.where(id: @config[:currencies].split(',').map(&:squish).reject(&:blank?)) # TODO: Print errors in the end of benchmark and include them into report. @errors = [] end def run_fee! # TODO: Check if Matching daemon is running before start (use queue_info[:consumers]). Kernel.puts "Creating members ..." if Member.count == 0 @members = Factories.create_list(:member, @config[:traders]) else @members = Member.all end Kernel.puts "Depositing funds ..." @members.map(&method(:become_billionaire)) Kernel.puts "Generating orders by injector and saving them in db..." # TODO: Add orders generation progress bar. @config[:round].times do @injector.generate!(@members) @orders_number = @injector.size Kernel.puts "Publishing messages to RabbitMQ..." @matching_started_at = @publish_started_at = Time.now # TODO: Add orders publishing progress bar. publish_messages end @publish_finished_at = Time.now Kernel.puts "Messages are published to RabbitMQ." Kernel.puts "Waiting for order processing by matching daemon..." wait_for_matching @matching_finished_at = Time.now end def run! # TODO: Check if Matching daemon is running before start (use queue_info[:consumers]). Kernel.puts "Creating members ..." if Member.count == 0 @members = Factories.create_list(:member, @config[:traders]) else @members = Member.all end Kernel.puts "Depositing funds ..." @members.map(&method(:become_billionaire)) Kernel.puts "Generating orders by injector and saving them in db..." # TODO: Add orders generation progress bar. @injector.generate!(@members) @orders_number = @injector.size Kernel.puts "Publishing messages to RabbitMQ..." @matching_started_at = @publish_started_at = Time.now # TODO: Add orders publishing progress bar. publish_messages @publish_finished_at = Time.now Kernel.puts "Messages are published to RabbitMQ." Kernel.puts "Waiting for order processing by matching daemon..." wait_for_matching @matching_finished_at = Time.now end def publish_messages Array.new(@config[:threads]) do Thread.new do loop do order = @injector.pop break unless order p Kernel.puts "user number #{order.member_id} and order number is #{order.id} maker fee = #{order.maker_fee} submiited order level is #{order.member.group} and trades = #{order.member.trades.last_month.inject(0){ |sum, x| sum + x.rls }}" # ::AMQP::Queue.enqueue(:matching, action: 'submit', order: order.to_matching_attributes) ::AMQP::Queue.enqueue(:order_processor, { action: 'submit', order: order.to_matching_attributes }, { persistent: false }) rescue StandardError => e Kernel.puts e @errors << e end end end.map(&:join) end # TODO: Find better solution for getting message number in queue. # E.g there is rabbitmqctl list_queues. def wait_for_matching last_log_time = Time.at(0) queue_status_file = File.open(queue_status_file_path('matching'), 'a') loop do queue_status = matching_queue_status break if queue_status[:messages].zero? && queue_status[:idle_since].present? && Time.parse("#{queue_status[:idle_since]} UTC") >= @publish_started_at if last_log_time + 5 < Time.now queue_status_file.puts(YAML.dump([queue_status.merge(timestamp: Time.now.iso8601).deep_stringify_keys])) last_log_time = Time.now end sleep 0.5 end end # TODO: Add more useful metrics to result. def result @result ||= begin publish_ops = @orders_number / (@publish_finished_at - @publish_started_at) matching_ops = @orders_number / (@matching_finished_at - @publish_started_at) # TODO: Deal with calling iso8601(6) everywhere. { config: @config, submit_publish: { started_at: @publish_started_at.iso8601(6), finished_at: @publish_started_at.iso8601(6), operations: @orders_number, ops: publish_ops }, matching: { finished_at: @matching_finished_at.iso8601(6), operations: @orders_number, ops: matching_ops, started_at: @matching_started_at.iso8601(6) } } end end def save_report report_path = Rails.root.join(@config[:report_path]) FileUtils.mkpath(report_path) report_name = "#{self.class.parent.name.demodulize.downcase}-"\ "#{self.class.name.humanize.demodulize}-#{@config[:orders][:injector]}-"\ "#{@config[:orders][:number]}-#{@publish_started_at.iso8601}.yml" File.open(report_path.join(report_name), 'w') do |f| f.puts YAML.dump(result.deep_stringify_keys) end end private # TODO: Use get queue by name. # TODO: Use Faraday instead of RabbitMQ::HTTP::Client. def matching_queue_status @rmq_http_client.list_queues.find { |q| q[:name] == ::AMQP::Config.binding_queue(:matching).first } end def queue_status_file_path(name) log_path = Rails.root.join(@config[:log_path]) FileUtils.mkpath(log_path) log_path.join("#{name}-#{@publish_started_at.iso8601}.yml") end end end end