Process::MetricsSourceProcessMetricsMemoryLinux

class Linux

Linux implementation of memory metrics using /proc/[pid]/smaps and /proc/[pid]/stat.

Definitions

def self.capture_faults(pid, usage)

Extract minor/major page fault counters from /proc/[pid]/stat and assign to usage.

Signature

parameter pid Integer

The process ID.

parameter usage Memory

The Memory instance to populate with fault counters.

Implementation

def self.capture_faults(pid, usage)
	stat = File.read("/proc/#{pid}/stat")
	# The comm field can contain spaces and parentheses; find the closing ')':
	rparen_index = stat.rindex(")")
	return unless rparen_index
	fields = stat[(rparen_index+2)..-1].split(/\s+/)
	# proc(5): field 10=minflt, 12=majflt; our fields array is 0-indexed from field 3.
	usage.minor_faults = fields[10-3].to_i
	usage.major_faults = fields[12-3].to_i
rescue
	# Ignore.
end

def self.total_size

Signature

returns Numeric

Total memory size in kilobytes.

Implementation

def self.total_size
	File.read("/proc/meminfo").each_line do |line|
		if /MemTotal:\s+(?<total>\d+) kB/ =~ line
			return total.to_i
		end
	end
end

SMAP = {...}

The fields that will be extracted from the smaps data.

Implementation

SMAP = {
	"Rss" => :resident_size,
	"Pss" => :proportional_size,
	"Shared_Clean" => :shared_clean_size,
	"Shared_Dirty" => :shared_dirty_size,
	"Private_Clean" => :private_clean_size,
	"Private_Dirty" => :private_dirty_size,
	"Referenced" => :referenced_size,
	"Anonymous" => :anonymous_size,
	"Swap" => :swap_size,
	"SwapPss" => :proportional_swap_size,
}

def self.supported?

Whether the memory usage can be captured on this system.

Implementation

def self.supported?
	true
end

def self.supported?

Whether the memory usage can be captured on this system.

Implementation

def self.supported?
	true
end

def self.capture(pid, **options)

Capture memory usage for the given process IDs.

Implementation

def self.capture(pid, **options)
	File.open("/proc/#{pid}/smaps_rollup") do |file|
		usage = Memory.zero
		
		file.each_line do |line|
			if /(?<name>.*?):\s+(?<value>\d+) kB/ =~ line
				if key = SMAP[name]
					usage[key] += value.to_i
				end
			end
		end
		
		usage.map_count += File.readlines("/proc/#{pid}/maps").size
		# Also capture fault counters:
		self.capture_faults(pid, usage)
		
		return usage
	end
rescue Errno::ENOENT, Errno::ESRCH, Errno::EACCES
	# Process doesn't exist or we can't access it.
	return nil
end

def self.capture(pid, **options)

Capture memory usage for the given process IDs.

Implementation

def self.capture(pid, **options)
	File.open("/proc/#{pid}/smaps") do |file|
		usage = Memory.zero
		
		file.each_line do |line|
			# The format of this is fixed according to:
			# https://github.com/torvalds/linux/blob/351c8a09b00b5c51c8f58b016fffe51f87e2d820/fs/proc/task_mmu.c#L804-L814
			if /(?<name>.*?):\s+(?<value>\d+) kB/ =~ line
				if key = SMAP[name]
					usage[key] += value.to_i
				end
			elsif /VmFlags:\s+(?<flags>.*)/ =~ line
				# It should be possible to extract the number of fibers and each fiber's memory usage.
				# flags = flags.split(/\s+/)
				usage.map_count += 1
			end
		end
		
		# Also capture fault counters:
		self.capture_faults(pid, usage)
		
		return usage
	end
rescue Errno::ENOENT, Errno::ESRCH, Errno::EACCES
	# Process doesn't exist or we can't access it.
	return nil
end