Class: Raptor::CLI
- Inherits:
-
Object
- Object
- Raptor::CLI
- Defined in:
- lib/raptor/cli.rb,
sig/generated/raptor/cli.rbs
Overview
Command-line interface for the Raptor web server.
CLI parses command-line arguments and starts the server cluster with the specified configuration options. It supports configuring the number of workers, ractors, threads, bind addresses, and various client timeout settings.
Constant Summary collapse
- DEFAULT_WORKER_COUNT =
Integer(Concurrent.available_processor_count)
- NESTED_OPTION_KEYS =
[:connection, :http1, :http2].freeze
- DEFAULT_OPTIONS =
{ binds: ["tcp://0.0.0.0:9292"].freeze, socket_backlog: 1024, drain_accept_queue: false, workers: DEFAULT_WORKER_COUNT, threads: 3, max_threads: Float::INFINITY, cpu_affinity: false, clean_thread_locals: true, clean_fiber_locals: false, rackup: "config.ru", chdir: nil, environment: nil, connection: { first_data_timeout: 30, chunk_data_timeout: 10, write_timeout: 5, max_body_size: nil, body_spool_threshold: 1024 * 1024, }, http1: { ractors: nil, persistent_data_timeout: 65, max_keepalive_requests: 1000, }, http2: { ractors: nil, max_concurrent_streams: 100, }, worker_boot_timeout: 60, worker_timeout: 60, worker_drain_timeout: 25, worker_shutdown_timeout: 30, refork_after: (RUBY_PLATFORM.include?("linux") ? 1000 : nil), before_fork: [].freeze, before_worker_boot: [].freeze, before_worker_shutdown: [].freeze, before_refork: [].freeze, stats_file: "tmp/raptor.json", control_url: nil, pid_file: nil, stdout_file: nil, stderr_file: nil, access_log_file: nil, }.freeze
- DEFAULT_CONFIG_PATHS =
["raptor.rb", "config/raptor.rb"].freeze
Class Method Summary collapse
-
.default_config_path(root = Dir.pwd) ⇒ String?
Returns the first existing path in DEFAULT_CONFIG_PATHS resolved against
root, or nil if none exist. -
.load_config_file(path) ⇒ Hash{Symbol => untyped}
Loads a Ruby config file and returns the options hash it evaluates to.
-
.parse_max_threads(value, threads:) ⇒ Integer, Float
Parses a maximum thread count from a CLI, config, or Rack handler value.
Instance Method Summary collapse
-
#apply_config_file(path) ⇒ void
Loads a config file and merges it into
@optionsover the defaults. -
#apply_environment ⇒ void
Applies worker and thread settings from the environment.
-
#create_parser ⇒ OptionParser
Creates the OptionParser instance with all supported command-line options.
-
#extract_config_path(argv) ⇒ String?
Returns the path from a
-c/--configflag inargv, recognising all four OptionParser-accepted forms (-c PATH,-cPATH,--config PATH,--config=PATH). -
#initialize(argv) ⇒ CLI
constructor
Creates a new CLI instance from
argv. -
#run ⇒ void
Runs the requested command.
-
#run_stats ⇒ void
Reads and prints the stats file.
Constructor Details
#initialize(argv) ⇒ CLI
Creates a new CLI instance from argv. A rackup file may be given
as the first positional argument; every other value is parsed as a
flag.
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/raptor/cli.rb', line 144 def initialize(argv) @options = DEFAULT_OPTIONS.dup NESTED_OPTION_KEYS.each { |key| @options[key] = @options[key].dup } @options[:launch_command] = $PROGRAM_NAME @options[:launch_argv] = argv.dup if argv.first == "stats" argv.shift @command = :stats else @command = :server end apply_config_file(extract_config_path(argv) || self.class.default_config_path) apply_environment @parser = create_parser @parser.parse!(argv) @options[:rackup] = argv.first if @command == :server && argv.first @options[:max_threads] = self.class.parse_max_threads(@options[:max_threads], threads: @options[:threads]) end |
Class Method Details
.default_config_path(root = Dir.pwd) ⇒ String?
Returns the first existing path in DEFAULT_CONFIG_PATHS resolved
against root, or nil if none exist.
102 103 104 |
# File 'lib/raptor/cli.rb', line 102 def self.default_config_path(root = Dir.pwd) DEFAULT_CONFIG_PATHS.find { |path| File.exist?(File.join(root, path)) } end |
.load_config_file(path) ⇒ Hash{Symbol => untyped}
Loads a Ruby config file and returns the options hash it evaluates
to. Evaluated at the top level so Raptor::* constants resolve the
same as in a regular script.
88 89 90 91 92 93 |
# File 'lib/raptor/cli.rb', line 88 def self.load_config_file(path) config = eval(File.read(path), TOPLEVEL_BINDING, path, 1) raise ArgumentError, "Config file at #{path.inspect} must return a Hash, got #{config.class}" unless config.is_a?(Hash) config end |
.parse_max_threads(value, threads:) ⇒ Integer, Float
Parses a maximum thread count from a CLI, config, or Rack handler value.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/raptor/cli.rb', line 115 def self.parse_max_threads(value, threads:) raise ArgumentError, "max_threads cannot be nil" unless value max_threads = if value == "unlimited" || value == Float::INFINITY Float::INFINITY elsif value.is_a?(Integer) value else Integer(value, 10) end raise ArgumentError, "max_threads must be greater than or equal to threads" if max_threads < threads max_threads end |
Instance Method Details
#apply_config_file(path) ⇒ void
This method returns an undefined value.
Loads a config file and merges it into @options over the defaults.
227 228 229 230 231 232 233 234 235 236 237 238 |
# File 'lib/raptor/cli.rb', line 227 def apply_config_file(path) return unless path config = self.class.load_config_file(path) config.each do |key, value| if NESTED_OPTION_KEYS.include?(key) && value.is_a?(Hash) @options[key] = @options[key].merge(value) else @options[key] = value end end end |
#apply_environment ⇒ void
This method returns an undefined value.
Applies worker and thread settings from the environment. Explicit CLI options are parsed afterwards and take precedence.
246 247 248 249 250 |
# File 'lib/raptor/cli.rb', line 246 def apply_environment @options[:workers] = Integer(ENV["RAPTOR_WORKERS"], 10) if ENV["RAPTOR_WORKERS"] @options[:threads] = Integer(ENV["RAPTOR_THREADS"], 10) if ENV["RAPTOR_THREADS"] @options[:max_threads] = ENV["RAPTOR_MAX_THREADS"] if ENV["RAPTOR_MAX_THREADS"] end |
#create_parser ⇒ OptionParser
Creates the OptionParser instance with all supported command-line options.
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/raptor/cli.rb', line 257 def create_parser OptionParser.new do |opts| opts. = "Usage: raptor [options] [rackup file]" opts.on("-c", "--config PATH", String, "Load configuration from PATH") do # Loaded in #initialize before parsing so CLI args can override config values end opts.on("-b", "--bind URI", String, "Bind address (default: tcp://0.0.0.0:9292)") do |bind| if @options[:binds].equal?(DEFAULT_OPTIONS[:binds]) @options[:binds] = [bind] else @options[:binds] << bind end end opts.on("--socket-backlog NUM", Integer, "Socket listen backlog (default: 1024)") do |num| @options[:socket_backlog] = num end opts.on("--[no-]drain-accept-queue", "Drain the kernel accept queue on shutdown (default: off)") do |bool| @options[:drain_accept_queue] = bool end opts.on("-w", "--workers NUM", Integer, "Number of worker processes (default: #{DEFAULT_WORKER_COUNT})") do |num| @options[:workers] = num end opts.on("-t", "--threads NUM", Integer, "Number of application threads per worker (default: 3)") do |num| @options[:threads] = num end opts.on("--max-threads NUM", "Maximum application threads per worker (`unlimited` for no limit; default: unlimited)") do |num| @options[:max_threads] = num end opts.on("--[no-]cpu-affinity", "Pin each worker process to a CPU (default: off)") do |bool| @options[:cpu_affinity] = bool end opts.on("--[no-]clean-thread-locals", "Clear application thread locals after each request (default: on)") do |bool| @options[:clean_thread_locals] = bool end opts.on("--[no-]clean-fiber-locals", "Process each request in a fresh Fiber (default: off)") do |bool| @options[:clean_fiber_locals] = bool end opts.on("-C", "--chdir PATH", String, "Change to PATH before loading the Rack application (default: none)") do |path| @options[:chdir] = path end opts.on("-e", "--environment ENV", String, "Application environment label; falls back to $RAILS_ENV, then $RACK_ENV, then development") do |env| @options[:environment] = env end opts.on("--first-data-timeout SECONDS", Integer, "First data timeout in seconds (default: 30)") do |timeout| @options[:connection][:first_data_timeout] = timeout end opts.on("--chunk-data-timeout SECONDS", Integer, "Chunk data timeout in seconds (default: 10)") do |timeout| @options[:connection][:chunk_data_timeout] = timeout end opts.on("--write-timeout SECONDS", Integer, "Per-write socket timeout in seconds (default: 5)") do |timeout| @options[:connection][:write_timeout] = timeout end opts.on("--max-body-size BYTES", Integer, "Maximum request body size in bytes (default: unlimited)") do |bytes| @options[:connection][:max_body_size] = bytes end opts.on("--body-spool-threshold BYTES", Integer, "Request body spool threshold in bytes (default: #{1024 * 1024})") do |bytes| @options[:connection][:body_spool_threshold] = bytes end opts.on("--http1-ractors NUM", Integer, "Number of HTTP/1.1 pipeline ractors per worker (default: `round(cores / workers)`, clamped to 1..3)") do |num| @options[:http1][:ractors] = num end opts.on("--http1-persistent-data-timeout SECONDS", Integer, "HTTP/1.1 keep-alive idle timeout in seconds (default: 65)") do |timeout| @options[:http1][:persistent_data_timeout] = timeout end opts.on("--http1-max-keepalive-requests NUM", Integer, "Maximum HTTP/1.1 requests per keep-alive connection (default: 1000)") do |num| @options[:http1][:max_keepalive_requests] = num end opts.on("--http2-ractors NUM", Integer, "Number of HTTP/2 pipeline ractors per worker (default: `round(cores / workers)`, clamped to 1..2)") do |num| @options[:http2][:ractors] = num end opts.on("--http2-max-concurrent-streams NUM", Integer, "Maximum HTTP/2 concurrent streams per connection (default: 100)") do |num| @options[:http2][:max_concurrent_streams] = num end opts.on("--worker-boot-timeout SECONDS", Integer, "Worker boot timeout in seconds (default: 60)") do |timeout| @options[:worker_boot_timeout] = timeout end opts.on("--worker-timeout SECONDS", Integer, "Worker check-in timeout in seconds (default: 60)") do |timeout| @options[:worker_timeout] = timeout end opts.on("--worker-drain-timeout SECONDS", Integer, "Worker request-drain timeout in seconds (default: 25)") do |timeout| @options[:worker_drain_timeout] = timeout end opts.on("--worker-shutdown-timeout SECONDS", Integer, "Worker shutdown timeout in seconds (default: 30)") do |timeout| @options[:worker_shutdown_timeout] = timeout end opts.on("--refork-after NUM", Integer, "Refork workers from a warmed source after any worker crosses NUM requests; 0 disables (default: 1000)") do |num| @options[:refork_after] = num end opts.on("--stats-file PATH", String, "Stats file path (default: tmp/raptor.json)") do |path| @options[:stats_file] = path end opts.on("--control-url URI", String, "Serve cluster stats on a unix:// URI (default: off)") do |uri| @options[:control_url] = uri end opts.on("--pid-file PATH", String, "PID file path (default: none)") do |path| @options[:pid_file] = path end opts.on("--stdout-file PATH", String, "Redirect stdout to PATH; reopened on SIGHUP (default: none)") do |path| @options[:stdout_file] = path end opts.on("--stderr-file PATH", String, "Redirect stderr to PATH; reopened on SIGHUP (default: none)") do |path| @options[:stderr_file] = path end opts.on("--access-log-file PATH", String, "Write Common Log Format access logs to PATH; reopened on SIGHUP (default: none)") do |path| @options[:access_log_file] = path end opts.on("--help", "Show this help") do puts opts exit end opts.on("-v", "--version", "Show version") do puts Raptor::VERSION exit end end end |
#extract_config_path(argv) ⇒ String?
Returns the path from a -c/--config flag in argv, recognising
all four OptionParser-accepted forms (-c PATH, -cPATH,
--config PATH, --config=PATH).
211 212 213 214 215 216 217 218 219 |
# File 'lib/raptor/cli.rb', line 211 def extract_config_path(argv) argv.each_with_index do |arg, index| case arg when "-c", "--config" then return argv[index + 1] when /\A--config=(.*)\z/, /\A-c(.+)\z/ then return Regexp.last_match(1) end end nil end |
#run ⇒ void
This method returns an undefined value.
Runs the requested command.
172 173 174 |
# File 'lib/raptor/cli.rb', line 172 def run @command == :stats ? run_stats : Cluster.run(@options) end |
#run_stats ⇒ void
This method returns an undefined value.
Reads and prints the stats file.
183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/raptor/cli.rb', line 183 def run_stats stats_file = @options[:stats_file] unless File.exist?(stats_file) warn "No stats file at #{stats_file.inspect}. Is Raptor running?" exit 1 end data = JSON.parse(File.read(stats_file), symbolize_names: true) puts "Master PID: #{data[:master_pid]}" data[:workers].each do |worker| status = worker[:booted] ? "booted" : "starting" last_checkin = Time.at(worker[:last_checkin]).strftime("%H:%M:%S") puts "Worker #{worker[:index]} (phase #{worker[:phase]}): pid=#{worker[:pid]}, requests=#{worker[:requests]}, " \ "busy=#{worker[:busy_threads]}/#{worker[:thread_capacity]}, backlog=#{worker[:backlog]}, " \ "#{status}, last_checkin=#{last_checkin}" end end |