class PhusionPassenger::AdminTools::MemoryStats

def apache_processes

not running. If the Apache executable name is unknown then nil will be returned.
Returns a list of Apache processes, which may be the empty list if Apache is
def apache_processes
	@apache_processes ||= begin
		if PlatformInfo.httpd
			processes = list_processes(:exe => PlatformInfo.httpd)
			if processes.empty?
				# On some Linux distros, the Apache worker processes
				# are called "httpd.worker"
				processes = list_processes(:exe => "#{PlatformInfo.httpd}.worker")
			end
			processes
		else
			nil
		end
	end
end

def determine_private_dirty_rss(pid)

Returns the private dirty RSS for the given process, in KB.
def determine_private_dirty_rss(pid)
	total = 0
	File.read("/proc/#{pid}/smaps").split("\n").each do |line|
		line =~ /^(Private)_Dirty: +(\d+)/
		if $2
			total += $2.to_i
		end
	end
	if total == 0
		return nil
	else
		return total
	end
rescue Errno::EACCES, Errno::ENOENT
	return nil
end

def list_processes(options)

list_processes(:match => 'Passenger FrameworkSpawner')
# Search by process name.

list_processes(:name => 'ruby1.8')
# Search by executable name.

list_processes(:exe => '/usr/sbin/apache2')
# Search by executable path.

Returns a list of Process objects that match the given search criteria.
def list_processes(options)
	if options[:exe]
		name = options[:exe].sub(/.*\/(.*)/, '\1')
		if os_name =~ /linux/
			ps = "ps -C '#{name}'"
		else
			ps = "ps -A"
			options[:match] = Regexp.new(Regexp.escape(name))
		end
	elsif options[:name]
		if os_name =~ /linux/
			ps = "ps -C '#{options[:name]}'"
		else
			ps = "ps -A"
			options[:match] = Regexp.new(" #{Regexp.escape(options[:name])}")
		end
	elsif options[:match]
		ps = "ps -A"
	else
		raise ArgumentError, "Invalid options."
	end
	
	processes = []
	case os_name
	when /solaris/
		list = `#{ps} -o pid,ppid,nlwp,vsz,rss,pcpu,comm`.split("\n")
		threads_known = true
	when /macosx/
		list = `#{ps} -w -o pid,ppid,vsz,rss,%cpu,command`.split("\n")
		threads_known = false
	else
		list = `#{ps} -w -o pid,ppid,nlwp,vsz,rss,%cpu,command`.split("\n")
		threads_known = true
	end
	list.shift
	list.each do |line|
		line.gsub!(/^ */, '')
		line.gsub!(/ *$/, '')
		
		p = Process.new
		if threads_known
			p.pid, p.ppid, p.threads, p.vm_size, p.rss, p.cpu, p.name = line.split(/ +/, 7)
		else
			p.pid, p.ppid, p.vm_size, p.rss, p.cpu, p.name = line.split(/ +/, 6)
		end
		p.name.sub!(/\Aruby: /, '')
		p.name.sub!(/ \(ruby\)\Z/, '')
		if p.name !~ /^ps/ && (!options[:match] || p.name.match(options[:match]))
			# Convert some values to integer.
			[:pid, :ppid, :vm_size, :rss].each do |attr|
				p.send("#{attr}=", p.send(attr).to_i)
			end
			p.threads = p.threads.to_i if threads_known
			if platform_provides_private_dirty_rss_information?
				p.private_dirty_rss = determine_private_dirty_rss(p.pid)
			end
			processes << p
		end
	end
	return processes
end

def nginx_processes

Nginx is not running.
Returns a list of Nginx processes, which may be the empty list if
def nginx_processes
	@nginx_processes ||= list_processes(:exe => "nginx")
end

def os_name

def os_name
	return PlatformInfo.os_name
end

def passenger_processes

Phusion Passenger is not running.
Returns a list of Phusion Passenger processes, which may be the empty list if
def passenger_processes
	@passenger_processes ||= list_processes(:match =>
		/((^| )Passenger|(^| )Rails:|(^| )Rack:|wsgi-loader.py)/)
end

def platform_provides_private_dirty_rss_information?

def platform_provides_private_dirty_rss_information?
	return os_name == "linux"
end

def root_privileges_required_for_private_dirty_rss?

Only meaningful if #platform_provides_private_dirty_rss_information? returns true.
Returns whether root privileges are required in order to measure private dirty RSS.
def root_privileges_required_for_private_dirty_rss?
	all_processes = (apache_processes || []) + nginx_processes + passenger_processes
	return all_processes.any?{ |p| p.private_dirty_rss.nil? }
end

def should_show_private_dirty_rss?

def should_show_private_dirty_rss?
	return platform_provides_private_dirty_rss_information? &&
		(::Process.euid == 0 || root_privileges_required_for_private_dirty_rss?)
end

def sum_memory_usage(processes)

if some process's memory usage cannot be determined.
and +accurate+ indicates whether this sum is accurate. This may be false
Returns a pair [usage, accurate]. +usage+ is the summed memory usage in KB,
Returns the sum of the memory usages of all given processes.
def sum_memory_usage(processes)
	total = 0
	if should_show_private_dirty_rss?
		accurate = true
		processes.each do |p|
			if p.private_dirty_rss.is_a?(Numeric)
				total += p.private_dirty_rss
			else
				accurate = true
			end
		end
		return [total, accurate]
	else
		processes.each do |p|
			total += p.rss
		end
		return [total, true]
	end
end

def system_ram_usage

if the system's RAM usage cannot be determined.
Returns a tuple [total, used] where both numbers are in KB, or nil
Determine the system's RAM usage, not including swap.
def system_ram_usage
	@total_system_ram ||= begin
		case os_name
		when /linux/
			free_text = `free -k`
			
			free_text =~ %r{Mem:(.+)$}
			line = $1.strip
			total = line.split(/ +/).first.to_i
			
			free_text =~ %r{buffers/cache:(.+)$}
			line = $1.strip
			used = line.split(/ +/).first.to_i
			
			[total, used]
		when /macosx/
			vm_stat = `vm_stat`
			vm_stat =~ /page size of (\d+) bytes/
			page_size = $1
			vm_stat =~ /Pages free: +(\d+)/
			free = $1
			vm_stat =~ /Pages active: +(\d+)/
			active = $1
			vm_stat =~ /Pages inactive: +(\d+)/
			inactive = $1
			vm_stat =~ /Pages wired down: +(\d+)/
			wired = $1
			
			if page_size && free && active && inactive && wired
				page_size = page_size.to_i
				free = free.to_i * page_size / 1024
				active = active.to_i * page_size / 1024
				inactive = inactive.to_i * page_size / 1024
				wired = wired.to_i * page_size / 1024
				
				used = active + wired
				[free + inactive + used, used]
			else
				nil
			end
		else
			`top` =~ /(\d+)(K|M) Active, (\d+)(K|M) Inact, (\d+)(K|M) Wired,.*?(\d+)(K|M) Free/
			if $1 && $2 && $3 && $4 && $5 && $6 && $7 && $8
				to_kb = lambda do |number, unit|
					if unit == 'K'
						number.to_i
					else
						number.to_i * 1024
					end
				end
				
				active = to_kb.call($1, $2)
				inactive = to_kb.call($3, $4)
				wired = to_kb.call($5, $6)
				free = to_kb.call($7, $8)
				
				used = active + wired
				[free + inactive + used, used]
			else
				nil
			end
		end
	end
end