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