class SippyCup::Runner

Attributes

sipp_pid[RW]

Public Class Methods

new(scenario, opts = {}) click to toggle source

Create a runner from a scenario

@param [Scenario, XMLScenario] scenario The scenario to execute @param [Hash] opts Options to modify the runner @option opts [optional, true, false] :full_sipp_output Whether or not to copy SIPp's stdout/stderr to the parent process. Defaults to true. @option opts [optional, Logger] :logger A logger to use in place of the internal logger to STDOUT. @option opts [optional, String] :command The command to execute. This is mostly available for testing.

# File lib/sippy_cup/runner.rb, line 21
def initialize(scenario, opts = {})
  @scenario = scenario
  @scenario_options = @scenario.scenario_options

  defaults = { full_sipp_output: true }
  @options = defaults.merge(opts)

  @command = @options[:command]
  @logger = @options[:logger] || Logger.new(STDOUT)
end

Public Instance Methods

run() click to toggle source

Runs the loaded scenario using SIPp

# File lib/sippy_cup/runner.rb, line 35
def run
  @input_files = @scenario.to_tmpfiles

  @logger.info "Preparing to run SIPp command: #{command}"

  execute_with_redirected_streams

  wait unless @options[:async]
ensure
  cleanup_input_files unless @options[:async]
end
stop() click to toggle source

Tries to stop SIPp by killing the target PID

@raises Errno::ESRCH when the PID does not correspond to a known process @raises Errno::EPERM when the process referenced by the PID cannot be killed

# File lib/sippy_cup/runner.rb, line 53
def stop
  Process.kill "KILL", @sipp_pid if @sipp_pid
end
wait() click to toggle source

Waits for the runner to finish execution

@raises Errno::ENOENT when the SIPp executable cannot be found @raises SippyCup::ExitOnInternalCommand when SIPp exits on an internal command. Calls may have been processed @raises SippyCup::NoCallsProcessed when SIPp exit normally, but has processed no calls @raises SippyCup::FatalError when SIPp encounters a fatal failure @raises SippyCup::FatalSocketBindingError when SIPp fails to bind to the specified socket @raises SippyCup::SippGenericError when SIPp encounters another type of error

@return Boolean true if execution succeeded without any failed calls, false otherwise

# File lib/sippy_cup/runner.rb, line 69
def wait
  exit_status = Process.wait2 @sipp_pid.to_i
  @err_rd.close if @err_rd
  @stdout_rd.close if @stdout_rd
  final_result = process_exit_status exit_status, @stderr_buffer
  if final_result
    @logger.info "Test completed successfully!"
  else
    @logger.info "Test completed successfully but some calls failed."
  end
  @logger.info "Statistics logged at #{File.expand_path @scenario_options[:stats_file]}" if @scenario_options[:stats_file]

  final_result
ensure
  cleanup_input_files
end

Private Instance Methods

cleanup_input_files() click to toggle source
# File lib/sippy_cup/runner.rb, line 205
def cleanup_input_files
  @input_files.values.compact.each do |value|
    value.close
    value.unlink
  end if @input_files
end
command() click to toggle source
# File lib/sippy_cup/runner.rb, line 88
def command
  @command ||= begin
    command = "sudo $(which sipp)"
    command_options.each_pair do |key, value|
      command << (value ? " -#{key} #{value}" : " -#{key}")
    end
    command << " #{@scenario_options[:destination]}"
  end
end
command_options() click to toggle source
# File lib/sippy_cup/runner.rb, line 98
def command_options
  options = {
    p: @scenario_options[:source_port] || '8836',
    sf: @input_files[:scenario].path,
  }

  max_concurrent = @scenario_options[:concurrent_max] || @scenario_options[:max_concurrent]
  options[:l] = max_concurrent if max_concurrent
  options[:m] = @scenario_options[:number_of_calls] if @scenario_options[:number_of_calls]
  options[:r] = @scenario_options[:calls_per_second] if @scenario_options[:calls_per_second]
  options[:s] = @scenario_options[:to].to_s.split('@').first if @scenario_options[:to]

  options[:i] = @scenario_options[:source] if @scenario_options[:source]
  options[:mp] = @scenario_options[:media_port] if @scenario_options[:media_port]

  if @scenario_options[:calls_per_second_max]
    options[:no_rate_quit] = nil
    options[:rate_max] = @scenario_options[:calls_per_second_max]
    options[:rate_increase] = @scenario_options[:calls_per_second_incr] || 1
    options[:rate_interval] = @scenario_options[:calls_per_second_interval] if @scenario_options[:calls_per_second_interval]
  end

  if @scenario_options[:stats_file]
    options[:trace_stat] = nil
    options[:stf] = @scenario_options[:stats_file]
    options[:fd] = @scenario_options[:stats_interval] || 1
  end

  if @scenario_options[:summary_report_file]
    options[:trace_screen] = nil
    options[:screen_file] = @scenario_options[:summary_report_file]
  end

  if @scenario_options[:errors_report_file]
    options[:trace_err] = nil
    options[:error_file] = @scenario_options[:errors_report_file]
  end

  if @scenario_options[:transport_mode]
    options[:t] = @scenario_options[:transport_mode]
  end

  if @scenario_options[:scenario_variables]
    options[:inf] = @scenario_options[:scenario_variables]
  end

  options.merge! @scenario_options[:options] if @scenario_options[:options]

  options
end
execute_with_redirected_streams() click to toggle source
# File lib/sippy_cup/runner.rb, line 149
def execute_with_redirected_streams
  @err_rd, err_wr = IO.pipe
  stdout_target = if @options[:full_sipp_output]
    @stdout_rd, stdout_wr = IO.pipe
    stdout_wr
  else
    '/dev/null'
  end

  @sipp_pid = spawn command, err: err_wr, out: stdout_target

  @stderr_buffer = String.new

  Thread.new do
    err_wr.close
    until @err_rd.eof?
      buffer = @err_rd.readpartial(1024).strip
      @stderr_buffer += buffer
      $stderr << buffer if @options[:full_sipp_output]
    end
  end

  if @stdout_rd
    @stdout_buffer = String.new

    Thread.new do
      stdout_wr.close
      until @stdout_rd.eof?
        buffer = @stdout_rd.readpartial(1024).strip
        @stdout_buffer += buffer
        $stdout << buffer
      end
    end
  end
end
process_exit_status(process_status, error_message = nil) click to toggle source
# File lib/sippy_cup/runner.rb, line 185
def process_exit_status(process_status, error_message = nil)
  exit_code = process_status[1].exitstatus
  case exit_code
  when 0
    true
  when 1
    false
  when 97
    raise SippyCup::ExitOnInternalCommand, error_message
  when 99
    raise SippyCup::NoCallsProcessed, error_message
  when 255
    raise SippyCup::FatalError, error_message
  when 254
    raise SippyCup::FatalSocketBindingError, error_message
  else
    raise SippyCup::SippGenericError, error_message
  end
end