class PhusionPassenger::AdminTools::MemoryStats
def apache_processes
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)
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)
# 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
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
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?
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)
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
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