155 lines
3.9 KiB
Ruby
155 lines
3.9 KiB
Ruby
#!/usr/bin/env ruby
|
|
# from: https://gist.github.com/kenn/5105061/raw/ac7ebc6be7008c35b72560cc4e05b7cc14eb4919/memstats.rb
|
|
|
|
#------------------------------------------------------------------------------
|
|
# Aggregate Print useful information from /proc/[pid]/smaps
|
|
#
|
|
# pss - Roughly the amount of memory that is "really" being used by the pid
|
|
# swap - Amount of swap this process is currently using
|
|
#
|
|
# Reference:
|
|
# http://www.mjmwired.net/kernel/Documentation/filesystems/proc.txt#361
|
|
#
|
|
# Example:
|
|
# # ./memstats.rb 4386
|
|
# Process: 4386
|
|
# Command Line: /usr/bin/mongod -f /etc/mongo/mongod.conf
|
|
# Memory Summary:
|
|
# private_clean 107,132 kB
|
|
# private_dirty 2,020,676 kB
|
|
# pss 2,127,860 kB
|
|
# rss 2,128,536 kB
|
|
# shared_clean 728 kB
|
|
# shared_dirty 0 kB
|
|
# size 149,281,668 kB
|
|
# swap 1,719,792 kB
|
|
#------------------------------------------------------------------------------
|
|
|
|
class Mapping
|
|
FIELDS = %w[ size rss shared_clean shared_dirty private_clean private_dirty swap pss ]
|
|
attr_reader :address_start
|
|
attr_reader :address_end
|
|
attr_reader :perms
|
|
attr_reader :offset
|
|
attr_reader :device_major
|
|
attr_reader :device_minor
|
|
attr_reader :inode
|
|
attr_reader :region
|
|
|
|
attr_accessor :size
|
|
attr_accessor :rss
|
|
attr_accessor :shared_clean
|
|
attr_accessor :shared_dirty
|
|
attr_accessor :private_dirty
|
|
attr_accessor :private_clean
|
|
attr_accessor :swap
|
|
attr_accessor :pss
|
|
|
|
def initialize( lines )
|
|
|
|
FIELDS.each do |field|
|
|
self.send("#{field}=", 0)
|
|
end
|
|
|
|
parse_first_line( lines.shift )
|
|
lines.each do |l|
|
|
parse_field_line(l)
|
|
end
|
|
end
|
|
|
|
def parse_first_line( line )
|
|
parts = line.strip.split
|
|
@address_start, @address_end = parts[0].split("-")
|
|
@perms = parts[1]
|
|
@offset = parts[2]
|
|
@device_major, @device_minor = parts[3].split(":")
|
|
@inode = parts[4]
|
|
@region = parts[5] || "anonymous"
|
|
end
|
|
|
|
def parse_field_line( line )
|
|
parts = line.strip.split
|
|
field = parts[0].downcase.sub(':','')
|
|
if respond_to? "#{field}="
|
|
value = Float(parts[1]).to_i
|
|
self.send( "#{field}=", value )
|
|
end
|
|
end
|
|
end
|
|
|
|
def consume_mapping( map_lines, totals )
|
|
m = Mapping.new( map_lines )
|
|
|
|
Mapping::FIELDS.each do |field|
|
|
totals[field] += m.send( field )
|
|
end
|
|
return m
|
|
end
|
|
|
|
def create_memstats_not_available( totals )
|
|
Mapping::FIELDS.each do |field|
|
|
totals[field] += Float::NAN
|
|
end
|
|
end
|
|
|
|
abort 'usage: memstats [pid]' unless ARGV.first
|
|
pid = ARGV.shift.to_i
|
|
totals = Hash.new(0)
|
|
mappings = []
|
|
|
|
begin
|
|
File.open( "/proc/#{pid}/smaps" ) do |smaps|
|
|
|
|
map_lines = []
|
|
|
|
loop do
|
|
break if smaps.eof?
|
|
line = smaps.readline.strip
|
|
case line
|
|
when /\w+:\s+/
|
|
map_lines << line
|
|
when /[0-9a-f]+:[0-9a-f]+\s+/
|
|
if map_lines.size > 0 then
|
|
mappings << consume_mapping( map_lines, totals )
|
|
end
|
|
map_lines.clear
|
|
map_lines << line
|
|
else
|
|
break
|
|
end
|
|
end
|
|
end
|
|
rescue
|
|
create_memstats_not_available( totals )
|
|
end
|
|
|
|
# http://rubyforge.org/snippet/download.php?type=snippet&id=511
|
|
def format_number( n )
|
|
n.to_s.gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/,'\1,\2')
|
|
end
|
|
|
|
def get_commandline( pid )
|
|
commandline = IO.read( "/proc/#{pid}/cmdline" ).split("\0")
|
|
if commandline.first =~ /java$/ then
|
|
loop { break if commandline.shift == "-jar" }
|
|
return "[java] #{commandline.shift}"
|
|
end
|
|
return commandline.join(' ')
|
|
end
|
|
|
|
if ARGV.include? '--yaml'
|
|
require 'yaml'
|
|
puts Hash[*totals.map do |k,v|
|
|
[k + '_kb', v]
|
|
end.flatten].to_yaml
|
|
else
|
|
puts "#{"Process:".ljust(20)} #{pid}"
|
|
puts "#{"Command Line:".ljust(20)} #{get_commandline(pid)}"
|
|
puts "Memory Summary:"
|
|
totals.keys.sort.each do |k|
|
|
puts " #{k.ljust(20)} #{format_number( totals[k] ).rjust(12)} kB"
|
|
end
|
|
end
|
|
|
|
|