Process::MetricsSourceProcessMetricsMemoryDarwin

class Darwin

Definitions

def self.supported?

Whether the memory usage can be captured on this system.

Implementation

def self.supported?
	File.executable?(VMMAP)
end

def self.total_size

Signature

returns Numeric

Total memory size in kilobytes.

Implementation

def self.total_size
	# sysctl hw.memsize
	IO.popen(["sysctl", "hw.memsize"], "r") do |io|
		io.each_line do |line|
			if line =~ /hw.memsize: (\d+)/
				return $1.to_i / 1024
			end
		end
	end
end

def self.parse_size(string)

Parse a size string into kilobytes.

Implementation

def self.parse_size(string)
	return 0 unless string
	
	case string.strip
	when /([\d\.]+)K/i then ($1.to_f).round
	when /([\d\.]+)M/i then ($1.to_f * 1024).round
	when /([\d\.]+)G/i then ($1.to_f * 1024 * 1024).round
	else (string.to_f / 1024).ceil
	end
end

def self.capture(pid, count: 1, **options)

Capture memory usage for the given process IDs.

Implementation

def self.capture(pid, count: 1, **options)
	usage = Memory.zero
	
	IO.popen(["vmmap", pid.to_s], "r") do |io|
		io.each_line do |line|
			if match = LINE.match(line)
				virtual_size = parse_size(match[:virtual_size])
				resident_size = parse_size(match[:resident_size])
				dirty_size = parse_size(match[:dirty_size])
				swap_size = parse_size(match[:swap_size])
				
				# Update counts
				usage.map_count += 1
				usage.resident_size += resident_size
				usage.swap_size += swap_size
				
				# Private vs. Shared memory
				# COW=copy_on_write PRV=private NUL=empty ALI=aliased 
				# SHM=shared ZER=zero_filled S/A=shared_alias
				case match[:sharing_mode]
				when "PRV"
					usage.private_clean_size += resident_size - dirty_size
					usage.private_dirty_size += dirty_size
				when "COW", "SHM"
					usage.shared_clean_size += resident_size - dirty_size
					usage.shared_dirty_size += dirty_size
				end
				
				# Anonymous memory: no region detail path or special names
				if match[:region_name] =~ /MALLOC|VM_ALLOCATE|Stack|STACK|anonymous/
					usage.anonymous_size += resident_size
				end
			end
		end
	end
	
	# Darwin does not expose proportional memory usage, so we guess based on the number of processes. Yes, this is a terrible hack, but it's the most reasonable thing to do given the constraints:
	usage.proportional_size = usage.resident_size / count
	usage.proportional_swap_size = usage.swap_size / count
	
	return usage
end