def perform_spread(*args)
      spread_duration = get_sidekiq_options['spread_duration'] || 1.hour
      spread_in = 0
      spread_at = nil
      spread_method = get_sidekiq_options['spread_method'] || :rand
      spread_mod_value = nil

      spread_method = spread_method.to_sym if spread_method.present?

      # process spread_* options

      has_options = false

      opts =
        if !args.empty? && args.last.is_a?(::Hash)
          has_options = true
          args.pop
        else
          {}
        end

      sd = _extract_spread_opt(opts, :duration)
      spread_duration = sd if sd.present?

      si = _extract_spread_opt(opts, :in)
      spread_in = si if si.present?

      sa = _extract_spread_opt(opts, :at)
      spread_at = sa if sa.present?

      sm = _extract_spread_opt(opts, :method)
      spread_method = sm.to_sym if sm.present?

      smv = _extract_spread_opt(opts, :mod_value)
      spread_mod_value = smv if smv.present?

      # get left over options / keyword args
      remaining_opts = opts.reject { |o| PERFORM_SPREAD_OPTS.include?(o.to_sym) }

      # check args
      num_args = args.length

      # figure out the require params for #perform
      params = new.method(:perform).parameters

      num_req_args = params.select { |p| p[0] == :req }.length
      num_opt_args = params.select { |p| p[0] == :opt }.length
      num_req_key_args = params.select { |p| p[0] == :keyreq }.length
      num_opt_key_args = params.select { |p| p[0] == :key }.length

      # Sidekiq doesn't play nicely with named args
      raise ArgumentError, "#{name}#perform should not use keyword args" if num_req_key_args > 0 || num_opt_key_args > 0

      if has_options
        # if we popped something off to process, push it back on
        # if it contains arguments we need
        if num_args < num_req_args
          args.push(remaining_opts)
        elsif num_args < (num_req_args + num_opt_args) && !remaining_opts.empty?
          args.push(remaining_opts)
        end
      end

      # if a spread_mod_value is not provided use the first argument,
      # assumes it is an Integer
      spread_mod_value = args.first if spread_mod_value.blank? && spread_method == :mod

      # validate the spread_* options
      _check_spread_args!(spread_duration, spread_method, spread_mod_value)

      # calculate the offset for this job
      spread = _set_spread(spread_method, spread_duration.to_i, spread_mod_value)

      # call the correct perform_* method
      if spread_at.present?
        t = spread_at.to_i + spread
        perform_at(t, *args)
      else
        t = spread_in.to_i + spread
        if t.zero?
          perform_async(*args)
        else
          perform_in(t, *args)
        end
      end
    end
def perform_spread(*args)
      spread_duration = get_sidekiq_options['spread_duration'] || 1.hour
      spread_in = 0
      spread_at = nil
      spread_method = get_sidekiq_options['spread_method'] || :rand
      spread_mod_value = nil

      spread_method = spread_method.to_sym if spread_method.present?

      # process spread_* options

      has_options = false

      opts =
        if !args.empty? && args.last.is_a?(::Hash)
          has_options = true
          args.pop
        else
          {}
        end

      sd = _extract_spread_opt(opts, :duration)
      spread_duration = sd if sd.present?

      si = _extract_spread_opt(opts, :in)
      spread_in = si if si.present?

      sa = _extract_spread_opt(opts, :at)
      spread_at = sa if sa.present?

      sm = _extract_spread_opt(opts, :method)
      spread_method = sm.to_sym if sm.present?

      smv = _extract_spread_opt(opts, :mod_value)
      spread_mod_value = smv if smv.present?

      # get left over options / keyword args
      remaining_opts = opts.reject { |o| PERFORM_SPREAD_OPTS.include?(o.to_sym) }

      # check args
      num_args = args.length

      # figure out the require params for #perform
      params = new.method(:perform).parameters

      num_req_args = params.select { |p| p[0] == :req }.length
      num_opt_args = params.select { |p| p[0] == :opt }.length
      num_req_key_args = params.select { |p| p[0] == :keyreq }.length
      num_opt_key_args = params.select { |p| p[0] == :key }.length

      # Sidekiq doesn't play nicely with named args
      raise ArgumentError, "#{name}#perform should not use keyword args" if num_req_key_args > 0 || num_opt_key_args > 0

      if has_options
        # if we popped something off to process, push it back on
        # if it contains arguments we need
        if num_args < num_req_args
          args.push(remaining_opts)
        elsif num_args < (num_req_args + num_opt_args) && !remaining_opts.empty?
          args.push(remaining_opts)
        end
      end

      # if a spread_mod_value is not provided use the first argument,
      # assumes it is an Integer
      spread_mod_value = args.first if spread_mod_value.blank? && spread_method == :mod

      # validate the spread_* options
      _check_spread_args!(spread_duration, spread_method, spread_mod_value)

      # calculate the offset for this job
      spread = _set_spread(spread_method, spread_duration.to_i, spread_mod_value)

      # call the correct perform_* method
      if spread_at.present?
        t = spread_at.to_i + spread
        perform_at(t, *args)
      else
        t = spread_in.to_i + spread
        if t.zero?
          perform_async(*args)
        else
          perform_in(t, *args)
        end
      end
    end
Randomly schedule worker over a window of time.
 Arguments are keys of the final options hash.

 @param spread_duration [Number] Size of window to spread workers out over
 @param spread_in [Number] Start of window offset from now
 @param spread_at [Number] Start of window offset timestamp
 @param spread_method [rand|mod] perform either a random or modulo spread,
   default: *:rand*
 @param spread_mod_value [Integer] value to use for determining mod offset
 @return [String] Sidekiq job id