|
1
|
|
|
#!/usr/bin/env ruby |
|
2
|
|
|
# from: https://gist.github.com/kenn/5105061/raw/ac7ebc6be7008c35b72560cc4e05b7cc14eb4919/memstats.rb |
|
3
|
|
|
|
|
4
|
|
|
#------------------------------------------------------------------------------ |
|
5
|
|
|
# Aggregate Print useful information from /proc/[pid]/smaps |
|
6
|
|
|
# |
|
7
|
|
|
# pss - Roughly the amount of memory that is "really" being used by the pid |
|
8
|
|
|
# swap - Amount of swap this process is currently using |
|
9
|
|
|
# |
|
10
|
|
|
# Reference: |
|
11
|
|
|
# http://www.mjmwired.net/kernel/Documentation/filesystems/proc.txt#361 |
|
12
|
|
|
# |
|
13
|
|
|
# Example: |
|
14
|
|
|
# # ./memstats.rb 4386 |
|
15
|
|
|
# Process: 4386 |
|
16
|
|
|
# Command Line: /usr/bin/mongod -f /etc/mongo/mongod.conf |
|
17
|
|
|
# Memory Summary: |
|
18
|
|
|
# private_clean 107,132 kB |
|
19
|
|
|
# private_dirty 2,020,676 kB |
|
20
|
|
|
# pss 2,127,860 kB |
|
21
|
|
|
# rss 2,128,536 kB |
|
22
|
|
|
# shared_clean 728 kB |
|
23
|
|
|
# shared_dirty 0 kB |
|
24
|
|
|
# size 149,281,668 kB |
|
25
|
|
|
# swap 1,719,792 kB |
|
26
|
|
|
#------------------------------------------------------------------------------ |
|
27
|
|
|
|
|
28
|
|
|
class Mapping |
|
29
|
|
|
FIELDS = %w[ size rss shared_clean shared_dirty private_clean private_dirty swap pss ] |
|
30
|
|
|
attr_reader :address_start |
|
31
|
|
|
attr_reader :address_end |
|
32
|
|
|
attr_reader :perms |
|
33
|
|
|
attr_reader :offset |
|
34
|
|
|
attr_reader :device_major |
|
35
|
|
|
attr_reader :device_minor |
|
36
|
|
|
attr_reader :inode |
|
37
|
|
|
attr_reader :region |
|
38
|
|
|
|
|
39
|
|
|
attr_accessor :size |
|
40
|
|
|
attr_accessor :rss |
|
41
|
|
|
attr_accessor :shared_clean |
|
42
|
|
|
attr_accessor :shared_dirty |
|
43
|
|
|
attr_accessor :private_dirty |
|
44
|
|
|
attr_accessor :private_clean |
|
45
|
|
|
attr_accessor :swap |
|
46
|
|
|
attr_accessor :pss |
|
47
|
|
|
|
|
48
|
|
|
def initialize(lines) |
|
49
|
|
|
|
|
50
|
|
|
FIELDS.each do |field| |
|
51
|
|
|
self.send("#{field}=", 0) |
|
52
|
|
|
end |
|
53
|
|
|
|
|
54
|
|
|
parse_first_line(lines.shift) |
|
55
|
|
|
lines.each do |l| |
|
56
|
|
|
parse_field_line(l) |
|
57
|
|
|
end |
|
58
|
|
|
end |
|
59
|
|
|
|
|
60
|
|
|
def parse_first_line(line) |
|
61
|
|
|
parts = line.strip.split |
|
62
|
|
|
@address_start, @address_end = parts[0].split("-") |
|
63
|
|
|
@perms = parts[1] |
|
64
|
|
|
@offset = parts[2] |
|
65
|
|
|
@device_major, @device_minor = parts[3].split(":") |
|
66
|
|
|
@inode = parts[4] |
|
67
|
|
|
@region = parts[5] || "anonymous" |
|
68
|
|
|
end |
|
69
|
|
|
|
|
70
|
|
|
def parse_field_line(line) |
|
71
|
|
|
parts = line.strip.split |
|
72
|
|
|
field = parts[0].downcase.sub(':', '') |
|
73
|
|
|
if respond_to? "#{field}=" |
|
74
|
|
|
value = Float(parts[1]).to_i |
|
75
|
|
|
self.send("#{field}=", value) |
|
76
|
|
|
end |
|
77
|
|
|
end |
|
78
|
|
|
end |
|
79
|
|
|
|
|
80
|
|
|
def consume_mapping(map_lines, totals) |
|
81
|
|
|
m = Mapping.new(map_lines) |
|
82
|
|
|
|
|
83
|
|
|
Mapping::FIELDS.each do |field| |
|
84
|
|
|
totals[field] += m.send(field) |
|
85
|
|
|
end |
|
86
|
|
|
return m |
|
87
|
|
|
end |
|
88
|
|
|
|
|
89
|
|
|
def create_memstats_not_available(totals) |
|
90
|
|
|
Mapping::FIELDS.each do |field| |
|
91
|
|
|
totals[field] += Float::NAN |
|
92
|
|
|
end |
|
93
|
|
|
end |
|
94
|
|
|
|
|
95
|
|
|
abort 'usage: memstats [pid]' unless ARGV.first |
|
96
|
|
|
pid = ARGV.shift.to_i |
|
97
|
|
|
totals = Hash.new(0) |
|
98
|
|
|
mappings = [] |
|
99
|
|
|
|
|
100
|
|
|
begin |
|
101
|
|
|
File.open("/proc/#{pid}/smaps") do |smaps| |
|
102
|
|
|
|
|
103
|
|
|
map_lines = [] |
|
104
|
|
|
|
|
105
|
|
|
loop do |
|
106
|
|
|
break if smaps.eof? |
|
107
|
|
|
line = smaps.readline.strip |
|
108
|
|
|
case line |
|
109
|
|
|
when /\w+:\s+/ |
|
110
|
|
|
map_lines << line |
|
111
|
|
|
when /[0-9a-f]+:[0-9a-f]+\s+/ |
|
112
|
|
|
if map_lines.size > 0 then |
|
113
|
|
|
mappings << consume_mapping(map_lines, totals) |
|
114
|
|
|
end |
|
115
|
|
|
map_lines.clear |
|
116
|
|
|
map_lines << line |
|
117
|
|
|
else |
|
118
|
|
|
break |
|
119
|
|
|
end |
|
120
|
|
|
end |
|
121
|
|
|
end |
|
122
|
|
|
rescue |
|
123
|
|
|
create_memstats_not_available(totals) |
|
124
|
|
|
end |
|
125
|
|
|
|
|
126
|
|
|
# http://rubyforge.org/snippet/download.php?type=snippet&id=511 |
|
127
|
|
|
def format_number(n) |
|
128
|
|
|
n.to_s.gsub(/(\d)(?=\d{3}+(?:\.|$))(\d{3}\..*)?/, '\1,\2') |
|
129
|
|
|
end |
|
130
|
|
|
|
|
131
|
|
|
def get_commandline(pid) |
|
132
|
|
|
commandline = IO.read("/proc/#{pid}/cmdline").split("\0") |
|
133
|
|
|
if commandline.first =~ /java$/ then |
|
134
|
|
|
loop { break if commandline.shift == "-jar" } |
|
135
|
|
|
return "[java] #{commandline.shift}" |
|
136
|
|
|
end |
|
137
|
|
|
return commandline.join(' ') |
|
138
|
|
|
end |
|
139
|
|
|
|
|
140
|
|
|
if ARGV.include? '--yaml' |
|
141
|
|
|
require 'yaml' |
|
142
|
|
|
puts Hash[*totals.map do |k, v| |
|
143
|
|
|
[k + '_kb', v] |
|
144
|
|
|
end.flatten].to_yaml |
|
145
|
|
|
else |
|
146
|
|
|
puts "#{"Process:".ljust(20)} #{pid}" |
|
147
|
|
|
puts "#{"Command Line:".ljust(20)} #{get_commandline(pid)}" |
|
148
|
|
|
puts "Memory Summary:" |
|
149
|
|
|
totals.keys.sort.each do |k| |
|
150
|
|
|
puts " #{k.ljust(20)} #{format_number(totals[k]).rjust(12)} kB" |
|
151
|
|
|
end |
|
152
|
|
|
end |
|
153
|
|
|
|