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)
	begin
		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 Errno::ENOENT, Errno::EACCES
		# The process may have exited or permissions are insufficient; ignore.
	rescue => error
		# Be robust to unexpected formats; ignore errors silently.
	end
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)
	usage = Memory.zero
	
	begin
		File.foreach("/proc/#{pid}/smaps_rollup") 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)
	rescue Errno::ENOENT => error
		# Ignore.
	end
	
	return usage
end

def self.capture(pid, **options)

Capture memory usage for the given process IDs.

Implementation

def self.capture(pid, **options)
	usage = Memory.zero
	
	begin
		File.foreach("/proc/#{pid}/smaps") 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)
	rescue Errno::ENOENT => error
		# Ignore.
	end
	
	return usage
end