Process::MetricsSourceProcessMetricsMemoryLinux

class Linux

Linux implementation of per-process memory metrics using /proc/[pid]/smaps or /proc/[pid]/smaps_rollup, and /proc/[pid]/stat for fault counters. Prefers smaps_rollup when readable (single summary); otherwise falls back to full smaps and counts maps from /proc/[pid]/maps.

Definitions

def self.capture_faults(pid, usage)

Extract minor and major page fault counters from /proc/[pid]/stat (proc(5): fields 10=minflt, 12=majflt) and assign to usage.

Signature

parameter pid Integer

Process ID.

parameter usage Memory

Memory instance to populate with minor_faults and major_faults.

Implementation

def self.capture_faults(pid, usage)
	stat_content = File.read("/proc/#{pid}/stat")
	# The comm field can contain spaces and parentheses; find the closing ')':
	closing_paren_index = stat_content.rindex(")")
	return unless closing_paren_index
	fields = stat_content[(closing_paren_index + 2)..].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

SMAP = {...}

Mapping from smaps/smaps_rollup line names to Memory struct members (values in kB, converted to bytes when parsing).

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, faults: true, **options)

Capture memory usage from /proc/[pid]/smaps_rollup and /proc/[pid]/maps. Optionally fill fault counters from /proc/[pid]/stat.

Signature

parameter pid Integer

Process ID.

parameter faults Boolean

Whether to capture minor_faults and major_faults (default: true).

returns Memory | Nil

Implementation

def self.capture(pid, faults: true, **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]
					# Convert from kilobytes to bytes:
					usage[key] += value.to_i * 1024
				end
			end
		end
		
		usage.map_count += File.readlines("/proc/#{pid}/maps").size
		
		# Also capture fault counters if requested:
		if faults
			self.capture_faults(pid, usage)
		end
		
		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, faults: true, **options)

Capture memory usage from /proc/[pid]/smaps (and map count from VmFlags) and /proc/[pid]/maps. Optionally fill fault counters from /proc/[pid]/stat.

Signature

parameter pid Integer

Process ID.

parameter faults Boolean

Whether to capture minor_faults and major_faults (default: true).

returns Memory | Nil

Implementation

def self.capture(pid, faults: true, **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]
					# Convert from kilobytes to bytes:
					usage[key] += value.to_i * 1024
				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 if requested:
		if faults
			self.capture_faults(pid, usage)
		end
		
		return usage
	end
rescue Errno::ENOENT, Errno::ESRCH, Errno::EACCES
	# Process doesn't exist or we can't access it.
	return nil
end