bin/zold-stress
#!/usr/bin/env ruby
# frozen_string_literal: true
# Copyright (c) 2018-2019 Yegor Bugayenko
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the 'Software'), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
STDOUT.sync = true
require 'slop'
require 'rainbow'
require 'concurrent'
require 'zold/log'
require 'zold/age'
require 'zold/wallet'
require 'zold/wallets'
require 'zold/sync_wallets'
require 'zold/cached_wallets'
require 'zold/remotes'
require 'zold/commands/list'
require_relative '../lib/zold/stress/round'
require_relative '../lib/zold/stress/stats'
require_relative '../lib/zold/stress/summary'
require_relative '../lib/zold/stress/air'
Thread.current.name = 'main'
Encoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
log = Zold::Log::REGULAR
vlog = Zold::Log::ERRORS
begin
opts = Slop.parse(ARGV, strict: false, suppress_errors: true) do |o|
o.banner = "Usage: zold-stress [options]
Available options:"
o.integer '-r', '--rounds',
'Total amount of paying rounds to complete (default: 16)',
default: 16
o.integer '-w', '--wait',
'For how to long to wait for all payments to arrive (default: 600 seconds)',
default: 600
o.integer '-p', '--pool',
'From how many wallets to send payments (default: 8)',
default: 8
o.integer '-t', '--threads',
"How many threads to use for each operation (default: #{Concurrent.processor_count * 8})",
default: Concurrent.processor_count * 8
o.integer '-b', '--batch',
'How many transactions to send in each round (default: 64)',
default: 64
o.string '--private-key',
'The location of RSA private key (default: ~/.ssh/id_rsa)',
require: true,
default: File.expand_path('~/.ssh/id_rsa')
o.string '--home',
"Home directory (default: #{Dir.pwd})",
default: Dir.pwd
o.string '--network',
"The name of the network we work in (default: #{Zold::Wallet::MAINET}",
required: true,
default: Zold::Wallet::MAINET
o.bool '--skip-update', 'Don\'t update the list of remote nodes'
o.bool '-h', '--help', 'Show these instructions'
o.on '--verbose', 'Enable extra logging information' do
log = Zold::Log::VERBOSE
vlog = Zold::Log::REGULAR
end
o.on '--no-colors', 'Disable colors in the ouput' do
Rainbow.enabled = false
end
end
if opts.help?
log.info(opts.to_s)
exit
end
Zold::Hands.start
home = File.expand_path(opts[:home])
FileUtils.mkdir_p(home)
Dir.chdir(home)
zoldata = File.join(home, '.zoldata')
wallets = Zold::SyncWallets.new(
Zold::CachedWallets.new(Zold::Wallets.new(home)),
log: log
)
remotes = Zold::Remotes.new(file: File.join(zoldata, 'remotes'), network: opts['network'])
if remotes.all.empty?
remotes.defaults
log.info("The list of remotes has got default nodes, there are #{remotes.all.count} total")
end
copies = File.join(zoldata, 'copies')
stats = Zold::Stress::Stats.new(log: vlog)
summary = Zold::Stress::Summary.new(stats, opts['batch'])
air = Zold::Stress::Air.new
round = Zold::Stress::Round.new(
pvt: Zold::Key.new(file: opts['private-key']),
wallets: wallets, remotes: remotes, copies: copies,
stats: stats, air: air, log: log, vlog: vlog, opts: opts
)
log.info("Time: #{Time.now.utc.iso8601}; CPUs: #{Concurrent.processor_count}")
log.info("Home directory: #{home}")
log.info("Ruby version: #{RUBY_VERSION}/#{RUBY_PLATFORM}")
log.info("Zold gem version: #{Zold::VERSION}")
log.info("Zold protocol version: #{Zold::PROTOCOL}")
log.info("Network ID: #{opts['network']}")
log.info("Rounds: #{opts['rounds']}, threads: #{opts['threads']}, pool: #{opts['pool']}, batch: #{opts['batch']}")
start = Time.now
round.update unless opts['skip-update']
round.prepare
opts['rounds'].times do |r|
round.update unless opts['skip-update']
round.send
round.pull
round.match
log.info(summary)
round.list if (r % 10).zero?
end
s = Time.now
loop do
break if Time.now > s + opts['wait']
break if air.fetch.empty?
round.pull
round.match
log.info(summary)
end
round.list
unless air.fetch.empty?
air.fetch.each do |p|
log.info(" #{p[:source]} -> #{p[:target]} #{p[:amount]} #{Zold::Age.new(p[:pushed])}")
end
raise "#{air.fetch.count} payments out of #{stats.total('paid')} are still somewhere, we lost them :("
end
log.info("Successfully sent and received #{Rainbow(stats.total('arrived')).green} transactions \
in #{Zold::Age.new(start)}")
rescue StandardError => ex
log.error(Backtrace.new(ex))
exit(-1)
end