module Stella module Adapter # SIEGE # Usage: siege [options] # siege [options] URL # siege -g URL # Options: # -V, --version VERSION, prints version number to screen. # -h, --help HELP, prints this section. # -C, --config CONFIGURATION, show the current configuration. # -v, --verbose VERBOSE, prints notification to screen. # -g, --get GET, pull down headers from the server and display HTTP # transaction. Great for web application debugging. # -c, --concurrent=NUM CONCURRENT users, default is 10 # -u, --url="URL" Deprecated. Set URL as the last argument. # -i, --internet INTERNET user simulation, hits the URLs randomly. # -b, --benchmark BENCHMARK, signifies no delay for time testing. # -t, --time=NUMm TIME based testing where "m" is the modifier S, M, or H # no space between NUM and "m", ex: --time=1H, one hour test. # -r, --reps=NUM REPS, number of times to run the test, default is 25 # -f, --file=FILE FILE, change the configuration file to file. # -R, --rc=FILE RC, change the siegerc file to file. Overrides # the SIEGERC environmental variable. # -l, --log LOG, logs the transaction to PREFIX/var/siege.log # -m, --mark="text" MARK, mark the log file with a string separator. # -d, --delay=NUM Time DELAY, random delay between 1 and num designed # to simulate human activity. Default value is 3 # -H, --header="text" Add a header to request (can be many) # -A, --user-agent="text" Sets User-Agent in request class Siege < Stella::Adapter::Base attr_accessor :version, :help, :config, :verbose, :get, :log, :mark, :delay, :header, :user_agent attr_accessor :reps, :concurrent, :rc, :file, :time, :benchmark, :internet def initialize(options={}, arguments=[]) super(options, arguments) @name = 'siege' @reps = 1 @concurrent = 1 @rc = File.join(ENV['HOME'], '.siegerc') @private_variables = ['private_variables', 'name', 'arguments', 'load_factor', 'working_directory', 'orig_logfile'] @load_factor = 1 end def version vsn = 0 text = "" Open3.popen3("#{@name} --version") do |stdin, stdout, stderr| text = stderr.readlines.join text.scan(/SIEGE (\d+?\.\d+)/) { |v| vsn = v[0] } end vsn end # loadtest # # True or false: is the call to siege a load test? If it's a call to help or version or # to display the config this with return false. It's no reason for someone to make this # call through Stella but it's here for goodness sake. def loadtest? !@arguments.empty? # The argument is a URI end def ready? @name && !instance_variables.empty? end # Before calling run def before # Keep a copy of the configuration file. copy_siegerc # Keep a copy of the URLs file. copy_urls_file if @file # TODO: Print message about neither --benchmark or --internet end def command raise CommandNotReady.new(self.class.to_s) unless ready? command = "#{@name} " instance_variables.each do |name| canon = name.tr('@', '') # instance_variables returns '@name' next if @private_variables.member?(canon) # It's important that we take the value from the getter method # because it applies the load factor. value = self.send(canon) if (value.is_a? Array) value.each { |el| command << "--#{cannon.tr('_', '-')} '#{el}' " } else command << "--#{canon.tr('_', '-')} '#{value}' " end end command << (@arguments.map { |uri| "'#{uri}'" }).join(' ') unless @arguments.empty? command end # After calling run def after update_orig_logfile if @orig_logfile save_stats end def process_options(arguments) options = OpenStruct.new opts = OptionParser.new opts.on('-V', '--version') do |v| options.version = v end opts.on('-h', '--help') do |v| options.help = v end opts.on('-C', '--config') do |v| options.config = v end opts.on('-v', '--verbose') do |v| options.verbose = v end opts.on('-g', '--get') do |v| options.get = v end opts.on('-l', '--log') do |v| options.log = v end opts.on('-m S', '--mark=S', String) do |v| options.mark = v end opts.on('-d N', '--delay=N', Float) do |v| options.delay = v end opts.on('-H S', '--header=S', String) do |v| options.header ||= []; options.header << v end opts.on('-r N', '--reps=N', Integer) do |v| options.reps = v.to_i end opts.on('-c N', '--concurrent=N', Integer) do |v| options.concurrent = v.to_i end opts.on('-R S', '--rc=S', String) do |v| options.rc = v end opts.on('-f S', '--file=S', String) do |v| options.file = v end opts.on('-t S', '--time=S', String) do |v| options.time = v end opts.on('-b', '--benchmark') do |v| options.benchmark = true; end opts.on('-i', '--internet') do |v| options.internet = true; end opts.on('-A S', '--user-agent=S', String) do |v| options.user_agent = v end raise "You cannot select both --internet and --benchmark" if options.internet && options.benchmark # parse! removes the options it finds. # It also fails when it finds unknown switches (i.e. -X) # Which should leave only the remaining arguments (URIs in this case) opts.parse!(arguments) options rescue OptionParser::InvalidOption => ex # We want to replace this text so we grab just the name of the argument badarg = ex.message.gsub('invalid option: ', '') raise InvalidArgument.new(badarg) end def vusers concurrent || 0 end def vusers=(v) @concurrent = v end def requests (@reps * concurrent_f).to_i end def requests=(v) @reps = (v / concurrent_f).to_i end def vuser_requests @reps end def concurrent (@concurrent * @load_factor).to_i end def concurrent_f (@concurrent * @load_factor).to_f end def reps @reps end # Take the last line of the siege.log file and write it to the log file # specified by the user. We don't this so running with Stella is # identical to running it standalone def update_orig_logfile return unless (@orig_logfile) log_str = FileUtil.read_file_to_array(log_file) || '' return if log_str.empty? if File.exists?(@orig_logfile) FileUtil.append_file(@orig_logfile, log_str[-1], true) else FileUtil.write_file(@orig_logfile, log_str.join(''), true) end end # We want to keep a copy of the configuration file and also # modify it a little bit to make sure we get all the mad info from siege def copy_siegerc # Read in the siegerc file so we can manipulate it siegerc_str = FileUtil.read_file(File.expand_path(@rc)) siegerc_vars = { :verbose => [false, true], # We want to maximize the data output by siege :logging => [false, true], :csv => [false, true] } #if (@agent) # siegerc_vars['user-agent'] = ['.*', 'dogs2'] #end # We'll set the variables in the siegerc file siegerc_vars.each_pair do |var,value| siegerc_str.gsub!(/#{var}\s*=\s*#{value[0]}/, "#{var} = #{value[1]}") # make true siegerc_str.gsub!(/^\#+\s*#{var}/, "#{var}") # remove comment end # Look for the enabled logile path # We will use this later to update it from the last line in our copy siegerc_str =~ /^\s*logfile\s*=\s*(.+?)$/ @orig_logfile = $1 || nil # Replace all environment variables with literal values @orig_logfile.gsub!(/\$\{#{$1}\}/, ENV[$1]) while (@orig_logfile =~ /\$\{(.+?)\}/ && ENV.has_key?($1)) @orig_logfile = File.expand_path(@orig_logfile) if @orig_logfile siegerc_str.gsub!(/^\#*\s*logfile\s*=\s*.*?$/, "logfile = " + log_file) FileUtil.write_file(rc_file, siegerc_str, true) @rc = rc_file end # We want to keep a copy of the URLs file too def copy_urls_file if @file File.copy(File.expand_path(@file), uris_file) @file = uris_file end end # Siege writes the summary to STDERR def stats_file File.new(stderr_path) end def rc_file File.join(@working_directory, "siegerc") end def log_file File.join(@working_directory, "siege.log") end def uris_file File.join(@working_directory, File.basename(@file)) end def stats return unless stats_file raw = {} stats_file.each_line { |l| l.chomp! nvpair = l.split(':') next unless nvpair && nvpair.size == 2 n = nvpair[0].strip.tr(' ', '_').downcase[/\w+/] v = nvpair[1].strip[/[\.\d]+/] raw[n.to_sym] = v.to_f } stats = Stella::Test::RunSummary.new stats.vusers = raw[:concurrency] stats.data_transferred = raw[:data_transferred] stats.elapsed_time = raw[:elapsed_time] stats.response_time = raw[:response_time] #stats.shortest_transaction = raw[:shortest_transaction] #stats.longest_transaction = raw[:longest_transaction] stats.transactions = raw[:transactions].to_i stats.transaction_rate = raw[:transaction_rate] stats.failed = raw[:failed_transactions].to_i stats.successful = raw[:successful_transactions].to_i #stats.raw = raw if @global_options.debug stats end end end end