discourse/script/memstats.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

139 lines
3.8 KiB
Ruby
Raw Normal View History

#!/usr/bin/env ruby
# frozen_string_literal: true
# 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)
2019-05-06 20:27:05 -05:00
FIELDS.each { |field| self.public_send("#{field}=", 0) }
parse_first_line(lines.shift)
lines.each { |l| parse_field_line(l) }
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
2019-05-06 20:27:05 -05:00
self.public_send("#{field}=", value)
end
end
end
def consume_mapping(map_lines, totals)
m = Mapping.new(map_lines)
2019-05-06 20:27:05 -05:00
Mapping::FIELDS.each { |field| totals[field] += m.public_send(field) }
m
end
def create_memstats_not_available(totals)
Mapping::FIELDS.each { |field| totals[field] += Float::NAN }
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+/
mappings << consume_mapping(map_lines, totals) if map_lines.size > 0
map_lines.clear
map_lines << line
else
break
end
end
end
rescue StandardError
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)
2021-10-27 03:39:28 -05:00
commandline = File.read("/proc/#{pid}/cmdline").split("\0")
if commandline.first =~ /java$/
loop { break if commandline.shift == "-jar" }
return "[java] #{commandline.shift}"
end
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 { |k| puts " #{k.ljust(20)} #{format_number(totals[k]).rjust(12)} kB" }
end