#!/usr/bin/env ruby # Phusion Passenger - https://www.phusionpassenger.com/ # Copyright (c) 2010-2013 Phusion # # "Phusion Passenger" is a trademark of Hongli Lai & Ninh Bui. # # 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 NONINFRINGEMENT. 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. ## Magic comment: set locations.ini ## source_root = File.expand_path("..", File.dirname(__FILE__)) $LOAD_PATH.unshift("#{source_root}/lib") require 'phusion_passenger' PhusionPassenger.locate_directories require 'phusion_passenger/platform_info' require 'phusion_passenger/admin_tools/server_instance' require 'phusion_passenger/utils/ansi_colors' require 'optparse' include PhusionPassenger::AdminTools include PhusionPassenger::Utils::AnsiColors DEFAULT_OPTIONS = { :show => 'pool' }.freeze ##### Show status command ##### def command_show_status(argv, options) if argv.empty? server_instance = find_sole_server_instance else server_instance = find_server_instance_on_pid(argv[0].to_i) end show_status(server_instance, options) end def find_sole_server_instance server_instances = ServerInstance.list if server_instances.empty? abort "ERROR: Phusion Passenger doesn't seem to be running." elsif server_instances.size == 1 return server_instances.first else puts "It appears that multiple Passenger instances are running. Please select a" puts "specific one by running:" puts puts " passenger-status " puts puts "The following Passenger instances are running:" server_instances.each do |instance| puts " PID: #{instance.pid}" end exit 1 end end def find_server_instance_on_pid(pid) if server_instance = ServerInstance.for_pid(pid) return server_instance else abort "ERROR: there doesn't seem to be a Phusion Passenger instance running on PID #{pid}." end end def show_status(server_instance, options) puts "Version : #{PhusionPassenger::VERSION_STRING}" puts "Date : #{Time.now}" puts "Instance: #{server_instance.pid}" case options[:show] when 'pool' client = server_instance.connect(:role => :passenger_status) begin puts client.pool_status(:verbose => options[:verbose], :colorize => true) rescue SystemCallError => e STDERR.puts "*** ERROR: Cannot query status for Phusion Passenger instance #{server_instance.pid}:" STDERR.puts e.to_s exit 2 end when 'requests' client = server_instance.connect(:role => :passenger_status) begin puts client.helper_agent_requests rescue SystemCallError => e STDERR.puts "*** ERROR: Cannot query status for Phusion Passenger instance #{server_instance.pid}:" STDERR.puts e.to_s exit 2 end when 'backtraces' client = server_instance.connect(:role => :passenger_status) begin text = client.helper_agent_backtraces rescue SystemCallError => e STDERR.puts "*** ERROR: Cannot query status for Phusion Passenger instance #{control_process.pid}:" STDERR.puts e.to_s exit 2 end # Colorize output text.gsub!(/^(Thread .*:)$/, BLACK_BG + YELLOW + '\1' + RESET) text.gsub!(/^( +in '.*? )(.*?)\(/, '\1' + BOLD + '\2' + RESET + '(') puts text when 'xml' client = server_instance.connect(:role => :passenger_status) begin xml = client.pool_xml rescue SystemCallError => e STDERR.puts "*** ERROR: Cannot query status for Phusion Passenger instance #{control_process.pid}:" STDERR.puts e.to_s exit 2 end indented = format_with_xmllint(xml) if indented puts indented else puts xml STDERR.puts "*** Tip: if you install the 'xmllint' command then the XML output will be indented." end when 'union_station' client = server_instance.connect(:role => :passenger_status, :socket_name => 'logging_admin') begin response = client.logging_agent_status rescue SystemCallError => e STDERR.puts "*** ERROR: Cannot query status for Phusion Passenger instance #{control_process.pid}:" STDERR.puts e.to_s exit 2 end puts response end rescue ServerInstance::RoleDeniedError require 'phusion_passenger/platform_info/ruby' STDERR.puts "*** ERROR: You are not authorized to query the status for this Phusion " << "Passenger instance. Please try again with '#{PhusionPassenger::PlatformInfo.ruby_sudo_command}'." exit 2 rescue ServerInstance::CorruptedDirectoryError STDERR.puts "*** ERROR: The server instance directory #{server_instance.path} is corrupted. " << "This could have two causes:\n" << "\n" << " 1. The Phusion Passenger instance is no longer running, but failed to cleanup the directory. " << "Please delete this directory and ignore the problem.\n" << " 2. An external program corrupted the directory. Please restart Phusion Passenger.\n" exit 2 ensure client.close if client end def format_with_xmllint(xml) return nil if !PhusionPassenger::PlatformInfo.find_command('xmllint') require 'open3' require 'thread' ENV['XMLLINT_INDENT'] = ' ' Open3.popen3("xmllint", "--format", "-") do |stdin, stdout, stderr| stdout_text = nil stderr_text = nil thread1 = Thread.new do stdin.write(xml) stdin.close end thread2 = Thread.new do stdout_text = stdout.read stdout.close end thread3 = Thread.new do stderr_text = stderr.read stderr.close end thread1.join thread2.join thread3.join if stdout_text.nil? || stdout_text.empty? if stderr_text !~ /No such file or directory/ && stderr_text !~ /command not found/ STDERR.puts stderr_text end return nil else return stdout_text end end end ##### Main command dispatcher ##### def create_option_parser(options) return OptionParser.new do |opts| opts.banner = "Usage: passenger-status [options] [Phusion Passenger's PID]" opts.separator "" opts.separator "Tool for inspecting Phusion Passenger's internal status." opts.separator "" opts.separator "Options:" opts.on("--show=pool|requests|backtraces|xml|union_station", String, "Whether to show the pool's contents,\n" << "#{' ' * 37}the currently running requests,\n" << "#{' ' * 37}the backtraces of all threads or an XML\n" << "#{' ' * 37}description of the pool.") do |what| if what !~ /\A(pool|requests|backtraces|xml|union_station)\Z/ STDERR.puts "Invalid argument for --show." exit 1 else options[:show] = what end end opts.on("--verbose", "-v", "Show verbose information.") do options[:verbose] = true end end end def parse_argv options = DEFAULT_OPTIONS.dup parser = create_option_parser(options) begin parser.parse! rescue OptionParser::ParseError => e puts e puts puts "Please see '--help' for valid options." exit 1 end return options end def infer_command if !ARGV[0] || ARGV[0] =~ /\A[0-9]+\Z/ return [:show_status, ARGV.dup] else command_name, *argv = ARGV if respond_to?("command_#{command_name}") return [command_name, argv] else abort "ERROR: unrecognized command '#{command_name}'" end end end def start options = parse_argv command, argv = infer_command send("command_#{command}", argv, options) end start