zold-io/zold-stress

View on GitHub
bin/zold-stress

Summary

Maintainability
Test Coverage
#!/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