Initial commit
This commit is contained in:
28
lib/peatio/bench/barong_session.rb
Normal file
28
lib/peatio/bench/barong_session.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
module Bench
|
||||
module Barong_session
|
||||
class Sessions
|
||||
def initialize(trader:)
|
||||
@trader = trader
|
||||
end
|
||||
|
||||
def run!
|
||||
config = YAML.load_file("#{Rails.root}/config/application.yml")
|
||||
file = "#{Rails.root}/public/user_data.csv"
|
||||
|
||||
Kernel.puts "Creating members ..."
|
||||
@members = Factories.create_list(:member, @trader)
|
||||
Kernel.puts "sign up URL #{config["development"]["SIGN_UP_URL"]}"
|
||||
|
||||
@members.each do |member|
|
||||
puts %x{http POST #{config["development"]["SIGN_UP_URL"]} email=#{member.email} password=#{config["development"]["SIGN_UP_PASSWORD"]}}
|
||||
Kernel.puts "#{member.email} was requested for sign up with password=#{config["development"]["SIGN_UP_PASSWORD"]}"
|
||||
end
|
||||
CSV.open(file, 'w+', write_headers: true) do |csv|
|
||||
@members.each do |member|
|
||||
csv << %w{email}.map { |attr| member.send(attr) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
85
lib/peatio/bench/factories.rb
Normal file
85
lib/peatio/bench/factories.rb
Normal file
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bench
|
||||
module Factories
|
||||
class << self
|
||||
def create(model, options = {})
|
||||
"#{self.name}/#{model}"
|
||||
.camelize
|
||||
.constantize
|
||||
.new(options)
|
||||
.create
|
||||
end
|
||||
|
||||
def create_list(model, number, options = {level: 2})
|
||||
"#{self.name}/#{model}"
|
||||
.camelize
|
||||
.constantize
|
||||
.new(options)
|
||||
.create_list(number)
|
||||
end
|
||||
end
|
||||
|
||||
class Member
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def create
|
||||
::Member.create!(construct_member)
|
||||
end
|
||||
|
||||
def create_list(number)
|
||||
Array.new(number) { create }
|
||||
end
|
||||
|
||||
def construct_member
|
||||
{ email: unique_email,
|
||||
uid: "U#{Faker::Number.number(9)}",
|
||||
level: 3,
|
||||
role: 'member',
|
||||
state: 'active' }.merge(@options)
|
||||
end
|
||||
|
||||
def unique_email
|
||||
@used_emails ||= ::Member.pluck(:email)
|
||||
loop do
|
||||
email = Faker::Internet.unique.email
|
||||
unless @used_emails.include?(email)
|
||||
@used_emails << email
|
||||
return email
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Deposit
|
||||
DEFAULT_DEPOSIT_AMOUNT = 1_000_000_000_000_000
|
||||
def initialize(options)
|
||||
@options = options
|
||||
@currency = Currency.find(options[:currency_id])
|
||||
end
|
||||
|
||||
def create
|
||||
if @currency.fiat?
|
||||
::Deposit.create!(construct_fiat_deposit).tap(&:charge!)
|
||||
else
|
||||
::Deposit.create!(construct_coin_deposit).tap { |d| d.with_lock { d.accept! } }
|
||||
end
|
||||
end
|
||||
|
||||
def construct_fiat_deposit
|
||||
{ amount: DEFAULT_DEPOSIT_AMOUNT,
|
||||
type: 'Deposits::Fiat' }.merge(@options)
|
||||
end
|
||||
|
||||
def construct_coin_deposit
|
||||
{ amount: DEFAULT_DEPOSIT_AMOUNT,
|
||||
address: Faker::Blockchain::Bitcoin.address,
|
||||
txid: Faker::Lorem.characters(64),
|
||||
txout: 0,
|
||||
type: 'Deposits::Coin' }.merge(@options)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
29
lib/peatio/bench/helpers.rb
Normal file
29
lib/peatio/bench/helpers.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bench
|
||||
module Helpers
|
||||
def become_billionaire(member)
|
||||
@currencies.each do |c|
|
||||
Factories.create(:deposit, member_id: member.id, currency_id: c.id)
|
||||
end
|
||||
|
||||
remove_locked
|
||||
end
|
||||
|
||||
### for remove locked create in billioner method
|
||||
def remove_locked
|
||||
::Account.where.not(locked: 0).each do |re|
|
||||
locked = re.locked
|
||||
re.update(balance: locked, locked: 0)
|
||||
end
|
||||
end
|
||||
|
||||
def my_create_order(options)
|
||||
Order.new(options)
|
||||
.tap(&:round_amount_and_price)
|
||||
.tap { |o| o.locked = o.origin_locked = o.compute_locked }
|
||||
.tap { |o| o.hold_account!.lock_funds(o.locked) }
|
||||
.tap(&:save)
|
||||
end
|
||||
end
|
||||
end
|
||||
60
lib/peatio/bench/injectors.rb
Normal file
60
lib/peatio/bench/injectors.rb
Normal file
@@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bench
|
||||
module Injectors
|
||||
|
||||
class << self
|
||||
def initialize_injector(config)
|
||||
"#{self.name}/#{config[:injector]}"
|
||||
.camelize
|
||||
.constantize
|
||||
.new(config)
|
||||
end
|
||||
end
|
||||
|
||||
class Base
|
||||
attr_reader :config
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
@number = config[:number].to_i
|
||||
@step = config.fetch(:step, 1000).to_i
|
||||
@markets = ::Market.where(id: config[:markets].split(',').map(&:squish).reject(&:blank?))
|
||||
end
|
||||
|
||||
def generate!(members = nil)
|
||||
@members = members || Member.all
|
||||
@queue = Queue.new
|
||||
Array.new(@number / @step) do
|
||||
::Rails.logger.info { "Created orders: #{@queue.size}" }
|
||||
ActiveRecord::Base.transaction do
|
||||
Array.new(@step) do
|
||||
create_order.tap { |o| @queue << o }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def pop
|
||||
# Use non_blocking pop.
|
||||
@queue.pop(true)
|
||||
rescue ThreadError
|
||||
# Return nil in case of empty queue.
|
||||
nil
|
||||
end
|
||||
|
||||
def size
|
||||
@queue.size
|
||||
end
|
||||
|
||||
def create_order
|
||||
Order.new(construct_order)
|
||||
.tap(&:round_amount_and_price)
|
||||
.tap { |o| o.locked = o.origin_locked = o.compute_locked }
|
||||
.tap { |o| o.hold_account!.lock_funds(o.locked) }
|
||||
.tap(&:save)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
37
lib/peatio/bench/injectors/bitfinex.rb
Normal file
37
lib/peatio/bench/injectors/bitfinex.rb
Normal file
@@ -0,0 +1,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bench
|
||||
module Injectors
|
||||
class Bitfinex < Base
|
||||
|
||||
def initialize(config)
|
||||
super
|
||||
if config[:data_load_path].present?
|
||||
@data = YAML.load_file(Rails.root.join(config[:data_load_path]))
|
||||
@index = 0
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def construct_order
|
||||
@index = 0 if @data[@index].blank?
|
||||
order_data = @data[@index]
|
||||
price = order_data[1]
|
||||
amount = order_data[2]
|
||||
market = @markets.sample
|
||||
type = amount > 0 ? 'OrderBid' : 'OrderAsk'
|
||||
@index += 1
|
||||
{ type: type,
|
||||
state: Order::WAIT,
|
||||
member: @members.sample,
|
||||
market: market,
|
||||
ask: market.base_unit,
|
||||
bid: market.quote_unit,
|
||||
ord_type: :limit,
|
||||
price: price,
|
||||
volume: amount.abs }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
42
lib/peatio/bench/injectors/dummy.rb
Normal file
42
lib/peatio/bench/injectors/dummy.rb
Normal file
@@ -0,0 +1,42 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bench
|
||||
module Injectors
|
||||
class Dummy < Base
|
||||
extend Memoist
|
||||
|
||||
def initialize(config)
|
||||
super
|
||||
config.reverse_merge!(default_config)
|
||||
%i[min_volume max_volume min_price max_price].each do |var|
|
||||
instance_variable_set(:"@#{var}", config[var])
|
||||
end
|
||||
end
|
||||
|
||||
def construct_order(memberes = nil, order_type = nil)
|
||||
market = @markets.sample
|
||||
@members = memberes if memberes.present?
|
||||
type = order_type || config.fetch(:side) { %w[OrderBid OrderAsk].sample }
|
||||
{ type: type,
|
||||
state: Order::PENDING,
|
||||
member: @members.sample,
|
||||
market: market,
|
||||
ask: market.base_unit,
|
||||
bid: market.quote_unit,
|
||||
ord_type: :limit,
|
||||
price: config.fetch(:price) { rand(@min_price..@max_price) },
|
||||
volume: rand(@min_volume..@max_volume) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def default_config
|
||||
{ min_volume: 0.1,
|
||||
max_volume: 1,
|
||||
min_price: 0.5,
|
||||
max_price: 2 }
|
||||
end
|
||||
memoize :default_config
|
||||
end
|
||||
end
|
||||
end
|
||||
181
lib/peatio/bench/matching/amqp.rb
Normal file
181
lib/peatio/bench/matching/amqp.rb
Normal file
@@ -0,0 +1,181 @@
|
||||
# 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
|
||||
81
lib/peatio/bench/matching/direct.rb
Normal file
81
lib/peatio/bench/matching/direct.rb
Normal file
@@ -0,0 +1,81 @@
|
||||
# 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 Direct
|
||||
include Helpers
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
|
||||
@injector = Injectors.initialize_injector(@config[:orders])
|
||||
@currencies = Currency.where(id: @config[:currencies].split(',').map(&:squish).reject(&:blank?))
|
||||
@matching = Workers::AMQP::Matching.new
|
||||
# TODO: Print errors in the end of benchmark and include them into report.
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def run!
|
||||
Kernel.puts "Creating members ..."
|
||||
@members = Factories.create_list(:member, @config[:traders])
|
||||
|
||||
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
|
||||
|
||||
@matching_started_at = Time.now
|
||||
|
||||
process_messages
|
||||
|
||||
@matching_finished_at = Time.now
|
||||
end
|
||||
|
||||
def process_messages
|
||||
loop do
|
||||
order = @injector.pop
|
||||
break unless order
|
||||
@matching.process({action: 'submit', order: order.to_matching_attributes}, 'metadata', 'delivery_info')
|
||||
rescue StandardError => e
|
||||
Kernel.puts e
|
||||
@errors << e
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Add more useful metrics to result.
|
||||
def result
|
||||
@result ||=
|
||||
begin
|
||||
matching_ops = @orders_number / (@matching_finished_at - @matching_started_at)
|
||||
|
||||
# TODO: Deal with calling iso8601(6) everywhere.
|
||||
{ config: @config,
|
||||
matching: {
|
||||
started_at: @matching_started_at.iso8601(6),
|
||||
finished_at: @matching_finished_at.iso8601(6),
|
||||
operations: @orders_number,
|
||||
ops: matching_ops
|
||||
}
|
||||
}
|
||||
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]}-#{@matching_started_at.iso8601}.yml"
|
||||
File.open(report_path.join(report_name), 'w') do |f|
|
||||
f.puts YAML.dump(result.deep_stringify_keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
95
lib/peatio/bench/order_processing/amqp.rb
Normal file
95
lib/peatio/bench/order_processing/amqp.rb
Normal file
@@ -0,0 +1,95 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bench
|
||||
module OrderProcessing
|
||||
class AMQP < TradeExecution::AMQP
|
||||
def run!
|
||||
# TODO: Check if OrderProcessing daemon is running before start (use queue_info[:consumers]).
|
||||
super
|
||||
Kernel.puts "Init wait orders queue..."
|
||||
@orders_for_cancel_number = init_wait_orders_queue!.size # TODO: If zero? raise Error.
|
||||
|
||||
Kernel.puts "Start wait orders publish..."
|
||||
@cancel_publish_started_at = @order_processing_started_at = Time.now
|
||||
publish_cancel_messages
|
||||
@cancel_publish_finished_at = Time.now
|
||||
|
||||
Kernel.puts "Messages are published to RabbitMQ."
|
||||
Kernel.puts "Waiting for order processing by order processor..."
|
||||
wait_for_order_processing
|
||||
@order_processing_finished_at = Time.now
|
||||
end
|
||||
|
||||
def publish_cancel_messages
|
||||
Array.new(@config[:threads]) do
|
||||
Thread.new do
|
||||
loop do
|
||||
break if @wait_orders_queue.blank?
|
||||
order = @wait_orders_queue.pop
|
||||
AMQP::Queue.enqueue(:matching, action: 'cancel', order: order.to_matching_attributes)
|
||||
rescue StandardError => e
|
||||
Kernel.puts e
|
||||
@errors << e
|
||||
end
|
||||
end
|
||||
end.map(&:join)
|
||||
end
|
||||
|
||||
def wait_for_order_processing
|
||||
last_log_time = Time.at(0)
|
||||
queue_status_file = File.open(queue_status_file_path('order-processing'), 'a')
|
||||
|
||||
loop do
|
||||
queue_status = order_processing_queue_status
|
||||
# NOTE: If no orders where cancelled idle_since would not change.
|
||||
break if queue_status[:messages].zero? &&
|
||||
queue_status[:idle_since].present? &&
|
||||
Time.parse("#{queue_status[:idle_since]} UTC") >= @order_processing_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
|
||||
|
||||
def result
|
||||
@result ||=
|
||||
begin
|
||||
cancel_publish_ops = @orders_for_cancel_number / (@cancel_publish_finished_at - @cancel_publish_started_at)
|
||||
order_processing_ops = @orders_for_cancel_number / (@order_processing_finished_at - @order_processing_started_at)
|
||||
|
||||
super.merge(
|
||||
cancel_publish: {
|
||||
started_at: @cancel_publish_started_at.iso8601(6),
|
||||
finished_at: @cancel_publish_finished_at.iso8601(6),
|
||||
operations: @orders_for_cancel_number,
|
||||
ops: cancel_publish_ops
|
||||
},
|
||||
order_processing: {
|
||||
started_at: @order_processing_started_at.iso8601(6),
|
||||
finished_at: @order_processing_finished_at.iso8601(6),
|
||||
operations: @orders_for_cancel_number,
|
||||
ops: order_processing_ops
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def init_wait_orders_queue!
|
||||
orders = Order.where(state: Order::WAIT).shuffle
|
||||
@wait_orders_queue =
|
||||
orders.each_with_object(Queue.new) do |o, queue|
|
||||
queue << o
|
||||
end
|
||||
end
|
||||
|
||||
def order_processing_queue_status
|
||||
@rmq_http_client.list_queues.find { |q| q[:name] == AMQP::Config.binding_queue(:order_processor).first }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
82
lib/peatio/bench/order_processing/direct.rb
Normal file
82
lib/peatio/bench/order_processing/direct.rb
Normal file
@@ -0,0 +1,82 @@
|
||||
# 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 OrderProcessing
|
||||
class Direct
|
||||
include Helpers
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
|
||||
@injector = Injectors.initialize_injector(@config[:orders])
|
||||
@currencies = Currency.where(id: @config[:currencies].split(',').map(&:squish).reject(&:blank?))
|
||||
@order_processor = Workers::AMQP::OrderProcessor.new
|
||||
# TODO: Print errors in the end of benchmark and include them into report.
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def run!
|
||||
Kernel.puts "Creating members ..."
|
||||
@members = Factories.create_list(:member, @config[:traders])
|
||||
|
||||
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
|
||||
|
||||
@processing_started_at = Time.now
|
||||
|
||||
process_orders
|
||||
|
||||
@processing_finished_at = Time.now
|
||||
end
|
||||
|
||||
def process_orders
|
||||
loop do
|
||||
order = @injector.pop
|
||||
break unless order
|
||||
|
||||
@order_processor.process({action: 'cancel', order: order.to_matching_attributes}.deep_stringify_keys!)
|
||||
rescue StandardError => e
|
||||
Kernel.puts e
|
||||
@errors << e
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: Add more useful metrics to result.
|
||||
def result
|
||||
@result ||=
|
||||
begin
|
||||
processing_ops = @orders_number / (@processing_finished_at - @processing_started_at)
|
||||
|
||||
# TODO: Deal with calling iso8601(6) everywhere.
|
||||
{ config: @config,
|
||||
order_processing: {
|
||||
started_at: @processing_started_at.iso8601(6),
|
||||
finished_at: @processing_finished_at.iso8601(6),
|
||||
operations: @orders_number,
|
||||
ops: processing_ops
|
||||
}
|
||||
}
|
||||
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]}-#{@processing_started_at.iso8601}.yml"
|
||||
File.open(report_path.join(report_name), 'w') do |f|
|
||||
f.puts YAML.dump(result.deep_stringify_keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
67
lib/peatio/bench/trade_execution/amqp.rb
Normal file
67
lib/peatio/bench/trade_execution/amqp.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bench
|
||||
module TradeExecution
|
||||
class AMQP < Matching::AMQP
|
||||
def run!
|
||||
# TODO: Check if TradeExecutor daemon is running before start (use queue_info[:consumers]).
|
||||
super
|
||||
Kernel.puts 'Waiting for trades processing by trade execution daemon...'
|
||||
@execution_started_at = @publish_started_at
|
||||
wait_for_execution
|
||||
@execution_finished_at = Time.now
|
||||
end
|
||||
|
||||
def run_fee!
|
||||
# TODO: Check if TradeExecutor daemon is running before start (use queue_info[:consumers]).
|
||||
super
|
||||
Kernel.puts 'Waiting for trades processing by trade execution daemon...'
|
||||
@execution_started_at = @publish_started_at
|
||||
wait_for_execution
|
||||
@execution_finished_at = Time.now
|
||||
end
|
||||
def wait_for_execution
|
||||
last_log_time = Time.at(0)
|
||||
queue_status_file = File.open(queue_status_file_path('trade-execution'), 'a')
|
||||
|
||||
loop do
|
||||
queue_status = trade_execution_queue_status
|
||||
# NOTE: If no orders where matched idle_since would not change.
|
||||
break if queue_status[:messages].zero? &&
|
||||
queue_status[:idle_since].present? &&
|
||||
Time.parse("#{queue_status[:idle_since]} UTC") >= @execution_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
|
||||
|
||||
def result
|
||||
@result ||=
|
||||
begin
|
||||
trades_number = Trade.where('created_at >= ?', @publish_started_at).length
|
||||
trades_ops = trades_number / (@execution_finished_at - @execution_started_at)
|
||||
|
||||
super.merge(
|
||||
trade_execution: {
|
||||
started_at: @execution_started_at.iso8601(6),
|
||||
finished_at: @execution_finished_at.iso8601(6),
|
||||
operations: trades_number,
|
||||
ops: trades_ops
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def trade_execution_queue_status
|
||||
@rmq_http_client.list_queues.find { |q| q[:name] == ::AMQP::Config.binding_queue(:trade_executor).first }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
85
lib/peatio/bench/trade_execution/direct.rb
Normal file
85
lib/peatio/bench/trade_execution/direct.rb
Normal file
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Bench
|
||||
module TradeExecution
|
||||
class Direct
|
||||
include Helpers
|
||||
|
||||
def initialize(config)
|
||||
@config = config
|
||||
raise "This benchmark doesn't support Bitfinex injector" if config[:orders][:injector] == 'bitfinex'
|
||||
|
||||
@bid_injector = Injectors.initialize_injector(@config[:orders].merge(price: 1, side: 'OrderBid'))
|
||||
@ask_injector = Injectors.initialize_injector(@config[:orders].merge(price: 0.9, side: 'OrderAsk'))
|
||||
@currencies = Currency.where(id: @config[:currencies].split(',').map(&:squish).reject(&:blank?))
|
||||
@executor = Workers::AMQP::TradeExecutor.new
|
||||
# TODO: Print errors in the end of benchmark and include them into report.
|
||||
@errors = []
|
||||
end
|
||||
|
||||
def run!
|
||||
Kernel.puts "Creating members ..."
|
||||
@members = Factories.create_list(:member, @config[:traders])
|
||||
|
||||
Kernel.puts "Depositing funds ..."
|
||||
@members.map(&method(:become_billionaire))
|
||||
|
||||
Kernel.puts "Generating orders by injector and saving them in db..."
|
||||
|
||||
Kernel.puts 'Waiting for trades processing by trade execution daemon...'
|
||||
@bid_injector.generate!(@members)
|
||||
@ask_injector.generate!(@members)
|
||||
|
||||
@execution_started_at = Time.now
|
||||
process_messages
|
||||
@execution_finished_at = Time.now
|
||||
end
|
||||
|
||||
def process_messages
|
||||
loop do
|
||||
ask = @ask_injector.pop
|
||||
bid = @bid_injector.pop
|
||||
break unless ask && bid
|
||||
volume = ask.volume > bid.volume ? bid.volume : ask.volume
|
||||
@executor.process({ market_id: ask.market_id,
|
||||
ask_id: ask.id,
|
||||
bid_id: bid.id,
|
||||
strike_price: ask.price,
|
||||
volume: volume,
|
||||
funds: volume * ask.price })
|
||||
rescue StandardError => e
|
||||
Kernel.puts e
|
||||
@errors << e
|
||||
end
|
||||
end
|
||||
|
||||
def result
|
||||
@result ||=
|
||||
begin
|
||||
trades_number = Trade.where('created_at >= ?', @execution_started_at).length
|
||||
trades_ops = trades_number / (@execution_finished_at - @execution_started_at)
|
||||
|
||||
{ config: @config,
|
||||
trade_execution: {
|
||||
started_at: @execution_started_at.iso8601(6),
|
||||
finished_at: @execution_finished_at.iso8601(6),
|
||||
operations: trades_number,
|
||||
ops: trades_ops
|
||||
}
|
||||
}
|
||||
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]}-#{@execution_started_at.iso8601}.yml"
|
||||
File.open(report_path.join(report_name), 'w') do |f|
|
||||
f.puts YAML.dump(result.deep_stringify_keys)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user